Skip to content

[build] generate and ship cargo-free SBOM and license notices with Selenium Manager - #17812

Closed
titusfortner wants to merge 1 commit into
SeleniumHQ:trunkfrom
titusfortner:selenium-manager-sbom
Closed

[build] generate and ship cargo-free SBOM and license notices with Selenium Manager#17812
titusfortner wants to merge 1 commit into
SeleniumHQ:trunkfrom
titusfortner:selenium-manager-sbom

Conversation

@titusfortner

Copy link
Copy Markdown
Member

🔗 Related Issues

Fixes #17808

💥 What does this PR do?

  • Generates two license-compliance artifacts inside every package that bundles the Selenium Manager binary — Python wheel, Java jar, Ruby gem, npm package, NuGet package.
  • selenium-manager.cdx.json — a CycloneDX SBOM inventorying the ~300 statically-linked Rust crates (name, version, checksum, dependency graph, SPDX license) for vulnerability and license scanning.
  • selenium-manager-THIRD-PARTY-NOTICES.txt — the third-party attribution file reproducing each bundled crate's copyright notice and full license text, which MIT/BSD/ISC/Apache-2.0 require in binary distributions.

🔧 Implementation Notes

  • Generated entirely within Bazel with no cargo dependency. A Bazel aspect walks the crate dependency graph and harvests the Cargo.toml manifests and LICENSE/NOTICE files crate_universe has already vendored; the SBOM component graph comes from rust/Cargo.lock.
  • Each package bundles all three OS binaries, which link different crate subsets (e.g. winapi only on Windows). A split transition seeds the aspect under every shipped target triple and unions the results, so both artifacts cover all platforms from a single build without cross-compiling — only the crates' source files are read.
  • SBOM component list is intentionally the full Cargo.lock graph — industry-standard, where over-inclusion is safe and omission is a coverage gap. Crates not linked on any platform (build-only, test-only) resolve to NOASSERTION. Three crates declare an SPDX license but bundle no license file and are listed as such.
  • Python sdist ships Rust source (including Cargo.lock) rather than the compiled binary so is excluded.

🤖 AI assistance

  • AI assisted (complete below)
    • Tool(s): Claude Code
    • What was generated: SBOM + NOTICE generators, Bazel aspect/rules, and per-binding packaging wiring
    • I reviewed all AI output and can explain the change

💡 Additional Considerations

  • Both files sit next to the binary (present and valid) rather than in the ecosystems' emerging reserved SBOM locations — .dist-info/sboms/ (PEP 770) for wheels and META-INF/sbom/ for jars — because the pinned rules_python py_wheel cannot write into .dist-info/ and the Java move would disturb the manager binary's resource-loading path. Deferred as a follow-up (gated on rules_python PEP 770 support) so a generic scanner can auto-discover the SBOM.

🔄 Types of changes

  • New feature (non-breaking change which adds functionality and tests!)

@selenium-ci selenium-ci added C-py Python Bindings C-rb Ruby Bindings C-dotnet .NET Bindings C-java Java Bindings C-nodejs JavaScript Bindings B-build Includes scripting, bazel and CI integrations C-rust Rust code is mostly Selenium Manager B-manager Selenium Manager labels Jul 22, 2026
@qodo-code-review

Copy link
Copy Markdown
Contributor

PR Summary by Qodo

Generate and bundle cargo-free SBOM + third-party notices for Selenium Manager

✨ Enhancement ⚙️ Configuration changes 🕐 40+ Minutes

Grey Divider

AI Description

• Add Bazel rules/aspects to generate CycloneDX SBOM and third-party NOTICE for Selenium Manager.
• Harvest crate licenses/texts from crate_universe-vendored sources and rust/Cargo.lock (no cargo
 required).
• Bundle both artifacts alongside Selenium Manager in NuGet, JAR, npm, Python wheel, and Ruby gem.
Diagram

graph TD
  V["Vendored crate files"] --> S(["crate_manifests_aspect"]) --> R(["selenium_manager_sbom rule"]) --> O[["SBOM (cdx.json)"]] --> P{{"Binding packages"}}
  L["rust/Cargo.lock"] --> R
  S --> N(["selenium_manager_notice rule"]) --> T[["NOTICE (txt)"]] --> P

  subgraph Legend
    direction LR
    _in["Input"] ~~~ _rule(["Bazel rule/aspect"]) ~~~ _out[["Generated artifact"]] ~~~ _pkg{{"Package"}}
  end
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Use cargo-based SBOM/NOTICE tooling (cargo-cyclonedx, cargo-about)
  • ➕ Less custom parsing of Cargo.lock/Cargo.toml
  • ➕ Typically richer license handling and SPDX edge cases
  • ➖ Introduces a cargo dependency and potentially non-hermetic behavior under Bazel
  • ➖ Harder to keep fully offline/reproducible and consistent across Bazel build environments
2. Generate SBOM from built binaries (Syft/tern) in release pipeline
  • ➕ Reflects actual linked contents per platform
  • ➕ No need to parse Rust metadata directly
  • ➖ Not hermetic; depends on external scanners and runtime environments
  • ➖ More moving parts and harder to ensure artifacts exist for every build output
3. Adopt Bazel-native license/SBOM frameworks (e.g., rules_license / bazel-sbom)
  • ➕ Leverages ecosystem-standard Bazel patterns and reporting formats
  • ➕ May reduce maintenance burden long-term
  • ➖ May not model Rust crate_universe inputs and Cargo.lock dependency graphs cleanly today
  • ➖ Could require significant integration work and still need custom Rust metadata extraction

Recommendation: The PR’s cargo-free, Bazel-hermetic approach is the best fit for generating always-present compliance artifacts during packaging. The main follow-ups to consider are (1) adding lightweight validation tests (e.g., SBOM JSON schema/smoke parse; NOTICE non-empty) and (2) later relocating artifacts into ecosystem-reserved SBOM paths once the relevant build tooling supports it.

Files changed (11) +702 / -0

Enhancement (2) +348 / -0
generate_notice.pyGenerate deduplicated third-party notices from crate manifests and license files +147/-0

Generate deduplicated third-party notices from crate manifests and license files

• Adds a hermetic NOTICE generator that parses Cargo.toml [package] metadata for name/version/license and pairs it with collected LICENSE/NOTICE texts. Deduplicates identical license bodies and outputs a components table referencing shared license text blocks.

common/manager/generate_notice.py

generate_sbom.pyGenerate CycloneDX 1.5 SBOM from Cargo.lock with licenses from manifests +201/-0

Generate CycloneDX 1.5 SBOM from Cargo.lock with licenses from manifests

• Adds an offline, cargo-free SBOM generator that parses Cargo.lock to build components (purls, checksums, dependency graph) and overlays SPDX license expressions from collected Cargo.toml manifests. Emits CycloneDX JSON with deterministic serialNumber support for reproducible builds.

common/manager/generate_sbom.py

Other (9) +354 / -0
BUILD.bazelAdd SBOM/NOTICE generators and Bazel targets for Selenium Manager artifacts +53/-0

Add SBOM/NOTICE generators and Bazel targets for Selenium Manager artifacts

• Introduces private py_binary targets for SBOM and NOTICE generation and wires them into two new public Bazel rules that emit CycloneDX JSON and third-party notices for Selenium Manager. Exposes these artifacts to each binding’s package via explicit visibility.

common/manager/BUILD.bazel

crate_metadata.bzlAdd Bazel aspect + rules to harvest crate metadata and generate SBOM/NOTICE +219/-0

Add Bazel aspect + rules to harvest crate metadata and generate SBOM/NOTICE

• Adds a CrateMetadataInfo provider and an aspect that walks Rust deps/proc-macro deps to collect transitive vendored Cargo.toml and LICENSE/NOTICE files from crate_universe. Implements split-transition execution across shipped target platforms to union crate closures, then defines selenium_manager_sbom and selenium_manager_notice rules that run the Python generators with param files.

common/manager/crate_metadata.bzl

BUILD.bazelBundle Selenium Manager SBOM and NOTICE into the NuGet package +16/-0

Bundle Selenium Manager SBOM and NOTICE into the NuGet package

• Adds copy steps for the generated SBOM and NOTICE into the dotnet manager staging directory and includes both artifacts in nuget_pack outputs alongside the platform binaries.

dotnet/src/webdriver/BUILD.bazel

Selenium.WebDriver.nuspecShip SBOM and third-party notices in the NuGet package manifest +2/-0

Ship SBOM and third-party notices in the NuGet package manifest

• Adds nuspec file entries to include selenium-manager.cdx.json and selenium-manager-THIRD-PARTY-NOTICES.txt at the package root so consumers/scanners can discover them.

dotnet/src/webdriver/Selenium.WebDriver.nuspec

BUILD.bazelInclude Selenium Manager SBOM and NOTICE as JAR resources +14/-0

Include Selenium Manager SBOM and NOTICE as JAR resources

• Adds copy_file targets for the SBOM and NOTICE and includes them in the java_export resources list so they are packaged with the Selenium Manager binaries.

java/src/org/openqa/selenium/manager/BUILD.bazel

BUILD.bazelBundle Selenium Manager SBOM and NOTICE in the npm package +14/-0

Bundle Selenium Manager SBOM and NOTICE in the npm package

• Adds copy_file targets producing bin/selenium-manager.cdx.json and bin/selenium-manager-THIRD-PARTY-NOTICES.txt and includes them in npm_package srcs alongside the manager binaries.

javascript/selenium-webdriver/BUILD.bazel

BUILD.bazelBundle Selenium Manager SBOM and NOTICE in Python wheel data files +16/-0

Bundle Selenium Manager SBOM and NOTICE in Python wheel data files

• Copies the SBOM and NOTICE into selenium/webdriver/common/ next to Selenium Manager binaries and includes them in the py_library data set. Ensures the sdist file collection excludes these binary-adjacent artifacts along with the binaries.

py/BUILD.bazel

BUILD.bazelBundle Selenium Manager SBOM and NOTICE in the Ruby gem +14/-0

Bundle Selenium Manager SBOM and NOTICE in the Ruby gem

• Copies the generated artifacts into bin/ and includes them in rb_gem_build sources so the gem ships SBOM + third-party notices alongside the manager binaries.

rb/BUILD.bazel

BUILD.bazelExpose Cargo.lock as a public Bazel filegroup for SBOM generation +6/-0

Expose Cargo.lock as a public Bazel filegroup for SBOM generation

• Adds a public filegroup target for Cargo.lock so the SBOM rule can depend on it as a hermetic input without reaching into the filesystem directly.

rust/BUILD.bazel

@qodo-code-review

Copy link
Copy Markdown
Contributor

Code Review by Qodo

🐞 Bugs (1) 📘 Rule violations (1) 📜 Skill insights (0)

Grey Divider


Action required

1. Aspect crashes on file labels 🐞 Bug ≡ Correctness
Description
_collect() in crate_manifests_aspect unconditionally calls dep.files.to_list() for
compile_data/data entries, which can fail when those attributes contain direct file labels (not
targets with .files). This can break analysis/build of //common/manager:selenium-manager-sbom
and //common/manager:selenium-manager-notice because //rust:selenium_manager uses compile_data
with file labels.
Code

common/manager/crate_metadata.bzl[R82-87]

+    for attr in ["compile_data", "data"]:
+        if not hasattr(ctx.rule.attr, attr):
+            continue
+        for dep in getattr(ctx.rule.attr, attr):
+            for file in dep.files.to_list():
+                if not _is_repo_root(file):
Evidence
The aspect’s _collect() currently assumes .files exists on every compile_data entry, but
//rust:selenium_manager demonstrates compile_data containing direct file labels; the aspect will
traverse that target via deps from the seeded //rust:selenium-manager crate.

common/manager/crate_metadata.bzl[79-93]
rust/BUILD.bazel[100-119]
common/manager/BUILD.bazel[34-63]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The aspect helper `_collect(ctx)` assumes every element of `ctx.rule.attr.compile_data` / `ctx.rule.attr.data` has a `.files` field and calls `dep.files.to_list()`. When those attributes contain file labels (which can happen in this repo), the aspect can crash during analysis, preventing SBOM/NOTICE generation.

## Issue Context
In `//rust:selenium_manager`, `compile_data` is populated with direct file labels (e.g., markdown resources). The SBOM/NOTICE rules seed the aspect from `//rust:selenium-manager`, which depends on `//rust:selenium_manager`, so the aspect will evaluate `_collect()` on a rule with file labels in `compile_data`.

## Fix Focus Areas
- common/manager/crate_metadata.bzl[79-93]

### Suggested implementation direction
Update `_collect()` to handle both cases:
- If an attribute entry is a file-like object, treat it as a single file.
- If it is a target, iterate `target.files`.

For example (Starlark sketch):
```starlark
for dep in getattr(ctx.rule.attr, attr):
   files = []
   if hasattr(dep, "files"):
       files = dep.files.to_list()
   else:
       # file label
       files = [dep]
   for file in files:
       ...
```
Alternatively, prefer `ctx.rule.files.<attr>` when present, to directly obtain `File` objects for file labels, and separately handle target entries if needed.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

2. SBOM/NOTICE generators untested 📘 Rule violation ☼ Reliability
Description
New Python generators for selenium-manager.cdx.json and selenium-manager-THIRD-PARTY-NOTICES.txt
are introduced without accompanying small/unit tests, increasing regression risk for license/SBOM
artifact correctness. This conflicts with the requirement to cover new behavior with unit tests
where feasible.
Code

common/manager/BUILD.bazel[R15-27]

+py_binary(
+    name = "sbom_generator",
+    srcs = ["generate_sbom.py"],
+    main = "generate_sbom.py",
+    visibility = ["//visibility:private"],
+)
+
+py_binary(
+    name = "notice_generator",
+    srcs = ["generate_notice.py"],
+    main = "generate_notice.py",
+    visibility = ["//visibility:private"],
+)
Evidence
PR Compliance ID 5 requires new/changed behavior to be covered with small/unit tests where feasible.
This PR adds new generator entrypoints in common/manager/BUILD.bazel and substantial
parsing/formatting logic in generate_sbom.py and generate_notice.py, but does not add any test
targets/files alongside these additions.

AGENTS.md: Testing: Prefer Small Unit Tests and Avoid Mocks That Misrepresent Contracts
common/manager/BUILD.bazel[15-27]
common/manager/generate_sbom.py[34-167]
common/manager/generate_notice.py[43-118]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The newly-added SBOM and NOTICE generator scripts include non-trivial parsing/formatting logic but no corresponding small/unit tests were added in this PR.

## Issue Context
These scripts generate compliance artifacts (CycloneDX SBOM + THIRD-PARTY-NOTICES). Without unit tests, changes to Cargo.lock parsing, manifest parsing, dependency resolution, and license-text deduplication can silently break artifact correctness.

## Fix Focus Areas
- common/manager/BUILD.bazel[15-27]
- common/manager/generate_sbom.py[34-167]
- common/manager/generate_notice.py[43-118]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Qodo Logo

Comment on lines +15 to +27
py_binary(
name = "sbom_generator",
srcs = ["generate_sbom.py"],
main = "generate_sbom.py",
visibility = ["//visibility:private"],
)

py_binary(
name = "notice_generator",
srcs = ["generate_notice.py"],
main = "generate_notice.py",
visibility = ["//visibility:private"],
)

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.

Remediation recommended

1. Sbom/notice generators untested 📘 Rule violation ☼ Reliability

New Python generators for selenium-manager.cdx.json and selenium-manager-THIRD-PARTY-NOTICES.txt
are introduced without accompanying small/unit tests, increasing regression risk for license/SBOM
artifact correctness. This conflicts with the requirement to cover new behavior with unit tests
where feasible.
Agent Prompt
## Issue description
The newly-added SBOM and NOTICE generator scripts include non-trivial parsing/formatting logic but no corresponding small/unit tests were added in this PR.

## Issue Context
These scripts generate compliance artifacts (CycloneDX SBOM + THIRD-PARTY-NOTICES). Without unit tests, changes to Cargo.lock parsing, manifest parsing, dependency resolution, and license-text deduplication can silently break artifact correctness.

## Fix Focus Areas
- common/manager/BUILD.bazel[15-27]
- common/manager/generate_sbom.py[34-167]
- common/manager/generate_notice.py[43-118]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +82 to +87
for attr in ["compile_data", "data"]:
if not hasattr(ctx.rule.attr, attr):
continue
for dep in getattr(ctx.rule.attr, attr):
for file in dep.files.to_list():
if not _is_repo_root(file):

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.

Action required

2. Aspect crashes on file labels 🐞 Bug ≡ Correctness

_collect() in crate_manifests_aspect unconditionally calls dep.files.to_list() for
compile_data/data entries, which can fail when those attributes contain direct file labels (not
targets with .files). This can break analysis/build of //common/manager:selenium-manager-sbom
and //common/manager:selenium-manager-notice because //rust:selenium_manager uses compile_data
with file labels.
Agent Prompt
## Issue description
The aspect helper `_collect(ctx)` assumes every element of `ctx.rule.attr.compile_data` / `ctx.rule.attr.data` has a `.files` field and calls `dep.files.to_list()`. When those attributes contain file labels (which can happen in this repo), the aspect can crash during analysis, preventing SBOM/NOTICE generation.

## Issue Context
In `//rust:selenium_manager`, `compile_data` is populated with direct file labels (e.g., markdown resources). The SBOM/NOTICE rules seed the aspect from `//rust:selenium-manager`, which depends on `//rust:selenium_manager`, so the aspect will evaluate `_collect()` on a rule with file labels in `compile_data`.

## Fix Focus Areas
- common/manager/crate_metadata.bzl[79-93]

### Suggested implementation direction
Update `_collect()` to handle both cases:
- If an attribute entry is a file-like object, treat it as a single file.
- If it is a target, iterate `target.files`.

For example (Starlark sketch):
```starlark
for dep in getattr(ctx.rule.attr, attr):
    files = []
    if hasattr(dep, "files"):
        files = dep.files.to_list()
    else:
        # file label
        files = [dep]
    for file in files:
        ...
```
Alternatively, prefer `ctx.rule.files.<attr>` when present, to directly obtain `File` objects for file labels, and separately handle target entries if needed.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

@titusfortner
titusfortner marked this pull request as draft July 22, 2026 17:26
@titusfortner

Copy link
Copy Markdown
Member Author

Unfortunately, I think the right way to do this is actually to extend our current release assets hack rather than try to get it working cross platform with bazel.

@titusfortner

Copy link
Copy Markdown
Member Author

Closing in favor of #17820
The bazel-only approach will need to be sorted in #17586

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

Labels

B-build Includes scripting, bazel and CI integrations B-manager Selenium Manager C-dotnet .NET Bindings C-java Java Bindings C-nodejs JavaScript Bindings C-py Python Bindings C-rb Ruby Bindings C-rust Rust code is mostly Selenium Manager

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[🚀 Feature]: Include SBOMs with Selenium Manager binaries

2 participants