Skip to content

Minimize sensitive content in evidence bundles while preserving the verifiable signal #1345

Description

@njhensley

Minimize sensitive content in evidence bundles while preserving the verifiable signal

Summary

An evidence bundle today ships the full backing payloads it attests to — the
validate-time snapshot (snapshot.yaml), the resolved recipe
(recipe.yaml), and per-phase CTRF reports (ctrf/<phase>.json) including
captured pod stdout. As snapshot collectors grow (more node/OS/topology detail)
and CTRF accumulates more log lines, the bundle increasingly carries
operational detail that is sensitive to publish — node names, cloud provider
instance IDs, the full node label/taint set, kernel/sysctl tuning, and raw
container stdout that can leak internal endpoints, hostnames, and identifiers.

The goal of this issue is to reduce what the bundle physically ships to the
minimum needed for a strong, verifiable conformance signal — without weakening
the cryptographic binding that makes the bundle trustworthy.

Key observation: the verifiable signal is already digest-based

The trust signal does not come from the raw payloads — it comes from the
signed in-toto predicate, which commits to every artifact by hash
(pkg/evidence/attestation/types.go):

  • Predicate.Recipe.Digest = sha256(canonicalize(recipe.yaml))
  • Predicate.Phases[phase].CTRFDigest = sha256(ctrf/<phase>.json)
  • Predicate.BOM.Digest, Predicate.BOM.ImageCount
  • Predicate.Manifest.Digestmanifest.json, which carries a per-file
    sha256 for every file in the bundle, transitively binding all of them to
    the signature
  • Per-phase Passed/Failed/Skipped counts, Fingerprint, CriteriaMatch,
    validator image digests, catalog version

So a verifier can confirm "recipe X, validated against a cluster matching
criteria Y, passed/failed N tests per phase, signed by identity Z, logged in
Rekor" entirely from the predicate — the bundled snapshot.yaml and CTRF
stdout are the backing content a digest points at, not the signal itself.
That gives us room to shrink or sanitize the backing content while keeping the
binding intact.

This is consistent with the direction already taken in ADR-007
(docs/design/007-recipe-evidence.md), which split large/sensitive logs
into a separate opt-in artifact precisely because they are "large,
contributor-controlled ... and frequently sensitive (cluster identifiers,
internal endpoints)." This issue extends that same reasoning to the snapshot
and CTRF payloads that still ship in the summary bundle by default.

What's currently bundled and where the sensitivity lives

Snapshot (snapshot.yaml) — emitted in full. Sensitive fields include:

  • pkg/collector/k8s/node.go: source-node (node name) and provider-id
    (e.g. aws:///us-west-2a/i-0123456789abcdef0)
  • pkg/collector/topology: the complete node label and taint set across the
    cluster — custom labels routinely encode account IDs, cost centers,
    environment/tenant names, regions
  • pkg/collector/os: GRUB/kernel boot args, full sysctl tree, loaded modules —
    reveals hardening/tuning posture
  • pkg/collector/gpu, systemd: driver/CUDA versions, service config

CTRF reports (ctrf/<phase>.json)pkg/validator/ctrf/types.go:

  • TestResult.Stdout (captured pod stdout, capped by line count/length) and
    TestResult.Message (termination-log text) — free-form log text that can
    contain IPs, DNS names, cert/secret names, internal URLs

Recipe (recipe.yaml) — generally the least sensitive (criteria, component
refs, version pins, constraints), but worth confirming nothing operator-specific
leaks via overlay resolution / matchedRules.

Goal

Define and implement a "minimal evidence" bundle that:

  1. Preserves the full cryptographic verification story (predicate digests,
    manifest binding, DSSE signature, Rekor).
  2. Ships only the data an external auditor needs to trust the conformance claim.
  3. Keeps potentially sensitive operational detail out of the published bundle by
    default, with an explicit opt-in for operators who want to publish more.

Possible approaches (to be discussed — not yet decided)

These are mutually compatible building blocks, not exclusive options:

  • A. Field-level snapshot redaction/allowlist. Emit a sanitized snapshot
    that drops/obfuscates known-sensitive fields (node names, provider IDs, the
    raw label/taint set) while retaining what constraints actually evaluated
    against. Risk: must keep the snapshot digest meaningful — decide whether the
    predicate attests to the sanitized snapshot or to the raw one (and keep the
    raw one local). An allowlist (fail-closed) is safer than a denylist as
    collectors grow.
  • B. CTRF stdout/message scrubbing or omission by default. Strip or redact
    Stdout/Message from published CTRF, or replace full logs with a digest +
    summary, keeping raw logs in the existing opt-in local/logs path. The
    pass/fail counts and per-test status — the actual signal — stay.
  • C. Digest-only backing content. Replace bundled payloads with their
    digests in the predicate and ship the raw payloads only when the operator
    opts in (mirrors the ADR-007 logs split). Verifiers still get the binding;
    publication of the bytes becomes a choice.
  • D. Built-in redaction transforms with a documented allowlist. A small,
    audited set of regex/structured redactors (cf. the existing ELB-hostname
    redaction in pkg/evidence/cncf/scripts/collect-evidence.sh:2263) applied
    consistently, with the redaction policy itself recorded in the predicate so
    verifiers know what was scrubbed.

Open questions

  • Should the predicate attest to the sanitized artifact (so the published
    bundle self-verifies) or to the raw artifact (so the local full bundle
    remains the canonical signed thing)? This is the central design decision.
  • Do downstream consumers (CNCF conformance submission, recipe-health) depend on
    any field we'd want to redact? Inventory those consumers first.
  • Default posture: minimal-by-default with --include-snapshot/--include-logs
    opt-in, vs. full-by-default with --minimal/--redact?
  • How do we make redaction auditable — record the redaction policy version in
    the predicate so a verifier can tell what was removed?

Acceptance criteria (draft)

  • A documented definition of "minimal evidence bundle" contents.
  • Sensitive snapshot fields and CTRF log text are excluded or redacted from
    the published bundle by default (or behind an explicit minimal mode).
  • aicr evidence verify still passes on the minimal bundle: predicate
    digests, manifest binding, and signature all validate.
  • Redaction policy is deterministic (same input → same bytes) and recorded
    so verification stays reproducible.
  • Docs updated (docs/design/007-recipe-evidence.md, CLI/evidence docs) and
    ADR amended for the new default posture.

References

  • pkg/evidence/attestation/types.goPredicate, *Ref digest fields,
    bundle file constants
  • pkg/evidence/attestation/manifest.go — per-file sha256 integrity inventory
  • pkg/collector/{k8s,os,gpu,systemd,topology} — snapshot data sources
  • pkg/validator/ctrf/types.go, pkg/validator/job/result.go — CTRF + stdout
    capture
  • docs/design/007-recipe-evidence.md — summary/logs split rationale (logs
    already treated as sensitive/opt-in)

Metadata

Metadata

Assignees

Fields

No fields configured for Enhancement.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions