Skip to main content
View Markdown ↗

Copy this page

Select and copy the Markdown below, then paste it into your LLM.

On this page

Block a banned dependency with a signed SBOM

An SBOM is an inventory. On its own it does not stop anything: a file that says your release contains a deprecated package is only useful if something reads it before the release goes out, and only trustworthy if you know which tool produced it and that nobody edited it afterwards.

CI/lock records the SBOM as signed evidence. The sbom attestor captures the CycloneDX or SPDX file a scanner writes, command-run records the exact scanner command, and a Rego rule over the SBOM's components decides whether the release passes. In this session the prompt asks Claude to block any release of a Go module that still ships github.com/dgrijalva/jwt-go (unmaintained, CVE-2020-26160), then to migrate the code and show the gate passing.

How this was recorded

TestifySec wrote the prompt and sent it into the session. Claude Code and cilock ran unedited, and the recording was not changed afterwards.

A Claude Code session recording a signed CycloneDX SBOM with cilock and syft, writing a policy that bans github.com/dgrijalva/jwt-go, showing it blocks the repo, migrating to golang-jwt/jwt/v5 and showing the same policy passing

Download terminal recording (.cast)

What happens, step by step

  1. Two keys. Claude generates an ed25519 key that signs the evidence and a separate one that signs the policy. Separate keys let you separate the roles: whoever holds only the evidence key cannot change the rules. In the recording Claude holds both, so that separation exists only once the policy key goes to someone else. cilock policy template --goal sbom is refused because there is no enrolled Pushgate agent, so Claude writes the policy by hand from cilock policy guide --goal sbom.

  2. Record the SBOM. Syft runs inside cilock run with the sbom attestor:

    cilock run --step sbom --offline -k keys/evidence.key -a sbom \
      -o evidence/sbom-before.att.json -- \
      syft dir:. --exclude ./keys --exclude ./evidence -o cyclonedx-json=sbom.cdx.json

    In the recording Claude spells "no platform" as an empty --platform-url; --offline is the documented flag and does the same thing. The SBOM lists github.com/dgrijalva/jwt-go v3.2.0+incompatible.

  3. Write the policy. The sbom step must be signed by the evidence key, syft must exit 0 with exactly the pinned arguments (so a hand-made SBOM cannot stand in), the SBOM must list at least one component, and a banned_jwt_go rule denies any component whose name or Go package URL is github.com/dgrijalva/jwt-go. The rule denies when the SBOM cannot be read, instead of passing. cilock policy validate --require-signed passes on the signed policy.

  4. The gate blocks today's repo. cilock verify sbom.cdx.json --offline -p policy/policy.signed.json -k keys/policy.pub -a evidence/sbom-before.att.json exits 1, and the ban is the only denial:

    policy was denied due to: banned dependency github.com/dgrijalva/[email protected]+incompatible
    (unmaintained, CVE-2020-26160): migrate to github.com/golang-jwt/jwt/v5
  5. Migrate. Only the import path changes in token.go and token_test.go; go.mod moves to github.com/golang-jwt/jwt/v5 v5.3.1 and go vet and go test pass.

  6. The same policy passes. Claude checks the signed policy's SHA-256 has not changed, records a new SBOM with the same command and verifies it: exit 0.

  7. Try to get around it. The new SBOM signed with a key the policy does not trust is refused, and the new SBOM checked against the earlier evidence is refused because the file's digest is not a subject of that evidence. Both exit 1.

Why the gate trusts the SBOM

QuestionAnswered by
Which tool wrote this SBOM, with which arguments?command-run and the pinned-command rule
Was it edited after the scan?cilock verify sbom.cdx.json hashes the file and requires that digest in the signed evidence
Who produced the evidence?the step's functionary names the evidence key
Is the banned package in it?the Rego rule over components[]

What the gate does not show is which source the SBOM describes. The release is verified against the SBOM file, so a clean SBOM from another checkout, presented with its own matching evidence, would pass; the stale-evidence check in step 7 only catches an SBOM paired with the wrong evidence. To bind the inventory to the source you release, verify against the commit: record the step with the git attestor and verify with the commit as the subject. A go.sum digest does not do this, because it names only the checksum file and different source trees and go.mod selections can share one. When you ship a binary, chain the SBOM step to the build step with artifactsFrom so the inventory is tied to the bytes you release; see Policy verification. The sbom attestor documents the fields a rule can read, and Syft the scanner side.

Run it yourself

The recording used cilock built from main at commit 5b614726 and syft 1.44.0. Keep the evidence key in a secret store, or sign keyless in CI with the job's OIDC identity; see Signing and identity. The recording ran offline, so the signatures carry no timestamp.

Reference generated from the product documentation. Match commands and support details to your installed release.