From e4f2b29d0ad6e22c9256d40014d926a977c1e307 Mon Sep 17 00:00:00 2001 From: stacknil Date: Mon, 27 Apr 2026 11:02:15 +0800 Subject: [PATCH] document production pypi decision gate --- tools/sbom-diff-and-risk/README.md | 761 +++++++++--------- .../pypi-production-publishing-decision.md | 145 ++++ .../docs/pypi-trusted-publishing-readiness.md | 308 +++---- .../docs/release-provenance.md | 174 ++-- .../docs/self-provenance.md | 232 +++--- tools/sbom-diff-and-risk/docs/verification.md | 86 +- 6 files changed, 930 insertions(+), 776 deletions(-) create mode 100644 tools/sbom-diff-and-risk/docs/pypi-production-publishing-decision.md diff --git a/tools/sbom-diff-and-risk/README.md b/tools/sbom-diff-and-risk/README.md index 760bee4..552dc92 100644 --- a/tools/sbom-diff-and-risk/README.md +++ b/tools/sbom-diff-and-risk/README.md @@ -1,379 +1,382 @@ -# sbom-diff-and-risk - -v0.5 PR 4 adds a TestPyPI / Trusted Publishing readiness dry-run path. It keeps production PyPI publishing disabled, keeps dependency analysis local and deterministic by default, and does not change CLI analysis behavior. - -`sbom-diff-and-risk` is a local, deterministic CLI for comparing two SBOMs or dependency manifests and producing JSON plus Markdown reports. - -It uses conservative heuristics for change intelligence. By default it does not resolve CVEs, does not act as a reputation oracle, and does not perform hidden network enrichment. - -## Start Here - -This project has two different provenance stories: - -1. If you want to verify `sbom-diff-and-risk` itself, start with [docs/verification.md](D:/OneDrive/Code/scientific-computing-toolkit/tools/sbom-diff-and-risk/docs/verification.md). -2. If you want to use `sbom-diff-and-risk` to analyze third-party dependency provenance, start with [Dependency provenance analysis](#dependency-provenance-analysis-opt-in) and [Dependency provenance reporting](#dependency-provenance-reporting). - -## Scope - -- Normalize two local inputs into a shared component schema. -- Diff components as `added`, `removed`, and `changed`. -- Apply conservative, heuristic risk buckets to newly added and changed components. -- Apply optional local policy enforcement over those findings. -- Produce machine-friendly JSON and reviewer-friendly Markdown reports. -- Stay fully local-file based by default. - -## v0.1 Internal Component Model - -The normalized schema is the core design choice for the project: - -- `name: str` -- `version: str | None` -- `ecosystem: str` -- `purl: str | None` -- `license_id: str | None` -- `supplier: str | None` -- `source_url: str | None` -- `bom_ref: str | None` -- `raw_type: str | None` -- `evidence: dict` - -Diff identity is intentionally conservative and uses this precedence: - -1. `purl` -2. `bom_ref` -3. `(ecosystem, name)` - -When a `purl` includes a version, the tool keeps the full value in `Component.purl` for auditability but uses the versionless package coordinate for identity so upgrades still diff as `changed`. - -## Non-goals - -- No vulnerability database integration in v0.1. -- No CVE, advisory, or exploit resolution in v0.1. -- No reputation scoring or malware verdicts. -- No hidden enrichment or implicit network access. -- No web UI. -- No packaged GitHub Marketplace Action. - -## Supported Formats - -- CycloneDX JSON -- SPDX JSON -- `requirements.txt` -- `pyproject.toml` via PEP 621 `[project]` metadata -- `pyproject.toml` dependency groups via PEP 735 `[dependency-groups]` with explicit selection - -## Risk Bucket Semantics - -The current heuristic buckets are: - -- `new_package` -- `major_upgrade` -- `version_change_unclassified` -- `unknown_license` -- `stale_package` -- `suspicious_source` -- `not_evaluated` - -Offline `stale_package` evaluation is intentionally deferred. When enrichment is disabled, the tool emits `not_evaluated` findings instead of guessing. - -## Output Formats - -- `report.json` -- `report.md` -- `report.sarif` - -## Install - -```bash -python -m pip install -e .[dev] -``` - -## Usage - -Generate reports from the bundled CycloneDX example inputs: - -```bash -sbom-diff-risk compare \ - --before examples/cdx_before.json \ - --after examples/cdx_after.json \ - --format auto \ - --out-json outputs/report.json \ - --out-md outputs/report.md -``` - -Generate reports from the `requirements.txt` examples: - -```bash -sbom-diff-risk compare \ - --before examples/requirements_before.txt \ - --after examples/requirements_after.txt \ - --format auto \ - --out-json outputs/requirements-report.json \ - --out-md outputs/requirements-report.md -``` - -Use explicit format flags when you do not want auto-detection: - -```bash -sbom-diff-risk compare \ - --before examples/spdx_before.json \ - --after examples/spdx_after.json \ - --before-format spdx-json \ - --after-format spdx-json \ - --out-json outputs/spdx-report.json \ - --out-md outputs/spdx-report.md -``` - -Generate reports from PEP 621 `pyproject.toml` examples: - -```bash -sbom-diff-risk compare \ - --before examples/pyproject_before.toml \ - --after examples/pyproject_after.toml \ - --format auto \ - --out-json outputs/pyproject-report.json \ - --out-md outputs/pyproject-report.md -``` - -Generate reports for a specific PEP 735 dependency group: - -```bash -sbom-diff-risk compare \ - --before examples/pyproject_groups_before.toml \ - --after examples/pyproject_groups_after.toml \ - --format pyproject-toml \ - --pyproject-group dev \ - --out-json outputs/pyproject-groups-report.json \ - --out-md outputs/pyproject-groups-report.md -``` - -## CLI Flags - -- `--before path` -- `--after path` -- `--format auto|cyclonedx-json|spdx-json|requirements-txt|pyproject-toml` -- `--before-format cyclonedx-json|spdx-json|requirements-txt|pyproject-toml` -- `--after-format cyclonedx-json|spdx-json|requirements-txt|pyproject-toml` -- `--pyproject-group name` -- `--out-json path` -- `--out-md path` -- `--out-sarif path` -- `--policy path` -- `--fail-on rule[,rule...]` -- `--warn-on rule[,rule...]` -- `--strict` -- `--enrich-pypi` -- `--pypi-timeout seconds` -- `--enrich-scorecard` -- `--scorecard-timeout seconds` -- `--source-allowlist pypi.org,files.pythonhosted.org,github.com` - -Offline mode remains the default. No network access occurs unless `--enrich-pypi` or `--enrich-scorecard` is set explicitly. - -## Dependency Provenance Analysis (Opt-in) - -This section is about analyzing third-party package provenance signals. It is not about verifying the `sbom-diff-and-risk` tool's own release artifacts. - -PyPI provenance and integrity enrichment is explicit and additive in this PR: - -- only Python / PyPI packages are queried -- no hidden network access occurs in default mode -- enrichment results are captured as evidence and summarized in the reports -- per-component `evidence.provenance` records stable lookup fields such as `supported`, `lookup_performed`, and per-file attestation totals -- lack of attestation is treated as unavailable metadata, not as proof of compromise -- policy evaluation can use these signals explicitly when configured -- SARIF stays conservative and only emits selected high-signal provenance policy violations - -When enabled, the tool queries PyPI-facing release metadata plus file-level provenance data and records stable evidence fields under component `evidence.provenance`, along with run metadata under `metadata.enrichment` and the top-level trust-signal report fields in the JSON report. - -```bash -sbom-diff-risk compare \ - --before examples/requirements_before.txt \ - --after examples/requirements_after.txt \ - --enrich-pypi \ - --pypi-timeout 3 \ - --out-json outputs/report-enriched.json -``` - -## Dependency Provenance Reporting - -When provenance enrichment is enabled, the reports surface trust signals directly instead of burying them in component evidence: - -- JSON includes `provenance_summary`, `attestation_summary`, `enrichment_metadata`, `trust_signal_notes`, and `provenance_policy_impact` -- Markdown includes `Provenance summary`, `Attestation gaps`, `Policy impact for provenance-related rules`, and `Trust signal notes` -- core diff semantics do not change when enrichment is enabled -- SARIF maps only selected high-signal provenance decisions such as `provenance_required`, blocking `missing_attestation`, and blocking `unverified_provenance` -- provenance-related SARIF alerts prefer file-level locations that point to the relevant compared manifest or SBOM input - -Routine enrichment outcomes remain JSON and Markdown evidence for review. Non-blocking enrichment facts do not automatically become SARIF alerts. - -## Opt-in Scorecard Enrichment - -OpenSSF Scorecard enrichment is also explicit and advisory: - -- no Scorecard requests are made unless `--enrich-scorecard` is set -- lookups only occur when a component can be mapped to a repository with high confidence from explicit metadata -- repository registry pages and ambiguous URLs are treated as unmapped instead of inferred -- Scorecard results are auxiliary trust signals, not proof of safety -- Scorecard-only SARIF alerts are emitted only when policy explicitly turns a threshold breach into a violation - -```bash -sbom-diff-risk compare \ - --before examples/cdx_before.json \ - --after examples/cdx_after.json \ - --enrich-scorecard \ - --scorecard-timeout 3 \ - --out-json outputs/report-scorecard.json -``` - -If you want policy gating, make it explicit with a v3 policy such as [policy-scorecard-minimal.yml](D:/OneDrive/Code/scientific-computing-toolkit/tools/sbom-diff-and-risk/examples/policy-scorecard-minimal.yml), which sets `minimum_scorecard_score` and opts into the `scorecard_below_threshold` rule. - -Setting `minimum_scorecard_score` alone is advisory metadata for review. It only affects policy outcomes when `scorecard_below_threshold` is configured explicitly in `block_on`, `warn_on`, or `ignore_rules`. - -## Tool Provenance And Verification - -This section is about verifying `sbom-diff-and-risk` itself. If you want the shortest path to the right verification instructions, start with [docs/verification.md](D:/OneDrive/Code/scientific-computing-toolkit/tools/sbom-diff-and-risk/docs/verification.md). - -This repository also records provenance for `sbom-diff-and-risk` itself by generating GitHub artifact attestations for the wheel and source distribution produced by the `sbom-diff-and-risk-ci` workflow. - -- the attested files are the wheel and source distribution built by `python -m build` from `tools/sbom-diff-and-risk` -- the build files are uploaded together as the `sbom-diff-and-risk-dist` workflow artifact -- version-tag runs also publish those same built files as GitHub Release assets for the matching tag -- only trusted non-PR runs publish the attestation -- consumers can verify workflow-built artifacts with `gh attestation verify` -- consumers can verify immutable releases and downloaded release assets with `gh release verify` and `gh release verify-asset` -- this complements the tool's analysis of third-party supply-chain inputs, but it does not replace that analysis - -Verification docs: - -- [docs/verification.md](D:/OneDrive/Code/scientific-computing-toolkit/tools/sbom-diff-and-risk/docs/verification.md) for the quick decision guide -- [docs/self-provenance.md](D:/OneDrive/Code/scientific-computing-toolkit/tools/sbom-diff-and-risk/docs/self-provenance.md) for workflow-artifact attestation -- [docs/release-provenance.md](D:/OneDrive/Code/scientific-computing-toolkit/tools/sbom-diff-and-risk/docs/release-provenance.md) for release-asset verification and immutable release guidance -- [docs/pypi-trusted-publishing-readiness.md](D:/OneDrive/Code/scientific-computing-toolkit/tools/sbom-diff-and-risk/docs/pypi-trusted-publishing-readiness.md) for TestPyPI Trusted Publishing readiness, exact external publisher setup, and production PyPI blockers - -## Examples - -The [examples/](D:/OneDrive/Code/scientific-computing-toolkit/tools/sbom-diff-and-risk/examples) directory includes: - -- before/after inputs for CycloneDX JSON, SPDX JSON, `requirements.txt`, and `pyproject.toml` -- dependency-group examples at `examples/pyproject_groups_before.toml` and `examples/pyproject_groups_after.toml` -- example policies at `examples/policy-minimal.yml` and `examples/policy-strict.yml` -- provenance-aware policy examples at `examples/policy-provenance-minimal.yml` and `examples/policy-provenance-strict.yml` -- a Scorecard-aware policy example at `examples/policy-scorecard-minimal.yml` -- a sample pass JSON report at [sample-report.json](D:/OneDrive/Code/scientific-computing-toolkit/tools/sbom-diff-and-risk/examples/sample-report.json) -- a sample pass Markdown report at [sample-report.md](D:/OneDrive/Code/scientific-computing-toolkit/tools/sbom-diff-and-risk/examples/sample-report.md) -- sample policy-warn reports at [sample-policy-warn-report.json](D:/OneDrive/Code/scientific-computing-toolkit/tools/sbom-diff-and-risk/examples/sample-policy-warn-report.json) and [sample-policy-warn-report.md](D:/OneDrive/Code/scientific-computing-toolkit/tools/sbom-diff-and-risk/examples/sample-policy-warn-report.md) -- sample policy-fail reports at [sample-policy-fail-report.json](D:/OneDrive/Code/scientific-computing-toolkit/tools/sbom-diff-and-risk/examples/sample-policy-fail-report.json) and [sample-policy-fail-report.md](D:/OneDrive/Code/scientific-computing-toolkit/tools/sbom-diff-and-risk/examples/sample-policy-fail-report.md) -- a sample SARIF export at [sample-sarif.sarif](D:/OneDrive/Code/scientific-computing-toolkit/tools/sbom-diff-and-risk/examples/sample-sarif.sarif) -- provenance-aware sample reports at [sample-provenance-report.json](D:/OneDrive/Code/scientific-computing-toolkit/tools/sbom-diff-and-risk/examples/sample-provenance-report.json), [sample-provenance-report.md](D:/OneDrive/Code/scientific-computing-toolkit/tools/sbom-diff-and-risk/examples/sample-provenance-report.md), and [sample-provenance-report.sarif](D:/OneDrive/Code/scientific-computing-toolkit/tools/sbom-diff-and-risk/examples/sample-provenance-report.sarif) -- Scorecard-aware sample reports at [sample-scorecard-report.json](D:/OneDrive/Code/scientific-computing-toolkit/tools/sbom-diff-and-risk/examples/sample-scorecard-report.json), [sample-scorecard-report.md](D:/OneDrive/Code/scientific-computing-toolkit/tools/sbom-diff-and-risk/examples/sample-scorecard-report.md), and [sample-scorecard-report.sarif](D:/OneDrive/Code/scientific-computing-toolkit/tools/sbom-diff-and-risk/examples/sample-scorecard-report.sarif) -- requirements-based sample reports at [sample-requirements-report.json](D:/OneDrive/Code/scientific-computing-toolkit/tools/sbom-diff-and-risk/examples/sample-requirements-report.json) and [sample-requirements-report.md](D:/OneDrive/Code/scientific-computing-toolkit/tools/sbom-diff-and-risk/examples/sample-requirements-report.md) - -## Enforcement Mode - -Policy enforcement is optional and deterministic. Exit codes are stable: - -- `0` = success / no blocking violations -- `1` = blocking policy violations -- `2` = usage, parse, policy, or runtime error - -Minimal policy enforcement example: - -```bash -sbom-diff-risk compare \ - --before examples/requirements_before.txt \ - --after examples/requirements_after.txt \ - --policy examples/policy-minimal.yml \ - --out-json outputs/report.json \ - --out-md outputs/report.md -``` - -Ad hoc enforcement without a policy file: - -```bash -sbom-diff-risk compare \ - --before examples/cdx_before.json \ - --after examples/cdx_after.json \ - --fail-on suspicious_source,unknown_license \ - --warn-on new_package \ - --out-json outputs/report.json \ - --out-md outputs/report.md -``` - -Failed runs still write reports on exit code `1`; stderr prints a concise blocking summary so CI logs are understandable without opening raw JSON. - -## SARIF Export - -SARIF export is intentionally conservative. The current renderer emits a GitHub-compatible SARIF 2.1.0 subset for: - -- `suspicious_source` -- `unknown_license` -- `major_upgrade` -- selected policy results such as `max_added_packages`, `allow_sources`, `provenance_required`, and blocking provenance violations like `missing_attestation` or `unverified_provenance` -- explicit Scorecard policy violations such as `scorecard_below_threshold` - -It does not turn every enrichment fact, diff, or informational heuristic into a code scanning alert. - -```bash -sbom-diff-risk compare \ - --before examples/sarif_before.json \ - --after examples/sarif_after.json \ - --policy examples/policy-strict.yml \ - --out-sarif outputs/report.sarif -``` - -For GitHub code scanning integration guidance and a minimal upload workflow, see [docs/github-code-scanning.md](D:/OneDrive/Code/scientific-computing-toolkit/tools/sbom-diff-and-risk/docs/github-code-scanning.md). - -For the shortest path to the tool-verification docs, start with [docs/verification.md](D:/OneDrive/Code/scientific-computing-toolkit/tools/sbom-diff-and-risk/docs/verification.md). - -For details on how this repository attests the tool's own wheel and source distribution artifacts, see [docs/self-provenance.md](D:/OneDrive/Code/scientific-computing-toolkit/tools/sbom-diff-and-risk/docs/self-provenance.md). - -For details on how version-tag releases publish those same build outputs as release assets, and how consumers can verify immutable releases with GitHub CLI, see [docs/release-provenance.md](D:/OneDrive/Code/scientific-computing-toolkit/tools/sbom-diff-and-risk/docs/release-provenance.md). - -For TestPyPI Trusted Publishing readiness, the manually gated dry-run workflow, and the reasons production PyPI upload remains disabled, see [docs/pypi-trusted-publishing-readiness.md](D:/OneDrive/Code/scientific-computing-toolkit/tools/sbom-diff-and-risk/docs/pypi-trusted-publishing-readiness.md). - -## Parser Boundaries - -Deterministic local mode intentionally supports a conservative subset of packaging syntax. The detailed matrix lives in [docs/parser-boundaries.md](D:/OneDrive/Code/scientific-computing-toolkit/tools/sbom-diff-and-risk/docs/parser-boundaries.md). - -### requirements.txt subset - -| Syntax | Status | Notes | -| --- | --- | --- | -| Plain PEP 508 requirement entries | Supported | Names, specifiers, extras, and markers | -| Comments, blank lines, line continuations | Supported | Normalized locally without installer behavior | -| `-r`, `--requirement` | Unsupported | Include chains fail closed | -| `-c`, `--constraint` | Unsupported | Constraint files fail closed | -| Editable installs | Unsupported | `-e` and `--editable` are rejected | -| Direct URL, VCS, and local path refs | Unsupported | Includes `pkg @ https://...`, `git+...`, wheels, archives, and local paths | -| Index and source options | Unsupported | Includes `--index-url`, `--extra-index-url`, `--find-links`, and related flags | - -### pyproject.toml subset - -- default parsing supports PEP 621 `[project.dependencies]` and `[project.optional-dependencies]` -- dependency groups are supported through PEP 735 `[dependency-groups]` -- dependency groups must be selected explicitly with `--pyproject-group ` -- dependency groups are not treated as aliases for `[project.optional-dependencies]` -- tool-specific layouts such as Poetry, Hatch, and PDM remain out of scope in v0.2 - -## Limitations - -- default mode is local-file based only. -- PyPI provenance enrichment is opt-in only via `--enrich-pypi`; default runs stay offline. -- `generated_at` remains `null` to preserve deterministic report output. -- `stale_package` is not resolved offline. The report emits `not_evaluated` instead. -- provenance evidence is recorded for supported PyPI packages only; unsupported and failed lookups remain explicit evidence gaps. -- SARIF export intentionally covers only a conservative subset of findings in v0.2, including only selected high-signal provenance policy violations. -- Scorecard enrichment is opt-in only via `--enrich-scorecard`, uses only high-confidence repository mappings, and remains advisory unless policy explicitly gates it. -- No vulnerability database integration, CVE matching, or advisory enrichment. -- `requirements.txt` support intentionally covers a conservative subset: plain PEP 508 requirement entries, comments, extras, markers, and line continuations. -- `requirements.txt` intentionally rejects include/constraint directives, editable installs, direct URL/path refs, index/source options, and other pip-only install flags in deterministic mode. -- `pyproject.toml` support intentionally covers a conservative subset: PEP 621 `[project.dependencies]`, `[project.optional-dependencies]`, and explicit PEP 735 `[dependency-groups]` selection. -- `pyproject.toml` intentionally does not support tool-specific layouts such as Poetry, Hatch, or PDM sections in v0.2. -- Risk buckets are heuristics, not security verdicts. -- Runtime-generated `outputs/` artifacts are ignored; tracked examples live in `examples/`. -- Policy files are YAML-only in v0.2 and unknown rule ids fail closed. - -## Current Status - -The project now normalizes local CycloneDX JSON, SPDX JSON, `requirements.txt`, and conservative `pyproject.toml` inputs, including explicit PEP 735 dependency-group selection, into the shared component model, diffs them deterministically, and generates stable JSON/Markdown/SARIF reports with tests and optional policy enforcement. +# sbom-diff-and-risk + +v0.5 PR 5 adds a production PyPI publishing decision gate. Production PyPI publishing remains deferred until the documented prerequisites are complete, dependency analysis stays local and deterministic by default, and CLI analysis behavior is unchanged. + +`sbom-diff-and-risk` is a local, deterministic CLI for comparing two SBOMs or dependency manifests and producing JSON plus Markdown reports. + +It uses conservative heuristics for change intelligence. By default it does not resolve CVEs, does not act as a reputation oracle, and does not perform hidden network enrichment. + +## Start Here + +This project has two different provenance stories: + +1. If you want to verify `sbom-diff-and-risk` itself, start with [docs/verification.md](D:/OneDrive/Code/scientific-computing-toolkit/tools/sbom-diff-and-risk/docs/verification.md). +2. If you want to use `sbom-diff-and-risk` to analyze third-party dependency provenance, start with [Dependency provenance analysis](#dependency-provenance-analysis-opt-in) and [Dependency provenance reporting](#dependency-provenance-reporting). + +## Scope + +- Normalize two local inputs into a shared component schema. +- Diff components as `added`, `removed`, and `changed`. +- Apply conservative, heuristic risk buckets to newly added and changed components. +- Apply optional local policy enforcement over those findings. +- Produce machine-friendly JSON and reviewer-friendly Markdown reports. +- Stay fully local-file based by default. + +## v0.1 Internal Component Model + +The normalized schema is the core design choice for the project: + +- `name: str` +- `version: str | None` +- `ecosystem: str` +- `purl: str | None` +- `license_id: str | None` +- `supplier: str | None` +- `source_url: str | None` +- `bom_ref: str | None` +- `raw_type: str | None` +- `evidence: dict` + +Diff identity is intentionally conservative and uses this precedence: + +1. `purl` +2. `bom_ref` +3. `(ecosystem, name)` + +When a `purl` includes a version, the tool keeps the full value in `Component.purl` for auditability but uses the versionless package coordinate for identity so upgrades still diff as `changed`. + +## Non-goals + +- No vulnerability database integration in v0.1. +- No CVE, advisory, or exploit resolution in v0.1. +- No reputation scoring or malware verdicts. +- No hidden enrichment or implicit network access. +- No web UI. +- No packaged GitHub Marketplace Action. + +## Supported Formats + +- CycloneDX JSON +- SPDX JSON +- `requirements.txt` +- `pyproject.toml` via PEP 621 `[project]` metadata +- `pyproject.toml` dependency groups via PEP 735 `[dependency-groups]` with explicit selection + +## Risk Bucket Semantics + +The current heuristic buckets are: + +- `new_package` +- `major_upgrade` +- `version_change_unclassified` +- `unknown_license` +- `stale_package` +- `suspicious_source` +- `not_evaluated` + +Offline `stale_package` evaluation is intentionally deferred. When enrichment is disabled, the tool emits `not_evaluated` findings instead of guessing. + +## Output Formats + +- `report.json` +- `report.md` +- `report.sarif` + +## Install + +```bash +python -m pip install -e .[dev] +``` + +## Usage + +Generate reports from the bundled CycloneDX example inputs: + +```bash +sbom-diff-risk compare \ + --before examples/cdx_before.json \ + --after examples/cdx_after.json \ + --format auto \ + --out-json outputs/report.json \ + --out-md outputs/report.md +``` + +Generate reports from the `requirements.txt` examples: + +```bash +sbom-diff-risk compare \ + --before examples/requirements_before.txt \ + --after examples/requirements_after.txt \ + --format auto \ + --out-json outputs/requirements-report.json \ + --out-md outputs/requirements-report.md +``` + +Use explicit format flags when you do not want auto-detection: + +```bash +sbom-diff-risk compare \ + --before examples/spdx_before.json \ + --after examples/spdx_after.json \ + --before-format spdx-json \ + --after-format spdx-json \ + --out-json outputs/spdx-report.json \ + --out-md outputs/spdx-report.md +``` + +Generate reports from PEP 621 `pyproject.toml` examples: + +```bash +sbom-diff-risk compare \ + --before examples/pyproject_before.toml \ + --after examples/pyproject_after.toml \ + --format auto \ + --out-json outputs/pyproject-report.json \ + --out-md outputs/pyproject-report.md +``` + +Generate reports for a specific PEP 735 dependency group: + +```bash +sbom-diff-risk compare \ + --before examples/pyproject_groups_before.toml \ + --after examples/pyproject_groups_after.toml \ + --format pyproject-toml \ + --pyproject-group dev \ + --out-json outputs/pyproject-groups-report.json \ + --out-md outputs/pyproject-groups-report.md +``` + +## CLI Flags + +- `--before path` +- `--after path` +- `--format auto|cyclonedx-json|spdx-json|requirements-txt|pyproject-toml` +- `--before-format cyclonedx-json|spdx-json|requirements-txt|pyproject-toml` +- `--after-format cyclonedx-json|spdx-json|requirements-txt|pyproject-toml` +- `--pyproject-group name` +- `--out-json path` +- `--out-md path` +- `--out-sarif path` +- `--policy path` +- `--fail-on rule[,rule...]` +- `--warn-on rule[,rule...]` +- `--strict` +- `--enrich-pypi` +- `--pypi-timeout seconds` +- `--enrich-scorecard` +- `--scorecard-timeout seconds` +- `--source-allowlist pypi.org,files.pythonhosted.org,github.com` + +Offline mode remains the default. No network access occurs unless `--enrich-pypi` or `--enrich-scorecard` is set explicitly. + +## Dependency Provenance Analysis (Opt-in) + +This section is about analyzing third-party package provenance signals. It is not about verifying the `sbom-diff-and-risk` tool's own release artifacts. + +PyPI provenance and integrity enrichment is explicit and additive in this PR: + +- only Python / PyPI packages are queried +- no hidden network access occurs in default mode +- enrichment results are captured as evidence and summarized in the reports +- per-component `evidence.provenance` records stable lookup fields such as `supported`, `lookup_performed`, and per-file attestation totals +- lack of attestation is treated as unavailable metadata, not as proof of compromise +- policy evaluation can use these signals explicitly when configured +- SARIF stays conservative and only emits selected high-signal provenance policy violations + +When enabled, the tool queries PyPI-facing release metadata plus file-level provenance data and records stable evidence fields under component `evidence.provenance`, along with run metadata under `metadata.enrichment` and the top-level trust-signal report fields in the JSON report. + +```bash +sbom-diff-risk compare \ + --before examples/requirements_before.txt \ + --after examples/requirements_after.txt \ + --enrich-pypi \ + --pypi-timeout 3 \ + --out-json outputs/report-enriched.json +``` + +## Dependency Provenance Reporting + +When provenance enrichment is enabled, the reports surface trust signals directly instead of burying them in component evidence: + +- JSON includes `provenance_summary`, `attestation_summary`, `enrichment_metadata`, `trust_signal_notes`, and `provenance_policy_impact` +- Markdown includes `Provenance summary`, `Attestation gaps`, `Policy impact for provenance-related rules`, and `Trust signal notes` +- core diff semantics do not change when enrichment is enabled +- SARIF maps only selected high-signal provenance decisions such as `provenance_required`, blocking `missing_attestation`, and blocking `unverified_provenance` +- provenance-related SARIF alerts prefer file-level locations that point to the relevant compared manifest or SBOM input + +Routine enrichment outcomes remain JSON and Markdown evidence for review. Non-blocking enrichment facts do not automatically become SARIF alerts. + +## Opt-in Scorecard Enrichment + +OpenSSF Scorecard enrichment is also explicit and advisory: + +- no Scorecard requests are made unless `--enrich-scorecard` is set +- lookups only occur when a component can be mapped to a repository with high confidence from explicit metadata +- repository registry pages and ambiguous URLs are treated as unmapped instead of inferred +- Scorecard results are auxiliary trust signals, not proof of safety +- Scorecard-only SARIF alerts are emitted only when policy explicitly turns a threshold breach into a violation + +```bash +sbom-diff-risk compare \ + --before examples/cdx_before.json \ + --after examples/cdx_after.json \ + --enrich-scorecard \ + --scorecard-timeout 3 \ + --out-json outputs/report-scorecard.json +``` + +If you want policy gating, make it explicit with a v3 policy such as [policy-scorecard-minimal.yml](D:/OneDrive/Code/scientific-computing-toolkit/tools/sbom-diff-and-risk/examples/policy-scorecard-minimal.yml), which sets `minimum_scorecard_score` and opts into the `scorecard_below_threshold` rule. + +Setting `minimum_scorecard_score` alone is advisory metadata for review. It only affects policy outcomes when `scorecard_below_threshold` is configured explicitly in `block_on`, `warn_on`, or `ignore_rules`. + +## Tool Provenance And Verification + +This section is about verifying `sbom-diff-and-risk` itself. If you want the shortest path to the right verification instructions, start with [docs/verification.md](D:/OneDrive/Code/scientific-computing-toolkit/tools/sbom-diff-and-risk/docs/verification.md). + +This repository also records provenance for `sbom-diff-and-risk` itself by generating GitHub artifact attestations for the wheel and source distribution produced by the `sbom-diff-and-risk-ci` workflow. + +- the attested files are the wheel and source distribution built by `python -m build` from `tools/sbom-diff-and-risk` +- the build files are uploaded together as the `sbom-diff-and-risk-dist` workflow artifact +- version-tag runs also publish those same built files as GitHub Release assets for the matching tag +- only trusted non-PR runs publish the attestation +- consumers can verify workflow-built artifacts with `gh attestation verify` +- consumers can verify immutable releases and downloaded release assets with `gh release verify` and `gh release verify-asset` +- this complements the tool's analysis of third-party supply-chain inputs, but it does not replace that analysis + +Verification docs: + +- [docs/verification.md](D:/OneDrive/Code/scientific-computing-toolkit/tools/sbom-diff-and-risk/docs/verification.md) for the quick decision guide +- [docs/self-provenance.md](D:/OneDrive/Code/scientific-computing-toolkit/tools/sbom-diff-and-risk/docs/self-provenance.md) for workflow-artifact attestation +- [docs/release-provenance.md](D:/OneDrive/Code/scientific-computing-toolkit/tools/sbom-diff-and-risk/docs/release-provenance.md) for release-asset verification and immutable release guidance +- [docs/pypi-trusted-publishing-readiness.md](D:/OneDrive/Code/scientific-computing-toolkit/tools/sbom-diff-and-risk/docs/pypi-trusted-publishing-readiness.md) for TestPyPI Trusted Publishing readiness and dry-run notes +- [docs/pypi-production-publishing-decision.md](D:/OneDrive/Code/scientific-computing-toolkit/tools/sbom-diff-and-risk/docs/pypi-production-publishing-decision.md) for the production PyPI decision gate, publisher identity, future workflow shape, and production prerequisites + +## Examples + +The [examples/](D:/OneDrive/Code/scientific-computing-toolkit/tools/sbom-diff-and-risk/examples) directory includes: + +- before/after inputs for CycloneDX JSON, SPDX JSON, `requirements.txt`, and `pyproject.toml` +- dependency-group examples at `examples/pyproject_groups_before.toml` and `examples/pyproject_groups_after.toml` +- example policies at `examples/policy-minimal.yml` and `examples/policy-strict.yml` +- provenance-aware policy examples at `examples/policy-provenance-minimal.yml` and `examples/policy-provenance-strict.yml` +- a Scorecard-aware policy example at `examples/policy-scorecard-minimal.yml` +- a sample pass JSON report at [sample-report.json](D:/OneDrive/Code/scientific-computing-toolkit/tools/sbom-diff-and-risk/examples/sample-report.json) +- a sample pass Markdown report at [sample-report.md](D:/OneDrive/Code/scientific-computing-toolkit/tools/sbom-diff-and-risk/examples/sample-report.md) +- sample policy-warn reports at [sample-policy-warn-report.json](D:/OneDrive/Code/scientific-computing-toolkit/tools/sbom-diff-and-risk/examples/sample-policy-warn-report.json) and [sample-policy-warn-report.md](D:/OneDrive/Code/scientific-computing-toolkit/tools/sbom-diff-and-risk/examples/sample-policy-warn-report.md) +- sample policy-fail reports at [sample-policy-fail-report.json](D:/OneDrive/Code/scientific-computing-toolkit/tools/sbom-diff-and-risk/examples/sample-policy-fail-report.json) and [sample-policy-fail-report.md](D:/OneDrive/Code/scientific-computing-toolkit/tools/sbom-diff-and-risk/examples/sample-policy-fail-report.md) +- a sample SARIF export at [sample-sarif.sarif](D:/OneDrive/Code/scientific-computing-toolkit/tools/sbom-diff-and-risk/examples/sample-sarif.sarif) +- provenance-aware sample reports at [sample-provenance-report.json](D:/OneDrive/Code/scientific-computing-toolkit/tools/sbom-diff-and-risk/examples/sample-provenance-report.json), [sample-provenance-report.md](D:/OneDrive/Code/scientific-computing-toolkit/tools/sbom-diff-and-risk/examples/sample-provenance-report.md), and [sample-provenance-report.sarif](D:/OneDrive/Code/scientific-computing-toolkit/tools/sbom-diff-and-risk/examples/sample-provenance-report.sarif) +- Scorecard-aware sample reports at [sample-scorecard-report.json](D:/OneDrive/Code/scientific-computing-toolkit/tools/sbom-diff-and-risk/examples/sample-scorecard-report.json), [sample-scorecard-report.md](D:/OneDrive/Code/scientific-computing-toolkit/tools/sbom-diff-and-risk/examples/sample-scorecard-report.md), and [sample-scorecard-report.sarif](D:/OneDrive/Code/scientific-computing-toolkit/tools/sbom-diff-and-risk/examples/sample-scorecard-report.sarif) +- requirements-based sample reports at [sample-requirements-report.json](D:/OneDrive/Code/scientific-computing-toolkit/tools/sbom-diff-and-risk/examples/sample-requirements-report.json) and [sample-requirements-report.md](D:/OneDrive/Code/scientific-computing-toolkit/tools/sbom-diff-and-risk/examples/sample-requirements-report.md) + +## Enforcement Mode + +Policy enforcement is optional and deterministic. Exit codes are stable: + +- `0` = success / no blocking violations +- `1` = blocking policy violations +- `2` = usage, parse, policy, or runtime error + +Minimal policy enforcement example: + +```bash +sbom-diff-risk compare \ + --before examples/requirements_before.txt \ + --after examples/requirements_after.txt \ + --policy examples/policy-minimal.yml \ + --out-json outputs/report.json \ + --out-md outputs/report.md +``` + +Ad hoc enforcement without a policy file: + +```bash +sbom-diff-risk compare \ + --before examples/cdx_before.json \ + --after examples/cdx_after.json \ + --fail-on suspicious_source,unknown_license \ + --warn-on new_package \ + --out-json outputs/report.json \ + --out-md outputs/report.md +``` + +Failed runs still write reports on exit code `1`; stderr prints a concise blocking summary so CI logs are understandable without opening raw JSON. + +## SARIF Export + +SARIF export is intentionally conservative. The current renderer emits a GitHub-compatible SARIF 2.1.0 subset for: + +- `suspicious_source` +- `unknown_license` +- `major_upgrade` +- selected policy results such as `max_added_packages`, `allow_sources`, `provenance_required`, and blocking provenance violations like `missing_attestation` or `unverified_provenance` +- explicit Scorecard policy violations such as `scorecard_below_threshold` + +It does not turn every enrichment fact, diff, or informational heuristic into a code scanning alert. + +```bash +sbom-diff-risk compare \ + --before examples/sarif_before.json \ + --after examples/sarif_after.json \ + --policy examples/policy-strict.yml \ + --out-sarif outputs/report.sarif +``` + +For GitHub code scanning integration guidance and a minimal upload workflow, see [docs/github-code-scanning.md](D:/OneDrive/Code/scientific-computing-toolkit/tools/sbom-diff-and-risk/docs/github-code-scanning.md). + +For the shortest path to the tool-verification docs, start with [docs/verification.md](D:/OneDrive/Code/scientific-computing-toolkit/tools/sbom-diff-and-risk/docs/verification.md). + +For details on how this repository attests the tool's own wheel and source distribution artifacts, see [docs/self-provenance.md](D:/OneDrive/Code/scientific-computing-toolkit/tools/sbom-diff-and-risk/docs/self-provenance.md). + +For details on how version-tag releases publish those same build outputs as release assets, and how consumers can verify immutable releases with GitHub CLI, see [docs/release-provenance.md](D:/OneDrive/Code/scientific-computing-toolkit/tools/sbom-diff-and-risk/docs/release-provenance.md). + +For TestPyPI Trusted Publishing readiness and the completed dry-run path, see [docs/pypi-trusted-publishing-readiness.md](D:/OneDrive/Code/scientific-computing-toolkit/tools/sbom-diff-and-risk/docs/pypi-trusted-publishing-readiness.md). + +For the production PyPI decision gate, including the intended package name, first-version rule, publisher identity, future workflow shape, and provenance boundaries, see [docs/pypi-production-publishing-decision.md](D:/OneDrive/Code/scientific-computing-toolkit/tools/sbom-diff-and-risk/docs/pypi-production-publishing-decision.md). + +## Parser Boundaries + +Deterministic local mode intentionally supports a conservative subset of packaging syntax. The detailed matrix lives in [docs/parser-boundaries.md](D:/OneDrive/Code/scientific-computing-toolkit/tools/sbom-diff-and-risk/docs/parser-boundaries.md). + +### requirements.txt subset + +| Syntax | Status | Notes | +| --- | --- | --- | +| Plain PEP 508 requirement entries | Supported | Names, specifiers, extras, and markers | +| Comments, blank lines, line continuations | Supported | Normalized locally without installer behavior | +| `-r`, `--requirement` | Unsupported | Include chains fail closed | +| `-c`, `--constraint` | Unsupported | Constraint files fail closed | +| Editable installs | Unsupported | `-e` and `--editable` are rejected | +| Direct URL, VCS, and local path refs | Unsupported | Includes `pkg @ https://...`, `git+...`, wheels, archives, and local paths | +| Index and source options | Unsupported | Includes `--index-url`, `--extra-index-url`, `--find-links`, and related flags | + +### pyproject.toml subset + +- default parsing supports PEP 621 `[project.dependencies]` and `[project.optional-dependencies]` +- dependency groups are supported through PEP 735 `[dependency-groups]` +- dependency groups must be selected explicitly with `--pyproject-group ` +- dependency groups are not treated as aliases for `[project.optional-dependencies]` +- tool-specific layouts such as Poetry, Hatch, and PDM remain out of scope in v0.2 + +## Limitations + +- default mode is local-file based only. +- PyPI provenance enrichment is opt-in only via `--enrich-pypi`; default runs stay offline. +- `generated_at` remains `null` to preserve deterministic report output. +- `stale_package` is not resolved offline. The report emits `not_evaluated` instead. +- provenance evidence is recorded for supported PyPI packages only; unsupported and failed lookups remain explicit evidence gaps. +- SARIF export intentionally covers only a conservative subset of findings in v0.2, including only selected high-signal provenance policy violations. +- Scorecard enrichment is opt-in only via `--enrich-scorecard`, uses only high-confidence repository mappings, and remains advisory unless policy explicitly gates it. +- No vulnerability database integration, CVE matching, or advisory enrichment. +- `requirements.txt` support intentionally covers a conservative subset: plain PEP 508 requirement entries, comments, extras, markers, and line continuations. +- `requirements.txt` intentionally rejects include/constraint directives, editable installs, direct URL/path refs, index/source options, and other pip-only install flags in deterministic mode. +- `pyproject.toml` support intentionally covers a conservative subset: PEP 621 `[project.dependencies]`, `[project.optional-dependencies]`, and explicit PEP 735 `[dependency-groups]` selection. +- `pyproject.toml` intentionally does not support tool-specific layouts such as Poetry, Hatch, or PDM sections in v0.2. +- Risk buckets are heuristics, not security verdicts. +- Runtime-generated `outputs/` artifacts are ignored; tracked examples live in `examples/`. +- Policy files are YAML-only in v0.2 and unknown rule ids fail closed. + +## Current Status + +The project now normalizes local CycloneDX JSON, SPDX JSON, `requirements.txt`, and conservative `pyproject.toml` inputs, including explicit PEP 735 dependency-group selection, into the shared component model, diffs them deterministically, and generates stable JSON/Markdown/SARIF reports with tests and optional policy enforcement. diff --git a/tools/sbom-diff-and-risk/docs/pypi-production-publishing-decision.md b/tools/sbom-diff-and-risk/docs/pypi-production-publishing-decision.md new file mode 100644 index 0000000..0aece77 --- /dev/null +++ b/tools/sbom-diff-and-risk/docs/pypi-production-publishing-decision.md @@ -0,0 +1,145 @@ +# Production PyPI publishing decision + +This page records the PR 5 production PyPI gate for `sbom-diff-and-risk`. + +## PR 5 decision + +Production PyPI publishing is **deferred, but conditionally allowed after the prerequisites below are complete**. In short: production PyPI is currently deferred. + +PR 5 does not add an enabled production publishing workflow and does not publish to production PyPI. The successful TestPyPI Trusted Publishing dry-run proves that the package metadata can render on TestPyPI and that the TestPyPI OIDC path can work, but it is not automatic proof that production PyPI publishing is ready. + +The production gate is intentionally conservative because: + +- the production PyPI project does not currently exist under the intended name +- the package metadata still declares version `0.4.1` +- the first production upload should be a deliberate release version, not an old dry-run version +- the production PyPI pending publisher or trusted publisher has not been configured +- the production GitHub environment has not yet been confirmed + +## Package name and external state + +The production package name should be `sbom-diff-and-risk`. + +As checked on April 26, 2026: + +- `https://pypi.org/pypi/sbom-diff-and-risk/json` returned `404` +- `https://test.pypi.org/pypi/sbom-diff-and-risk/json` returned `200` +- TestPyPI reports `sbom-diff-and-risk` version `0.4.1` + +This means the intended production project name is not currently visible on production PyPI, while the TestPyPI dry-run project exists. Treat the production name as available for this decision, but re-check immediately before configuration because PyPI can reserve, prohibit, or receive new projects at any time. The first production upload should use a production PyPI pending publisher unless the project is created by a maintainer before the publishing workflow is enabled. + +## First production version + +Do not publish `0.4.1` to production PyPI casually. + +The first production PyPI version should be `0.5.0` only if v0.5 is approved as the first production package release. Otherwise, defer to a later GitHub release tag. + +For the first production upload: + +- the GitHub tag should be `v` +- `tools/sbom-diff-and-risk/pyproject.toml` should declare the matching `` +- the GitHub release and release assets should be available for the same tag +- the production PyPI workflow should run from the matching tag ref +- the production PyPI upload should use the checked distributions from that workflow run + +## Production publisher identity + +Configure the production PyPI publisher to match this identity exactly: + +| Field | Value | +| --- | --- | +| PyPI project name | `sbom-diff-and-risk` | +| GitHub owner | `stacknil` | +| GitHub repository | `scientific-computing-toolkit` | +| Future workflow file path | `.github/workflows/sbom-diff-and-risk-pypi.yml` | +| Trusted Publisher workflow name field | `sbom-diff-and-risk-pypi.yml` | +| GitHub environment | `pypi` | + +If production PyPI still has no project for this name, configure a pending publisher for a new project. If the project exists by the time production publishing is implemented, add the trusted publisher to the existing project instead. + +Do not create or document a PyPI API token for this workflow. Production upload should use Trusted Publishing / OIDC only. + +PyPI-side setup should use these paths: + +- for a new production project, create a pending publisher on production PyPI for project `sbom-diff-and-risk` with the owner, repository, workflow, and environment values above +- for an existing production project, open that project on production PyPI and add a trusted publisher with the same owner, repository, workflow, and environment values +- leave the environment field as `pypi`; if the PyPI publisher omits the environment, it will not match the future publish job identity +- do not add a PyPI API token, PyPI password, or GitHub publishing secret as a fallback + +## Prerequisites before enabling production publishing + +Before adding `.github/workflows/sbom-diff-and-risk-pypi.yml`, maintainers should complete all of these checks: + +- confirm the intended production package name still resolves as expected on production PyPI +- choose the first production version, likely `0.5.0` or a later release tag +- update `pyproject.toml` to that version +- create or verify the matching GitHub tag and release assets +- create the GitHub environment named `pypi` +- configure required reviewers or equivalent repository controls on the `pypi` environment +- create the PyPI pending publisher or existing-project trusted publisher with the exact identity above +- run the future workflow in no-publish mode first and confirm the publish job is skipped +- verify the checked distributions with `python -m twine check dist/*` + +## Future workflow shape + +PR 5 intentionally documents the future workflow shape without enabling it. + +The future production workflow should: + +- use `workflow_dispatch` only for the initial production publishing process +- require an explicit boolean input such as `publish_to_pypi` +- require a confirmation string such as `publish sbom-diff-and-risk to production PyPI` +- require an expected version input and assert that it matches `pyproject.toml` +- require the run ref to be a version tag such as `refs/tags/v0.5.0` +- build the wheel and source distribution once +- run `python -m twine check dist/*` +- upload the checked distributions as a workflow artifact +- publish only from a separate gated job that downloads that artifact +- use the GitHub environment `pypi` on the publish job +- grant `id-token: write` only to the publish job +- avoid production upload on ordinary push or pull request events + +The publish step should use `pypa/gh-action-pypi-publish@release/v1` without a `repository-url` override so it targets production PyPI. + +## Provenance boundaries + +Production PyPI Trusted Publishing provenance, GitHub workflow artifact attestations, and GitHub Release asset verification answer related but different questions. + +PyPI Trusted Publishing provenance answers: + +- was this distribution uploaded to this PyPI project through the configured GitHub publisher identity? +- did the upload use the expected owner, repository, workflow file, and environment? + +GitHub workflow artifact attestations answer: + +- were these local wheel or source distribution bytes built by `.github/workflows/sbom-diff-and-risk-ci.yml`? +- do the downloaded files match the attested workflow subjects? + +GitHub Release verification answers: + +- does the GitHub Release record have a valid release attestation? +- does a downloaded release asset match an attested asset from an immutable release? + +Do not treat one provenance surface as a replacement for the others. A PyPI package can have valid Trusted Publishing provenance without proving that it is byte-for-byte identical to a GitHub Release asset. Consumers who need cross-surface verification should compare hashes between a PyPI download and the corresponding GitHub Release asset, then use the GitHub verification flows documented in `self-provenance.md` and `release-provenance.md`. + +## Consumer guidance after a future production release + +After production publishing is enabled in a later PR, consumers should: + +- install only the intended project name, `sbom-diff-and-risk` +- check that the PyPI version matches the expected GitHub release tag +- inspect PyPI's Trusted Publishing provenance for the expected publisher identity +- use GitHub artifact attestation verification for workflow-built files when downloading from GitHub +- use GitHub Release verification for immutable release assets when relying on GitHub Releases +- continue to treat TestPyPI as a dry-run environment only + +## PR 5 local verification + +PR 5 should remain a documentation and gate-design change. Local verification is still required to prove the existing package surface did not regress: + +```powershell +python -m build +python -m twine check dist/* +python -m pytest +git diff --check +``` diff --git a/tools/sbom-diff-and-risk/docs/pypi-trusted-publishing-readiness.md b/tools/sbom-diff-and-risk/docs/pypi-trusted-publishing-readiness.md index 005f107..feb1d2e 100644 --- a/tools/sbom-diff-and-risk/docs/pypi-trusted-publishing-readiness.md +++ b/tools/sbom-diff-and-risk/docs/pypi-trusted-publishing-readiness.md @@ -1,153 +1,155 @@ -# PyPI Trusted Publishing readiness - -This page documents the PR 4 TestPyPI / Trusted Publishing dry-run path for `sbom-diff-and-risk`. - -The repository now has a safe GitHub Actions path that always builds and checks the Python distributions, and can publish those already-checked distributions to TestPyPI only when a maintainer explicitly enables the manual upload input. It does not publish to production PyPI. - -Official references: - -- [PyPI Trusted Publishers](https://docs.pypi.org/trusted-publishers/) -- [Adding a Trusted Publisher to an existing PyPI project](https://docs.pypi.org/trusted-publishers/adding-a-publisher/) -- [Creating a PyPI project with a Trusted Publisher](https://docs.pypi.org/trusted-publishers/creating-a-project-through-oidc/) -- [Publishing with a Trusted Publisher](https://docs.pypi.org/trusted-publishers/using-a-publisher/) -- [Configuring OpenID Connect in PyPI](https://docs.github.com/en/actions/how-tos/secure-your-work/security-harden-deployments/oidc-in-pypi) -- [PyPA gh-action-pypi-publish](https://github.com/pypa/gh-action-pypi-publish) - -## PR 4 decision - -Use a TestPyPI-first readiness workflow, but do not claim that a TestPyPI dry-run is complete until the external TestPyPI publisher is configured and a maintainer runs the manual upload. - -Current outcome for this PR: - -- **Trusted Publishing readiness only** by default -- **TestPyPI dry-run blocked by external configuration** until TestPyPI has the matching pending publisher or trusted publisher -- **No production PyPI publishing** - -The workflow file is `.github/workflows/sbom-diff-and-risk-testpypi.yml`. - -It has two separate jobs: - -- `build-and-check` builds the wheel and source distribution, runs `twine check`, and uploads the checked files as a workflow artifact -- `publish-testpypi` downloads that artifact and publishes to TestPyPI with OIDC only when `workflow_dispatch` input `publish_to_testpypi` is set to `true` - -The upload job does not rebuild the package. - -## Current package and project status - -As checked on April 25, 2026: - -- `https://pypi.org/pypi/sbom-diff-and-risk/json` returned `404` -- `https://test.pypi.org/pypi/sbom-diff-and-risk/json` returned `404` - -That means neither the production PyPI project nor the TestPyPI project currently exists under `sbom-diff-and-risk`. - -Because the TestPyPI project does not exist yet, the first upload must use a **pending publisher** on TestPyPI, unless a maintainer creates the TestPyPI project some other way first. For production PyPI, defer all configuration and upload work to PR 5. - -## Workflow identity - -Configure the TestPyPI publisher to match this GitHub workflow identity exactly: - -| Field | Value | -| --- | --- | -| Package/project name | `sbom-diff-and-risk` | -| GitHub owner | `stacknil` | -| GitHub repository | `scientific-computing-toolkit` | -| Workflow file in repository | `.github/workflows/sbom-diff-and-risk-testpypi.yml` | -| Trusted Publisher workflow name field | `sbom-diff-and-risk-testpypi.yml` | -| GitHub environment | `testpypi` | - -The workflow uses `environment: testpypi` for the upload job, so the TestPyPI publisher must also include `testpypi` as the environment name. If the publisher is configured without an environment, the OIDC identity will not match this workflow. - -## What this PR validates - -Locally and in GitHub Actions, this PR validates: - -- package metadata still points at `PYPI_DESCRIPTION.md` as the PyPI-facing long description -- the package can build a wheel and source distribution -- the built distributions pass `twine check` -- the GitHub workflow separates build/check from upload -- the TestPyPI upload job uses OIDC-compatible permissions with `id-token: write` -- no PyPI token secret is required or documented -- production PyPI upload is absent - -This PR does not validate: - -- that TestPyPI has the pending publisher configured -- that TestPyPI accepts the first upload -- that production PyPI has a project, pending publisher, or trusted publisher -- that production PyPI publishing should happen for version `0.4.1` - -## TestPyPI setup required before upload - -Do these steps only after this workflow is merged. - -1. In GitHub, create or verify the repository environment named `testpypi`. -2. In TestPyPI, create a pending publisher for the new `sbom-diff-and-risk` project. -3. Use the identity values from [Workflow identity](#workflow-identity). -4. Do not add a PyPI API token or GitHub secret for publishing. -5. Run the workflow manually and set `publish_to_testpypi` to `true`. - -If the pending publisher is missing or any identity field differs, the upload job should fail instead of silently falling back to a token or pretending the dry-run succeeded. - -## Local validation - -From `tools/sbom-diff-and-risk`: - -```powershell -python -m pip install --upgrade build twine -python -m build -$files = (Get-ChildItem dist -File).FullName -python -m twine check $files -``` - -What this proves: - -- the local package can be built -- the built distributions have valid metadata and long-description rendering according to Twine - -What this does not prove: - -- the GitHub OIDC identity matches TestPyPI -- the TestPyPI pending publisher exists -- the upload job can mint a TestPyPI publishing token - -## GitHub Actions validation - -Without uploading anything: - -1. Open **Actions**. -2. Run **sbom-diff-and-risk-testpypi** with `publish_to_testpypi` left as `false`. -3. Confirm `build-and-check` succeeds. -4. Confirm the run uploads `sbom-diff-and-risk-testpypi-dist`. -5. Confirm `publish-testpypi` is skipped. - -With TestPyPI pending publisher configured: - -1. Open **Actions**. -2. Run **sbom-diff-and-risk-testpypi** with `publish_to_testpypi` set to `true`. -3. Confirm `build-and-check` succeeds before upload. -4. Confirm `publish-testpypi` downloads `sbom-diff-and-risk-testpypi-dist`. -5. Confirm `publish-testpypi` uses OIDC and publishes to `https://test.pypi.org/legacy/`. -6. Open `https://test.pypi.org/project/sbom-diff-and-risk/` and confirm the uploaded version appears. - -Only after those steps pass can maintainers describe the result as **TestPyPI dry-run completed**. - -## Production PyPI boundary - -Production PyPI remains intentionally out of scope for PR 4. - -Do not add a production PyPI publish job here. Do not configure production PyPI Trusted Publishing as part of this PR unless it is documented as future preparation only and no upload path is enabled. - -PR 5 should decide: - -- the first production PyPI version -- whether to use a pending publisher or an existing-project trusted publisher -- the production workflow file identity -- the GitHub environment name for production, if any -- how PyPI distribution provenance should be documented alongside GitHub artifact and release verification - -## Current decision - -PR 4 stops at a clean readiness state unless a maintainer performs the explicit TestPyPI setup and manual upload after merge. - -Until that happens, the correct status is **Trusted Publishing readiness only; TestPyPI upload blocked by external configuration**. +# PyPI Trusted Publishing readiness + +This page documents the PR 4 TestPyPI / Trusted Publishing dry-run path for `sbom-diff-and-risk`. + +The PR 5 production PyPI decision gate is documented separately in [pypi-production-publishing-decision.md](D:/OneDrive/Code/scientific-computing-toolkit/tools/sbom-diff-and-risk/docs/pypi-production-publishing-decision.md). + +The repository now has a safe GitHub Actions path that always builds and checks the Python distributions, and can publish those already-checked distributions to TestPyPI only when a maintainer explicitly enables the manual upload input. It does not publish to production PyPI. + +Official references: + +- [PyPI Trusted Publishers](https://docs.pypi.org/trusted-publishers/) +- [Adding a Trusted Publisher to an existing PyPI project](https://docs.pypi.org/trusted-publishers/adding-a-publisher/) +- [Creating a PyPI project with a Trusted Publisher](https://docs.pypi.org/trusted-publishers/creating-a-project-through-oidc/) +- [Publishing with a Trusted Publisher](https://docs.pypi.org/trusted-publishers/using-a-publisher/) +- [Configuring OpenID Connect in PyPI](https://docs.github.com/en/actions/how-tos/secure-your-work/security-harden-deployments/oidc-in-pypi) +- [PyPA gh-action-pypi-publish](https://github.com/pypa/gh-action-pypi-publish) + +## PR 4 decision + +Use a TestPyPI-first readiness workflow, but do not treat TestPyPI success as automatic production PyPI readiness. + +Current outcome for this PR: + +- **Trusted Publishing readiness and TestPyPI dry-run completed** after the external TestPyPI publisher was configured and a maintainer manually enabled upload +- **No production PyPI publishing** +- **Production PyPI deferred** to the decision gate in [pypi-production-publishing-decision.md](D:/OneDrive/Code/scientific-computing-toolkit/tools/sbom-diff-and-risk/docs/pypi-production-publishing-decision.md) + +The workflow file is `.github/workflows/sbom-diff-and-risk-testpypi.yml`. + +It has two separate jobs: + +- `build-and-check` builds the wheel and source distribution, runs `twine check`, and uploads the checked files as a workflow artifact +- `publish-testpypi` downloads that artifact and publishes to TestPyPI with OIDC only when `workflow_dispatch` input `publish_to_testpypi` is set to `true` + +The upload job does not rebuild the package. + +## Current package and project status + +As checked on April 26, 2026: + +- `https://pypi.org/pypi/sbom-diff-and-risk/json` returned `404` +- `https://test.pypi.org/pypi/sbom-diff-and-risk/json` returned `200` +- TestPyPI reports `sbom-diff-and-risk` version `0.4.1` + +That means the production PyPI project is not currently visible under `sbom-diff-and-risk`, while the TestPyPI dry-run project exists. For production PyPI, use the PR 5 decision gate before adding any production workflow or publisher configuration. + +## Workflow identity + +Configure the TestPyPI publisher to match this GitHub workflow identity exactly: + +| Field | Value | +| --- | --- | +| Package/project name | `sbom-diff-and-risk` | +| GitHub owner | `stacknil` | +| GitHub repository | `scientific-computing-toolkit` | +| Workflow file in repository | `.github/workflows/sbom-diff-and-risk-testpypi.yml` | +| Trusted Publisher workflow name field | `sbom-diff-and-risk-testpypi.yml` | +| GitHub environment | `testpypi` | + +The workflow uses `environment: testpypi` for the upload job, so the TestPyPI publisher must also include `testpypi` as the environment name. If the publisher is configured without an environment, the OIDC identity will not match this workflow. + +## What this PR validates + +Locally and in GitHub Actions, this PR validates: + +- package metadata still points at `PYPI_DESCRIPTION.md` as the PyPI-facing long description +- the package can build a wheel and source distribution +- the built distributions pass `twine check` +- the GitHub workflow separates build/check from upload +- the TestPyPI upload job uses OIDC-compatible permissions with `id-token: write` +- no PyPI token secret is required or documented +- production PyPI upload is absent + +This PR does not validate: + +- that production PyPI has a project, pending publisher, or trusted publisher +- that production PyPI publishing should happen for version `0.4.1` +- that a TestPyPI upload is sufficient proof of production PyPI readiness + +## TestPyPI setup used for upload + +The completed TestPyPI dry-run required these external steps. Use them again only if the TestPyPI publisher must be recreated. + +1. In GitHub, create or verify the repository environment named `testpypi`. +2. In TestPyPI, create a pending publisher for the new `sbom-diff-and-risk` project. +3. Use the identity values from [Workflow identity](#workflow-identity). +4. Do not add a PyPI API token or GitHub secret for publishing. +5. Run the workflow manually and set `publish_to_testpypi` to `true`. + +If the pending publisher or trusted publisher is missing, or any identity field differs, the upload job should fail instead of silently falling back to a token or pretending the dry-run succeeded. + +## Local validation + +From `tools/sbom-diff-and-risk`: + +```powershell +python -m pip install --upgrade build twine +python -m build +$files = (Get-ChildItem dist -File).FullName +python -m twine check $files +``` + +What this proves: + +- the local package can be built +- the built distributions have valid metadata and long-description rendering according to Twine + +What this does not prove: + +- the GitHub OIDC identity matches TestPyPI +- the TestPyPI pending publisher exists +- the upload job can mint a TestPyPI publishing token + +## GitHub Actions validation + +Without uploading anything: + +1. Open **Actions**. +2. Run **sbom-diff-and-risk-testpypi** with `publish_to_testpypi` left as `false`. +3. Confirm `build-and-check` succeeds. +4. Confirm the run uploads `sbom-diff-and-risk-testpypi-dist`. +5. Confirm `publish-testpypi` is skipped. + +With TestPyPI pending publisher configured: + +1. Open **Actions**. +2. Run **sbom-diff-and-risk-testpypi** with `publish_to_testpypi` set to `true`. +3. Confirm `build-and-check` succeeds before upload. +4. Confirm `publish-testpypi` downloads `sbom-diff-and-risk-testpypi-dist`. +5. Confirm `publish-testpypi` uses OIDC and publishes to `https://test.pypi.org/legacy/`. +6. Open `https://test.pypi.org/project/sbom-diff-and-risk/` and confirm the uploaded version appears. + +After those steps pass, maintainers can describe the result as **TestPyPI dry-run completed**. + +## Production PyPI boundary + +Production PyPI remains intentionally separate from the TestPyPI dry-run. + +Do not add a production PyPI publish job to the TestPyPI workflow. Do not configure production PyPI Trusted Publishing from the TestPyPI readiness process. + +PR 5 decides: + +- the first production PyPI version +- whether to use a pending publisher or an existing-project trusted publisher +- the production workflow file identity +- the GitHub environment name for production, if any +- how PyPI distribution provenance should be documented alongside GitHub artifact and release verification + +See [pypi-production-publishing-decision.md](D:/OneDrive/Code/scientific-computing-toolkit/tools/sbom-diff-and-risk/docs/pypi-production-publishing-decision.md) for the current production gate. + +## Current decision + +PR 4 established the TestPyPI readiness workflow and the manual dry-run path. After the external TestPyPI publisher was configured and a maintainer ran the manual upload, the dry-run completed for version `0.4.1`. + +Production PyPI remains deferred behind the PR 5 gate. diff --git a/tools/sbom-diff-and-risk/docs/release-provenance.md b/tools/sbom-diff-and-risk/docs/release-provenance.md index 4d10ef5..3913a23 100644 --- a/tools/sbom-diff-and-risk/docs/release-provenance.md +++ b/tools/sbom-diff-and-risk/docs/release-provenance.md @@ -1,87 +1,87 @@ -# Release provenance and release asset verification - -`sbom-diff-and-risk` now has two GitHub-hosted provenance surfaces for its packaged wheel and source distribution: - -1. workflow-artifact attestations for the files built by `.github/workflows/sbom-diff-and-risk-ci.yml` -2. GitHub Release verification for version-tag releases that publish those same built files as release assets - -This document is about the second surface: verifying a GitHub Release and a downloaded release asset. - -This page is only about the `sbom-diff-and-risk` tool's own GitHub Releases. If you want the quick "which verification page do I need?" guide, start with [verification.md](D:/OneDrive/Code/scientific-computing-toolkit/tools/sbom-diff-and-risk/docs/verification.md). - -## What the release workflow now does - -For version tags matching `v*`, the `sbom-diff-and-risk-ci` workflow: - -1. builds the wheel and source distribution in `build-and-attest` -2. uploads them as the workflow artifact `sbom-diff-and-risk-dist` -3. generates a workflow artifact attestation for those built files -4. downloads that same workflow artifact in `publish-release-assets` -5. publishes those exact `.whl` and `.tar.gz` files as GitHub Release assets for the matching tag - -This intentionally reuses the same workflow-built bytes for both the workflow artifact and the release asset surfaces. It does not add PyPI publishing or a separate rebuild-only release pipeline. - -## What release verification covers - -GitHub Release verification is distinct from workflow artifact attestation: - -- `gh attestation verify` checks a file against the workflow artifact attestation produced by `build-and-attest` -- `gh release verify` checks that a GitHub Release has a valid release attestation -- `gh release verify-asset` checks that a local file exactly matches an attested asset from that release - -Release verification only works for immutable releases. Per GitHub's release integrity and immutable release documentation, immutable releases automatically generate a release attestation and protect release assets from modification after publication. - -If immutable releases are not enabled for the repository, the release may still contain assets, but `gh release verify` and `gh release verify-asset` are not the source of truth. In that case, use the workflow-artifact attestation flow from [self-provenance.md](D:/OneDrive/Code/scientific-computing-toolkit/tools/sbom-diff-and-risk/docs/self-provenance.md). - -## Manual verification for a release - -Use this path after a successful version-tag run such as `v0.4.0`. - -1. Open the repository's **Releases** page. -2. Open the release for the version tag you want to verify. -3. Check whether the release is immutable before relying on release verification: - -```bash -gh release view v0.4.0 --repo stacknil/scientific-computing-toolkit --json isImmutable,assets,url -``` - -4. Confirm the release includes the packaged assets: - - `sbom_diff_and_risk--py3-none-any.whl` - - `sbom_diff_and_risk-.tar.gz` -5. If the repository uses immutable releases, confirm GitHub shows `Immutable` on the release page. -6. Download one of the release assets locally. -7. Verify the release itself with GitHub CLI: - -```bash -gh release verify v0.4.0 --repo stacknil/scientific-computing-toolkit -``` - -8. Verify the downloaded asset against the release attestation: - -```bash -gh release verify-asset v0.4.0 path/to/sbom_diff_and_risk-0.4.0-py3-none-any.whl \ - --repo stacknil/scientific-computing-toolkit -``` - -If `isImmutable` is `false`, the release asset can still be downloaded, but the supported provenance path for this repository remains the workflow-artifact attestation flow from [self-provenance.md](D:/OneDrive/Code/scientific-computing-toolkit/tools/sbom-diff-and-risk/docs/self-provenance.md). - -You can inspect structured output as JSON: - -```bash -gh release verify v0.4.0 \ - --repo stacknil/scientific-computing-toolkit \ - --format json -``` - -```bash -gh release verify-asset v0.4.0 path/to/sbom_diff_and_risk-0.4.0.tar.gz \ - --repo stacknil/scientific-computing-toolkit \ - --format json -``` - -## Important boundary notes - -- `gh release verify-asset` verifies a local file path against a release attestation. It does not verify a workflow artifact download directly unless that file is also a release asset. -- GitHub's generated source-code ZIP and tarball downloads are not covered by `gh release verify-asset`. -- A successful release verification does not replace the workflow-artifact attestation story; it complements it. -- This repository now has a separate TestPyPI Trusted Publishing readiness workflow, but production PyPI publishing remains disabled. For prerequisites, workflow identity, and sequencing, see [pypi-trusted-publishing-readiness.md](D:/OneDrive/Code/scientific-computing-toolkit/tools/sbom-diff-and-risk/docs/pypi-trusted-publishing-readiness.md). +# Release provenance and release asset verification + +`sbom-diff-and-risk` now has two GitHub-hosted provenance surfaces for its packaged wheel and source distribution: + +1. workflow-artifact attestations for the files built by `.github/workflows/sbom-diff-and-risk-ci.yml` +2. GitHub Release verification for version-tag releases that publish those same built files as release assets + +This document is about the second surface: verifying a GitHub Release and a downloaded release asset. + +This page is only about the `sbom-diff-and-risk` tool's own GitHub Releases. If you want the quick "which verification page do I need?" guide, start with [verification.md](D:/OneDrive/Code/scientific-computing-toolkit/tools/sbom-diff-and-risk/docs/verification.md). + +## What the release workflow now does + +For version tags matching `v*`, the `sbom-diff-and-risk-ci` workflow: + +1. builds the wheel and source distribution in `build-and-attest` +2. uploads them as the workflow artifact `sbom-diff-and-risk-dist` +3. generates a workflow artifact attestation for those built files +4. downloads that same workflow artifact in `publish-release-assets` +5. publishes those exact `.whl` and `.tar.gz` files as GitHub Release assets for the matching tag + +This intentionally reuses the same workflow-built bytes for both the workflow artifact and the release asset surfaces. It does not add PyPI publishing or a separate rebuild-only release pipeline. + +## What release verification covers + +GitHub Release verification is distinct from workflow artifact attestation: + +- `gh attestation verify` checks a file against the workflow artifact attestation produced by `build-and-attest` +- `gh release verify` checks that a GitHub Release has a valid release attestation +- `gh release verify-asset` checks that a local file exactly matches an attested asset from that release + +Release verification only works for immutable releases. Per GitHub's release integrity and immutable release documentation, immutable releases automatically generate a release attestation and protect release assets from modification after publication. + +If immutable releases are not enabled for the repository, the release may still contain assets, but `gh release verify` and `gh release verify-asset` are not the source of truth. In that case, use the workflow-artifact attestation flow from [self-provenance.md](D:/OneDrive/Code/scientific-computing-toolkit/tools/sbom-diff-and-risk/docs/self-provenance.md). + +## Manual verification for a release + +Use this path after a successful version-tag run such as `v0.4.0`. + +1. Open the repository's **Releases** page. +2. Open the release for the version tag you want to verify. +3. Check whether the release is immutable before relying on release verification: + +```bash +gh release view v0.4.0 --repo stacknil/scientific-computing-toolkit --json isImmutable,assets,url +``` + +4. Confirm the release includes the packaged assets: + - `sbom_diff_and_risk--py3-none-any.whl` + - `sbom_diff_and_risk-.tar.gz` +5. If the repository uses immutable releases, confirm GitHub shows `Immutable` on the release page. +6. Download one of the release assets locally. +7. Verify the release itself with GitHub CLI: + +```bash +gh release verify v0.4.0 --repo stacknil/scientific-computing-toolkit +``` + +8. Verify the downloaded asset against the release attestation: + +```bash +gh release verify-asset v0.4.0 path/to/sbom_diff_and_risk-0.4.0-py3-none-any.whl \ + --repo stacknil/scientific-computing-toolkit +``` + +If `isImmutable` is `false`, the release asset can still be downloaded, but the supported provenance path for this repository remains the workflow-artifact attestation flow from [self-provenance.md](D:/OneDrive/Code/scientific-computing-toolkit/tools/sbom-diff-and-risk/docs/self-provenance.md). + +You can inspect structured output as JSON: + +```bash +gh release verify v0.4.0 \ + --repo stacknil/scientific-computing-toolkit \ + --format json +``` + +```bash +gh release verify-asset v0.4.0 path/to/sbom_diff_and_risk-0.4.0.tar.gz \ + --repo stacknil/scientific-computing-toolkit \ + --format json +``` + +## Important boundary notes + +- `gh release verify-asset` verifies a local file path against a release attestation. It does not verify a workflow artifact download directly unless that file is also a release asset. +- GitHub's generated source-code ZIP and tarball downloads are not covered by `gh release verify-asset`. +- A successful release verification does not replace the workflow-artifact attestation story; it complements it. +- This repository now has a separate TestPyPI Trusted Publishing readiness workflow, but production PyPI publishing remains deferred. For the production decision gate, publisher identity, future workflow shape, and provenance boundary, see [pypi-production-publishing-decision.md](D:/OneDrive/Code/scientific-computing-toolkit/tools/sbom-diff-and-risk/docs/pypi-production-publishing-decision.md). diff --git a/tools/sbom-diff-and-risk/docs/self-provenance.md b/tools/sbom-diff-and-risk/docs/self-provenance.md index ef2e7fa..94c3604 100644 --- a/tools/sbom-diff-and-risk/docs/self-provenance.md +++ b/tools/sbom-diff-and-risk/docs/self-provenance.md @@ -1,115 +1,117 @@ -# Self-provenance and artifact attestations - -`sbom-diff-and-risk` analyzes third-party dependency changes, but consumers should also be able to verify where the tool itself came from. This repository generates GitHub artifact attestations for the packaged build outputs produced by the `sbom-diff-and-risk-ci` workflow. - -This page is only about verifying the `sbom-diff-and-risk` tool's own build artifacts. If you want the top-level decision guide, start with [verification.md](D:/OneDrive/Code/scientific-computing-toolkit/tools/sbom-diff-and-risk/docs/verification.md). If you want to analyze third-party dependency provenance with the CLI, go back to the README's dependency provenance sections instead of this page. - -## What is attested in this repository - -The attested subjects are the exact Python distributables built from `tools/sbom-diff-and-risk` via `python -m build`: - -- the wheel: `dist/sbom_diff_and_risk--py3-none-any.whl` -- the source distribution: `dist/sbom_diff_and_risk-.tar.gz` - -Those two files are uploaded together as the workflow artifact named `sbom-diff-and-risk-dist`. The attestation applies to the built files themselves, not just to the artifact bundle name shown in the Actions UI. - -On version tags matching `v*`, the same workflow also publishes those exact wheel and sdist files as GitHub Release assets for the matching tag. The workflow-artifact attestation story remains the build-provenance source of truth for the files themselves; release verification is an additional GitHub-hosted surface layered on top of those same bytes. - -This repository does not currently publish production PyPI Trusted Publishing provenance. The separate TestPyPI readiness workflow is a pre-production validation path and is documented in [pypi-trusted-publishing-readiness.md](D:/OneDrive/Code/scientific-computing-toolkit/tools/sbom-diff-and-risk/docs/pypi-trusted-publishing-readiness.md). Release verification is separate from workflow-artifact attestations and depends on GitHub immutable releases being enabled for the repository. When immutable releases are enabled, GitHub automatically generates a release attestation covering the published release record and its attached assets. - -## Workflow and permissions - -The attestation is generated in `.github/workflows/sbom-diff-and-risk-ci.yml` by the `build-and-attest` job in the `sbom-diff-and-risk-ci` workflow. - -That job runs only for trusted non-PR events in this repository: - -- `push` -- `workflow_dispatch` - -Pull request runs still execute the `test` job, but they do not publish artifact attestations. - -On version tags matching `v*`, the same workflow also runs `publish-release-assets`, which downloads the already-built `sbom-diff-and-risk-dist` artifact from the workflow run and uploads those same files to the GitHub Release for that tag. - -The `build-and-attest` job uses the minimum explicit permissions required for GitHub-hosted build provenance: - -- `contents: read` for repository checkout -- `id-token: write` for GitHub's signing identity -- `attestations: write` to publish the attestation - -Regular branch pushes remain path-filtered to the `sbom-diff-and-risk` workflow file and tool directory. The workflow also accepts version tags matching `v*`, which gives the repository a minimal release-oriented build path that now covers workflow artifact attestation plus GitHub Release asset publication, without adding production PyPI publishing. - -## Where provenance evidence appears in GitHub - -After a successful non-PR run of `sbom-diff-and-risk-ci`, consumers can find the evidence in two useful places: - -1. On the workflow run page: - - the run name starts with `sbom-diff-and-risk ci / / ` - - the uploaded artifact appears as `sbom-diff-and-risk-dist` - - this is the run consumers should use to confirm the workflow name, job name, and downloaded artifact bundle before verification -2. In the repository-wide attestations view: - - open **Actions** - - in the left sidebar, under **Management**, open **Attestations** - - search for `sbom_diff_and_risk-` or filter by recent creation date - -On the **Attestations** page, the relevant subjects are the wheel and sdist filenames, not the workflow artifact bundle name. On the workflow run page, the main visible bundle name is still `sbom-diff-and-risk-dist`. - -## Manual verification for one workflow run - -Use this path after a merge to the default branch, a version-tag push such as `v0.4.0`, or an intentional `workflow_dispatch` run. - -1. Open the repository's **Actions** tab. -2. Open a successful `sbom-diff-and-risk-ci` run triggered by `push` or `workflow_dispatch`. - - for a release-oriented check, prefer a run whose visible name looks like `sbom-diff-and-risk ci / push / v0.4.0` -3. Confirm that the `build-and-attest` job ran successfully. -4. Download the `sbom-diff-and-risk-dist` artifact from that run. -5. Confirm the downloaded archive contains exactly the expected build outputs for that version: - - `sbom_diff_and_risk--py3-none-any.whl` - - `sbom_diff_and_risk-.tar.gz` -6. Verify one of the files with the GitHub CLI: - -```bash -gh attestation verify path/to/sbom_diff_and_risk--py3-none-any.whl \ - --repo OWNER/scientific-computing-toolkit \ - --signer-workflow OWNER/scientific-computing-toolkit/.github/workflows/sbom-diff-and-risk-ci.yml -``` - -You can verify the source distribution the same way: - -```bash -gh attestation verify path/to/sbom_diff_and_risk-.tar.gz \ - --repo OWNER/scientific-computing-toolkit \ - --signer-workflow OWNER/scientific-computing-toolkit/.github/workflows/sbom-diff-and-risk-ci.yml -``` - -If you want more inspection detail during review, ask the CLI for structured output: - -```bash -gh attestation verify path/to/sbom_diff_and_risk--py3-none-any.whl \ - --repo OWNER/scientific-computing-toolkit \ - --signer-workflow OWNER/scientific-computing-toolkit/.github/workflows/sbom-diff-and-risk-ci.yml \ - --format json -``` - -A successful verification confirms that: - -- the downloaded file matches an attested subject -- the attestation was linked to `OWNER/scientific-computing-toolkit` -- the attestation was signed by `.github/workflows/sbom-diff-and-risk-ci.yml` - -## Release-consumer note - -If these same wheel or source distribution bytes are attached to a GitHub release, consumers now have two related but distinct verification surfaces: - -- use `gh attestation verify` when you want to verify the workflow-built file against the workflow artifact attestation -- use `gh release verify` and `gh release verify-asset` when you want to verify the GitHub Release record and a downloaded release asset from an immutable release - -These flows complement each other. The workflow-artifact attestation answers "were these bytes built by this workflow?", while immutable release verification answers "does this published release and local release asset exactly match GitHub's release attestation?" See [release-provenance.md](D:/OneDrive/Code/scientific-computing-toolkit/tools/sbom-diff-and-risk/docs/release-provenance.md) for the release-specific consumer flow. - -## How this complements the tool's own analysis - -Self-provenance and dependency analysis solve different problems: - -- artifact attestations help consumers verify where `sbom-diff-and-risk` itself was built -- `sbom-diff-and-risk` helps users review and gate third-party dependency changes in their own projects - -These attestations strengthen trust in the tool's own distributable artifacts, but they do not replace the tool's analysis of external SBOM inputs, policy decisions, or trust-signal reporting for third-party packages. +# Self-provenance and artifact attestations + +`sbom-diff-and-risk` analyzes third-party dependency changes, but consumers should also be able to verify where the tool itself came from. This repository generates GitHub artifact attestations for the packaged build outputs produced by the `sbom-diff-and-risk-ci` workflow. + +This page is only about verifying the `sbom-diff-and-risk` tool's own build artifacts. If you want the top-level decision guide, start with [verification.md](D:/OneDrive/Code/scientific-computing-toolkit/tools/sbom-diff-and-risk/docs/verification.md). If you want to analyze third-party dependency provenance with the CLI, go back to the README's dependency provenance sections instead of this page. + +## What is attested in this repository + +The attested subjects are the exact Python distributables built from `tools/sbom-diff-and-risk` via `python -m build`: + +- the wheel: `dist/sbom_diff_and_risk--py3-none-any.whl` +- the source distribution: `dist/sbom_diff_and_risk-.tar.gz` + +Those two files are uploaded together as the workflow artifact named `sbom-diff-and-risk-dist`. The attestation applies to the built files themselves, not just to the artifact bundle name shown in the Actions UI. + +On version tags matching `v*`, the same workflow also publishes those exact wheel and sdist files as GitHub Release assets for the matching tag. The workflow-artifact attestation story remains the build-provenance source of truth for the files themselves; release verification is an additional GitHub-hosted surface layered on top of those same bytes. + +This repository does not currently publish production PyPI Trusted Publishing provenance. The separate TestPyPI readiness workflow is a pre-production validation path and is documented in [pypi-trusted-publishing-readiness.md](D:/OneDrive/Code/scientific-computing-toolkit/tools/sbom-diff-and-risk/docs/pypi-trusted-publishing-readiness.md). The production PyPI decision gate is documented in [pypi-production-publishing-decision.md](D:/OneDrive/Code/scientific-computing-toolkit/tools/sbom-diff-and-risk/docs/pypi-production-publishing-decision.md). Release verification is separate from workflow-artifact attestations and depends on GitHub immutable releases being enabled for the repository. When immutable releases are enabled, GitHub automatically generates a release attestation covering the published release record and its attached assets. + +## Workflow and permissions + +The attestation is generated in `.github/workflows/sbom-diff-and-risk-ci.yml` by the `build-and-attest` job in the `sbom-diff-and-risk-ci` workflow. + +That job runs only for trusted non-PR events in this repository: + +- `push` +- `workflow_dispatch` + +Pull request runs still execute the `test` job, but they do not publish artifact attestations. + +On version tags matching `v*`, the same workflow also runs `publish-release-assets`, which downloads the already-built `sbom-diff-and-risk-dist` artifact from the workflow run and uploads those same files to the GitHub Release for that tag. + +The `build-and-attest` job uses the minimum explicit permissions required for GitHub-hosted build provenance: + +- `contents: read` for repository checkout +- `id-token: write` for GitHub's signing identity +- `attestations: write` to publish the attestation + +Regular branch pushes remain path-filtered to the `sbom-diff-and-risk` workflow file and tool directory. The workflow also accepts version tags matching `v*`, which gives the repository a minimal release-oriented build path that now covers workflow artifact attestation plus GitHub Release asset publication, without adding production PyPI publishing. + +## Where provenance evidence appears in GitHub + +After a successful non-PR run of `sbom-diff-and-risk-ci`, consumers can find the evidence in two useful places: + +1. On the workflow run page: + - the run name starts with `sbom-diff-and-risk ci / / ` + - the uploaded artifact appears as `sbom-diff-and-risk-dist` + - this is the run consumers should use to confirm the workflow name, job name, and downloaded artifact bundle before verification +2. In the repository-wide attestations view: + - open **Actions** + - in the left sidebar, under **Management**, open **Attestations** + - search for `sbom_diff_and_risk-` or filter by recent creation date + +On the **Attestations** page, the relevant subjects are the wheel and sdist filenames, not the workflow artifact bundle name. On the workflow run page, the main visible bundle name is still `sbom-diff-and-risk-dist`. + +## Manual verification for one workflow run + +Use this path after a merge to the default branch, a version-tag push such as `v0.4.0`, or an intentional `workflow_dispatch` run. + +1. Open the repository's **Actions** tab. +2. Open a successful `sbom-diff-and-risk-ci` run triggered by `push` or `workflow_dispatch`. + - for a release-oriented check, prefer a run whose visible name looks like `sbom-diff-and-risk ci / push / v0.4.0` +3. Confirm that the `build-and-attest` job ran successfully. +4. Download the `sbom-diff-and-risk-dist` artifact from that run. +5. Confirm the downloaded archive contains exactly the expected build outputs for that version: + - `sbom_diff_and_risk--py3-none-any.whl` + - `sbom_diff_and_risk-.tar.gz` +6. Verify one of the files with the GitHub CLI: + +```bash +gh attestation verify path/to/sbom_diff_and_risk--py3-none-any.whl \ + --repo OWNER/scientific-computing-toolkit \ + --signer-workflow OWNER/scientific-computing-toolkit/.github/workflows/sbom-diff-and-risk-ci.yml +``` + +You can verify the source distribution the same way: + +```bash +gh attestation verify path/to/sbom_diff_and_risk-.tar.gz \ + --repo OWNER/scientific-computing-toolkit \ + --signer-workflow OWNER/scientific-computing-toolkit/.github/workflows/sbom-diff-and-risk-ci.yml +``` + +If you want more inspection detail during review, ask the CLI for structured output: + +```bash +gh attestation verify path/to/sbom_diff_and_risk--py3-none-any.whl \ + --repo OWNER/scientific-computing-toolkit \ + --signer-workflow OWNER/scientific-computing-toolkit/.github/workflows/sbom-diff-and-risk-ci.yml \ + --format json +``` + +A successful verification confirms that: + +- the downloaded file matches an attested subject +- the attestation was linked to `OWNER/scientific-computing-toolkit` +- the attestation was signed by `.github/workflows/sbom-diff-and-risk-ci.yml` + +## Release-consumer note + +If these same wheel or source distribution bytes are attached to a GitHub release, consumers now have two related but distinct verification surfaces: + +- use `gh attestation verify` when you want to verify the workflow-built file against the workflow artifact attestation +- use `gh release verify` and `gh release verify-asset` when you want to verify the GitHub Release record and a downloaded release asset from an immutable release + +These flows complement each other. The workflow-artifact attestation answers "were these bytes built by this workflow?", while immutable release verification answers "does this published release and local release asset exactly match GitHub's release attestation?" See [release-provenance.md](D:/OneDrive/Code/scientific-computing-toolkit/tools/sbom-diff-and-risk/docs/release-provenance.md) for the release-specific consumer flow. + +Future production PyPI Trusted Publishing provenance will be a third, separate surface. It will answer whether a PyPI distribution was uploaded through the configured GitHub publisher identity, not whether the file is byte-identical to a GitHub workflow artifact or release asset. See [pypi-production-publishing-decision.md](D:/OneDrive/Code/scientific-computing-toolkit/tools/sbom-diff-and-risk/docs/pypi-production-publishing-decision.md) for that boundary. + +## How this complements the tool's own analysis + +Self-provenance and dependency analysis solve different problems: + +- artifact attestations help consumers verify where `sbom-diff-and-risk` itself was built +- `sbom-diff-and-risk` helps users review and gate third-party dependency changes in their own projects + +These attestations strengthen trust in the tool's own distributable artifacts, but they do not replace the tool's analysis of external SBOM inputs, policy decisions, or trust-signal reporting for third-party packages. diff --git a/tools/sbom-diff-and-risk/docs/verification.md b/tools/sbom-diff-and-risk/docs/verification.md index 074338c..bbd8665 100644 --- a/tools/sbom-diff-and-risk/docs/verification.md +++ b/tools/sbom-diff-and-risk/docs/verification.md @@ -1,42 +1,44 @@ -# Verification guide - -Use this page when you are trying to figure out which provenance or verification instructions apply to `sbom-diff-and-risk`. - -## Choose the question you are trying to answer - -### 1. "How do I verify `sbom-diff-and-risk` itself?" - -Use the tool provenance docs: - -- [self-provenance.md](D:/OneDrive/Code/scientific-computing-toolkit/tools/sbom-diff-and-risk/docs/self-provenance.md) if you want to verify the workflow-built wheel or source distribution with `gh attestation verify` -- [release-provenance.md](D:/OneDrive/Code/scientific-computing-toolkit/tools/sbom-diff-and-risk/docs/release-provenance.md) if you want to verify a GitHub Release or a downloaded release asset with `gh release verify` or `gh release verify-asset` -- [pypi-trusted-publishing-readiness.md](D:/OneDrive/Code/scientific-computing-toolkit/tools/sbom-diff-and-risk/docs/pypi-trusted-publishing-readiness.md) if you want to know whether this package is ready for PyPI Trusted Publishing - -Current boundaries: - -- the workflow name is `sbom-diff-and-risk-ci` -- the workflow artifact name is `sbom-diff-and-risk-dist` -- version-tag runs matching `v*` can publish the same built files as GitHub Release assets -- release verification depends on immutable releases being enabled for the repository -- the TestPyPI readiness workflow is `sbom-diff-and-risk-testpypi` -- production PyPI publishing is still absent; see the readiness checklist before enabling that path - -### 2. "How do I use `sbom-diff-and-risk` to analyze third-party dependency provenance?" - -Use the dependency-analysis docs in the README: - -- [Dependency provenance analysis](D:/OneDrive/Code/scientific-computing-toolkit/tools/sbom-diff-and-risk/README.md#dependency-provenance-analysis-opt-in) -- [Dependency provenance reporting](D:/OneDrive/Code/scientific-computing-toolkit/tools/sbom-diff-and-risk/README.md#dependency-provenance-reporting) -- [Enforcement mode](D:/OneDrive/Code/scientific-computing-toolkit/tools/sbom-diff-and-risk/README.md#enforcement-mode) - -Current boundaries: - -- default CLI behavior remains local and deterministic -- no hidden network access occurs unless enrichment flags are set explicitly -- dependency provenance analysis is about third-party packages, not about verifying the `sbom-diff-and-risk` tool's own artifacts -- release verification and workflow artifact attestation do not change CLI analysis behavior - -## One-line summary - -- Verify the tool itself: use `self-provenance.md` or `release-provenance.md` -- Analyze dependencies with the tool: use the README's dependency provenance sections +# Verification guide + +Use this page when you are trying to figure out which provenance or verification instructions apply to `sbom-diff-and-risk`. + +## Choose the question you are trying to answer + +### 1. "How do I verify `sbom-diff-and-risk` itself?" + +Use the tool provenance docs: + +- [self-provenance.md](D:/OneDrive/Code/scientific-computing-toolkit/tools/sbom-diff-and-risk/docs/self-provenance.md) if you want to verify the workflow-built wheel or source distribution with `gh attestation verify` +- [release-provenance.md](D:/OneDrive/Code/scientific-computing-toolkit/tools/sbom-diff-and-risk/docs/release-provenance.md) if you want to verify a GitHub Release or a downloaded release asset with `gh release verify` or `gh release verify-asset` +- [pypi-trusted-publishing-readiness.md](D:/OneDrive/Code/scientific-computing-toolkit/tools/sbom-diff-and-risk/docs/pypi-trusted-publishing-readiness.md) if you want to know whether this package is ready for PyPI Trusted Publishing + +Current boundaries: + +- the workflow name is `sbom-diff-and-risk-ci` +- the workflow artifact name is `sbom-diff-and-risk-dist` +- version-tag runs matching `v*` can publish the same built files as GitHub Release assets +- release verification depends on immutable releases being enabled for the repository +- the TestPyPI readiness workflow is `sbom-diff-and-risk-testpypi` +- the TestPyPI Trusted Publishing dry-run has completed for version `0.4.1` +- production PyPI publishing remains deferred behind the gate in [pypi-production-publishing-decision.md](D:/OneDrive/Code/scientific-computing-toolkit/tools/sbom-diff-and-risk/docs/pypi-production-publishing-decision.md) + +### 2. "How do I use `sbom-diff-and-risk` to analyze third-party dependency provenance?" + +Use the dependency-analysis docs in the README: + +- [Dependency provenance analysis](D:/OneDrive/Code/scientific-computing-toolkit/tools/sbom-diff-and-risk/README.md#dependency-provenance-analysis-opt-in) +- [Dependency provenance reporting](D:/OneDrive/Code/scientific-computing-toolkit/tools/sbom-diff-and-risk/README.md#dependency-provenance-reporting) +- [Enforcement mode](D:/OneDrive/Code/scientific-computing-toolkit/tools/sbom-diff-and-risk/README.md#enforcement-mode) + +Current boundaries: + +- default CLI behavior remains local and deterministic +- no hidden network access occurs unless enrichment flags are set explicitly +- dependency provenance analysis is about third-party packages, not about verifying the `sbom-diff-and-risk` tool's own artifacts +- release verification and workflow artifact attestation do not change CLI analysis behavior + +## One-line summary + +- Verify the tool itself: use `self-provenance.md` or `release-provenance.md` +- Decide whether production PyPI publishing is ready: use `pypi-production-publishing-decision.md` +- Analyze dependencies with the tool: use the README's dependency provenance sections