Manual

blastradius / docs

Using br

br answers one question: where did this bad thing land in our org, who owns each place, and what do we do next. It works from a local index and only reads.

Get started

Install #

With Node 18 or newer there is nothing else to set up:

shell
$ npx -y @jondot/blastradius demo        # run it once
$ npm i -g @jondot/blastradius           # or put br on your PATH

Without Node, the installer:

shell
$ curl -fsSL https://raw.githubusercontent.com/jondot/blastradius/main/install.sh | sh

This downloads the latest release for your OS and architecture, checks its sha256, and installs br to ~/.local/bin (or $BR_INSTALL_DIR). Pin a version with sh -s -- --version v0.1.2.

For an AI agent, add the skill. It teaches the agent when to reach for br, how to phrase a hunt, and how to read the answer:

shell
$ npx skills add jondot/blastradius

Or give it br as an MCP server (see MCP for agents):

shell
$ claude mcp add blastradius -- npx -y @jondot/blastradius mcp

In GitHub Actions, use jondot/blastradius@v1 (see CI and the Action).

Builds: Linux (x86_64 and arm64, static musl), macOS (x86_64 and arm64) and Windows (x86_64). Archives are on the releases page. From source: cargo install --path .

Get started

Try it with no token #

br demo uses a sample org built into the binary. No token, no network, nothing of yours is read. It prints a six-step tour of a made-up incident. The first step:

shell
$ br demo hunt --advisory GHSA-q7vr-4xhc-m2jw --format summary
Yes. 7 repos, 2 images, 2 machines, 2 caches and 2 build artifacts contain or contained the affected packages (GHSA-q7vr-4xhc-m2jw).
4 people and 2 machines may also be affected but could not be confirmed.
Still present: 5 repos, 2 images, 2 machines, 2 caches and 2 build artifacts.
...
Data as of 05-08 10:00Z–05-12 06:30Z (oldest: laptop-priya). Not checked: 4 sources (acme/mobile-web; ci-runner-04; laptop-priya; laptops: 11 of 12 members visible to this token in GitHub have no laptop scan here). The number can still change.

Get started

Build the index #

Build it once. A read-only GitHub token is enough.

shell
$ export GITHUB_TOKEN=...
$ br init --org acme

The index lives in ~/.blastradius (override with BR_HOME). Keep one per client with br --home ~/clients/acme init --org acme (the same as BR_HOME=~/clients/acme). Re-scans use conditional requests, so an unchanged repo costs about 3 API calls.

Get started

Your first hunt #

Ask from an advisory id, an indicator, or pasted advisory text.

shell
$ br hunt --advisory GHSA-xxxx-xxxx-xxxx
$ br hunt --advisory GHSA-xxxx-xxxx-xxxx --owner frontend   # one team's places
$ br hunt --advisory GHSA-xxxx-xxxx-xxxx --repo acme/web    # is my repo ok?

The answer lists every repo, CI run, cache, artifact, image and machine that holds the bad version, now or at any time in the exposure window. It names the secrets the CI runs could read and the team that owns each place. It says where the thing was installed, not that the bad code ran.

A hunt reads only the index. It touches the network or a machine only when you pass --now, --live or --path.

Use

Indicators #

An indicator says what to look for. Names are matched whole: lodash never matches lodash.merge. Advisories are matched by their real version ranges, per package.

FormFinds
npm:@scope/name@1.2.3A package version. Also pypi:, cargo:, go:
action:owner/repo@refA GitHub Action at a ref
sha256:HASHA file by hash, including inside image layers
runtime:NAME@fixed:V1,V2A language runtime below the fixed release of its line
cert:HEX, cert:subject:TEXTA CA certificate in a trust store, by fingerprint or subject
secret:NAMEA secret by name, with * globs and --since DATE

Use

Runtimes, certificates and secrets #

Runtimes

shell
$ br hunt runtime:php@fixed:8.5.12,8.4.27,8.3.36,8.2.35
$ br hunt runtime:node@fixed:22.23.4,24.22.1,26.12.1

fixed: lists the first fixed release of each line. A version on one of those lines but below the fix is found, and so is a line with no fix at all. br reads version files, Dockerfile FROM tags and ENV versions, setup-php and setup-node steps, the runtimes installed in images, and the runtimes on runners and laptops. A floating tag such as node:lts is reported apart, as possible.

Certificates

shell
$ br hunt "cert:subject:emSign ECC Root CA - G3"
$ br hunt cert:SHA256-FINGERPRINT

It reads the certificates themselves out of OS bundles in every image layer, certifi copies, Java cacerts, PEM bundles in repos, and the trust stores on scanned machines.

Secrets

shell
$ br hunt 'secret:RAILWAY_*' --since DATE

Every matching org, repo or environment secret, with when it was last updated. Anything not updated since --since is NOT ROTATED. It also lists the workflows that read it, the images that name it in their environment, and the config files that mention it.

It works from names and dates only. br never reads a secret value.

Use

After the hunt #

shell
$ br fix --advisory GHSA-xxxx-xxxx-xxxx          # per repo: how to reach a fixed version
$ br notify --advisory GHSA-xxxx-xxxx-xxxx       # one draft per owning team
$ br purge --advisory GHSA-xxxx-xxxx-xxxx        # caches, artifacts, images and paths to delete
$ br hunt --advisory GHSA-xxxx-xxxx-xxxx --progress   # the day after: still open, fixed, new

Someone sent you a sample? br hunt --similar suspect.js finds files that look like it, and br hunt sha256:HASH --similar-related also finds the files shipped with it in the same install.

Use

Laptops and hosts #

Laptops with no network path to the index scan themselves and send the result.

shell
$ br scan --local --export laptop.brx --seal     # on the laptop
$ br import laptop.brx                           # on your machine
$ br hosts                                        # who is still missing

A second file for a machine already in the index is merged by path, and the newer scan wins. --replace swaps the whole machine. Tell br what it can't know from outside with br hosts add NAME --kind runner|laptop|server.

Use

CI and agents #

shell
$ br hunt npm:name@1.2.3 --path . --fail-if found --strict       # no index, no network; exit 10 when found
$ br hunt --advisory GHSA-xxxx-xxxx-xxxx --format sarif          # SARIF 2.1.0 for code scanning
$ br claimcheck --path . --org acme --fail-if exposed      # asks the public registries about your names; exit 10 when exposed, 3 if it checked nothing

The GitHub Action (jondot/blastradius@v1), the SARIF output and the pre-commit hook are in CI and the Action. br mcp is a read-only MCP server over stdio so an AI agent can ask br during an incident: MCP for agents.

Use

All commands #

CommandWhat it does
initBuild the index from a GitHub org, a directory, or hosts
huntFind where an advisory or indicator lives, with owners
fixPer repo, how to reach a fixed version
notifyOne draft message per owning team
evidenceA sealed record, optionally signed, optionally with a customer-safe statement
verifyCheck an evidence file's manifest and signature
purgeThe caches, artifacts, images and paths to delete
claimcheckWhich internal package names a stranger could already own on npm, PyPI, crates.io or the Go proxy
mcpA read-only MCP server for an AI agent
statusHow fresh each source is, and what the answer did not cover
demoThe same commands on a built-in sample org
scan, import, hostsRe-read sources, bring in laptop files, list machines
diff, teams, advisory, sqlCompare, inspect owners, manage advisories, query the index

Run br --help or br COMMAND --help for every flag.

Trust

Answers and exit codes #

Every answer says how old the index is, how old its oldest source is, and which sources it could not check: a 403, an expired log, an unreadable lockfile. A clear answer with gaps is reported as such, never as plain clear. --max-age DUR refuses to answer (exit 5) if a source is older.

CodeMeaning
0Clear
10Found
3Clear with gaps, under --strict
5No index, or a stale one
11Nothing found, under --fail-if none
4, 2, 1Auth problem, usage error, failure

Trust

What it reads #

br never writes to anything it reads. Every GitHub call is a GET or HEAD, and the only POSTs are the registry token exchanges. Why br lists what it sends, and where.

  • GitHub org: repos, lockfiles and manifests with their commit history (npm, yarn, pnpm, PyPI, Cargo, Go), CODEOWNERS, workflow files, Actions runs, caches and artifacts, releases and their assets, teams and members, registered runners, and org container images on GHCR.
  • Container registries: images, layers and their files by hash, including files a later layer deleted. Uses your existing docker login.
  • Machines: a directory on this machine, ssh-reachable hosts and runners, and .brx files exported by laptops.
  • Advisories: OSV, for GHSA and MAL ids; advisory ids in pasted text are fetched too.
  • Public registries, only for claimcheck: read-only GETs to npmjs.org, pypi.org, crates.io and proxy.golang.org about your own package names. A hunt never does this.

Trust

Evidence #

Evidence is sealed with a sha256 manifest, which proves integrity, and with --sign it is signed with ssh-keygen -Y sign, which proves identity.

shell
$ br evidence --advisory GHSA-xxxx-xxxx-xxxx --out evidence.json --sign ~/.ssh/id_ed25519
$ br evidence --advisory GHSA-xxxx-xxxx-xxxx --out evidence.json --audience customers
$ br verify evidence.json --allowed-signers allowed_signers

--audience customers adds a plain statement with no internal names.