Last year I was debugging a production incident where a system prompt had been changed without anyone noticing. The model started giving weird responses, and it took us two days to figure out that someone had pushed a "minor" prompt tweak that completely changed the tone and safety behaviour of the system.
That's when it hit me: we spend enormous effort signing container images and validating SBOMs. But the actual AI components, the prompts, the training data configs, the eval benchmarks , flow through our pipelines with zero integrity verification.
So I built a tool to fix that. This is how I built it.
The Gap That Bugged Me
I work with Kubernetes, Terraform, and CI/CD pipelines daily. Tools like Sigstore, SLSA, and in-toto have made traditional software supply-chain security really solid. But when I looked at how my team handled LLM artifacts, it was basically the wild west.
Think about what goes into a production LLM system:
- System prompts that define the model's personality and safety boundaries
- Training corpora or RAG document sets that ground the model's knowledge
- Evaluation benchmarks that prove the model meets quality bars
- Routing configurations that decide which model handles which request
- SLO definitions that set latency, cost, and error budgets
All of these are just files sitting in git repos or S3 buckets. None of them get the cryptographic treatment we give to a Docker image. Any of them could be tampered with, and nobody would know until something breaks in production.
What I Built
I called it llmsa (LLM Supply-Chain Attestation). It's a Go CLI that creates typed cryptographic attestations for those five artifact categories.
The concept is simple enough: for each artifact type, the tool reads the relevant files, computes SHA-256 digests, bundles them with metadata into a statement, signs the whole thing with a DSSE envelope, and stores it. Later, at verification time, it recomputes all the digests and checks that nothing changed.
Here's what a typical workflow looks like:
# Create attestations for each artifact type
llmsa attest create --type prompt --config configs/prompt.yaml
llmsa attest create --type eval --config configs/eval.yaml
# Sign everything
llmsa sign --key key.pem
# Verify integrity
llmsa verify --source .
# Enforce policy gates
llmsa gate --policy policy.yaml
Each command returns semantic exit codes. 0 for pass, 12 for tamper detected, 11 for signature failure, and so on. This makes it easy to wire into any CI pipeline.
The Provenance Chain Was the Hard Part
The tricky design problem wasn't individual attestations. It was the dependencies between them.
Eval results are meaningless unless they reference the exact prompt and corpus versions that were tested. A routing config should only be trusted if it points to eval results that actually passed. SLO definitions should reference the routing config they were designed for.
I modelled this as a directed acyclic graph:
eval depends on → prompt + corpus
route depends on → eval
slo depends on → route
The verification engine checks referential integrity (does this eval actually point to an existing prompt attestation?), temporal ordering (was the prompt created before the eval that references it?), and type constraints (route can't skip eval and depend on corpus directly).
Getting this right took about a month of iteration and a lot of edge case tests.
Signing: Why Sigstore Changed Everything
I initially started with plain Ed25519 keys stored as PEM files. That works fine locally, but key management in CI is painful. You need to distribute keys, rotate them, handle revocation.
Then I integrated Sigstore's keyless signing. In GitHub Actions, the workflow's OIDC token is available automatically. Sigstore binds the signature to the workflow identity, so you get proof of who signed what without managing any keys.
The tool falls back to PEM signing when Sigstore isn't available, so it works in air-gapped environments too. My contribution to the cosign project was actually related to a certificate parsing edge case I found while building this.
Policy Enforcement: Two Engines for Different Needs
I built two policy engines because one wasn't enough.
For simple rules like "all five attestation types must be present and signed", there's a YAML gate engine. You write declarative rules, and it evaluates them. Covers maybe 80% of real-world use cases.
For complex rules like "eval attestations must reference corpus versions from the last 30 days" or "route changes require signatures from two different CI pipelines", there's an OPA Rego engine. It receives structured input about all the attestation results and can express arbitrary policy logic.
Both engines produce the same violation format, so the rest of the pipeline doesn't care which one you use.
The Kubernetes Webhook
The last piece was deployment-time enforcement. I built a validating admission webhook that intercepts pod creation, looks up attestation bundles from an OCI registry based on the container image reference, and runs the full verification pipeline.
If the attestations are missing, tampered, or violate policy, the pod doesn't get admitted. Fail-closed by default, with a fail-open option for gradual rollout.
This means you can have a complete chain: artifacts are attested in CI, signed with Sigstore, pushed to an OCI registry, and verified at deployment time before any pod runs.
What the Numbers Look Like
I'm a firm believer in measuring things, so here's what the test suite shows:
- Tamper detection: I wrote a 20-case test suite that seeds specific corruptions (modified digests, forged signatures, wrong schemas, broken chain references). Detection rate is 20/20.
- Verify latency: p95 of 27ms for 100 statements on a standard CI runner. Not a bottleneck.
- Determinism: Running attestation creation twice on the same inputs produces identical outputs. Important for reproducibility.
- Test coverage: 85%+ across the core packages. The sign package is lower (~77%) because the Sigstore keyless path requires a live OIDC environment that can't be unit-tested.
Honest Limitations
I want to be upfront about what this tool does NOT do:
- It's not a runtime prompt-injection defence. It verifies integrity before deployment, not during inference.
- It doesn't guarantee model quality. It ensures that whatever was evaluated is what gets deployed, but the evaluation itself could be flawed.
- It doesn't replace threat modelling or security review. It's one control in a defence-in-depth strategy.
- Performance numbers are from my test setup. Your mileage will vary.
What I'd Do Differently
If I started over, I'd probably build the provenance chain verification first instead of last. That turned out to be the most valuable feature , catching stale references is where most real-world integrity problems live.
I'd also invest more time in the OCI distribution layer earlier. Being able to store and pull attestation bundles from the same registry as your container images makes the operational story much cleaner.
Give It a Try
The whole project is open source under Apache 2.0:
- GitHub: LLM-Supply-Chain-Attestation
- Latest release: v1.0.1 with signed binaries and SBOM
- Docs: Quickstart guide, threat model, policy guide, and architecture decision records are all in the repo
If you're dealing with similar problems in your LLM pipeline, or if you think I'm approaching this wrong. I'd genuinely love to hear about it.
Find me on GitHub or LinkedIn. Always happy to talk about supply-chain security and LLM ops.
Top comments (0)