ADR-019: Withdraw the security profile catalog¶
Status: Accepted (supersedes ADR-017 and ADR-018).
Context: ADR-017 introduced profile
enrichment: compose-lint would match a service's image: against a catalog of
csd-derived security profiles and append an image-specific profile hint to a
finding's fix text — the observed-minimum cap_add for postgres, say,
instead of a generic <SPECIFIC_CAP> placeholder. It shipped as an opt-in
experimental preview: profiles.enabled off by default, no bundled catalog
(§7), and a stderr notice whenever enrichment was active.
The consumption machinery landed in full — loader, schema, reference resolver,
enrich_fix, a validator script, and a profile-validate CI gate, roughly
600 lines of implementation against 950 lines of tests. What never landed was
the other half: #360, the
derive → validate → publish automation that §7 makes a precondition for
endorsing any profile as validated. That epic depends on csd emitting the
catalog schema and on access to a BPF-capable runner, neither of which
materialised.
So compose-lint carried a fully built consumer of a catalog that does not exist, gated behind a flag whose only honest setting was off.
Decision: Withdraw the feature. Remove the profiles package, the
validator, the profile-validate CI gate, the profiles config block, and the
profile_lookup engine parameter. compose-lint does not consume a security
profile catalog, and does not ship per-image capability data.
Rationale:
- The trust precondition was never met. ADR-017 §7 is explicit that compose-lint endorses only profiles its own automation can re-derive. Absent that automation, every hint the pipeline could emit came from a catalog nobody had independently reproduced — exactly the "hand-authored security spec" failure mode §7 exists to prevent.
- Static analysis cannot validate a runtime claim. compose-lint reads
YAML. A derived minimum is valid only for the precise invocation it was
produced under (digest,
user:,command:, mounts). The preview's own stderr notice conceded this — it told users the hint was advisory, unverified and possibly wrong for their deployment. Guidance that ships with that caveat attached is weak guidance. - Dead weight has a cost. ~2,200 lines across source, tests and docs, plus
a CI job and two ADRs reading as Accepted, all of which anyone touching
engine.pyorconfig.pyhad to reason about.
Consequences:
profiles:in a.compose-lint.ymlis now an unrecognized top-level key. It takes the standard warn-and-continue path — a stderr warning, exit code unchanged — so an ordinary run does not break. Under--strict-configit is a hard error (exit 2), like any other unrecognized key.- #4 (make CL-0006's
<SPECIFIC_CAP>actionable) reopens as the original need. The remaining path is to teach users how to determine an image's required set —docker diff, thecapableBPF tool, the "entrypoint switches users ⇒ likely SETUID + SETGID" heuristic — rather than to ship a per-image answer. - #360 is closed as won't-do.
- csd's
internal/paritygate pins compose-lint's curated capability set by value in its owntestdata/, so it does not break; but its premise — that the two sides must not drift — no longer has a compose-lint side. Reconciling it is csd's call and is out of scope here. - Reversal is a
git revertaway if the derivation automation is ever built.
Alternatives considered:
- Deprecate now, remove later. Rejected: the feature is already off by default and self-described as experimental, so a deprecation window protects nobody. It would defer identical work while keeping the maintenance and comprehension cost.
- Keep the consumer, wait for the catalog. Rejected: #360's blockers are external (csd schema support, BPF runner access) with no timeline, and csd itself is under a maintenance freeze.
- Delete ADR-017/018 outright. Rejected: ADRs are decision records, not documentation of current state. Deleting them erases why the catalog was attempted, which is the most useful thing to know before attempting it again.