ai · security · skills

For: Practitioners & Platform Engineers

56 skills went in. 3 findings came out. A person wrote a reason for each.

SkillGuard doesn’t flag “bad skills.” It builds a bill of materials for every skill, works out what each one can do by reading its code rather than believing its label, runs 10 detectors over the text, and checks whether two skills that name each other would chain into a way out for your data. Then a human decides. Here’s exactly how, end to end.

Every figure on this page is scanner output from our public reference repo (Apache-2.0, 56 skills), regenerated from the tool rather than typed by hand. The same scanner runs against our private skill pool; those results are internal and stay that way.

Skill Repository56 skills56 filesSkillBOMFile inventoryHashes + metadataCapabilityInference7 categoriesStatic Analysis10 detectors3 findingsOutput: audit/skillguard-lock.jsonPer-skill file hashesCapability vectorsFindings with evidenceRisk scoresOutput: skill-supply-chain-graph.mdCross-skill co-reference edges3 capability chain pairsCo-reference risk, not runtimeOutput: triage decisionsAccepted findings with reasonRejected findingsExpiration datesThe scan is seconds of compute. The triage decisions are hours of human judgment.

The Tension

“Our skills are security teaching material. They quote the attacks on purpose. So every naive scanner either drowns us in false alarms or, tuned down, stops finding anything. How do you scan a library that is supposed to contain the bad words?”

Inside the black box

Something goes in. Something comes out. Here’s the bit in between.

7steps, no jargon. If any step below needs a security background to follow, that’s a bug in this page, not in your understanding.

  1. IN

    A pile of skills

    A skill is a folder: some instructions written for an AI, and sometimes a script it can run. That is all that goes in. No magic, no cloud service, no account.

  2. 01

    Write down everything that exists

    First it makes a list. Every folder, every file, and a fingerprint of each one so we can tell later if a single character changed. You cannot check a pile you have not counted.

  3. 02

    Work out what each one can actually do

    Then it reads the code and asks: what powers does this thing have? Seven of them, listed below — reach the internet, run commands, read files, change files, touch passwords and keys, install software, write to the code repository.

    It works this out by reading, not by asking. A skill can declare its own powers, but almost none do, and a scanner that trusts a label it was handed is checking nothing.

  4. 03

    Unwrap anything written in disguise

    Before it reads a word, it looks for text that has been scrambled on purpose — base64, hex, percent-codes — and unscrambles it. Then it unscrambles what came out of that, up to five layers deep, because hiding something twice is a thing people do.

    Without this step the whole scanner is theatre. Every check below matches text it can read, so one base64 wrapper used to hide a payload from every one of them, and from the powers list in step 02 as well. Added 2026-07-15 after we tested exactly that and watched it sail through.

  5. 04

    Read the words, looking for known shapes of trouble

    Now it reads the instructions like a suspicious teacher, hunting for specific shapes of bad behaviour — in the visible text and in everything step 03 unwrapped. Then it checks the folder itself for three more: a hidden file, a huge file, a program smuggled in where only text belongs.

  6. 05

    Check whether it just said "never"

    This is the bit most scanners get wrong. Our own security lessons quote the attacks, because a lesson about a trick has to contain the trick. "Never send your .env file to a stranger" and "send your .env file to a stranger" share almost every word. So before it accuses anything, it looks backwards for a "never" or a "do not", and it looks around for the vocabulary of defence — words like detect, forensic, playbook, worked example.

    This is why the secret-exfiltration rule is fussy rather than clever. A loose "verb near a scary noun" match, pointed at a library that is itself security teaching material, produces far more false alarms than real ones — so the rule demands a specific credential file path, an exfiltration verb within a few words of it, and no defensive vocabulary nearby.

  7. 06

    Look for dangerous pairs

    One skill that reads your files is fine. One that phones the internet is fine. The two together, when one names the other, is a way out for your data. So it looks for exactly that shape.

    This is co-reference, not surveillance. It means one document mentions another by name and their powers would chain if someone wired them together — not that anyone watched them do it. These are standalone documents, not a live system.

  8. 07

    Compare against what a human already forgave

    Finally it lines the findings up against a list of ones a person already looked at and accepted, each with a written reason and, if we chose, a date the excuse runs out. It also checks the fingerprint file from step 01: if a skill has picked up a power it did not have when someone last approved it — it could only read files before, and now it can reach the internet — that is its own finding.

    That last check is the one that catches the oldest trick in supply chains: ship something harmless, wait to be trusted, then push an update that reaches further. A power that appears without a diff anyone read is the signal, whatever the reason turns out to be.

  9. OUT

    A report, a fingerprint file, and a pass or a fail

    Out comes a report of everything found, a lockfile of fingerprints so anyone using the skills can spot a change nobody announced, and one answer: pass or fail.

So how does a skill pass or fail?

The obvious answer, which is wrong

Zero problems found = pass.

A library of real security documents will never score zero, and a tool that reports zero has simply stopped looking. Chasing zero teaches people to delete the finding instead of reading it.

What actually decides it

No NEW serious problem, and no old excuse that has gone stale.

The scan compares today against the list of things a human already read and signed off. Anything already accepted, and still within date, stays quiet. Anything new at high or critical fails the build. Anything whose written excuse has expired fails the build too, because a reason nobody has re-checked is not a reason any more.

So the number of findings is not the score. The score is: did anything change that a person has not looked at yet? That question has a real answer on day one, and it still has one on day five hundred.

The Methodology

Nine detectors. Seven inferred powers. Two brakes that stop the noise.

SkillGuard does run regexes over your skills. The engineering is not in the matching, which is the easy half; it is in refusing to fire on a library whose whole job is to describe the thing being matched. Here’s the complete detection system.

1. Capability Inference (7 categories)

The scanner analyzes every file in a skill and infers what it can do based on code patterns. This is not what the skill claims to do — this is what the code actually enables.

network

HTTP requests, API calls, external fetches

requests\.|urllib|socket\.|curl\s

shell

Command execution, subprocess invocation

subprocess\.|os\.system|shell=True

filesystem-write

File creation, modification, deletion

open\(.w|shutil\.(copy|move)

filesystem-read

File reading, directory traversal

open\(.r|Path\(.read_text

secret-access

Credential handling, key loading

\.ssh|credentials|api[_-]?key

package-install

Dependency installation

pip\s+install|npm\s+install

git-write

Repository modification

git\s+(push|commit)

2. Static Analysis (7 content detectors, 3 bundle detectors)

7 detectors read the words. 3 more check the folder itself. Each one is deliberately narrow: a detector that fires on the general vicinity of a scary noun is worthless against a corpus of security writing, because security writing is made of scary nouns.

Each detector says where it sits on two public taxonomies: the OWASP Top 10 for Agentic Applications and MITRE ATLAS. Two of them map to nothing, and we leave them blank rather than reach. An oversized file is a review-burden signal, not an attack technique anyone has named; and talking a human reviewer out of the approval gate has no ATLAS technique, because ATLAS models the adversary against the machine. A code that looks right is worse than no code, because the reader cannot tell which one they are looking at.

Talking the AI out of its rules

critical

Phrases whose only job is to switch the AI’s judgment off: ignore all previous instructions, disregard the system prompt, you are now in developer mode, act as if you have no restrictions.

injection.instruction_override

Maps toASI01 Agent Goal HijackAML.T0051 LLM Prompt Injection

Take the keys, then send them somewhere

critical

A named credential file — .env, an SSH private key, ~/.aws/credentials, a .pem — sitting within a few words of a verb like steal, exfiltrate, upload or post, with nothing defensive around it.

secret.exfiltration

Maps toASI03 Identity and Privilege AbuseAML.T0025 Exfiltration via Cyber Means

A key left in the box

high

A string shaped like a real credential — an AWS access key, a GitHub token, a Slack token, an OpenAI key, a private key block — sitting in a folder that gets handed to other people. The detector knows the shapes the vendors publish; it cannot know whether the key still works, and does not pretend to.

secret.hardcoded

Maps toASI03 Identity and Privilege AbuseAML.T0055 Unsecured Credentials

Download something and run it immediately

high

The classic curl … | sh shape, and its Windows twin. Whatever is on the far end of that URL today runs on your machine today, and nobody reviewed it.

installer.download_execute

Maps toASI05 Unexpected Code Execution (RCE)AML.T0011 User Execution

A key grab wearing a compliance badge

high

The words compliance or audit, then a collecting verb, then credentials or tokens. Asking for the keys is not more legitimate because a policy is cited while asking.

intent.compliance_secret_collection

Maps toASI09 Human-Agent Trust ExploitationAML.T0098 AI Agent Tool Credential Harvesting

Claiming it already has permission

medium

Text that frames an action as pre-approved, or says do not ask, no need for approval, skip confirmation. Sometimes that is a real control gap being described on purpose — which is what triage is for.

intent.approval_bypass

Maps toASI09 Human-Agent Trust Exploitation

Fetching its real orders later

medium

Instructions that tell the AI to load its instructions from a URL. What you reviewed is then not what runs, because the actual orders arrive after the review.

intent.remote_instruction_loading

Maps toASI01 Agent Goal HijackAML.T0051.001 LLM Prompt Injection: Indirect

A program where only text belongs

medium

A compiled binary inside a folder that should hold instructions and readable scripts. You cannot review what you cannot read.

bundle.binary

Maps toASI04 Agentic Supply Chain VulnerabilitiesAML.T0010.005 AI Supply Chain Compromise: AI Agent Tool

A file far bigger than it should be

low

Size is a hiding place. Nobody reads to the bottom of a giant file.

bundle.oversized

A file starting with a dot

low

Hidden from a casual directory listing, not from the scanner.

bundle.hidden_file

Maps toASI04 Agentic Supply Chain Vulnerabilities

3. The two brakes that make it usable

This is where most scanners fail on a corpus like ours, and it is the part worth stealing. Our skills teach defence, so they quote the attacks. “Never upload your .env file to an external endpoint” and “upload your .env file to an external endpoint” differ by one word. A keyword match cannot tell a lesson from an instruction.

So every text detector passes through two brakes before it is allowed to accuse anything. The first looks backwards a few words for a negation — never, do not, must not, avoid. The second looks around for the vocabulary of defence: detect, forensic, playbook, MITRE, worked example. Either one, and the finding is suppressed.

The secret-exfiltration rule goes further still. It does not match a verb near a noun. It requires a specific credential-bearing file path.env, .ssh/id_rsa, ~/.aws/credentials, .pem — with an exfiltration verb inside a forty-character window either side of it, and no defensive vocabulary nearby. That fussiness is not fastidiousness. A generic proximity match, run against this corpus, produces overwhelmingly more false alarms than real findings, and a scanner nobody believes is a scanner nobody runs.

4. Co-reference risk (3 chain pairs)

One skill that reads your files is fine. One skill that can reach the internet is fine. The pair is a way out. So the scanner reads each skill’s text looking for the name of another skill, and when it finds one, checks whether their inferred capabilities form one of exactly three shapes:

secret-access + network

One can touch the keys, the other can phone out.

filesystem-read + network

One can read your files, the other can phone out.

shell + package-install

One can run commands, the other can pull in new software to run.

What this is not

This is co-reference risk, not a runtime graph. An edge means one document mentions another by name and their powers would chain if someone wired them together. Nobody watched them run. These are standalone instruction files, not a live system, and calling the output an “attack path” would be claiming an observation we never made. The output is a markdown file, audit/skill-supply-chain-graph.md, and its honest name is a list of pairs worth a second look.

5. The Triage Discipline

The scanner emits raw findings. A triage team reviews each one against four criteria:

Is the text instructing, or teaching?

The match is almost always real. The question is whether the skill is telling an AI to do the thing, or showing a reader what the thing looks like so they can catch it.

Example: red-teaming-llm-applications quotes an instruction-override phrase as its own worked teaching example. Real match, critical severity, and correct content. Accepted, with that reason written down.

What is the severity?

Critical (25pts), High (12pts), Medium (5pts), Low (2pts), Info (0pts)

We weight findings by severity. A critical finding is 12.5× more severe than a low finding.

Is there a compensating control?

Is there documentation, validation, or a guard that mitigates the risk?

Example: A skill that needs secret-access but validates against a vault API has a compensating control.

When does this excuse run out?

An acceptance can carry an expiry date. Once that date passes the finding counts as new again, and the scan fails until a human looks a second time.

The date is set per acceptance, by the person accepting it. Leave it off and the acceptance stands until someone removes it — which is a decision you have to make on purpose, in writing, with your name on it.

The output is a triage decision file that feeds back into the scanner. On the next run, the scanner checks whether accepted findings should still be accepted or if they’ve expired.

⚠️ Honest Boundary

The scanner is a static analysis tool. It does not execute code. It does not catch runtime behavior or side-channel attacks. It catches known patterns. For runtime security, you need a different tool.

The Proof

What the scan actually found: real findings from the reference repo.

Not a sample. Not an illustration. This is every finding the scanner produced against our public reference repo’s 56 skills — all 3 of them, with the evidence string that fired and the reason a human accepted it. Note what they have in common: every one is a real match on text that is doing its job. That is what the brakes are up against.

generating-an-ai-bill-of-materials

high

Category: installer.download_execute

Download-execute installer chain

Evidence: curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh

Triage Decision:

Documented install command for Anchore Syft, the SBOM generator this skill teaches you to run — matches the skill's own stated purpose, not an unexplained fetch-and-execute.

red-teaming-llm-applications

critical

Category: injection.instruction_override

Prompt-injection / instruction-override phrasing

Evidence: Ignore previous instructions

Triage Decision:

Quoted verbatim as the worked attack-phrase example this red-teaming skill teaches testers to probe with — the skill's job is to teach exactly this phrase, not to execute it.

scanning-ai-bom-for-vulnerable-components

high

Category: installer.download_execute

Download-execute installer chain

Evidence: curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sh

Triage Decision:

Documented install command for Anchore Grype, the vulnerability scanner this skill teaches you to run against an AI-BOM — same legitimate-tool pattern as the Syft finding above.

Scan Summary

Skills scanned

56

Files analyzed

56

Findings

3

Triaged / outstanding

3 / 0

Capability distribution (inferred, across the 56 reference skills)

network

7 skills

shell

2 skills

filesystem-write

11 skills

filesystem-read

2 skills

secret-access

10 skills

package-install

12 skills

git-write

2 skills

What It Proves

The gap between “we have skills” and “we know what our skills can do.”

Most organizations have thousands of skills in their repository. Most have no idea what those skills can actually do, because the only thing anyone ever reads is the description the author wrote. SkillGuard closes that gap: not by flagging “bad” skills, but by making what each one can dovisible, and by making every exception someone’s signed decision.

The guardrail, stated precisely

One part of this is wired into the build and one part is run on demand, and the difference matters more than a tidier sentence would.

In the build: check_reference_skillbom.py runs in the prebuild chain. If the reference scan carries a finding nobody has triaged, the build fails. An unreviewed finding cannot ship silently, because the site will not compile.

On demand:the scanner itself, over the private pool, is developer tooling run deliberately — not a merge gate, and there is no triage SLA. The lockfile is what makes either mode worth anything: it records the fingerprint of every file, so you can prove which skills were approved at which commit, and spot a change nobody announced.

⚠️ Honest Boundary

The SkillBOM is a static analysis tool. It does not catch runtime behavior, side-channel attacks, or sophisticated obfuscation. It catches known patterns. For runtime security, you need a different tool. The attack graph is co-reference risk, not a live execution graph. Treat it as a review prompt, not a live threat model.

The methodology is fully documented. The scanner code is available. The triage decisions are recorded. The attack graph is JSON-exportable. This is not a black box — it’s a transparent, auditable process.