Agent Safety22 min read

Supply Chain Verification: Signing Images and Artifacts

Unsigned pulls make every downstream control irrelevant. Require Cosign/Kyverno provenance before anything runs — digest-pin images, verify signatures at admission, and treat ClawHub skills with the same zero-trust discipline.

Unsigned pulls make every downstream control irrelevant. Require Cosign/Kyverno provenance before anything runs — digest-pin images, verify signatures at admission, and treat ClawHub skills with the same zero-trust discipline.

Part 11 hardened the edge wire. This post answers the residual question: when every hop is mTLS and every host is dieted, what happens if the bytes you scheduled were never yours — a floating latest tag, an unsigned hotfix, a popular skill with a poisoned post-publish update?


The Wrong DNA

Monday’s agent image is green — distroless, seccomp, Tetragon, scoped MinIO. Tuesday, a kubectl set image or a Renovate auto-merge without gates pulls ghcr.io/example/clawql-agent:latest. The tag moved. Inside: a postinstall that Parts 4–7 would have caught if the binary path was still named curl. It isn’t. ATR is happy. mTLS is happy. The knowledge base leaves through an allowlisted API using the agent’s own short-lived token.

Separately: a popular ClawHub skill ships a signed manifest on day one; fourteen days later the maintainer account pushes a “perf fix.” Agents with newSkillsBehavior: auto quietly load it. Download counts are not Cosign.

Phases 1–3 secure a process. Supply chain decides whether that process was ever the one you built.


Two Supply Chains, One Posture

ArtifactMutation riskGate
Base / app imagesTag float, public registry poisonDigest pin + Cosign + Kyverno + Harbor allowlist
Golden basesWeekly rebuilds with new CVEsSame, plus Renovate PRs that re-scan
ClawHub skillsPost-publish updates, dependency confusionManifest signature + hash allowlist + sandbox observation
Edge offline pullsUSB / side-loaded “emergency” imagesSame admission rules on the edge cluster/host

Images are verified once before scheduling. Skills run inside a live ATR session — cryptographic proof plus behavioral observation, not star ratings.

Reputation badges are social. Digests and signatures are controls.


The Architecture Pattern: Supply Chain Verification

Build or mirror → scan (Trivy / OSV) → then Cosign sign, preferring keyless OIDC. Produce SBOM alongside the digest; store as an OCI artifact. Cluster and edge admission: deny tag-only refs; deny missing or invalid signatures; deny privileged escape mounts. Prefer distroless golden bases so Part 4’s kill-switch has fewer binaries to chase. For skills: verify-blob → lint → Kata observe → pin manifestHash → per-agent allowlist → 7-day quarantine. failurePolicy: Fail on security webhooks — downtime must deny deploys, not allow unsigned ones.

CI / release
  build → SBOM → Trivy/OSV → tests → Cosign sign → push Harbor/approved@sha256:…

kubectl / GitOps / Job create ────────────────────────┤

                                            Kyverno validating webhook

                         ┌────────────────────────────┼────────────────────────────┐
                         │ DENY: tag-only, unsigned,  │ ALLOW: digest + valid sig  │
                         │ privileged, docker.sock    │ (+ PSS restricted)         │
                         └────────────────────────────┴────────────────────────────┘


                                              Scheduler / runtime

Digest Pinning and Golden Images

Tag pinning is not pinning:

# Bad
FROM node:22

# Good — pin digest; Renovate opens PRs when it should move
FROM node@sha256:abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789

Multi-stage distroless pattern:

FROM node@sha256:… AS builder
WORKDIR /app
COPY . .
RUN npm ci && npm run build

FROM gcr.io/distroless/nodejs22@sha256:…
COPY --from=builder /app/dist /app
USER nonroot:nonroot
CMD ["index.js"]

Harbor (or equivalent) as the single pull source of truth: only harbor.example.com/approved/...@sha256:... in production. CI alone pushes; pods pull-only with namespace-scoped secrets. Weekly golden rebuilds get scanned, signed, and forced through the same Renovate PR gates.


Cosign: Proving Origin

Sign after scans and tests pass — never before:

# Keyless / OIDC preferred in CI
cosign sign --yes \
  harbor.example.com/approved/clawql-agent@sha256:${DIGEST}

cosign verify \
  --certificate-identity-regexp 'https://github.com/.+/.+/.github/workflows/.+' \
  --certificate-oidc-issuer 'https://token.actions.githubusercontent.com' \
  harbor.example.com/approved/clawql-agent@sha256:${DIGEST}

Stolen deploy keys that skip CI still fail cluster verification if signing keys never leave the trusted OIDC identity.


Kyverno: The Cluster Says No

RequestResult
image: repo/app:latestDENY (not a digest)
Digest present, no Cosign signatureDENY
Valid signature, runs as root / privileged / docker.sockDENY
image: harbor/.../app@sha256:… + sig + restricted PSSALLOW

Policy sketch:

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: require-signed-digests
spec:
  validationFailureAction: Enforce
  background: true
  failurePolicy: Fail
  rules:
    - name: deny-tags
      match:
        any:
          - resources:
              kinds: ['Pod']
      validate:
        message: 'Images must be digest-pinned (@sha256:…)'
        pattern:
          spec:
            containers:
              - image: '*@sha256:*'
    - name: verify-images
      match:
        any:
          - resources:
              kinds: ['Pod']
      verifyImages:
        - imageReferences: ['harbor.example.com/approved/*']
          attestors:
            - entries:
                - keys:
                    publicKeys: |-
                      -----BEGIN PUBLIC KEY-----

                      -----END PUBLIC KEY-----

Also enforce: no root, no privilege escalation, no docker.sock, pod-security.kubernetes.io/enforce: restricted on agent namespaces.

Audit → Enforce: 7 days of audit logs, fix legitimate violators, then flip. Security webhooks use Fail — an outage must not become an unsigned open door.


Skills: The Other Artifact

ClawHub skills are not images. Vetting pipeline:

cosign verify-blob \
  --key cosign.pub \
  --signature skill.manifest.sig \
  skill.manifest.json

clawql skill lint --strict ./skill/
# then Kata sandbox observe ~30m with networkEgress: deny
# then pin hash in org allowlist + per-agent allowlist

Allowlist entry (version-controlled, signed process):

- skillId: summarizer-v2
  manifestHash: sha256:7f8a3b9c…
  approvedBy: security-team
  approvedAt: 2026-05-18T14:22:00Z
  expiryDate: 2026-11-18

Gateway strict-skill-allowlist — hash mismatch refuses start. newSkillsBehavior: block — no auto-install onto every agent. Quarantine 7 days with reduced ATR before full promotion. Private registry for skill dependencies; committed lockfiles; no public fallback for unscoped names.

Signing proves integrity, not intent. Lint and sandbox observation supply behavior. Post-publish updates are the knife; hash pins are the sheath.


Edge and CI

Edge hosts still pull images. Same rules: digest + signature verification before run. “Break-glass unsigned for the outage” must be a dated, audited exception — identical four-eyes instinct as ATR expansion in Part 3. Emergency kubectl apply under pressure is exactly why admission exists.


Honest Failure Modes

Keyless vs long-lived Cosign keys. Prefer OIDC keyless; if you must use keys, HSM/KMS and dual control — a laptop private key is a static secret.

Mirror lag. Harbor allowlisting adds friction and removes “Docker Hub was having a day” as an incident class.

Distroless debugging. Keep a separate debug image signed and admitted only to break-glass namespaces.

Webhook availability. Admission is a dependency; plan HA and DR so Fail doesn’t become an accidental production pause without runbooks.

Skill false comfort. A signature on a malicious-but-consistent manifest still needs lint and observation.

SBOM rot. Generate at build; query on CVE day — without SBOMs you re-scan the world blindly.


Getting Started

Convert Dockerfiles to digest pins and distroless/golden bases; use Renovate for digest PRs. Stand up Harbor allowlist; stop production pulls from public registries. Sign post-scan with Cosign (keyless if possible); attach SBOMs. Deploy Kyverno policies in Audit for 7 days; then Enforce with failurePolicy: Fail. Turn on skill verify-blob + lint + sandbox observation + hash allowlists; disable auto-install. Canary: tag-only and unsigned pods must deny; pinned signed pods must run.

Part 13: encrypt and classify agent memory so a correct binary still can’t gift a plaintext knowledge base to disk thieves.


Companion: DevSecOps-boilerplate. Docs: Image pinning / distroless · Admission signing · Skill vetting.

About the author

Daniel Smith builds ClawQL, an agent operating system for token-efficient discovery and execution over APIs — with observability, hardened tool boundaries, and production routing for LLM workloads. He writes here about the systems problems behind shipping agents.