-
Notifications
You must be signed in to change notification settings - Fork 1
docs: explain how CycloneDX and SPDX SBOM attestations differ #420
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
ec5339a
03c12db
560b7bc
1ef31d2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -306,6 +306,50 @@ Currently, we support the following types of evidence: | |||||||||||||||||||||
| Nothing in the SBOM is checked against the artifact. It is recorded as reported, so the | ||||||||||||||||||||||
| attestation says what the SBOM claims, not whether the claim is true. | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| **What you get depends on the tool, not just the format.** Kosli records what the document | ||||||||||||||||||||||
| declares, and tools fill the same fields differently. Three differences catch people out. | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| *The subject's digest is often absent from CycloneDX.* Kosli reads the subject's SHA-256 | ||||||||||||||||||||||
| `hashes` entry into `subject.sha256`, in both formats. Syft's SPDX output fills it. Snyk's | ||||||||||||||||||||||
| and Syft's CycloneDX output does not: both leave `hashes` empty and write the digest into | ||||||||||||||||||||||
| `version`, where it reads as a version string rather than a checksum. Kosli does not infer | ||||||||||||||||||||||
| a checksum from a version, so the field is empty for those two. That is the tool's choice | ||||||||||||||||||||||
| rather than a limit of CycloneDX, so check what yours writes instead of assuming either | ||||||||||||||||||||||
| way. | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| *A digest that is present is not automatically the artifact's.* The subject identifies what | ||||||||||||||||||||||
| the generator scanned. Point one at a tag and it records whatever that tag resolved to on | ||||||||||||||||||||||
| that machine. For a multi-architecture image that is a single architecture, and it can be a | ||||||||||||||||||||||
| local image id rather than a registry digest. Kosli does not check the subject against the | ||||||||||||||||||||||
| artifact, so a digest that is present can still belong to something else. Compare the two | ||||||||||||||||||||||
| only where your pipeline pointed the generator at the exact artifact it attests. Otherwise | ||||||||||||||||||||||
| check it in the pipeline, where the build can fail, rather than in a policy. | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| *Package counts are not comparable between formats.* `package_count` counts what each | ||||||||||||||||||||||
| format calls a package. A CycloneDX component with `type: file` is skipped, while the SPDX | ||||||||||||||||||||||
| package describing that same file is counted. Syft reports one package for | ||||||||||||||||||||||
| `kosli_Linux_arm64.rpm` in CycloneDX and two for the same file in SPDX. | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| Of the two policy mechanisms, only [Rego](/policy-reference/rego_policy#input-data) can read | ||||||||||||||||||||||
| these fields. Environment policy expressions expose the artifact's name and fingerprint, | ||||||||||||||||||||||
| not attestation content. Evaluation copies an attestation's own fields onto its status | ||||||||||||||||||||||
| entry, so the summary sits under `attestation_data`: | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| ```rego | ||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Improvement — the snippet will not parse or run as written, and it is the only Rego block on the site that omits the preamble.
Either show the two preamble lines and the |
||||||||||||||||||||||
| sbom_attestation_name := data.params.sbom_attestation_name | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| sbom_describes(artifact) if { | ||||||||||||||||||||||
| sbom := artifact.attestations_statuses[sbom_attestation_name] | ||||||||||||||||||||||
| sbom.attestation_data.document.subject.sha256 == artifact.artifact_fingerprint | ||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Improvement —
If the key on a status entry is |
||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
Comment on lines
+341
to
+344
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Improvement — the only code on the page does the one thing the paragraph three lines above tells the reader not to do, and it has no guard for the case the section opens with. Lines 324-326: "Kosli does not check the subject against the artifact, so a digest that is present can still belong to something else. Compare the two only where your pipeline pointed the generator at the exact artifact it attests. Otherwise check it in the pipeline… rather than in a policy." The snippet is then a policy-side digest comparison, presented without restating that precondition. A reader who skims to the code block — which is what a reader looking for a path does — takes away the pattern the prose just cautioned against, and a multi-arch image gives them a rule that denies a correctly built artifact. Separately, on a Snyk or Syft CycloneDX SBOM — the case the section exists to warn about —
Suggested change
Worth a clause above the block saying the comparison holds only under the precondition in the paragraph above — or making the example read |
||||||||||||||||||||||
| ``` | ||||||||||||||||||||||
|
Comment on lines
+338
to
+345
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Improvement — the snippet does not do the thing the paragraph above it advises, and it can't be run as written. Line 318 tells the reader to "assert the field is present before comparing it, so a missing digest fails the rule instead of skipping it". The snippet compares straight away. On a Snyk or Syft CycloneDX SBOM — the case the section is about — sbom_describes(artifact) if {
sbom := artifact.attestations_statuses[sbom_attestation_name]
digest := sbom.attestation_data.document.subject.sha256
digest != ""
digest == artifact.artifact_fingerprint
}Two smaller things in the same block: Line 340 then writes the trail-scoped path as |
||||||||||||||||||||||
|
|
||||||||||||||||||||||
| A trail-scoped SBOM sits at `trail.compliance_status.attestations_statuses[name]` instead. | ||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Suggestion — the trail-scoped path stops short of the fields, and swaps the key name the snippet just established. The snippet aliases the attestation name as
Suggested change
|
||||||||||||||||||||||
|
|
||||||||||||||||||||||
| If you narrow the input with `kosli evaluate trail --attestations`, name the SBOM there too, | ||||||||||||||||||||||
| dot-qualified as `<artifact>.<name>` for an artifact-scoped one. Anything left out is absent | ||||||||||||||||||||||
| from the input, and a rule reading it does not match rather than failing. | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| The CLI refuses an SBOM file larger than 9 MiB, which leaves room for the attestation | ||||||||||||||||||||||
| itself within the 10 MB the server accepts. We are working on raising this. | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
|
|
||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Improvement — this page is now the site's only source for the
attestation_data.documentwrapper, and the reference it links to describes a different shape.policy-reference/rego_policy.mdx:145says anattestations_statusesvalue "contains the attestation's data, including type-specific fields enriched via--attestations" and illustrates it withpull_requestssitting directly on the object; its own example readssnyk.processed_snyk_results.results(:252) with no wrapper.attestation_dataanddocumentappear in no other.md/.mdxon the site. A reader who follows the#input-datalink this sentence gives them, then writessbom.subject.sha256orsbom.document.subject.sha256off the reference's description, gets the silent non-match this section was written to prevent.Since the path itself has been verified, the cheap durable fix is on the other side: extend the
attestations_statusesParamFieldinrego_policy.mdx's## Input datato note that some attestation types nest their summary underattestation_data(SBOM underattestation_data.document, withsubject.sha256andpackage_counton it), while others expose their fields directly. Then this accordion's one-sentence claim has a reference source behind it instead of standing alone.Fix this →