Skip to content

PRD: security-advisory-scanner recipe (CVE blast-radius over Search Jobs API) #3

Description

@jdorfman

Problem Statement

A security advisory drops (a CVE against a package, or a vulnerable code pattern) and a security engineer has to answer one question fast: where does this touch our code? Interactive search answers it for one repo at a time and only for direct imports. The hard cases — transitive dependencies pinned in lockfiles, vendored copies, and code copied wholesale from the vulnerable project — hide across hundreds of repos, and nobody has a tool that sweeps all of them exhaustively and produces a defensible blast-radius report.

Solution

A security-advisory-scanner recipe: a CLI that takes a CVE ID or a package name (with optional version range), expands it into a set of exhaustive Search Job queries covering direct usage, lockfile pins, vendored paths, and copied-code signatures, runs those jobs in parallel over the Search Jobs API, and aggregates the JSONL results into one report. The report groups findings by repository and classifies each match as direct, transitive, vendored, or copied, so the engineer can hand it to an incident channel as-is.

User Stories

  1. As a security engineer, I want to pass a CVE ID and get affected repositories back, so that I can size an incident within minutes of an advisory publishing
  2. As a security engineer, I want to pass a bare package name and ecosystem when no CVE exists yet, so that I can scan ahead of an official advisory
  3. As a security engineer, I want the CVE resolved to affected package names and version ranges automatically, so that I do not have to read the advisory and translate it into queries myself
  4. As a security engineer, I want matches in dependency manifests (package.json, go.mod, requirements.txt, pom.xml, Gemfile, Cargo.toml) classified as direct usage, so that I know which teams import the package deliberately
  5. As a security engineer, I want matches in lockfiles (package-lock.json, yarn.lock, pnpm-lock.yaml, go.sum, poetry.lock, Cargo.lock) classified as transitive, so that I catch repos that never mention the package in a manifest
  6. As a security engineer, I want matches under vendor directories (vendor/, third_party/, node_modules checked into git) classified as vendored, so that I find copies that dependency scanners miss
  7. As a security engineer, I want distinctive code signatures from the vulnerable package (function names, unique string literals) searched as content queries, so that I find code copied out of the project entirely
  8. As a security engineer, I want version-range checking against the pinned versions found in manifests and lockfiles, so that repos already on a fixed version are marked safe instead of flagged
  9. As a security engineer, I want all queries to run as parallel Search Jobs with live progress, so that a slow job never blocks the sweep
  10. As a security engineer, I want a single aggregated report grouped by repository with per-match classification and file paths, so that I can assign remediation per team
  11. As a security engineer, I want the report written as both human-readable markdown and machine-readable JSONL, so that I can paste it into an incident doc and feed it into tooling
  12. As a security engineer, I want a repo-level severity roll-up (direct beats transitive beats vendored beats copied), so that the report leads with the worst exposure
  13. As an engineering manager, I want a summary line with total repos affected and counts per classification, so that I can report blast radius upward without reading the detail
  14. As a platform engineer, I want raw per-job JSONL kept on disk, so that I can re-run aggregation without re-running the jobs
  15. As a developer running the recipe, I want it to work with only SRC_ENDPOINT and SRC_ACCESS_TOKEN set, so that setup matches every other recipe in the cookbook
  16. As a developer running the recipe, I want clear errors when my token lacks externalapi scopes, so that I fix auth instead of debugging false empties
  17. As a viewer of the video, I want a README that stands alone, so that I can run the scan without watching the video
  18. As a cautious operator, I want a --dry-run flag that prints the generated queries without creating jobs, so that I can review query cost before running an org-wide sweep

Implementation Decisions

  • New recipe directory following the cookbook rule: one directory, standalone README, runnable end to end.
  • TypeScript under bare node, matching the search-jobs-api recipe's zero-dependency shape. The recipe is about the Search Jobs API, not about a dependency, so no packages.
  • CVE resolution uses the OSV.dev public API (no auth, no client library — one fetch call) to map CVE ID to affected packages and version ranges. Offline or air-gapped users can skip resolution by passing package and version range directly.
  • Query generation is a pure function: advisory metadata in, list of {query, classification} pairs out. Manifest, lockfile, and vendor-path queries are templated per ecosystem; ecosystems covered at launch: npm, Go, Python, Java/Maven, Ruby, Rust.
  • Copied-code signature queries are optional and user-supplied (a --signature flag accepting a literal or regex), since deriving signatures automatically from a CVE is unreliable.
  • Jobs are created in parallel and polled independently, reusing the polling shape established by the existing recipes; each job's JSONL streams to disk before aggregation.
  • Aggregation is a pure function: JSONL lines plus classification map in, report structure out. Version-range comparison uses a small hand-rolled semver-range check rather than a library.
  • Report emitted as markdown to stdout and JSONL to a file; exit code is nonzero when any repo is affected, so the tool composes with CI.

Testing Decisions

  • Tests exercise external behavior only: query generation (advisory in, expected query strings out), classification (file path in, classification out), version-range checks (pinned version plus range in, affected verdict out), and aggregation (fixture JSONL in, report out).
  • No test touches the network or needs a token. Fixture JSONL files checked into the recipe, following the prior art of the TUI recipe's layout tests and its checked-in JSONL fixtures.
  • OSV resolution is tested against a recorded fixture response, not the live API.

Out of Scope

  • Automated remediation (opening PRs to bump versions).
  • Binary or container-image scanning; this scans source as indexed by Sourcegraph.
  • Full SCA-grade transitive resolution (walking dependency graphs); lockfile matching is the transitive signal.
  • Continuous monitoring or scheduling; the recipe is a one-shot scan.
  • SBOM ingestion or export.

Further Notes

This is recipe three in the Search Jobs API video series. The demo arc for the video: paste a fresh CVE ID, watch four job classes fan out in parallel, end on the markdown report showing a vendored copy that a dependency scanner would have missed — the moment that lands with senior engineers.

Metadata

Metadata

Assignees

No one assigned

    Labels

    ready-for-agentPRD ready for agent implementation

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions