Skip to content

v1.5 — ecosystem integrations (license, dual-source vulns, CycloneDX, Dependabot context)#26

Merged
SeanLF merged 12 commits into
mainfrom
feat/v1.5-ecosystem-integrations
May 23, 2026
Merged

v1.5 — ecosystem integrations (license, dual-source vulns, CycloneDX, Dependabot context)#26
SeanLF merged 12 commits into
mainfrom
feat/v1.5-ecosystem-integrations

Conversation

@SeanLF
Copy link
Copy Markdown
Owner

@SeanLF SeanLF commented May 23, 2026

v1.5 — ecosystem integrations

Five features that make still_active compose with the rest of the Ruby supply-chain ecosystem rather than duplicate it. Each shipped as its own commit, TDD'd, individually reviewed, and dogfooded against real data.

Features

  • Gem license surfaced from the RubyGems versions payload we already fetch (no extra request) — License column in terminal/markdown, additive license field in JSON.
  • Dual-source vulnerabilities: when bundler-audit is installed (with a current bundle audit update checkout), advisories from rubysec/ruby-advisory-db are merged with deps.dev, deduped on shared identifiers, each tagged with its source (deps.dev / ruby-advisory-db / merged). Closes the "why do bundler-audit and still_active disagree?" gap.
  • Dependabot/Renovate awareness: bot-authored runs lead with a narrative ("Dependabot bump: rack 2.0.0 → 2.0.6") and JSON gains a top-level pr_context. Detected primarily via the event-payload PR author (pull_request.user.login, same authoritative signal dependabot/fetch-metadata uses), with conservative fallbacks. Bump extraction tolerates custom commit-message.prefix/scope configs.
  • CycloneDX SBOM (--cyclonedx): default 1.6 (the version Trivy/Dependency-Track/Snyk ingest today), --cyclonedx-version=1.7 to opt in. Native fields for name/version/purl/licenses/vulnerabilities; maintenance signals in still_active:-namespaced properties; deterministic content-derived serialNumber.
  • dependency-review-action interop: comparison matrix + dual-job workflow recipe in the README.

Plus: a warning when mutually-exclusive output flags are combined, and a CI step so our own PR-diff workflow exercises the dual-source merge.

Invariants held

  • schema_version stays 1 (all new fields additive); SARIF rule IDs SA001SA007 unchanged.
  • Zero new runtime dependencies — bundler-audit is dev-only, detected at runtime.

Verification

  • 423 examples, 0 failures; rubocop clean; gem builds as 1.5.0.
  • Dogfooded end-to-end against a real vulnerable gem (rack 2.0.0): 25 ruby-advisory-db advisories flowed correctly through terminal / JSON / SARIF / CycloneDX, with pr_context in JSON.
  • A full-branch review caught a multi-license CycloneDX bug and a doc claim (dependency-submission API) that didn't survive verification — both fixed.

See CHANGELOG.md for the full 1.5.0 entry.

🤖 Generated with Claude Code

SeanLF and others added 12 commits May 23, 2026 09:35
Reuses the per-version `licenses` field already present in the RubyGems
versions payload we fetch — no extra request — and exposes it as a
`License` column (terminal + markdown) and an additive `license` JSON
field. Comma-joined for multi-license gems; nil/"-" for git/path sources
with no RubyGems metadata.

Read-only metadata only: license policy (allow/deny gating) stays the
domain of license_finder. schema_version stays 1 (additive field).

First of the v1.5 ecosystem-integration commits; unblocks the CycloneDX
renderer, which maps this into component.licenses.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
still_active reads vulnerabilities from deps.dev; bundler-audit reads
rubysec/ruby-advisory-db. The two diverge on Ruby-specific advisories the
rubysec maintainers curate before they reach OSV. Users running both saw
different counts with no explanation.

When bundler-audit is installed with a current advisory checkout, we now
read its advisories through its own Database loader (we're a consumer — no
YAML parsing or range matching of our own) and merge them with deps.dev,
deduplicating on shared identifiers. Each advisory carries a `source`
(deps.dev / ruby-advisory-db / merged); deps.dev wins on CVSS/title/vector,
ruby-advisory-db fills gaps. Opt-in by composition: no bundler-audit, no
second source — falls back silently to deps.dev with a hint to run
`bundle audit update`.

A malformed advisory in the checkout is surfaced with a warning, not
swallowed as []: a silent empty result there would hide a real
vulnerability, the exact gap this feature closes. Verified against
bundler-audit 0.9.3 (CVSS in #to_h; Database.new raises ArgumentError when
the checkout is absent) and dogfooded against the real ruby-advisory-db.

schema_version stays 1 (source is additive); SA003 unchanged. bundler-audit
is a dev dependency only — detected at runtime, never a runtime dep.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
In --baseline (and terminal/markdown/JSON) runs, a bot-authored update is
now recognized and the report leads with a narrative ("Dependabot bump:
rack 2.0.0 -> 2.0.6") so reviewers see intent, not just a list. JSON gains
an additive top-level `pr_context` ({ bot, bumps: [{ gem, from, to }] }).

Detection cascade: GITHUB_ACTOR -> dependabot/|renovate/ branch prefix ->
commit subject. The subject patterns are deliberately conservative because
false positives are worse than misses: Dependabot's `bump X from Y to Z`
skeleton is safe unprefixed, but the Renovate `update X to vN` pattern
requires a v-prefixed version so it can't fire on ordinary "Update README
to mention SARIF" commits. Git probes rescue SystemCallError so a
missing/unlaunchable git only costs the narrative, never the run.

schema_version stays 1 (pr_context additive); SARIF unaffected.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Makes still_active's data portable: --cyclonedx[=PATH] emits a CycloneDX
SBOM so the dep graph plus our signals flow into Trivy / Dependency-Track /
Snyk / GitHub's dependency-submission API instead of being walled in our
own JSON.

Defaults to 1.6 — the version mainstream consumers actually ingest today
(cyclonedx-core-java/Dependency-Track and cyclonedx-go/Trivy both cap at
1.6 as of 2026); --cyclonedx-version=1.7 opts into the latest. Our emitted
subset is identical across both, so only the specVersion string changes,
and a consumer that only knows 1.6 would reject a 1.7-stamped doc — hence
the conservative default.

Native fields carry name/version/purl/licenses/vulnerabilities; maintenance
signals with no CycloneDX home (archived, OpenSSF, libyear, last commit,
yanked) ride in still_active:-namespaced properties. serialNumber is derived
from the component set, so two SBOMs of the same lockfile are byte-identical
apart from the generation timestamp (injectable, so tests are deterministic).

Separate output format — the schema_version:1 JSON envelope is untouched.
Stdlib only (json/digest/time); no new runtime deps.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Documents the v1.5 features (license column, dual-source vulns, CycloneDX,
Dependabot/Renovate context) across the README and bumps the version.

Adds an "Alongside dependency-review-action" section: a comparison matrix
and a dual-job workflow. GitHub's first-party action gates CVEs/licenses
server-side but surfaces no maintenance signals and is GitHub-only — so the
recipe runs both, letting still_active add the maintenance lens. Records on
paper the "complementary, not a replacement" positioning the gem is built on.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
VersionHelper joins multiple licenses with ", " for terminal/markdown
display ("Hippocratic-2.1, MIT"); the CycloneDX renderer was feeding that
joined string straight into license.id, which must be a single SPDX
identifier. A joined value is neither a valid id nor a valid SPDX
expression, so SBOM validators and Dependency-Track's license matcher
reject or drop it — undercutting the "feeds Trivy/Dependency-Track" claim.

Split back into one license entry per id. Surfaced by a full-branch review
(every test used single-license gems, so it slipped through); confirmed
fixed against vcr (Hippocratic-2.1, MIT) and debug (Ruby, BSD-2-Clause).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…extracting bumps

Dependabot's commit-message.prefix / prefix-development / include:scope (and
Renovate's commitMessagePrefix) change the subject prefix, so a configured
"chore(deps): bump …" or "deps: bump …" previously fell back to the generic
"Dependabot dependency update" narrative even though the bot was detected.

Separate the two concerns: detection patterns stay anchored and conservative
(they must never false-positive on a human commit), while extraction now uses
unanchored skeleton patterns. Extraction only runs after a bot is already
confirmed via GITHUB_ACTOR/branch/anchored-subject, so ignoring the prefix
there can't widen detection — the human-commit false-positive guards still
pass. Detection was already prefix-independent in CI (it keys off GITHUB_ACTOR
and the branch); this fixes the narrative for custom-prefix configs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
--baseline, --sarif, and --cyclonedx are mutually exclusive and resolved by
precedence (baseline > sarif > cyclonedx > terminal/markdown/json). Combining
them previously dropped the losers silently. Now a stderr warning names the
mode that wins and the ones ignored, and a separate warning fires when
--cyclonedx-version is set without --cyclonedx (no effect otherwise).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…n 1.5.0

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…CTOR

Research (dependabot/fetch-metadata) confirms the authoritative signal is the
PR author in the GitHub event payload (pull_request.user.login), not
GITHUB_ACTOR. GITHUB_ACTOR reflects who triggered *this* run, so it flips to a
human who re-runs the workflow or pushes to the bot's branch, while the PR
author does not. Make pull_request.user.login the primary signal, keeping
GITHUB_ACTOR/branch/subject as fallbacks.

pr_author_login never raises: a missing/unreadable/malformed-or-wrong-shape
payload falls through to the weaker signals (rescue includes TypeError so a
top-level array or non-Hash pull_request/user can't crash the audit over a
cosmetic narrative — detect runs unguarded in CLI#run).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Verified against the docs: the dependency-submission API ingests a
proprietary snapshot format, not CycloneDX directly (submission goes through
SPDX/Anchore converter actions). Our --cyclonedx output doesn't feed it, so
the claim was false. Trivy / Dependency-Track / Snyk are confirmed direct
consumers (Snyk's `sbom test` takes CycloneDX 1.4-1.6 and requires the purl
we emit), so they stay.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ource

The diff workflow runs `bundle exec still_active`, so bundler-audit (a dev
dependency) is on the load path — but its advisory DB ships separately and
wasn't fetched, leaving the v1.5 dual-source merge dormant. Add a
`bundle-audit update` step so our own CI actually exercises the deps.dev +
ruby-advisory-db merge. Best-effort: on failure still_active falls back to
deps.dev only.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Comment thread Gemfile.lock
metrics (~> 0.12)
traces (~> 0.18)
bigdecimal (4.1.2)
bundler-audit (0.9.3)
@github-actions
Copy link
Copy Markdown

still_active diff

0 regressions · 1 added · 0 removed · 1 bumped · 0 signal-changes

Added

  • bundler-audit (v0.9.3, OpenSSF 2.7, 0.0y behind)

Version bumps

  • still_active 1.4.2 → 1.5.0

@SeanLF SeanLF merged commit ce5e560 into main May 23, 2026
11 checks passed
@SeanLF SeanLF deleted the feat/v1.5-ecosystem-integrations branch May 23, 2026 19:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants