Conversation
Add time = 0.3 and sha2 = 0.10 (sha2 lands here to keep the dep churn in one commit; used by Phase A SARIF fingerprints). New src/clock.rs is the single source of truth for date/time: - now()/today() honor SOURCE_DATE_EPOCH (env read per-call so fixtures can vary it between scenarios) - parse_ymd is strict: rejects non-zero-padded YYYY-MM-DD - format_rfc3339 + format_ymd byte-deterministic emitters No public surface change yet; subsequent phases consume this. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Doctest on clock::now() locks env, calls now(), asserts the returned timestamp matches. Combined with F1's now_is_read_per_call_not_cached unit test this proves the env is consulted at every call site so later phases (baseline expiry, VEX) get reproducible output. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Extend VulnRef with aliases: Vec<String> (sorted, primary id excluded)
and a cves() iterator over CVE-prefixed identifiers (primary + aliases).
osv::fetch_detail returns (severity, aliases) from /v1/vulns/{id}.aliases
and the cache hit path keeps aliases empty (v0.7 cache schema only stored
severity; aliases populate on next live fetch).
JSON shape additive: aliases serializes via skip_serializing_if=is_empty
so existing consumers see no churn.
Tests: parse fixture (GHSA primary + CVE alias both present, primary
excluded from aliases, sort order stable), cves() iterator on both
GHSA-keyed and CVE-keyed advisories.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add --debug-calibration-format <pipe|jsonl>, default pipe (back-compat).
JSONL emits {kind,key,score,threshold} per line; numeric scores stay
numeric, severity buckets ('HIGH', 'high+') stay strings. Adding new
finding kinds in subsequent phases is one call to write_calibration_row,
not a fork.
Also pre-add --output-file <PATH> flag (used by Phase A SARIF Code
Scanning workflow to avoid YAML > redirection quoting hazards). Wiring
into run_diff lands in Phase A; the flag is no-op for now.
Config: debug_calibration, debug_calibration_format, output_file all
mergeable from .bomdrift.toml.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
SARIF results now carry partialFingerprints."primaryHash/v1" hashed from a stable per-rule identity tuple (ruleId + purl + per-rule discriminator): - bomdrift.cve: ruleId | purl | advisoryId - bomdrift.typosquat: ruleId | purl | closest - bomdrift.version-jump: ruleId | purl | beforeVersion | afterVersion - bomdrift.young-maint.: ruleId | purl | topContributor - bomdrift.license-change: ruleId | purl | beforeLicensesSorted | afterLicensesSorted Two CVEs on the same purl now produce distinct fingerprints (the duck-flagged collision case). The /v1 suffix on the fingerprint key lets us evolve identity later without churning GitHub alert state. New rule bomdrift.license-violation registered in tool.driver.rules ahead of Phase D's policy violation emission. CLI: --output-file <PATH> writes the chosen output format to a file instead of stdout. Avoids YAML > redirection quoting in CI templates. GitHub Action: new input upload-to-code-scanning (default false) gates a github/codeql-action/upload-sarif@v3 step. Requires the calling workflow to have permissions.security-events: write. entrypoint.sh always passes --output-file when output=sarif so the file path the upload step expects is populated. Docs: new docs/src/sarif.md chapter; SUMMARY entry under Output. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Two new best-effort enrichers piggyback on OSV's VulnRefs after the core enrichment runs: - src/enrich/epss.rs queries https://api.first.org/data/v1/epss in 100-CVE batches, populates VulnRef.epss_score (max-of-aliases), caches per-CVE at <XDG_CACHE>/bomdrift/epss/<cve>.json (24h TTL). - src/enrich/kev.rs downloads CISA's known_exploited_vulnerabilities feed once daily, populates VulnRef.kev when any CVE alias matches, caches the bulk catalog at <XDG_CACHE>/bomdrift/kev/catalog.json (24h TTL). Both enrichers fail closed-without-blocking: a network failure logs at BOMDRIFT_DEBUG=1 and the diff renders with empty fields. VulnRef extended (additive JSON shape via skip_serializing_if): pub epss_score: Option<f32>, pub kev: bool, CLI surface: - --no-epss / --no-kev: skip the enricher (network + cache). - --fail-on kev: new FailOn variant; --fail-on any includes KEV too. - --fail-on-epss <FLOAT>: sibling flag (--fail-on is a clap ValueEnum, parsing 'epss>=N' inside it would break v0.7 callers; sibling flag is cleaner). Trips exit 2 when any advisory's score >= threshold. Render paths: - Markdown: "EPSS 0.87 · **KEV**" badges in CVE rows. - Term: "EPSS 0.87 KEV" plain badges. - SARIF: bomdrift.cve result properties.epssScore, properties.kev. Calibration rows for both enrichers (pipe + JSONL formats). Phase D ahead-of-time scaffolding: Enrichment.license_violations field, LicenseViolation/LicenseViolationKind types, FailOn::LicenseViolation variant. Population lands in Phase D. Docs: docs/src/enrichers/epss.md, docs/src/enrichers/kev.md, SUMMARY entries. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
v0.8 baseline schema is purely additive. Each entry in
suppressed_advisories may now be either:
- a bare string (v0.5 form, unchanged), or
- an object {id, purl?, expires?, reason?} (v0.8 form).
Behavior:
- expires field parsed via clock::parse_ymd (strict YYYY-MM-DD).
Malformed dates surface as a load error naming the offending entry.
- expires < today() (clock honors SOURCE_DATE_EPOCH): entry skipped
for suppression and recorded on Baseline.expired_entries. lib.rs
prints one warning per expired entry to stderr after baseline load.
- No expires: entry suppresses indefinitely (v0.5 semantics).
CLI:
bomdrift baseline add GHSA-X --expires 2026-12-31 --reason '...'
The new flags route through baseline::add_suppression_full, which
emits the v0.8 object form when either field is set; otherwise the
v0.5 string form is preserved. Idempotency now matches by id across
both shapes.
comment-suppress companion action picks up an optional 'reason: <text>'
line in the trigger comment body and forwards it via --reason.
Tests cover: expired warns + still renders, active suppresses, no-
expiry suppresses, malformed errors, round-trip, SOURCE_DATE_EPOCH
override, idempotent re-add against object-form entry.
Docs: docs/src/baseline.md extended with the new fields, CLI usage,
warning message format, worked rotation example.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… handling
New src/enrich/license.rs evaluates each Added or VersionChanged
component against a configured Policy { allow, deny, allow_ambiguous }:
- Atomic license: exact compare against allow/deny; trailing-* glob
for deny ('AGPL-*' matches 'AGPL-3.0-only'). Deny wins when both match.
- Compound expression (any of AND/OR/WITH/parens): treated as
ambiguous. With allow_ambiguous=false (default) and any policy
configured, emits an Ambiguous violation. With allow_ambiguous=true,
permitted (with the understanding that v0.9's spdx evaluator will
replace this).
- NOASSERTION / OTHER / empty: ambiguous (same fail-closed semantics).
Distinct from existing ChangeSet::license_changed (same-version
license drift) — that's a heuristic, this is a policy gate.
CLI: --allow-licenses, --deny-licenses, --allow-ambiguous-licenses
(matches Dependency Review Action flag names exactly). [license]
block in .bomdrift.toml; CLI flags override (not merge) when set.
Render paths:
- Markdown: new 'License violations' section + summary-table row.
- Term: [LIC] tag with matched rule.
- JSON: enrichment.license_violations array (already wired in B).
- SARIF: bomdrift.license-violation results emit with stable
partialFingerprints.primaryHash/v1 hashed from
ruleId | purl | license. Rule was registered in Phase A.
--fail-on license-violation trips exit 2; --fail-on any includes it.
--debug-calibration row: license|<purl>|<spdx>|<deny|ambiguous|not-allowed>.
Tests cover: allow-pass, deny-fail, glob expansion, ambiguous fail-closed,
ambiguous permitted, allow+deny precedence (deny wins), version-changed
evaluation, empty-policy no-op, NOASSERTION ambiguous, SARIF roundtrip
with stable fingerprint, fail-on threshold gating.
Docs: docs/src/license-policy.md, SUMMARY entry under Output.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Cargo.toml + Cargo.lock: 0.7.0 → 0.8.0 - README.md, docs/src/quickstart.md, .github/ISSUE_TEMPLATE: pin examples bumped v0.7.0 → v0.8.0 - CHANGELOG.md: 0.8.0 entry covering F1-F4 + A-D (foundations: time crate, SOURCE_DATE_EPOCH, OSV aliases, JSONL debug; features: SARIF Code Scanning, EPSS+KEV, license policy, baseline expiry). Explicit Scope notes section listing v0.9-deferred items. - STATUS.md: new ✓ rows for SARIF Code Scanning, EPSS, KEV, license policy, baseline expiry. Bitbucket / Azure DevOps + VEX moved to 'Planned for v0.9'. - docs/src/roadmap.md: new 'Shipped (v0.8)' section; 'Planned (v0.9)' refreshed with VEX consume + emit, SPDX evaluator, multi-SCM, registry enrichers, GitLab comment-suppress, non-goals doc. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
SBOM diff
Added (14)Show details
Version changed (1)Show details
False positive? Report it · Suppress a finding? Comment |
The CI audit + deny gates caught RUSTSEC-2026-0009 (DoS via stack exhaustion in time's RFC 2822 parser, fixed in 0.3.47+). Bumping to 0.3.47 requires Rust 1.88, so MSRV moves 1.85 -> 1.88. bomdrift does not parse user-supplied RFC 2822 input — the advisory is not exploitable here — but tightening the dep is the right call rather than carrying an audit-deny exception. Two clippy lints surfaced under 1.88 and were also fixed: - src/baseline.rs: collapsed nested if into &&-chain. - benches/diff.rs: used i.is_multiple_of(2). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
v0.8 finishes the SARIF integration for GitHub Code Scanning, lights up
exploit-prediction (EPSS) and known-exploited-in-the-wild (CISA KEV)
signals, introduces an explicit license allow/deny policy, and adds
time-boxed risk acceptance to the baseline.
Feature themes
SARIF + GitHub Code Scanning end-to-end. Stable
partialFingerprints.primaryHash/v1per result so Code Scanning'salert dedup threads correctly. New action input
upload-to-code-scanning: truewiresgithub/codeql-action/upload-sarif@v3for one-line opt-in. New--output-file <PATH>CLI flag.EPSS + CISA KEV scoring. Every CVE-aliased advisory carries an
EPSS probability badge and a KEV flag in markdown / terminal /
SARIF / JSON.
--fail-on-epss <FLOAT>and--fail-on kevforthreshold gating.
--no-epss/--no-kevopt-outs. Both enrichersare best-effort with 24h disk caches.
License allow/deny policy. New
[license]block in.bomdrift.toml(or--allow-licenses/--deny-licensesmatchingDependency Review Action names). Atomic exact match +
*-suffixglob; compound expressions like `(MIT OR GPL-3.0)` fail closed
unless
allow_ambiguous=true. New SARIF rule`bomdrift.license-violation`.
Baseline
expires+reason. Object-form entries`{id, expires?, reason?}` for time-boxed risk acceptance. Expired
entries warn to stderr and stop suppressing. The
`comment-suppress` action picks up an optional `reason: `
line in the trigger comment body.
Foundations
These foundation phases land first; subsequent features depend on them:
timecrate adoption +clockmodule. Single source of truthfor date/time. Honors `SOURCE_DATE_EPOCH` (read per call so test
fixtures can vary it). Replaces all v0.7 hand-rolled date math.
contexts get deterministic timestamps for VEX (v0.9) and audit-log
output paths.
responses now feed CVE aliases into `VulnRef.aliases` (sorted,
byte-deterministic). Prerequisite for EPSS + KEV; powers v0.9 VEX
matching too.
pipe-delimited format. Numeric scores stay numeric in JSON.
Scope notes — deferred to v0.9
five distinct security guards (token verification, event-type
filter, project allowlist, commenter-permission check, fork-MR
safety). Shipping without those is a vulnerability.
Baseline expiry+reason fields here feed directly into VEX's
`status_notes` when emit lands.
expressions; v0.9 adopts the `spdx` crate for proper evaluation.
CI gates
`fmt` / `clippy` / `audit` / `deny` / `diff` / `test`
(ubuntu/macOS/windows). After-commit local verification:
`cargo fmt --check && cargo clippy --all-targets --all-features --release -- -D warnings && cargo test --release` — all green.
Test count: 294 (v0.7) → 343 (v0.8). New deps: `time = "0.3"`,
`sha2 = "0.10"`.
Maintainer-side checklist (post-merge)
artifact upload).