Software supply-chain security has an unfortunate tendency to become a shopping list.
Add a scanner. Add a dashboard. Add another workflow. Add a JSON file nobody knows whether to trust.
What interested me this week was a different question: where should the evidence become true in the first place? I ended up chasing that question through a burst of Apache pull requests, and the code got smaller as the leverage got larger.
The path was not really about SBOM generation. It was about authority. A filename can claim one version while a binary embeds another. An SBOM can list packages without making clear which build graph it describes. A container tag can move. A signature can prove who signed some bytes without proving those are the bytes the consumer thinks they are.
Supply-chain evidence becomes useful when every claim has an authoritative subject.
Iceberg
Prove which release binary this actually is, without rebuilding after the vote.
ASF Parent
Put the evidence primitive where downstream Maven releases already inherit behavior.
01 — Release identityFix the claim without changing the object.
The first useful seam showed up in the Apache Terraform Provider for Iceberg. The release-candidate binaries were promoted bit-for-bit after the Apache vote. That is exactly the trust boundary you want to preserve. But Go 1.24 and newer stamps module version information from the VCS state it sees during the build, so a final artifact named as the final release could still report the RC tag in its embedded build info.
The tempting fix would be to rebuild after the vote. That would make the metadata look prettier by changing the thing the vote covered. Wrong direction.
The patch instead changes only the local VCS view during the RC build: an ephemeral final-version tag exists at the same commit, Go sees the
final semantic version, and the binary still records the exact voted source revision. Then every packaged binary is independently inspected
with go version -m to verify the module version, the exact vcs.revision, and vcs.modified=false.
# The vote covers the RC commit and its built convenience binaries. vX.Y.Z-rcN commit ↓ local-only vX.Y.Z tag at the SAME SHA ↓ build voted convenience binaries ↓ go version -m module version = vX.Y.Z vcs.revision = exact RC SHA vcs.modified = false ↓ promote the same bytes after the vote
That became the discipline for everything that followed: identify the authoritative object first, then make the metadata describe it honestly.
02 — Dependency graphAn SBOM needs an honest boundary.
Apache Celix made the next problem explicit. Celix already had a real Conan dependency graph. The information existed before the SBOM did.
The first proposal, #844, resolved one documented Linux / Release / full-feature graph,
preserved the resolution as a conan.lock receipt, and generated CycloneDX from that same graph.
The important part was not pretending that one configuration represented every platform, debug mode, sanitizer build, test dependency, or system-package path. An inventory only becomes evidence when you can answer inventory of what?
An SBOM without a statement of which build graph it describes is inventory-shaped ambiguity.
Then the reviewer made the implementation better. #844 was closed without merge after feedback that Conan's native CycloneDX deployer could
do the job with a much simpler conan install ... --deployer=cyclone_1.6 path. That is not a failure of the idea. It is almost a perfect
demonstration of it.
Within minutes, #845 superseded it and took the smaller seam:
the existing Conan CI now invokes the built-in cyclone_1.6 deployer directly, publishes sbom/sbom-cyclonedx-1.6.json as the
celix-conan-sbom artifact, and carries no custom SBOM generator or lockfile handling.
That change also sharpens the claim boundary. The lockfile in #844 was useful as a resolution receipt, but #845 deliberately removes it to keep this patch scoped to native SBOM generation. If exact dependency-resolution provenance is needed later, it can be added as a distinct evidence layer instead of being smuggled into the SBOM change. Reproducibility and provenance are related, but they are not the same claim.
03 — Artifact bindingPublishing an SBOM is not the same as binding one.
Apache Camel K #6777 pushed the question farther. The release already had SBOM machinery. The gap was whether the evidence described the actual independently published binaries and container images.
A module-level SBOM sitting beside several release artifacts is useful context. It is not yet a release contract. The stronger design binds every claim to an immutable subject: exact source SHA, embedded binary revision, archive digest, SBOM digest, immutable OCI manifest digest, image SBOM, attestation, and then a deterministic receipt tying the whole thing together.
source SHA ├─→ client binary → embedded VCS revision │ ├─→ archive SHA-256 │ └─→ CycloneDX SBOM SHA-256 │ └─→ pushed image → immutable OCI digest ├─→ image SBOM └─→ CycloneDX attestation all subjects └─→ deterministic release-assurance.json └─→ keyless signature / attestations └─→ consumer-side verifier └─→ TRUST | REJECT
This is the distinction that matters: publishing an SBOM tells me what a tool saw. Binding the SBOM to an immutable artifact tells me what the SBOM is evidence about.
The assurance pipeline also has to obey the discipline it asks of the product. During review, the release tooling itself got tightened: unnecessary credentials were removed for public-image scanning, tool declarations were centralized, and assurance tools were pinned rather than floated. The verifier fails closed on missing, modified, mismatched, or differently attested evidence.
04 — InheritanceThen look one layer up.
Once the lower-level behavior is understood, the next question changes. It is no longer “can this project generate an SBOM correctly?” It becomes: where should correct release behavior live so projects stop repeating it?
That is what made ASF Maven Parent #597 interesting.
Apache Maven's own parent already had the prior art: bind the CycloneDX Maven Plugin to the release profile, run makeAggregateBom during package,
attach JSON and XML SBOM artifacts, and let normal Maven deployment publish them with the release.
The ASF parent issue asks to generalize that proven behavior one inheritance layer higher. The proposed change does not invent a scanner, a format, a service,
or a separate publication channel. Ordinary developer builds do not change. It adds the native evidence generator to the existing apache-release profile.
The smallest patch had the largest blast radius.
One project patch improves one project. A parent-POM patch can change release behavior for downstream projects that already inherit from it. The leverage is architectural, not volumetric.
+ <version.cyclonedx-maven-plugin>2.9.3</version.cyclonedx-maven-plugin> <profile> <id>apache-release</id> <build> <plugins> + <plugin> + <groupId>org.cyclonedx</groupId> + <artifactId>cyclonedx-maven-plugin</artifactId> + <version>${version.cyclonedx-maven-plugin}</version> + <executions> + <execution> + <goals><goal>makeAggregateBom</goal></goals> + <phase>package</phase> + </execution> + </executions> + </plugin>
The hard part was not writing eighteen lines of XML. The hard part was proving those were the right eighteen lines: release-only, compatible with the ASF parent's baseline, aligned with behavior Apache Maven already uses, and attached to the normal release path instead of creating parallel compliance machinery.
That is the point where the whole run snapped into focus. Iceberg established source identity. Celix forced an honest dependency boundary—and then reviewer feedback produced a smaller native follow-up almost immediately. Camel K made the evidence-to-artifact binding explicit. Maven Parent asked where the successful primitive should live organizationally.
The highest-leverage compliance patch may be the one that disappears into inheritance.
05 — The design ruleNative evidence first. External assurance second.
Conan should know the Conan graph. Maven should know the Maven reactor and the artifacts it attaches. Go should know what source revision was compiled. OCI registries should provide immutable digests. CycloneDX should represent inventory. Cosign should bind signed claims to subjects.
An external assurance layer can verify, retain, compare, and reason over those receipts. It should not rebuild every package manager's semantics from scratch.
Find the authoritative subject ↓ Find the native system that knows its graph / identity ↓ Emit evidence there ↓ Bind evidence to immutable artifacts ↓ Verify from the consumer side ↓ Move successful policy upward to the inheritance point
06 — What this does not proveKeep the claims smaller than the evidence.
None of this means “Apache now has SBOMs everywhere.” Maven Parent #597, Camel K #6777, Iceberg #105, and Celix #845 are open upstream proposals at this update. Celix #845 supersedes the closed, unmerged #844. A single SBOM never represents every possible build configuration unless the build model actually makes that true.
SBOM publication alone also does not prove release integrity. Signatures, provenance, immutable identities, vulnerability analysis, policy evaluation, and independent verification are separate layers. The point is to connect them without blurring their claims.
Open source is especially good at this kind of correction because the architecture is not just something you describe. A maintainer can look at a larger implementation and say, in effect, “the native tool already does this.” The right response is not to defend the lines. It is to take the smaller seam—and, in this case, send the smaller patch right back upstream.
Infrastructure leverage is funny that way. The better you understand the boundary, the less code you may need to own.
An SBOM is a file.
An SBOM bound to a release is evidence.
Evidence generation inherited from the release substrate is infrastructure.
Primary threads
- Apache Terraform Provider for Iceberg #105 — final-version RC build provenance.
- Apache Camel K #6777 — artifact-bound SBOM assurance and keyless attestations.
- Apache Celix #845 — native Conan CycloneDX deployer implementation.
- Apache Celix #844 — superseded first proposal whose review produced the smaller native path.
- Apache Maven Parent #597 — inherited CycloneDX generation in
apache-release.
Apache and the Apache project names and logos shown here are trademarks of The Apache Software Foundation. Project marks are shown unmodified solely to identify the referenced projects and link to their official Apache pages.



