Skip to content
askalfPublic

About

Own your agent skills: vet, sign and pin every skill and MCP server before it runs, then fail the build when the bytes change.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

2 stars

Watchers

0 watching

Forks

Repository files navigation

Own your agent skills. Vet, sign and pin before it runs. MCP servers, skills and marketplaces flow through scan, pin, verify and enforce into truecopy.lock, and a poisoned skill is stopped at scan. npm i -g @askalf/truecopy. 68,560 skills poison-scanned.

truecopy

The supply-chain gate for agent skills and MCP servers.

npm version GitHub Marketplace: truecopy-action Marketplace watch: the latest daily scan of the official Claude Code plugin directory CI CI on pitlane OpenSSF Scorecard OpenSSF Best Practices MIT license

68,560 skills poison-scanned · the official Claude Code plugin directory re-scanned every day · deterministic and offline · one runtime dependency · MIT

See it · Quick start · The attack · How it works · What it gates · In CI · Proof · Trust · Docs


An agent runs its tools with your privileges, and it believes what their descriptions tell it. truecopy is the supply-chain gate in front of those tools. It poison-scans a skill or MCP server before you trust it, pins exactly the bytes you vetted into truecopy.lock (signed, if you want to know who vetted them), re-checks every pin on every run and every CI pass, and enforces the lock at runtime. A poisoned tool is refused at the door; a tool you trusted last week that quietly changed is stopped before the agent ever sees it.

See it catch one

A recorded run. truecopy scan ./poisoned-mcp.json flags productivity-helpers: the summarize tool carries an instruction-override, exfiltration intent, a sensitive-path exfil instruction and a sensitive-path reference; exit 1. truecopy add ./mcp-server.json --sign pins filesystem with its hash, signed. truecopy verify prints filesystem ok, signed by self, all 1 pinned skills verified. Later, upstream ships a silent update in which one tool description gains an exfil instruction. truecopy verify prints filesystem drifted, the changed tool read_file, 1/1 FAILED; exit 1.

That picture is a recording, not a mock-up. scripts/readme/terminal.mjs runs each command with this checkout's CLI in a throwaway directory, against the two manifests in demo/, and refuses to redraw it if any verdict or exit code changes.

Quick start

npm i -g @askalf/truecopy                  # Node 20 or newer

truecopy scan ./mcp-server.json            # poison-scan it before you trust it
truecopy add  ./mcp-server.json --sign     # pin the vetted bytes into truecopy.lock, signed
truecopy verify                            # re-check every pin; exit 1 on drift or poison

Commit truecopy.lock next to your code, the way you commit package-lock.json. Then choose where it is enforced:

# Claude Code: pin every skill, then check each one at the moment it is invoked
truecopy add --claude --claude-plugins --sign && truecopy hook install

# any MCP client: launch the server through the proxy; only pinned, unchanged tools get through
truecopy-mcp --lock truecopy.lock --name filesystem -- <server command>

# any process: refuse to launch while a pin is bad
truecopy guard -- npm start

No install needed for a one-off: npx -y @askalf/truecopy verify. In a clone, npm run demo plays the whole story offline.

The attack

Agents pull tools from places you do not control: MCP servers, skill marketplaces, a teammate's repo. OpenClaw's poisoned-skills marketplace showed what that costs. Three things go wrong, and truecopy answers each one:

The attack What it looks like What truecopy does
Tool poisoning A description, schema or SKILL.md that tells the model to "ignore previous instructions" and send ~/.ssh/id_rsa somewhere. The model reads it as an instruction; you never read it at all. scan flags injection and exfiltration instructions in every name, description, schema and skill file, and add refuses to pin a poisoned one.
The silent update A server or skill you vetted last week ships new bytes. No diff you will ever read shows it, and the agent loads it on the next run. Every pin records a content hash plus one hash per tool or file. verify, the hook, the proxy and guard re-derive them and fail on any change, naming what changed.
The wrong signer Someone substitutes the lock entry, or signs a tampered version with a key of their own. Ed25519 signatures are checked against your trust set: your machine's own key, the keys you trust globally and the truecopy.trust you commit. A valid signature from a key you do not trust fails closed as untrusted; --require-signed fails anything unsigned.

How it works

Stage Command What happens
scan truecopy scan Deterministic poison detection over everything the model will read. Prose in skills is severity-aware: an instruction flags, a mere mention of a secret path is an advisory. Tool definitions keep the strict any-finding rule.
pin truecopy add The vetted version goes into truecopy.lock: source, content hash, per-part hashes, the verdict and detection engine version at pin time, and optionally an Ed25519 signature.
verify truecopy verify Every run or CI pass re-hashes and re-scans every pin. Changed bytes are drifted; unchanged bytes that newer detection now flags are poisoned. Exit 1 on any failure.
enforce hook · proxy · guard The runtime gate keeps an unvetted or drifted tool from reaching the agent at all: the Claude Code hook, the truecopy-mcp proxy, and the guard launch gate.

A lock entry, as committed in this repo's own truecopy.lock:

"filesystem": {
  "source": "demo/clean-mcp.json",
  "kind": "mcp",
  "hash": "a881946cef3fb45d7c78d3297070a179f4c55ba65f00a576ada922c39f4e1f57",
  "scannedAt": "2026-07-16T17:25:56.559Z",
  "verdict": "clean",
  "findings": 0,
  "detection": { "engine": "redstamp", "version": "0.4.1" },
  "parts": {
    "read_file": "cfe9acc21dbc05b0b8baa751e71fe524fd8fa15578d20610fc90b8d533bd8d8f",
    "list_directory": "ac606087a4a47e23d801beb12699d87364430677f04b6709eacbaec062156227",
    "(manifest)": "cac4676bf307456c45dfa470fa0b350b6b305b0d3cb86ffc3b68396625261b46"
  }
}

parts is why a failure can name ~read_file instead of just saying something changed: one hash per tool for an MCP server, one per file for a skill, and one for the manifest's own fields, so a swapped launch command is caught too. Paths in the lock are separator-independent, so a lock committed on one OS verifies on another.

Every verdict verify can return
Status Meaning Fails?
ok Same bytes as the pin, still scans clean (or carries findings you accepted for exactly these bytes with --force), and any signature is valid from a trusted key. no
drifted The bytes changed since the pin. Lists the tools or files added, removed or changed. yes
poisoned Same bytes, but they now scan poisoned: detection improved after you pinned. yes
untrusted A valid signature from a key outside your trust set. yes
unsigned An entry recorded as signed whose signature no longer checks; or, under --require-signed, any entry without a trusted signature. yes
missing The pinned source cannot be read. yes

Exit codes: 0 when every pin is ok, 1 on any failure. The Claude Code hook blocks with exit 2 and tells the model why. scan, verify, list and diff take --json with the same exit codes.

What it gates

Surface Checked A bad pin
Claude Code skills
project, user and marketplace plugins
hook install
The moment a skill is invoked, against the exact directory about to run Blocked, and the model is told the skill drifted or turned poisonous. --strict makes the lock a whitelist that fails closed. More
MCP servers
any client that launches stdio servers
truecopy-mcp
Every tools/list response, and every call against it Stripped from tools/list, and a call to it errors. --strict blocks the whole server. More
Process launch
guard -- <cmd>
Before the command starts The command never starts; exit 1.
CI
askalf/truecopy-action@v1
Every workflow run The build fails. In CI
Plugins already installed
check-manifest
On demand, against the daily watch Fails when an installed skill's bytes differ from what the watch vetted, or the watch flagged it. More
A marketplace before you install
scan --marketplace
On demand, against your clone Every flagged skill is listed; exit 1.

What you can pin: an MCP manifest (a .json holding the server's tools array, the shape tools/list returns; its other top-level fields, such as the launch command and args, are hashed and scanned too), a skill directory (SKILL.md plus its files, hashed per file), or any single file.

In CI

One line from the GitHub Marketplace verifies the committed lock and fails the build on drift or poison:

- uses: askalf/truecopy-action@v1

Or scan a marketplace or manifest with no lock at all:

- uses: askalf/truecopy-action@v1
  with:
    command: scan
    marketplace: ./the-repo-you-cloned

Where trust matters, require a signature from a publisher you trust, and keep the one private signing key in a single CI secret instead of on every laptop:

# every pin must carry a valid signature from a key in the runner's trust set
- run: npx -y @askalf/truecopy verify --require-signed

# sign with the CI secret: no key file and no keychain on the runner
- run: npx -y @askalf/truecopy add ./mcp-server.json --sign
  env:
    CANON_SIGNING_KEY: ${{ secrets.CANON_SIGNING_KEY }}

On a workstation the trust set is your machine's own key, the keys you trust globally and the repo's truecopy.trust. A clean runner has no key and no global store, so there it is truecopy.trust alone: commit every publisher's public key to it, the public half of CANON_SIGNING_KEY included. That key signs but is never trusted just for being set.

Publisher keys, the committed truecopy.trust, JSON reports and the signing setup: docs/signing-and-ci.md. This repo runs the same gate on itself in truecopy-gate.yml.

Proven at ecosystem scale

Corpus Skills Result
The official Claude Code plugin directory plus nine community marketplaces 2,019 0 poisoned. Tuning detection against this corpus took the flag rate from 126 to 12 (0.6%), and every one of the 12 was benign on manual review.
ClawHub, the registry whose poisoning incident started the category 66,541 0 confirmed malicious. 813 deterministic alarms, every one benign on cross-check against ClawHub's own scanner.
The official directory, re-scanned every day 2,471 The 2026-10-29 run covered 315 plugins: 0 under review, 463 advisories. The watch badge above always carries the latest run.

The daily watch fetches every catalog plugin, including vendor plugins at their catalog-pinned commits, and publishes each snapshot to WATCH.md and the live observatory at truecopy.sprayberrylabs.com. Each run also publishes a directory-manifest.json of the exact bytes it vetted, so truecopy check-manifest can hold the plugins on your own machine to the same standard.

Trust and transparency

A supply-chain gate has to survive the scrutiny it applies to others.

Promise How it holds
Offline No network calls and no telemetry. You clone or download; truecopy only reads.
Deterministic No model in the loop. The same bytes get the same verdict on every machine, every time.
One runtime dependency @askalf/redstamp, the detection engine, pinned to an exact release tarball. It has no dependencies of its own.
Releases Published to npm only from CI, through trusted publishing (OIDC) with provenance. Check yours with npm audit signatures.
Eats its own cooking This repo pins its demo manifest in its own truecopy.lock and verifies it with truecopy-action on every pull request.
Tested Node 20, 22 and 24 on Linux and Node 22 on Windows, plus the packed npm tarball on both. Every row of the Claude Code policy table was also verified live, each in its own fresh Claude Code session.
Hardened Jazzer.js fuzz targets for canonical JSON, the scanner and the MCP gate; CodeQL; OpenSSF Scorecard; OpenSSF Best Practices passing.

What truecopy is not. It is not a sandbox: once a vetted tool runs, containing what that call does is redstamp's job. A clean scan means no known poisoning shape was found, not that a tool is safe; the pin is what makes your review last. For an MCP server, truecopy pins the surface the model reads (tool names, descriptions and schemas, plus any manifest fields you include, such as the launch command), not the server's code, so pin the server's own version with your package manager as well. Found a gap? Report it privately.

FAQ

Does it send anything anywhere? No. There is no network code in the CLI. Even the daily watch manifest is something you download yourself and point truecopy at.

Does it use an LLM to judge skills? No. Detection is deterministic, shared with redstamp, and every finding can be reproduced from the bytes.

What about a false positive? Read the skill, then pin it with --force: the findings are recorded as accepted for exactly those bytes, shown as · accepted findings (--force pin), and any change re-flags it. Please also report it so detection improves for everyone.

Can I use it from code? Yes: import { scan, pin, verify, diff } from '@askalf/truecopy'. See docs/commands.md.

Remote (HTTP) MCP servers? truecopy-mcp gates stdio servers. For a remote server, pin a manifest of its tools/list and verify it in CI.

Windows? Supported and tested. In Git Bash, MSYS rewrites Unix-looking path arguments before truecopy sees them; see the Windows note.

Why is the signing key variable called CANON_SIGNING_KEY? truecopy was called canon before its npm release. The repo redirects, the canon and canon-mcp commands still work, an existing canon.lock or canon.trust is read automatically, and the CANON_* environment variables keep their names so existing setups keep working.

Docs

Guide Covers
Commands, sources and the library Every command, pinned installs, what you can pin, the library API
Gate Claude Code skills hook install, default vs --strict, severity-aware verdicts, per-repo lockdown
Runtime gate truecopy-mcp, the container and its standalone tools, truecopy guard, the Windows note
Lockfile, signatures and CI The lock format, publisher trust, --require-signed, signing in CI
The marketplace watch The daily scan and check-manifest

Also: Changelog · Contributing · Security policy

The agent-security stack

Three layers, one defense, each usable on its own:

Project Layer Answers
redstamp the call Is this action safe to run right now?
truecopy (you are here) the tool Is this the exact skill or server I vetted?
plumbline the trajectory Did the whole sequence of actions stay on the declared job?

truecopy and redstamp share one detection engine, so they are a pair, not a duplicate: truecopy vets the tool, redstamp contains the call. plumbline watches from above, out of band and read-only, and catches an escape assembled from steps that were each allowed.


Part of Own Your Stack: own your AI infrastructure instead of renting it by the token.
Built by Thomas Sprayberry · MIT

About

Own your agent skills: vet, sign and pin every skill and MCP server before it runs, then fail the build when the bytes change.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages