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
| Artifact | Mutation risk | Gate |
|---|---|---|
| Base / app images | Tag float, public registry poison | Digest pin + Cosign + Kyverno + Harbor allowlist |
| Golden bases | Weekly rebuilds with new CVEs | Same, plus Renovate PRs that re-scan |
| ClawHub skills | Post-publish updates, dependency confusion | Manifest signature + hash allowlist + sandbox observation |
| Edge offline pulls | USB / side-loaded “emergency” images | Same 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
| Request | Result |
|---|---|
image: repo/app:latest | DENY (not a digest) |
| Digest present, no Cosign signature | DENY |
Valid signature, runs as root / privileged / docker.sock | DENY |
image: harbor/.../app@sha256:… + sig + restricted PSS | ALLOW |
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.
