Skip to content

feat(sbom): inventory installed Python, JavaScript and Lua packages - #472

Merged
reyreavman merged 5 commits into
mainfrom
feat/sbom/installed-components
Oct 26, 2026
Merged

reyreavman merged 5 commits into
mainfrom
feat/sbom/installed-components

Conversation

@reyreavman

@reyreavman reyreavman commented Oct 25, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Stapel SBOMs now include installed Python, JavaScript and Lua packages even when no packages directive declares them. This adds transitive and separately installed packages to the inventory without making them application root dependencies.

What

  • Recognized Python metadata directories, Node installation layouts and versioned LuaRocks trees contribute package identities and scanner-readable licenses; a recognized layout does not prove that an application uses the package.
  • File and directory symlinks are resolved when copying metadata, while the scanner sees the logical installation paths, including nested links into package stores.
  • Existing declaration/lock components and dependency edges are retained; installed scans do not add inferred root or optional-dependency edges.
  • Only Lua scan copies drop conditional blocks and unsupported boolean statements. Installed-directory identity covers computed versions, and supported local bindings and continued strings survive; image contents are not modified and Lua code is not executed. Arbitrary Lua evaluation is not supported.
  • Metadata reads are bounded at 4 MiB per file, 64 MiB total and 65,536 files; linked-path inspection is bounded separately. Exceeding these limits warns that inventory may be incomplete. A selected metadata read failure aborts the build.
  • Existing SBOM artifacts are regenerated using format version 15; image stage cache keys do not change. No new configuration, dependency or scanner image is introduced, and Dockerfile scanning is unchanged.
  • The three installed-inventory e2e cases pass on the Linux VM, including Python, JavaScript and Lua identities and licenses.
  • UNVERIFIED: full SBOM regression coverage is not green: the VM suite reported five rsync network-unreachable failures in dependency-import stages and one suite timeout; CI also remains blocked by its runner runtime-directory issue.

Why

Declaration-only scans do not enumerate everything installed by a package manager, including dependencies absent from the scanned declarations. Reusing the final image index provides installed metadata without another filesystem inventory pass.

The pinned Syft parser discards complete rockspecs when source-selection conditionals or boolean fields are present. A scan-only workaround allows packages such as luassert and say to be reported without executing image-supplied Lua or waiting for a different scanner distribution; it is intentionally not a general Lua interpreter.

Packages installed transitively or outside a packages directive were missing
from the SBOM: only declaration and lock files were scanned. Read installed
metadata (dist-info, node_modules manifests, LuaRocks trees) and its license
files from the built image index, resolving symlinks, and add the components
without inventing dependency edges; existing declaration graphs stay intact.

Signed-off-by: Radmir Khurum <radmir.khurum@flant.com>
Syft drops a whole rockspec when it meets a conditional statement or a
boolean expression, so rocks such as luassert and say never reached the
SBOM. Strip those lines from the scan copy, which never carry the name,
version or license, and take name and version from the LuaRocks tree.

Signed-off-by: Radmir Khurum <radmir.khurum@flant.com>
A node_modules entry linking into a store whose directories are themselves
links, as pnpm-style layouts do, left the package out of the inventory:
only one level of directory links was projected. Follow links nested in a
linked tree up to a fixed depth, skip links back to an ancestor, and bound
the paths examined through links so many aliases cannot multiply the work.

Signed-off-by: Radmir Khurum <radmir.khurum@flant.com>
Signed-off-by: Radmir Khurum <radmir.khurum@flant.com>
Preserve scanner-readable local bindings and continued strings when filtering installed rockspec scan copies. Match the pinned scanner’s value boundaries and arity instead of classifying every local from its first value; do not evaluate expressions or fabricate metadata.

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

reyreavman commented Oct 25, 2026 •

Copy link
Copy Markdown
Collaborator Author

Verification

  • Direct official Syft 1.45.1 checks: raw rockspec fixtures produce four packages; rewritten fixtures produce all eight with expected versions, PURLs and licenses. This includes real luassert 1.9.0-1 and say 1.4.1-3 files, simple/mixed local bindings, quoted-key prefixes, continued strings and quoted boolean operands. Syft retains luassert's license as its upstream license name rather than an SPDX ID.
  • The exact npm fixture scanned with the JavaScript lock cataloger produces is-odd and both is-number versions, but no yocto-queue. Consequently, the yocto-queue MIT assertion cannot be satisfied by its lock entry instead of the installed scan.
  • Mutation: bypassing the rewrite, retaining boolean fields, ignoring quote boundaries, ignoring resolved read paths, dropping inventory imports, restoring the old cache format/no-declarations guard, removing nested-link traversal or its limits, and removing local arity/blank-line/bracket guards each failed the corresponding test. New local-binding and continued-string regressions were red before their fixes and green afterward.
  • Current local task format, task build, task lint and full task test:unit passed. The unit run used a process-local tag.gpgSign=false override because the host's global setting otherwise makes an unrelated Git fixture request a signing passphrase; no Git configuration was changed.
  • Linux VM execution is currently blocked by SSH authentication. Local installed e2e runs stopped before SBOM generation: the Docker daemon could not reach the loopback HTTP registry, and the reachable LAN endpoint required daemon-side TLS configuration. Shared Docker/SSH settings were not changed. Linux checks were delegated to the existing CI jobs.
  • CI run 40220505704 finished failure: lint, unit, test-binary build, integration_main and integration_git passed; e2e_simple, e2e_complex and e2e_extra failed. Failure logs show an absent CI user's runtime directory during Buildah initialization and project-image cleanup, including Docker-only tests outside the SBOM changes. The complete result of the new installed-inventory e2e cases is not established. No runner configuration or test assertion was changed to hide this failure.

Review focus

  • The inventory uses bounded sequential metadata reads; large package trees can incur many Docker API calls, and aliases can consume the read budget more than once. No large-image latency claim is made.
  • An image-index failure retains the existing warn-and-cache behavior, which can cache an incomplete inventory; selected-file read errors instead fail the build.
  • The Lua preprocessing is a compatibility workaround, not arbitrary Lua support. Computed or unsupported metadata can remain unavailable. Long Lua comments retain the pinned scanner's existing limitation.

Follow-up

  • BLOCKER: restore a valid CI-user runtime directory for Buildah, then rerun the failed tests:all e2e jobs and verify all installed-inventory cases before this draft is considered ready. Integration jobs have passed. Do not merge automatically.

@reyreavman

Copy link
Copy Markdown
Collaborator Author

Linux VM e2e results at 98c4d09

VM access was restored. No source changes were made for these runs.

  • Full SBOM suite: task test:e2e paths=./test/e2e/sbom labelFilter=sbom parallel=1 -- -no-color — 65 passed, 6 failed, 1 pending, 6 skipped. Five failures are rsync: Network unreachable (101) during dependenciesBeforeInstall/dependenciesBeforeSetup; the sixth is the suite timeout during the GOST JavaScript/npm case. The full suite is not green.
  • Isolated new installed-inventory cases: task test:e2e paths=./test/e2e/sbom labelFilter=installed parallel=1 -- -no-color — 3 passed, 0 failed in 196.848 seconds, exit 0. Python, JavaScript and Lua image-build assertions, including licenses, passed.

The local waiting connection timed out or lost SSH, but the remote runs continued; saved remote logs confirm both exit codes (201 and 0). These VM results do not change the failed CI conclusion. The PR remains draft; no merge was performed.

@reyreavman
reyreavman marked this pull request as ready for review October 26, 2026 07:08
@reyreavman
reyreavman merged commit 7fde219 into main Oct 26, 2026
11 of 14 checks passed
@reyreavman
reyreavman deleted the feat/sbom/installed-components branch October 26, 2026 07:09
reyreavman pushed a commit that referenced this pull request Oct 26, 2026
🤖 I have created a release *beep* *boop*
---


##
[3.12.0-dk.3](v3.12.0-dk.2...v3.12.0-dk.3)
(2026-10-26)


### Features

* **build, stapel:** sign files matched by a glob pattern
([#471](#471))
([ffda24e](ffda24e))
* **sbom:** inventory installed Python, JavaScript and Lua packages
([#472](#472))
([7fde219](7fde219))


### Bug Fixes

* **build, stapel, import:** stop imports failing with sbom enabled
([#470](#470))
([478b751](478b751))
* **sbom:** avoid marking editable Python packages as absent
([#474](#474))
([e0b4b66](e0b4b66))
* **sbom:** retain legacy Python bytecode after source removal
([#476](#476))
([36debcb](36debcb))
* **sbom:** stop marking os-pm packages under /lib64 as not shipped
([#475](#475))
([cd0a029](cd0a029))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).
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