A compromised package. One page of answers.
Reachable reads your lockfiles and commit history and tells you which services are exposed, which pulled the package in while it was still installable, and what the same maintainers could reach next — with the statement that produced every number.
Six questions, answered on one page.
Which of my services are exposed, and at what level?
A verdict per service, highest across its lockfiles, with the dependency that pulled it in.
Which versions were installable, and for how long?
Exact publish times, the last moment each version was observed installable, and an explicit upper bound.
Who pulled it in while it was still installable?
Every lockfile write intersected with the window, down to the commit that wrote it.
What else could the same maintainers reach?
The other packages those accounts publish, and how many of your services each one would reach.
Which look-alike names exist?
Names one edit away, scope confusion, hyphen and homoglyph swaps — grouped by kind with edit distance.
What is the blast radius, service by service?
The final ledger: act now, imported, present only, unscanned — plus the provenance that produced it.
Who pulled it in while it was still installable?
A removed version is not a resolved incident. Reachable intersects every lockfile write with the window the version was actually installable in — and marks the edge of that window as an upper bound, because registries do not record removal times.
MATCH (a:Advisory {id: 2971413083072216})-[af:AFFECTS]->(v:Version)<-[r:RESOLVED]-(l:Lockfile)<-[:HAS_LOCKFILE]-(sv:Service)
WHERE r.at >= af.live_from AND r.at <= af.live_to
RETURN sv.key AS service, l.key AS lockfile, l.sha AS sha, r.at AS resolved_at, v.key AS version, v.removed AS removed, af.live_from AS live_from, af.live_to AS live_to, af.live_to_kind AS live_to_kind
ORDER BY r.at ASCEvery number ships with the statement behind it.
Each answer card carries the executed query, the row count and the measured latency. Collapse it if you trust it. It is never hidden, and nothing on the page is an estimate: an uncomputed value reads “not computed”, an unread service reads “unscanned”.
Colour means one thing here.
A page with no red is the message. Verdicts are computed per service — the highest level across its lockfiles — and never softened.
First-party code references the affected package's vulnerable symbol. This is the level that needs action now.
First-party code imports the package; the vulnerable symbol is not referenced.
In the install tree, never imported by any scanned file.
Exposed, but its source was not read. Styled as unknown, never as safe, and never counted as zero.
One node. One console. Nothing else to install.
Console
Incidents · report · board · ask · graph. Every page reads the graph and shows the statement that produced it.
Self-hosted core
One HydraDB node and one worker: GitHub lockfile history, npm versions and maintainers, OSV advisories, an import scan at the exposed commit. Single-tenant — the token never leaves it.
Coding agents
An MCP server exposes the same questions to Claude Code, Codex, Cursor and others, so an answer arrives where the fix is written.
Twelve tools, for the agent fixing it.
An MCP server over stdio, on the official SDK. It computes nothing — each tool is one call to the worker, so an agent gets the same answers as the console, with the statement that produced them. Eleven read; one writes, and says so.
Point it at one repository and read the first report tonight.
Read-only GitHub access, lockfiles and commit metadata linked into one graph, six answers per advisory from the first ingest onward.
