docs: state which images CertificateAudit can assess - #175
Conversation
The rule reads each expected digest from a sidecar the image build writes, so an image with no sidecar has nothing to compare against and fails rather than skipping. That is deliberate — the sidecar-exists criteria exist so a missing one cannot pass vacuously — but it puts a floor on which images the rule can meaningfully assess, and nothing said so. Pin the floor to a version rather than the commit the doc already cited: v1.2.30 is the first apko release carrying writeCABundleChecksums; v1.2.29 does not have it. Every current image is well past that, so this matters when scanning something older — an archived release, a pinned image from before the change, or an image built by other tooling. The part worth writing down is how to tell such a failure from a real one, since the rule verdict is `fail` either way. It is distinguishable from the scan artifact rather than needing the image: with --oval-results (or an ARF), tst:4 false with tst:2 error means no usable sidecar, and tst:4 true with tst:2 false means a sidecar was read and disagreed. The error is the tell — the variable behind the comparison collected no values because there was nothing to read one from. Measured both ways against built images rather than reasoned from the criteria. The same reading applies to tst:6/tst:7 and tst:11/tst:12. That also makes the distinction available retrospectively, on a results file kept as evidence, which an in-image check cannot do. Two things went stale and are corrected while here. CertificateAuditTest.xml is no longer missing — chainguard-dev#164 added it, and validate_checks now covers all 8 definitions — so that entry leaves Known gaps. The /kaniko branch a real image takes has changed. kaniko/ssl/certs/ca-certificates.crt was not in apko's caBundlePaths at v1.2.35 but is at v1.2.43, and the published kaniko image now ships its own sidecar where in August it did not. Confirmed by running the guard against the image: the copy matches its own sidecar and that sidecar matches obj:10's pattern, which is the first time that pattern has been checked against anything but a synthetic fixture. So production kaniko is on tst:11/tst:12 and the fallback is now what an older kaniko image would take. Both branches stay fixture-covered; nothing needs changing. Docs only. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
0xDom-S
left a comment
There was a problem hiding this comment.
🤖: PR #175 adds a "Which images this rule can assess" section to docs/certificate-audit.md documenting the apko v1.2.30 sidecar floor, a two-test triage table for distinguishing "no usable sidecar" from real trust-store drift, an update to the /kaniko branch discussion, and two stale-content corrections (removed validate_checks Known gap; ECS bundle version bump v1.2.35→v1.2.43).
This is a careful, well-evidenced documentation-only change. I independently re-derived the load-bearing OVAL mechanism (tst:4 only_one_exists over obj:4; tst:2's comparator resolves via var:1←obj:4, so an absent sidecar yields error, not a vacuous pass — tst:4 is AND-ed at CertificateAuditTest.xml:20 and fixtures_test.go:619-622 pins Fail) and it holds. No critical issues; nothing security-blocking. Inline comments below cover the accuracy issues worth fixing before merge (see S1–S6 in review). Two supplementary points could not be anchored to a diff line and are recorded here:
- docs/certificate-audit.md:399-402 (unchanged) — This deferral rationale ("the copy is currently byte-identical to its system bundle... revisit if the /kaniko copy ever starts diverging, or gains a sidecar of its own") is contradicted by the new text at :185-194, which reports the /kaniko copy now does have its own sidecar and real images have moved onto tst:11/tst:12. The PR's own evidence fires the stated revisit trigger, then the new text concludes "nothing needs changing." Recommend updating :399-402 so the file doesn't argue both sides.
- docs/certificate-audit.md:413-416 (unchanged) — The Known gap here should be restated on the new facts: the branch production images now take (obj:10, per the new :185-194 text) is the unguarded one — tests/stamps/run.sh:61 marks obj:10 required=no, and .github/workflows/update-ca-cert.yaml:35 stamps only the jre image daily. Deferral may still be correct; the rationale just needs updating.
- docs/certificate-audit.md:370 (unchanged) — Says the /kaniko copy exists "only" on cgr.dev/chainguard-private/kaniko, in tension with the new :187-189 wording "the published kaniko image." One word (e.g. "published private image") would remove the ambiguity.
- tests/oscap-offline/internal/scan/fixtures_test.go:341-343 (unchanged) — This comment ("cgr.dev/chainguard/kaniko ships none today, so the copy falls back to the stamp at caStampPath") now reads false against the new doc text at :185-194 describing real kaniko images shipping their own sidecar. Comment-only fix, but will mis-scope future fixture work if left stale.
Verdict from the underlying review: COMMENT. Fix the unqualified generalization at :114-115 (see inline comment) and reconcile the /kaniko deferral rationale — both are one-clause fixes. The apko version claims (:86-87, :90, :187, :407) have no in-repo regression guard and rest on the author's stated manual verification; flagged for reviewer awareness, not a defect.
| So `tst:4 false` says the rule *could not assess* this image — it predates the | ||
| mechanism, or the sidecar is malformed — which is a different statement from | ||
| "this image's trust stores were modified". `tst:4 true` with `tst:2 false` is | ||
| the real finding. The same reading applies to `tst:6`/`tst:7` for the Java |
There was a problem hiding this comment.
🤖: The generalization "The same reading applies to tst:6/tst:7 for the Java truststore and tst:11/tst:12 for a /kaniko copy" is unqualified and will mis-triage ordinary images.
The reading only transfers when the truststore or /kaniko copy actually exists. On a non-Java image (the common case): tst:5 is none_exist over obj:5 (CertificateAuditTest.xml:64-66) → true, satisfying the Java OR at :39-45. But tst:6 is only_one_exists over obj:6 (:67-69, :117-122) → false, and tst:7's state ste:3 resolves via var:2←obj:6 (:152-155, :168-170) → error. That is byte-for-byte the "no usable sidecar" column of the new triage table (:102-105) appearing on a fully assessable, passing scan. The same holds for non-kaniko images with tst:11/tst:12.
An analyst triaging a fail per :111-115 could wrongly conclude "the Java truststore / /kaniko copy could not be assessed" on an image that has neither.
Suggested fix: qualify the sentence — "…where that truststore or copy is present. On an image with neither, tst:6/tst:11 are false and tst:7/tst:12 error as a matter of course; tst:5 and tst:3 carry those branches and the absence is not a finding."
| collected no values, because there was no sidecar to read one from. A `false` | ||
| there means a sidecar was read and disagreed. | ||
|
|
||
| So `tst:4 false` says the rule *could not assess* this image — it predates the |
There was a problem hiding this comment.
🤖: The disjunction "tst:4 false says the rule could not assess this image — it predates the mechanism, or the sidecar is malformed" omits post-build sidecar deletion.
Because tst:2's comparator comes from var:1←obj:4 and not from the bundle, the tst:4 false / tst:2 error signature is content-independent on the bundle side. A sidecar deleted after build — maliciously or via an image-slimming step — produces the identical signature to a pre-v1.2.30 image. The doc's own measurement at :117-118 was taken with the bundle intact, so it doesn't separate the two cases.
Note that in-image corroboration is not available either: sidecars are unowned (apk info -W → "Could not find owner package"), and apk audit always reports benign A for them, so the X ("owned but missing") code can never fire for a deleted sidecar (apk audit --system will NOT surface this).
Suggested fix: add "or the sidecar was removed after the build" to :111-112 and state that tst:4 false is not an exoneration. As a disambiguator, the bundle's actual SHA-256 is collected by obj:2 (filehash58_object, :100-103) and can be compared out-of-band against the signed build.
| **A failure caused by the floor is not distinguishable from a real one by the | ||
| rule verdict alone** — both are `fail`. It *is* distinguishable from the scan | ||
| artifact, in the per-test OVAL results, so no access to the image is needed and | ||
| an archived results file can be read after the fact. Scan with `--oval-results` |
There was a problem hiding this comment.
🤖: Two issues with the --oval-results guidance here.
(a) Invocation: this is the only occurrence of --oval-results/ARF anywhere in the repo; in OpenSCAP, --oval-results is functional only alongside --results. Worth spelling out the full invocation so a reader doesn't get an empty result — the repo's own harness (tests/oscap-offline/internal/scan/command.go:148-150, tests/e2e/run.sh:150-152) passes only --results and won't produce this per-test detail.
(b) Sensitivity: full OVAL results/ARF serialize textfilecontent54_item matched text for every rule in the profile, not just CertificateAudit — including /etc/shadow-matching rules co-selected in the same profile (e.g. UserPasswordConfiguredTest.xml, NoUsersCheck.xml use a regex whose [^:\n]* group captures the crypt field). This is bounded (check_existence="none_exist", instance=1, first match only, image already failing that rule) and not a vulnerability, but since :98-99 explicitly anticipates the file being read by someone with no image access, a clause noting full OVAL output is credential-bearing (or scoping the guidance to oscap oval eval --id oval:org.CABundleHash:def:1) is cheap and correct.
|
|
||
| | image | outcome | | ||
| | --- | --- | | ||
| | built by apko **v1.2.30 or later** | assessable | |
There was a problem hiding this comment.
🤖: The apko v1.2.30 sidecar-floor claim here (and the related version claims at :90, :187, and :407 for the ECS bundle bump v1.2.35→v1.2.43) are the only apko version references in the tree and nothing pins them in-repo. They rest entirely on the author's stated manual verification, which could not be checked offline in this review. Not a defect — flagged so reviewers know which claims carry no regression guard.
| - **`tst:13` before falling back to `tst:9`.** A sidecar beside the `/kaniko` | ||
| copy takes precedence, so a divergent copy cannot sidestep its own sidecar by | ||
| appealing to the system one. | ||
| appealing to the system one. Which branch a real image takes has |
There was a problem hiding this comment.
🤖: This new text (asserting the /kaniko copy now ships its own sidecar and real images have moved onto tst:11/tst:12) contradicts the unchanged deferral rationale later in the file at docs/certificate-audit.md:399-402, which still reads: "The copy is currently byte-identical to its system bundle, so the drift being guarded against is remote… Revisit if the /kaniko copy ever starts diverging, or gains a sidecar of its own." This PR's own evidence fires that revisit trigger verbatim, yet :193-194 concludes "nothing needs changing."
Two follow-ups worth doing in this PR (both landing in unchanged lines outside the diff, so not separately anchorable):
- Update :399-402 so the file doesn't argue both sides.
- The Known gap at :413-416 should note that the branch production images now take (obj:10) is the unguarded one — tests/stamps/run.sh:61 marks obj:10 required=no, and .github/workflows/update-ca-cert.yaml:35 stamps only cgr.dev/chainguard/jre:latest daily. Deferral may still be the right call; the rationale just needs restating on the new facts.
| appealing to the system one. | ||
| appealing to the system one. Which branch a real image takes has | ||
| changed. `kaniko/ssl/certs/ca-certificates.crt` was **not** in apko's | ||
| `caBundlePaths` at v1.2.35 but **is** at v1.2.43, and the published kaniko |
There was a problem hiding this comment.
🤖: Minor wording ambiguity: this line says "the published kaniko image", while the unchanged line at docs/certificate-audit.md:370 says the /kaniko copy exists "only" on cgr.dev/chainguard-private/kaniko, and :413-414 says the daily workflow "inspects only public images." Both readings are individually defensible, but one word here (e.g. "published private image", or naming the ref explicitly) would remove the ambiguity for future readers.
Docs only — no datastream, fixture or tooling change.
Why
CertificateAuditreads each expected digest from a sidecar the image buildwrites, so an image with no sidecar has nothing to compare against, and the
rule fails rather than skipping. That is deliberate —
tst:4/tst:6/tst:11exist so a missing sidecar cannot pass vacuously, and
certificate_audit/fail_missing_stamppins it — but it puts a floor on whichimages the rule can meaningfully assess, and nothing said so.
The floor, pinned to a version
The doc already cited the apko commit; a version is more useful to whoever hits
this:
v1.2.30 is the first release carrying
writeCABundleChecksums. Checked eachcached version and pulled v1.2.30 specifically to close the bracket. Every
current image is well past it, so this matters only when scanning something
older — an archived release, a customer's pinned image from before the change,
or an image built by other tooling.
Telling that failure from a real one
The rule verdict is
faileither way, but the scan artifact distinguishesthem — so no access to the image is needed, and a results file kept as
evidence can be read after the fact:
tst:4— sidecar exists and parsesfalsetruetst:2— bundle matches the sidecar digesterrorfalseThe
erroris the tell: the variable behind the comparison collected no valuesbecause there was nothing to read one from. A
falsethere means a sidecar wasread and disagreed.
Measured, not reasoned from the criteria — I built one image with the sidecar
removed and the bundle intact, and one with the sidecar intact and the bundle
appended to, and scanned both. Same reading applies to
tst:6/tst:7(Java) andtst:11/tst:12(/kaniko).Two stale things corrected while here
CertificateAuditTest.xmlleaves Known gaps. #164 added it andvalidate_checksnow covers all 8 definitions; the doc still claimed it wasmissing.
The
/kanikobranch a real image takes has changed.kaniko/ssl/certs/ca-certificates.crtwas not in apko'scaBundlePathsatv1.2.35 but is at v1.2.43, and the published kaniko image now ships its own
sidecar where in August it did not. Confirmed by running the guard against it:
the copy matches its own sidecar, and that sidecar matches
obj:10's pattern —the first time that pattern has been checked against anything but a synthetic
fixture. So production kaniko is on
tst:11/tst:12, and the fallback is nowwhat an older kaniko image would take. Both branches remain fixture-covered,
so nothing needs changing.
The ECS bundle reference also moves v1.2.35 → v1.2.43; it is still unstamped by
this rule and stays in Known gaps.
🤖 Generated with Claude Code