
Preventing the Claude Code Leak with Attestation Policies
On March 31, 2026, Anthropic shipped a routine update to Claude Code via npm. Version 2.1.88 included a source map file that exposed 512,000 lines of unobfuscated TypeScript, the complete internal source code for their AI coding agent. A missing .npmignore entry. That's all it took.
Within hours the code was mirrored, dissected, rewritten in Python and Rust, and studied by tens of thousands of developers. Anthropic scrambled to issue DMCA takedowns, accidentally taking down thousands of unrelated GitHub repositories in the process.
This wasn't a sophisticated attack. It was a build pipeline that didn't verify what it was shipping.
The root cause
The leak happened because a debugging artifact got bundled into the npm package. A source map. It maps minified JavaScript back to the original TypeScript source, and it's useful during development. It has no business in a production package.
The fix Anthropic needed was a .npmignore file that excluded *.map files. But that's a procedural fix. Procedural fixes rely on humans remembering to do the right thing every time. The architectural fix is a policy that automatically rejects builds containing files that shouldn't be there.
What attestation-based verification looks like
At TestifySec, we built Witness and Archivista and contributed them to in-toto under the CNCF. Building on that work, we created Aflock AI, an open source AI attestation framework. Rookery is its modular attestation core, and cilock is the CLI that wires it into your build pipeline. Here's how it works.
When you wrap a build step with cilock, it records materials (what existed before the step ran) and products (what exists after) with cryptographic hashes. Every file that enters or exits the step is attested and signed.
A policy defines what's allowed. If the products don't match the policy, verification fails and the artifact never ships.
Let's prove it.
Setting up the demo
First, generate a signing keypair and create a simulated npm package source:
# Generate signing keys openssl genpkey -algorithm ed25519 -out testkey.pem openssl pkey -in testkey.pem -pubout -out testpub.pem
We'll create two build scripts — one that produces a clean package, and one that simulates the Anthropic bug by including a source map:
# build-clean.sh — correct build
#!/bin/bash
set -e
cp src/index.ts pkg/index.js
echo '{"name":"@anthropic/claude-code","version":"2.1.87"}' > pkg/package.json# build-leaked.sh — the bug: source map included
#!/bin/bash
set -e
cp src/index.ts pkg/index.js
echo '{"name":"@anthropic/claude-code","version":"2.1.88"}' > pkg/package.json
# THE BUG: source map with full unobfuscated source gets bundled
cat > pkg/index.js.map << 'EOF'
{"version":3,"sources":["../src/index.ts","../src/agent/core.ts",
"../src/agent/memory.ts","../src/agent/orchestrator.ts"],
"sourcesContent":["// Full internal source code..."]}
EOFRecording attestations with cilock
Wrap each build with cilock run. The --step flag names the pipeline step. The product attestor captures every file produced:
# Attest the clean build cilock run \ --step npm-pack \ --signer-file-key-path testkey.pem \ --attestor-product-include-glob "*" \ -o attestations/clean-build.json \ -d pkg/ \ -- bash build-clean.sh # Attest the leaked build cilock run \ --step npm-pack \ --signer-file-key-path testkey.pem \ --attestor-product-include-glob "*" \ -o attestations/leaked-build.json \ -d pkg/ \ -- bash build-leaked.sh
The attestations capture exactly what each build produced:
Clean build products:
index.js sha256:8aa66ae7... package.json sha256:312f10e4...
Leaked build products:
index.js sha256:8aa66ae7... index.js.map sha256:822f40d3... ← the source map package.json sha256:0a4789d4...
The cryptographic evidence is now on record. The leaked build produced a .map file that the clean build didn't.
Writing the policy
The policy is a JSON document that defines what each step is allowed to produce. The key mechanism is Rego policies, the same policy language used by Open Policy Agent (OPA), attached to each attestation type.
Here's the Rego rule that catches source maps:
package products
deny[msg] {
some name
input[name]
endswith(name, ".map")
msg := sprintf(
"BLOCKED: source map file detected in package: %s", [name])
}
deny[msg] {
some name
input[name]
endswith(name, ".ts")
msg := sprintf(
"BLOCKED: TypeScript source file detected in package: %s", [name])
}This iterates over every product file in the attestation. If any filename ends in .map or .ts, the policy denies it with a clear message. You could extend this to check file sizes, MIME types, or any other attribute the attestation captures.
The full policy wraps this Rego module alongside the step definition and the signing key:
{
"expires": "2027-12-31T23:59:59Z",
"steps": {
"npm-pack": {
"name": "npm-pack",
"functionaries": [{ "type": "publickey", "publickeyid": "<key-hash>" }],
"attestations": [
{
"type": "https://aflock.ai/attestations/command-run/v0.1",
"regopolicies": [{ "name": "exit-code-check", "module": "<base64-rego>" }]
},
{
"type": "https://aflock.ai/attestations/product/v0.1",
"regopolicies": [{ "name": "no-source-maps", "module": "<base64-rego>" }]
}
]
}
}
}Sign the policy so it can't be tampered with:
cilock sign \ -f policy.json \ -o signed-policy.json \ --signer-file-key-path testkey.pem
Verification: the moment of truth
Now verify each build against the policy:
# Clean build — should PASS
$ cilock verify \
-p signed-policy.json \
-k testpub.pem \
-a attestations/clean-build.json \
-f pkg/index.js
Verification succeeded
Evidence:
Step: npm-pack
0: attestations/clean-build.jsonThe clean build passes. Two expected files, no policy violations.
# Leaked build — should FAIL
$ cilock verify \
-p signed-policy.json \
-k testpub.pem \
-a attestations/leaked-build.json \
-f pkg/index.js
Verification failed
Evidence:
Step: npm-pack
collection rejected: npm-pack
Reason: rego policy evaluation failed for attestor type
https://aflock.ai/attestations/product/v0.1:
BLOCKED: source map file detected in package:
index.js.map — this would leak internal source codeThe policy catches the source map. Verification fails. The package never ships. 512,000 lines of source code stay where they belong.
What this means in practice
This demo uses local key signing, but the same approach works with Sigstore/Fulcio for keyless signing in CI, Archivista for attestation storage, and any OPA-compatible policy. Pick the attestors and signers you need.
In a production pipeline, cilock run wraps your npm pack or npm publish step. The product attestor records every file in the package with its hash. A policy gate runs before the package hits the registry, and if the policy fails, the publish step is blocked. No human in the loop. No .npmignore to forget.
The Rego policy can enforce anything you can express as a rule. No source maps in production packages. No files over 10MB (the Claude Code source map was 59.8 MB). Only allowlisted file extensions. No files containing patterns like API keys or internal URLs.
The broader point
The Anthropic leak wasn't caused by a missing .npmignore file. It was caused by a pipeline that didn't verify its own output. The .npmignore was the proximate cause. The root cause was the absence of an automated policy gate between the build and the registry.
This is the same structural problem behind the LiteLLM compromise, the Trivy compromise, the Axios hijack, and every other supply chain incident from March 2026. The build pipeline is a trust boundary, and most organizations treat it like a conveyor belt — whatever goes in comes out, no questions asked.
Attestation-based verification changes the model. Every step produces cryptographic evidence, policies define what's acceptable, and verification happens before anything leaves the pipeline. If the evidence doesn't match the policy, nothing ships.
The standards behind this (NIST SP 800-204D, the CNCF Supply Chain Best Practices, in-toto, SLSA) have been written. The tooling is open source. The solutions are composable and, frankly, just a prompt away.
Aflock AI is an open source project built by TestifySec, based on our work with Witness and in-toto. Try it yourself:
- Rookery: github.com/aflock-ai/rookery
- cilock-action: github.com/aflock-ai/cilock-action