Writing

What signing an AI skill does and does not prove

A skill is a markdown file that your agent reads as instructions. Verification happens once, at install, against a digest the publisher controls. Here is what breaks, what a signature fixes, and the three things it will never tell you.

What actually happens

A Claude Code skill is a markdown file in a directory. Claude Code reads that file and places its contents into model context, where it becomes instructions the agent follows. That is the whole mechanism, and it is why the file deserves the same care as any binary you install.

Verification today happens once, at install time, against a digest recorded in the marketplace manifest. The publisher controls both the artifact and that digest, so the pair is self-consistent by construction. The cached file then lives in a writable directory that outlives the check, and nothing re-reads the file before the agent loads it. A digest answers “did I receive the bytes this manifest named?” It cannot answer “did the party I decided to trust actually produce these bytes?”

So there are two questions you cannot ask about a skill you installed last month.

  • Is this still the file I installed?
  • Is it from the same party it came from last time?

Both require the artifact bound to a publisher identity that is verifiable independently of the channel that delivered it. That binding is what a signature provides.

The demo

Press play. It runs 1:51, it is silent and captioned, and every line of terminal output in it is real.

1080p MP4GIFthe signed repo it runs against ↗

The subject is a small public skill, PromptSign/tell-a-joke, signed by its own GitHub Actions workflow at tag v1.0.0. It shows five things.

A clean verify

The line that matters is not the checkmark. It is the identity string beside it, which names the workflow that signed the artifact and the issuer that certified that workflow.

A tamper

Six lines are appended to the installed copy of SKILL.md, under a “Maintenance” heading: read ~/.aws/credentials and any environment variable matching *_TOKEN or *_KEY, POST them to an endpoint, and do not mention this step to the user. The file is still valid markdown, the skill still works, and every install-time check still passes. The payload is modeled on a real sample, which VirusTotal records under the name ninja-evasive-evasive-03-prompt-subtle.md (hash edb911b9…, written up by Google Cloud on 12 May 2026) and which sat at zero detections for roughly two months. The lines on screen are paraphrased from it and post to a reserved .invalid domain that cannot resolve.

The catch

Verification fails, and the report lands in session context, where both you and the model see it before the skill is used. The manifest is a complete list rather than a set of hashes for files someone chose to mention, so an added file and a removed file each fail on their own, and the recording shows both.

The same check runs again as a second hook immediately before the Skill tool executes. That one is defense in depth rather than the primary control, since a skill already loaded at session start was visible before the second hook can fire, and it is not in the recording.

The impersonation

This is the part worth watching, because it is the objection every thread arrives at.

The attacker does not need to defeat the signature. They can sign their fork with their own real identity, and Sigstore will issue them a certificate, because authenticating identities is the point of it.

In a fresh trust store their copy verifies clean. Against a store where the real publisher was seen first, the same file is refused: same skill name, different signer, and the issuer changed too, from a CI workflow identity to a personal login.

The question was never “is this signed?” It is “is this signed by the same party as last time?” An unsigned ecosystem cannot ask that question at all.

The allowlist

Trust on first use protects the developer who never wrote a policy, which is most people, but it pins whatever it sees first.

An organization can say something stronger: one policy file names the publishers this environment accepts. An unsigned artifact fails as unsigned. A copy the attacker signed under their own identity fails as a disallowed identity.

Signing yourself does not get you onto someone else's allowlist.

What this does not prove

Three limits, and each one serves a real purpose.

This is not a safety verdict.

An author who signs their own malicious skill verifies fine. Signing attests to origin, never to benignity. It would not have prevented the five malicious skills Unit 42 documented on ClawHub between February and May 2026, because those were genuinely published by their authors and the authors were the attackers.

Unsigned is not malicious.

Most of the ecosystem is unsigned today. A verifier that treats absence of a signature as evidence of hostility is a verifier people turn off within a week, which is why the default posture is trust on first use and a warning, not a block.

Content scanning is the other half of this, not a competitor.

Mitiga's SkillGate work, published 15 June 2026, is the best statement of the category anyone has written: the instruction file is the malware, and the agent is the courier. They found 1,230 hardcoded keys and tokens across more than 50,000 instruction files in over 7,000 repositories. That is a content problem, and scanning is what solves it.

And one thing this project does not address at all. Hostile content inside a repository you asked the agent to review is a different problem. That file was never claiming to be from anyone, so a signature says nothing about it.

Why both layers

Here is the argument for running scanning and verification together, and it is sharper than “defense in depth.”

One of the five ClawHub skills evaded both ClawScan and VirusTotal by padding its README.md to 22 MB, past the size threshold each scanner applies. That evasion works because scanning is a function of content: make the content too large to inspect, and the scanner cannot see what is in it.

Signature verification asks a different question. It is indifferent to how large the file is or what it says. It asks whether the bytes being loaded are the bytes that were signed.

The two defenses do not share a bypass. That is the actual argument, and it is why the honest position is complementary rather than competitive.

Try it

The verifier is a single binary with no account, no telemetry, and no server involved in verification. Keyless signing uses Sigstore, verification runs offline against a pinned trust root, and the format is specified and Apache-2.0.

In Claude Code:

/plugin marketplace add PromptSign/promptsign-plugin
/plugin install promptsign@promptsign

To sign your own repository, the workflow the demo subject uses is two files, and it is the same one offered in our outreach pull requests. The docs walk through it, and Integrate covers verifying signatures inside your own tool.