Skip to content

docs: explain how CycloneDX and SPDX SBOM attestations differ - #420

Open
AlexKantor87 wants to merge 4 commits into
mainfrom
claude/docs-sbom-format-differences
Open

AlexKantor87 wants to merge 4 commits into
mainfrom
claude/docs-sbom-format-differences

Conversation

@AlexKantor87

Copy link
Copy Markdown
Contributor

The SBOM section describes the subject and the package count as though both come back the same way whatever the format. They do not.

A reader who follows this page to write a policy meets the difference as a rule that silently never matches. That is the worst way to find out.

The two differences

The subject's digest is usually missing from CycloneDX. In SPDX the subject is a package and its checksum sits on that package. So subject.sha256 is filled. CycloneDX has a hashes field for the same job. Snyk and Syft both leave it empty. They write the digest into version instead, where it reads as a version string. Kosli does not infer a checksum from a version, so the field stays empty.

Package counts are not comparable. package_count counts what each format calls a package. CycloneDX entries describing a file are skipped. The SPDX equivalents are not.

Measured

Ran the CLI parser over three real documents rather than reasoning from the schemas.

document subject.sha256 package_count
Snyk CycloneDX 1.6, server image empty 1023
Syft CycloneDX 1.6, kosli_Linux_arm64.rpm empty 1
Syft SPDX 2.3, same rpm the artifact fingerprint 2

The first reproduces the live attestation on the server flow exactly. The last two are the same artifact from the v2.41.0 release. That pair is where the count difference shows up.

One file, prose only. No new pages and no new links, so navigation is unaffected.

🤖 Generated with Claude Code

The page describes the subject and the package count as though both come
back the same way whatever the format. They do not, and a reader following
this to write a policy meets the difference as a rule that never matches.

In SPDX the subject is a package and its checksum sits on it, so
subject.sha256 is filled. CycloneDX has a hashes field for the same job,
but Snyk and Syft both leave it empty and put the digest in version. Kosli
does not infer a checksum from a version, so the field is empty there.

package_count counts what each format calls a package. CycloneDX skips
entries describing a file and SPDX does not, so one artifact reports a
different count in each format.

verified: ran internal/sbom.ProcessSBOMFile from cli origin/main over three
real documents -- Snyk CycloneDX 1.6 from a server build (sha256 null,
count 1023, matching the live attestation), and Syft CycloneDX 1.6 (sha256
null, count 1) and Syft SPDX 2.3 (sha256 = the artifact fingerprint, count
2) for the same rpm from the v2.41.0 release. Read subjectFromComponent,
subjectFromSPDX and packageCount for why.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@AlexKantor87
AlexKantor87 requested a review from a team as a code owner September 15, 2026 15:13
@mintlify

mintlify Bot commented Sep 15, 2026

Copy link
Copy Markdown
Contributor

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated
kosli 🟢 Ready View Preview Sep 17, 2026, 3:07 PM

@claude

claude Bot commented Sep 15, 2026

Copy link
Copy Markdown
Contributor

Claude finished @AlexKantor87's task in 3m 49s —— View job


Doc review

  • Read getting_started/attestations.md at branch head (commit 1ef31d2)
  • Cross-checked the Rego paths against policy-reference/rego_policy.mdx, environment_policy.mdx, policy_builder.mdx, tutorials/evaluate_trails_with_opa.mdx, client_reference/kosli_get_trail.md
  • Cross-checked --attestations semantics against client_reference/kosli_evaluate_trail.md:29
  • Verified link targets and anchors, navigation, redirects
  • Posted findings

Four inline findings on the Rego block and its paths (lines 333-347), plus one placement finding below.

getting_started/attestations.md

Improvement — the SBOM accordion has outgrown the list it lives in, and the reader who needs it will not look here

  • Location: getting_started/attestations.md:293-357
  • Issue: This accordion is now 65 lines; its seven siblings in the same <AccordionGroup> run 6 to 17 and each closes with a one-line "See [attest X …] for usage details". The list's own framing is orientation — "Currently, we support the following types of evidence". It now carries the parser behavior of two named tools, a measured package-count pair, a Rego fragment, and --attestations filtering advice. Two costs: a reader scanning evidence types scrolls past a policy example to reach "Jira issues", and a reader writing an SBOM policy searches policy-reference/ and client_reference/kosli_attest_sbom, finds nothing in either (neither mentions subject.sha256 or package_count), and never reaches the one page that has it. That is the wrong-path condition, and it is why the attestation_data.document inline finding matters: the material is in the one place its audience does not look.
  • Recommendation: Keep in the accordion what belongs to "what is this evidence type": the two-sentence caveat that what you get depends on the tool, and that counts are not comparable across formats. Move the field semantics and the Rego example to /policy-reference/rego_policy#examples or the OPA tutorial, next to the Snyk example the fragment already mirrors, and link to it from here. client_reference/kosli_attest_sbom.md is generated from kosli-dev/clicmd/kosli/attestSbom.go, so it is not the place for it in this repo.

Fix this →

Notes on what was checked

artifact.artifact_fingerprint is sound — client_reference/kosli_get_trail.md:83 shows it on an artifacts_statuses entry, which is the object the snippet binds. The dot-qualified --attestations form matches client_reference/kosli_evaluate_trail.md:29. "Of the two policy mechanisms" holds: policy_builder.mdx is a UI over environment policies, not a third one. No page added, moved, or deleted, so navigation and redirects are unaffected. /policy-reference/rego_policy#input-data resolves (## Input data, rego_policy.mdx:116); all links on the page are root-relative. I could not run gh pr checks in this environment (command not permitted), so the link-rot result is unverified — the internal targets above were confirmed by hand instead.

4 Improvements, 1 Suggestion. Advisory only — nothing here blocks the merge, and the two that matter most are the unguarded comparison at line 343 and the snippet's missing preamble.
· branch claude/docs-sbom-format-differences

Comment thread getting_started/attestations.md Outdated
Comment thread getting_started/attestations.md Outdated
Comment thread getting_started/attestations.md Outdated
Comment on lines +322 to +324
*Package counts are not comparable between formats.* `package_count` counts what each
format calls a package. CycloneDX entries describing a file are not counted; the SPDX
equivalents are. The same artifact can report a different count in each format.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion — "CycloneDX entries describing a file are not counted; the SPDX equivalents are" is too abstract to act on: a reader cannot map "the SPDX equivalents" onto anything they can look for in their own document, so they can't predict a count or explain one to an auditor.

The measurements in the PR description do this work and aren't on the page. Naming the mechanic (a CycloneDX component with type: file is skipped) and giving the measured pair — the same kosli_Linux_arm64.rpm reporting 1 from Syft's CycloneDX output and 2 from its SPDX output — turns a caveat into something the reader can check against their own SBOM.

Fix this →

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Taken. The abstraction was hiding the one thing a reader can check.

The paragraph now names the mechanic and gives the measured pair. 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.

Kept as prose, no table, as suggested.

verified: ran internal/sbom.ProcessSBOMFile from cli origin/main over the two SBOMs the
v2.41.0 release produced for that rpm. CycloneDX 1.6 gives package_count 1,
SPDX 2.3 gives 2. Read packageCount, which skips ComponentTypeFile and recurses
into nested components, against the SPDX path which is len(doc.Packages) with
no filter.

no mutation: this reply adds no test. The claim is a pair of parser runs over two real
documents, and the numbers above are that output.

search: grep -nE "package_count|type: file" getting_started/attestations.md
:321 and :322-323, this paragraph only. No other page states a package count rule,
per the site-wide grep in the first thread.

…y path

Three review findings, all taken.

The takeaway generalised to the format after the prose had correctly blamed
the tools. A reader using a generator that fills CycloneDX hashes would have
read the field as untrustworthy. It now says Kosli reads the hashes entry in
both formats, that Snyk and Syft leave it empty, and that this is a tool
choice.

The page told readers the differences matter for a policy without saying
which kind can reach the fields. Environment policy expressions expose only
the flow and the artifact name and fingerprint, so only Rego can. The
artifact-scoped path is given, with the trail-scoped variant named.

The package count rule named no mechanic a reader could look for. It now
says a CycloneDX component with type file is skipped, and gives the measured
pair for one rpm.

verified: ran internal/sbom.ProcessSBOMFile from cli origin/main over four
          documents. Live Snyk CycloneDX 1.6, subject.sha256 null. The same
          document with a SHA-256 added to metadata.component.hashes,
          populated. Syft CycloneDX 1.6 and SPDX 2.3 for kosli_Linux_arm64.rpm,
          package_count 1 and 2. Resolved the documented Rego path with opa
          eval against the live attestation shape.

mutation: drop the added hashes entry -> subject.sha256 returns null, so the
          populated result comes from hashes and not the version beside it.

search: grep -rnE "subject\.sha256|package_count" over all md and mdx finds
        only this file. mint broken-links reports one broken link, in
        tutorials/working_with_controls.mdx, present on main and untouched here.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Comment thread getting_started/attestations.md Outdated
Comment on lines +326 to +330
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 but
not attestation content. An artifact-scoped SBOM is reachable at
`input.trail.compliance_status.artifacts_statuses.<artifact>.attestations_statuses.<name>.attestation_data.document`,
and a trail-scoped one at the same path without the `artifacts_statuses.<artifact>` step.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Improvement — the new path disagrees with every other Rego example on the site, and omits the prerequisite that makes it populated.

1. The attestation_data.document step appears nowhere else. rego_policy.mdx:145 describes the attestations_statuses value as the object that "contains the attestation's data, including type-specific fields enriched via --attestations" — fields directly on the status object, no wrapper. Both its own example (rego_policy.mdx:252) and the tutorial (tutorials/evaluate_trails_with_opa.mdx:157, :221) read snyk := artifact.attestations_statuses[...] then snyk.processed_snyk_results.results. A site-wide grep finds attestation_data only on this line. So either this page adds a step the reader's rule will trip over, or the other three places are describing a shape SBOM doesn't follow — and a reader has no way to tell which. Given that a wrong path here produces a rule that silently never matches, it's worth settling from kosli evaluate trail --show-input --output json on a real trail carrying an SBOM attestation rather than from an input object assembled by hand, since building the shape by hand assumes the answer.

2. --attestations filters by name. The flag "limits which attestations are enriched with full detail" (tutorials/evaluate_trails_with_opa.mdx:393, client_reference/kosli_evaluate_trail.md:30), and the tutorial recommends narrowing with it for speed. A reader already passing --attestations dashboard.snyk-container-scan gets an undefined lookup for the SBOM — the same silent non-match this section exists to prevent. One clause naming the flag closes it.

Two smaller things in the same sentence: the path is dot notation with <artifact>/<name> placeholders, while the site's examples use bracket lookup with a params-aliased name — which rego_policy.mdx makes a design rule, so a missing param fails the rule rather than proceeding. And the path stops at .document, leaving the reader to join it to the two fields the section is about (.document.subject.sha256, .document.package_count).

Fix this →

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Point 2 and the two smaller ones taken. Point 1 I checked and it does not hold.

attestation_data is not a step this page invented. Evaluation merges each attestation record onto its status entry: rehydrateAttestationMap copies every top-level key of the detail record across, unless the entry already has that key. pull_requests, processed_snyk_results and html_url are top-level fields of an attestation record. So is attestation_data. The other three places and this one read top-level keys of the same merged object, so there is nothing to settle.

Production confirms it resolves. kosli-dev/server evaluates SDLC-CTRL-0004 with artifact.attestations_statuses[name].attestation_data.content. On the trail this PR takes its numbers from, the live decision records allow: true and violations: null. A non-resolving path empties lock_content, which empties lock_pins, which fires the "records no exact (==) version pins" rule. allow would be false. It is not.

I could not run --show-input, having no CLI token here.

Point 2 is now on the page, dot-qualified for artifact scope. The example also moved to bracket lookup with a params-aliased name, and reaches the two fields.

verified: read internal/evaluate/transform.go rehydrateAttestationMap for the merge rule.
The CLI golden check in cmd/kosli/evaluateTrail_test.go asserts
input.trail.compliance_status.artifacts_statuses.cli.attestations_statuses.art-att.html_url,
which is the artifacts_statuses step. Ran opa check on the snippet now in the
page, then opa eval three ways.

mutation: set subject.sha256 to null -> allow undefined, the silent non-match. Remove the
attestation from attestations_statuses, as --attestations would -> allow
undefined again. Populate the digest -> allow true. So both warnings on the
page describe the behaviour the snippet has.

search: grep -rn "attestation_data" over the kosli-dev/server control policies finds
SDLC-CTRL-0004 only. The other three read status, is_compliant, has_audit_package
and pull_requests directly off the entry. Both idioms are live and green in one
pipeline, which is what the merge rule predicts.

The path was prose with placeholders, stopped at the document object, and
said nothing about input filtering. A reader narrowing the input with
--attestations and leaving the SBOM out gets a rule that never matches,
which is the failure this section exists to prevent.

It is now a snippet in the site's own idiom: bracket lookup with a
params-aliased attestation name, reaching subject.sha256 and comparing it
to the artifact fingerprint. The trail-scoped path is named beside it. A
closing line says to include the SBOM in --attestations, dot-qualified for
artifact scope, because anything left out is absent from the input.

The attestation_data step stands. Evaluation copies every top-level key of
an attestation record onto its status entry, so pull_requests and
attestation_data sit at the same level, and the site's other examples and
this one are both reading top-level keys.

verified: read internal/evaluate/transform.go rehydrateAttestationMap for
          the merge rule. cmd/kosli/evaluateTrail_test.go asserts
          input.trail.compliance_status.artifacts_statuses.cli.attestations_statuses.art-att.html_url,
          confirming the artifacts_statuses step. kosli-dev/server's
          SDLC-CTRL-0004 reads attestation_data.content and its live
          decision on this trail is allow true, violations null, which an
          unresolved path would have made false. opa check passes on the
          snippet.

mutation: subject.sha256 null -> allow undefined. Attestation removed from
          attestations_statuses -> allow undefined. Digest populated ->
          allow true. Both warnings match the snippet's behaviour.

search: grep -rn attestation_data over the server control policies finds
        SDLC-CTRL-0004 only; the other three read fields directly off the
        entry. mint broken-links reports one broken link, in
        tutorials/working_with_controls.mdx, present on main and untouched.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

sbom_describes(artifact) if {
sbom := artifact.attestations_statuses[sbom_attestation_name]
sbom.attestation_data.document.subject.sha256 == artifact.artifact_fingerprint

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Improvementartifact.artifact_fingerprint is the one field in this snippet that nothing on the site backs up, and it is the field the comparison turns on.

  • rego_policy.mdx:148-150 documents an artifacts_statuses entry as having "its own attestations_statuses map" and names no other field on it, so the fingerprint key is undocumented there.
  • The only in-repo occurrences of artifact_fingerprint are in the trail events array (client_reference/kosli_get_trail.md:83, :404) — a different object from an artifact status entry, so it is not evidence for this path.
  • The nearest documented spelling is the environment-policy one, artifact.fingerprint (policy-reference/environment_policy.mdx:144).

If the key on a status entry is fingerprint (or absent, with the name-keyed map being the only artifact identity in the input), the == compares against undefined, sbom_describes never fires, and the reader gets the silent non-match this whole section was written to prevent — from the example meant to show them the way. Worth pinning with kosli evaluate trail --show-input --output json | jq '.input.trail.compliance_status.artifacts_statuses[] | keys' on a real trail rather than a hand-built input, and then documenting the confirmed key in rego_policy.mdx's ## Input data so the example has a second source.

Fix this →

Comment on lines +331 to +338
```rego
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
}
```

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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 — subject.sha256 is absent, the == is undefined, and the body does not fire. Under a positive allow that fails safe; inside a violations rule it is the silent skip rego_policy.mdx:84 warns about. Since the snippet shows neither call site, the reader can't tell which they get. A presence check makes the advice visible in the code:

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: if in a rule head needs import rego.v1, which both examples in rego_policy.mdx (:212, :245) and the tutorial carry but this fragment doesn't mention; and artifact arrives unbound, where the site's idiom shows the every name, artifact in trail.compliance_status.artifacts_statuses that produces it (rego_policy.mdx:258-262). Either add that line or say the fragment plugs into the Snyk example's shape.

Line 340 then writes the trail-scoped path as attestations_statuses[name], using name where the snippet aliases sbom_attestation_name, and dropping the input. prefix the reference uses throughout.

Fix this →

The page said to assert the digest field is present before comparing it.
Present is not the same as correct, and a reader following that advice
gets a comparison that looks sound and is not.

The subject identifies what the generator scanned. Pointed at a tag rather
than a digest, it records whatever that tag resolved to on that machine.
For a multi-architecture image that is one architecture, and it can be a
local image id that is not a registry digest at all.

The page now says to compare the two only where the pipeline pointed the
generator at the exact artifact, and to check it in the build otherwise.

verified: kosli-public flow cli, artifact
          ece2be992ca243c8f601e42e019974df05f773a310265c328f66d0c01f3350e2,
          attestation container-sbom, spdx-2.3 from syft. Its
          subject.sha256 is 02eb3b331a64..., which returns 404 from the
          ghcr manifest API for that repo. The attested index holds amd64
          0a222bfc... and arm64 d7f84a11..., whose config digests are
          4e3e6f6b... and 5bcab99e.... None is the reported subject. The
          subject purl carries arch=amd64, and the tag resolves to the
          attested index, so the tag had not moved.

no mutation: prose only, no test claim.

search: grep -nE "present|populat|assert|compare|trust"
        getting_started/attestations.md finds only the new paragraph and
        line 272, which is the pull-request --assert flag and not this
        class. No other advice on the page treats a populated field as
        trustworthy. mint broken-links still reports only the pre-existing
        tutorials/working_with_controls.mdx break.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Comment on lines +341 to +344
sbom_describes(artifact) if {
sbom := artifact.attestations_statuses[sbom_attestation_name]
sbom.attestation_data.document.subject.sha256 == artifact.artifact_fingerprint
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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 — subject.sha256 is absent, so the == is undefined and sbom_describes does not fire. Under a positive allow that fails safe; inside a violations rule it is the silent skip policy-reference/rego_policy.mdx:84 warns about. The snippet shows neither call site, so the reader cannot tell which they get. The earlier draft carried "assert the field is present before comparing it"; commit 1ef31d2 removed that sentence, and the snippet never grew the check, so nothing on the page now covers it.

Suggested change
sbom_describes(artifact) if {
sbom := artifact.attestations_statuses[sbom_attestation_name]
sbom.attestation_data.document.subject.sha256 == artifact.artifact_fingerprint
}
sbom_describes(artifact) if {
sbom := artifact.attestations_statuses[sbom_attestation_name]
digest := sbom.attestation_data.document.subject.sha256
digest != ""
digest == artifact.artifact_fingerprint
}

Worth a clause above the block saying the comparison holds only under the precondition in the paragraph above — or making the example read package_count, which is always present, so the code demonstrates reaching the fields without also modelling the comparison the prose discourages.

Fix this →

not attestation content. Evaluation copies an attestation's own fields onto its status
entry, so the summary sits under `attestation_data`:

```rego

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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.

  • if in a rule head requires import rego.v1 under OPA before 1.0. Every other Rego block on the site carries package policy + import rego.v1policy-reference/rego_policy.mdx:212, :244, tutorials/evaluate_trails_with_opa.mdx:37, :152, :213. This one has neither, so a reader who pastes it gets a parse error rather than the field path they came for.
  • artifact arrives unbound and sbom_describes has no call site. The site's idiom shows where it comes from: every name, artifact in trail.compliance_status.artifacts_statuses (rego_policy.mdx:258-262).

Either show the two preamble lines and the every binding, or say in one clause that the fragment plugs into the Snyk example's shape at /policy-reference/rego_policy#examples — the latter keeps the accordion short and gives the reader a runnable whole to drop it into.

Fix this →

Comment on lines +333 to +336
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`:

Copy link
Copy Markdown
Contributor

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.document wrapper, and the reference it links to describes a different shape.

policy-reference/rego_policy.mdx:145 says an attestations_statuses value "contains the attestation's data, including type-specific fields enriched via --attestations" and illustrates it with pull_requests sitting directly on the object; its own example reads snyk.processed_snyk_results.results (:252) with no wrapper. attestation_data and document appear in no other .md/.mdx on the site. A reader who follows the #input-data link this sentence gives them, then writes sbom.subject.sha256 or sbom.document.subject.sha256 off 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_statuses ParamField in rego_policy.mdx's ## Input data to note that some attestation types nest their summary under attestation_data (SBOM under attestation_data.document, with subject.sha256 and package_count on 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 →

}
```

A trail-scoped SBOM sits at `trail.compliance_status.attestations_statuses[name]` instead.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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 sbom_attestation_name (deliberately, per rego_policy.mdx's params rule); this line writes [name], an identifier that is undefined in the surrounding example. And it ends at the status entry, leaving the reader to append the .attestation_data.document step themselves — the exact step that has no second source on the site, so it is the one they are least able to reconstruct.

Suggested change
A trail-scoped SBOM sits at `trail.compliance_status.attestations_statuses[name]` instead.
A trail-scoped SBOM sits at `trail.compliance_status.attestations_statuses[sbom_attestation_name].attestation_data.document` instead.

Fix this →

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant