Skip to content

feat(sbom): report the source language of cataloged packages - #328

Draft
reyreavman wants to merge 5 commits into
feat/sbom/targeted-dir-scanfrom
feat/sbom/gost-source-langs
Draft

reyreavman wants to merge 5 commits into
feat/sbom/targeted-dir-scanfrom
feat/sbom/gost-source-langs

Conversation

@reyreavman

@reyreavman reyreavman commented Sep 15, 2026

Copy link
Copy Markdown
Collaborator

Summary

SBOMs of stapel images now carry the GOST:source_langs property the FSTEC tabular component listing is generated from: every component cataloged through a packages directive is stamped with the source language of that directive's ecosystem, os-pm components get the languages declared in the pm package catalogue, and werf sbom merge rolls the languages up to the image and product level. Nothing to configure; the property appears on rebuild.

What

Per-package property

  • A component cataloged by a packages directive carries GOST:source_langs: go-modGo, python-pip/python-poetry/python-uvPython, rust-cargoRust, javascript-npm/javascript-yarn/javascript-pnpmJavaScript, lua-rockLua.
  • The value is a single property with the languages joined by ", " — never repeated properties, because the ISPRAS exporters read only the first property with a given name and split its value on commas.
  • An os-pm component carries the languages declared for the package in the pm catalogue (the srcLanguages field of /var/lib/pm/index.json); a package without the declaration carries no property — the language of a prebuilt binary is never guessed.
    • UNVERIFIED: end-to-end against a real image — the builder images have not been rebuilt with the pm release that writes srcLanguages yet; unit-tested against the index.json shape pm main serializes.
  • Dockerfile images do not change: werf generates no SBOM for them.

Merge-level aggregation

  • After werf sbom merge, the product component carries the sorted, deduplicated union of the languages of all images.
  • In the container output format, each image's container component additionally carries the union of the languages of the components below it; the oss format has no container components and gets only the product-level property.
  • Languages already present on a component (e.g. from a user-imported BOM) are unioned with the aggregated ones, not suppressed.

Cache

  • Cached SBOM artifacts are regenerated once: the artifact format version is bumped 4 → 5, and the directive's language is part of the scan checksum.

Why

The "programming language(s)" column of the tabular component listing (ППК) is produced by the ISPRAS exporters from GOST:source_langs; without the property the column stays empty for every package, image and product row. The language comes from the werf.yaml packages directive, not from syft package metadata: stapel images are scanned one directive at a time, so the attribution is exact and does not depend on what syft happens to emit. Deriving the language from purl types (the efs-sbom approach) was rejected as the primary source — it is a heuristic, while the directive type and the pm catalogue are ground truth someone declared.

@reyreavman

reyreavman commented Sep 15, 2026

Copy link
Copy Markdown
Collaborator Author

Verification

  • Scoped e2e (task test:e2e paths="./test/e2e/sbom" labelFilter="gost" parallel=1) against a local registry on macOS arm64: the new "source language" specs (pip + npm directives) pass; the two os-pm image-level GOST overrides specs fail identically on a clean tree (linux/amd64 builder base image on an arm64 host), not caused by this branch.
  • Mutation: suppressing aggregated languages on a container component that already had some → pkg/sbom/ispras unit test failed during development.
  • Mutation: removing the SetComponentSourceLangs call in ConvertToCycloneDX → "should set source languages only for packages that declare them" in pkg/sbom/packages/os_pm failed.
  • Mutation not run for the stamping loop in scanCatalogerDir: the unit test asserting per-component languages in pkg/build/sbom_step_test.go was written after the implementation; deleting the stamping loop is the mutation to try.

Review focus

  • pkg/sbom/ispras/gost.go: union semantics for pre-existing GOST:source_langs on a container component — the other GOST properties use "keep existing" instead; the divergence is deliberate (languages are a set, attack surface is an override).
  • Value format decision (single comma-joined property) rests on the ISPRAS exporters' get_prop returning only the first property with a name; checked against 3p-ispras-sbom-checker:master and efs-sbom 1.0.5.

Follow-up

  • Stamp GOST:source_langs on os-pm components once pm ships srcLanguages — landed in this PR after the pm release.
  • Re-verify the os-pm property end-to-end once the builder images are rebuilt with the released pm and the catalogue declares srcLanguages.
  • BLOCKER: PR feat(sbom): generate file-based package SBOMs without docker.sock #307 (feat/sbom/targeted-dir-scan) is the base branch of this stacked PR and has to merge first; retarget this PR to main afterwards.

Every component cataloged through a packages directive now carries the
GOST:source_langs property required by the FSTEC component listing: a single
property with the languages of the ecosystem that installed the package, which
is the form the ISPRAS tooling reads (it takes the first property with that name
and splits its value on commas).

The language comes from the packages directive itself, registered per ecosystem
and carried down to the per-directive scan, so it does not depend on scanner
metadata. Packages installed by os-pm are prebuilt binaries of an arbitrary
language and stay without the property. The SBOM artifact format version is
bumped so that cached SBOMs are regenerated.

Signed-off-by: Radmir Khurum <radmir.khurum@flant.com>
A merged SBOM now carries GOST:source_langs on the product component and, in
the container format, on the container component of every image, holding the
sorted union of the languages of the components below it. Without it the image
and product rows of the tabular component listing, which is generated from
these properties, stayed empty while the package rows were filled in.

Languages already present on a component (e.g. from a user-imported BOM) are
unioned with the aggregated ones instead of suppressing them.

Signed-off-by: Radmir Khurum <radmir.khurum@flant.com>
Assert GOST:source_langs on a component cataloged by the python-pip and
javascript-npm directives, so the language registry stays wired from the
packages directive down to the generated SBOM.

Signed-off-by: Radmir Khurum <radmir.khurum@flant.com>
Describe how GOST:source_langs is derived from the packages directive, why
os-pm packages carry no language, and how the languages are collected on merge.

Signed-off-by: Radmir Khurum <radmir.khurum@flant.com>
…alogue

pm now records the srcLanguages declared for a package in its catalogue into
/var/lib/pm/index.json, so an os-pm component can carry GOST:source_langs
without guessing: the languages are read from the package's own description.
Packages without the declaration stay without the property, and the image and
product level aggregation picks the languages up with no further changes.

Signed-off-by: Radmir Khurum <radmir.khurum@flant.com>
@reyreavman
reyreavman force-pushed the feat/sbom/gost-source-langs branch from a223005 to af1e841 Compare September 21, 2026 08:55
@reyreavman

Copy link
Copy Markdown
Collaborator Author

CI status (run 35580475428, 8 attempts)

Every failure class is environmental; none is attributable to this diff:

  • unit / phantom ./pkg/build suite failure — ginkgo reports "failures detected" with 157/157 specs green and the suite's own SUCCESS! verdict. Ginkgo CLI 2.20.1 vs library 2.28.1 mismatch on the runners (the warning is printed at the top of every unit step). Same phantom failure on an unrelated branch: run 35327395334 (fix/sbom/external-ref-dup-and-checks). Failed attempts 1/3/6, passed 2/4/7/8 with an identical tree.
  • e2e_simple / SBOM go-mod packages: keeps the license of a registry module (base-branch spec) — go mod download inside the build container falls back to direct VCS because proxy.golang.org is unreachable from the runner, and the builder image has no git: exec: "git" not found in $PATH. Passed on the base branch run on Sep 20; deterministic on the runners today.
  • e2e_extra — specs interrupted at the 10m node timeout (runner overload); docs_check_links — external link 504.

Also: gh run rerun --failed is useless for the e2e jobs — the kind registry lives only within a full run (kind_setup), so partial reruns fail with connection refused on every registry access. Only full reruns are meaningful.

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