Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Implement software supply chain integrity verification for container builds using the in-toto framework to create cryptographically signed attestations across CI/CD pipeline steps.
.claude/skills/implementing-supply-chain-security-with-in-toto/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-04 | ✗→✓ | ▲ Improved | — | — |
| case-18 | ✗→✓ | ▲ Improved | — | — |
| case-14 | ✗→✓ | ▲ Improved | — | — |
| case-12 | ✗→✓ | ▲ Improved | — | — |
| case-06 | ✓→✓ | = Same ✓ | — | — |
in-toto is a CNCF graduated project that ensures the integrity of software supply chains from initiation to end-user installation. It creates a verifiable record of the entire software development lifecycle by generating cryptographically signed attestations (called "link metadata") at each step, proving what happened, who performed it, and what artifacts were produced. For container environments, in-toto verifies that images deployed to Kubernetes followed approved build processes and have not been tampered with.
The layout is the central policy document that defines:
pythonfrom in_toto.models.layout import Layout, Step, Inspection from securesystemslib.interface import import_ed25519_privatekey_from_file # Create the supply chain layout layout = Layout() layout.set_relative_expiration(months=3) # Define the code clone step step_clone = Step(name="clone") step_clone.expected_materials = [] step_clone.expected_products = [["CREATE", "src/*"]] step_clone.pubkeys = [clone_functionary_keyid] step_clone.expected_command = ["git", "clone"] step_clone.threshold = 1 # Define the build step step_build = Step(name="build") step_build.expected_materials = [["MATCH", "src/*", "WITH", "PRODUCTS", "FROM", "clone"]] step_build.expected_products = [["CREATE", "image.tar"]] step_build.pubkeys = [build_functionary_keyid] step_build.expected_command = ["docker", "build"] step_build.threshold = 1 # Define the scan step step_scan = Step(name="scan") step_scan.expected_materials = [["MATCH", "image.tar", "WITH", "PRODUCTS", "FROM", "build"]] step_scan.expected_products = [["CREATE", "scan-report.json"]] step_scan.pubkeys = [scan_functionary_keyid] step_scan.threshold = 1 layout.steps = [step_clone, step_build, step_scan]
Each step execution generates a link file containing:
At deployment time, the verifier checks:
bash# Generate Ed25519 key pairs for each functionary mkdir -p keys # Project owner key (signs the layout) in-toto-keygen --type ed25519 keys/owner # CI builder key in-toto-keygen --type ed25519 keys/builder # Security scanner key in-toto-keygen --type ed25519 keys/scanner
python#!/usr/bin/env python3 """Generate in-toto supply chain layout for container builds.""" from in_toto.models.layout import Layout, Step, Inspection from in_toto.models.metadata import Envelope from securesystemslib.signer import CryptoSigner from securesystemslib.interface import import_ed25519_publickey_from_file def create_container_build_layout(): layout = Layout() layout.set_relative_expiration(months=6) # Load functionary public keys builder_key = import_ed25519_publickey_from_file("keys/builder.pub") scanner_key = import_ed25519_publickey_from_file("keys/scanner.pub") layout.keys = { builder_key["keyid"]: builder_key, scanner_key["keyid"]: scanner_key, } # Step 1: Source code checkout checkout = Step(name="checkout") checkout.expected_materials = [] checkout.expected_products = [ ["CREATE", "Dockerfile"], ["CREATE", "src/*"], ["CREATE", "requirements.txt"], ] checkout.pubkeys = [builder_key["keyid"]] checkout.threshold = 1 # Step 2: Build container image build = Step(name="build") build.expected_materials = [ ["MATCH", "Dockerfile", "WITH", "PRODUCTS", "FROM", "checkout"], ["MATCH", "src/*", "WITH", "PRODUCTS", "FROM", "checkout"], ] build.expected_products = [["CREATE", "image-digest.txt"]] build.pubkeys = [builder_key["keyid"]] build.threshold = 1 # Step 3: Security scan scan = Step(name="scan") scan.expected_materials = [ ["MATCH", "image-digest.txt", "WITH", "PRODUCTS", "FROM", "build"] ] scan.expected_products = [ ["CREATE", "vulnerability-report.json"], ["CREATE", "sbom.json"], ] scan.pubkeys = [scanner_key["keyid"]] scan.threshold = 1 # Inspection: Verify no critical vulnerabilities inspect_vulns = Inspection(name="verify-no-critical-vulns") inspect_vulns.expected_materials = [ ["MATCH", "vulnerability-report.json", "WITH", "PRODUCTS", "FROM", "scan"] ] inspect_vulns.run = [ "python", "-c", "import json,sys; r=json.load(open('vulnerability-report.json')); " "sys.exit(1) if any(v['severity']=='CRITICAL' for v in r.get('vulnerabilities',[])) else sys.exit(0)" ] layout.steps = [checkout, build, scan] layout.inspect = [inspect_vulns] return layout if __name__ == "__main__": layout = create_container_build_layout() # Sign with owner key and save owner_signer = CryptoSigner.from_priv_key_uri("file:keys/owner") envelope = Envelope.from_signable(layout) envelope.create_signature(owner_signer) envelope.dump("root.layout") print("Layout created and signed: root.layout")
bash# In CI/CD pipeline - record each step # Step 1: Checkout in-toto-run --step-name checkout \ --key keys/builder \ --products Dockerfile src/* requirements.txt \ -- git clone https://github.com/org/app.git . # Step 2: Build in-toto-run --step-name build \ --key keys/builder \ --materials Dockerfile src/* \ --products image-digest.txt \ -- bash -c "docker build -t app:latest . && docker inspect --format='{{.Id}}' app:latest > image-digest.txt" # Step 3: Scan in-toto-run --step-name scan \ --key keys/scanner \ --materials image-digest.txt \ --products vulnerability-report.json sbom.json \ -- bash -c "trivy image --format json app:latest > vulnerability-report.json && syft app:latest -o json > sbom.json"
bash# Verify the entire supply chain in-toto-verify --layout root.layout \ --layout-key keys/owner.pub \ --link-dir ./link-metadata/ # If verification passes, proceed with deployment if [ $? -eq 0 ]; then kubectl apply -f deployment.yaml echo "Supply chain verification passed - deploying" else echo "SUPPLY CHAIN VERIFICATION FAILED - blocking deployment" exit 1 fi
Integrate with a policy engine to verify attestations at admission:
yamlapiVersion: admissionregistration.k8s.io/v1 kind: ValidatingWebhookConfiguration metadata: name: in-toto-verifier webhooks: - name: verify.in-toto.io rules: - apiGroups: ["apps"] resources: ["deployments"] operations: ["CREATE", "UPDATE"] clientConfig: service: name: in-toto-webhook namespace: security path: /verify failurePolicy: Fail sideEffects: None admissionReviewVersions: ["v1"]
in-toto attestations map directly to SLSA (Supply chain Levels for Software Artifacts) requirements:
| SLSA Level | in-toto Requirement | |------------|-------------------| | Level 1 | Build process documented (layout exists) | | Level 2 | Signed attestations from hosted build service | | Level 3 | Hardened build platform, non-falsifiable provenance | | Level 4 | Two-party review, hermetic builds |
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-04 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-01 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-19 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-20 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-18 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-07 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-03 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-06 | pass→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-14 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-02 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-22 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-08 | pass→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-12 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-16 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-21 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-11 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-05 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-09 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-10 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-13 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-15 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-17 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-23 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 23 cases were attempted. The headline lift of +17 percentage points is the difference between those two pass rates over the 23 comparable cases.
The per-case answers from this run were removed by the retention sweep, so the case table below shows the verdicts without the text either arm produced. The counts above were recorded at the time and are unaffected. Answers are now kept for 180 days.
Other measured skills in the registry, with their headline benchmark lift.