Skip to content

Geiger's “drift alarm” reported no change while a new MCP server was added to the repo

We planted 19 agent surfaces on a machine and ran geiger against them. It found 14, and its own recommended cron drift alarm reported no change while a new MCP server appeared in a repo it never scanned.

Geiger's “drift alarm” reported no change while a new MCP server was added to the repo

Nobody can tell you what agent tooling is on the laptops in their company, which is why a one-command inventory that runs read-only and phones nothing home got attention this week. Gritt ran it against a machine we built on purpose, with nineteen agent surfaces planted at their documented paths, and found the number it prints is scoped to the directories you hand it - including in the cron recipe its own README recommends. Read it for the reading rule at the end. The tool is four days old and the rule outlives it. - Muximus


Run it. It is worth the two minutes, and it will name things on a developer machine nobody would have listed from memory.

Then read the output twice, because the count it prints is a floor and it looks exactly like a total.

What it is

geiger-scan is a read-only scanner that inventories the AI agent surface on a machine. One command, no install: npx geiger-scan. It walks known configuration locations and reports every agent, MCP server, hook, plugin, skill, subagent and AI editor extension it can identify, tagging each with what it can reach - EXECUTES, HOLDS-SECRETS, BROAD-FILESYSTEM, NETWORK, BROAD-WEB - and the evidence path behind the finding.

The packaging is unusually clean. Zero runtime dependencies. 69,702 bytes unpacked across 22 files. The README makes three promises: the only file it writes is the --json report you name; there is no telemetry and no endpoint to send anything to; and credentials are reported by key name and shape only, never any part of the value, with a redaction pass enforced by the test suite.

It is MIT and free. It is not disinterested. Geiger is published by Atomburst, which sells DomainGuard - a commercial AI-and-web policy enforcement product the README positions as "how organizations put a policy layer in front of this surface." The README says so rather than hiding it: its FAQ opens a question with "Why should I trust a security company's free scanner?", and answers a later one by routing companies onward - "At a company: that's policy enforcement, which is a different product." The sample scan in the README, labeled "Real output from a real machine", shows a server wrapped by domainguard-agent.exe as its second finding, and src/engine.js handles policy wrappers as a first-class case, naming DomainGuard as its example in the source comment. None of that makes the scanner worse. It does mean the free inventory and the paid control are two ends of one funnel.

Speed is a non-issue: about four-tenths of a second warm through npx, roughly a second cold. The scan itself is tens of milliseconds. Nearly all the wall time is npx resolving the package, so that number moves with a connection rather than with the tool.

The output is better than a list. Every finding carries remediation text attached to the specific thing found - "If you keep it, pin an exact version instead of a floating tag", "Rotate the credential(s) listed here and move them out of plaintext config - use your platform's secret storage or an env manager." That is the difference between a scanner and a report somebody acts on.

What the bench did

We built a synthetic machine with a hand-authored inventory: nineteen agent surfaces, each placed at its documented conventional path.

Claude Code with a global MCP server carrying a GitHub token, a per-project server in its projects map, a PreToolUse hook, a plugin, a skill, a subagent. Codex, Gemini and Ollama in their home directories, with an MCP server declared inside Codex's TOML config. A Cursor global MCP config. Three AI editor extensions, none of them on geiger's list - Kilo Code, Augment and Sourcery, each carrying its real published manifest. One agent CLI installed globally through npm. And three project-level surfaces inside repositories: an .mcp.json holding a live-shaped Stripe key, a .cursor/mcp.json pointing at a remote server, and a .claude/settings.json with a hook that runs a shell script.

Pointed at the home directory with no other arguments, geiger found 14 of the 19.

Given --path for all three repositories, 17 of 19.

Two were never found either way.

The credential counter says it more sharply. Without --path it printed one credential. With --path, two. The one that only appears when the repository is named is the Stripe key.

The catch

The scoping is invisible in a one-off scan and load-bearing in the usage the README recommends next.

Drift alarm: once you've reviewed a machine, save a baseline (--json baseline.json) and put geiger-scan --strict --diff baseline.json in cron or CI. It exits 2 only when something new can execute code or hold secrets - the standing, already-reviewed inventory stays quiet. Same mental model as a lockfile: accept what's there, alarm on change.

Good idea. Right analogy. We tested it as written.

Save a baseline. Add a new MCP server to a repository's .mcp.json - the exact change an alarm exists to catch. Run the recommended one-liner.

$ geiger-scan --strict --diff baseline.json
  no drift - this machine matches the baseline
$ echo $?
0

Now the same command, one flag longer:

$ geiger-scan --strict --diff baseline.json --path work/repo-a
  + appeared  stripe (project work/repo-a)  MCP server  [EXECUTES] [HOLDS-SECRETS] [BROAD-FILESYSTEM]
  + appeared  exfil (project work/repo-a)   MCP server  [EXECUTES] [BROAD-FILESYSTEM]
  2 new finding(s) can execute code or hold secrets - review before accepting a new baseline
$ echo $?
2

Paths abbreviated and columns aligned; wording verbatim.

Nothing is broken here. --strict works on its own - exit 2 on the populated machine, exit 0 on an empty one. The diff works when it can see the file. The alarm is only ever as wide as the paths it was given, and the recommended command gives it none.

That matters more in month three than on day one, because the repositories a developer clones next month are not in a cron job written this month.

And it is not one paragraph. The README's fleet recommendation for MSPs and IT - run --json per machine on a schedule through an RMM task, keep each machine's baseline, let --diff report per-machine drift - has the same shape and the same omission, aimed at exactly the reader most likely to adopt it. The capability table a few screens up promises MCP coverage for "VS Code (user + project)". The project half arrives with a flag the table never mentions. Across the whole README, --path appears on exactly one line: the usage block.

Why the gap sits where it does

Geiger identifies things from hardcoded lists small enough to read in a sitting. data/known-agents.json is 1,265 bytes - 13 VS Code extension IDs, 7 npm package names, 18 browser-extension name substrings. Elsewhere, twelve hardcoded home directories and nine hardcoded MCP config paths, plus two more for each directory handed over with --path.

The interesting part is that the two detectors built on those lists behave nothing alike.

The VS Code detector earns credit. It is not purely list-bound: after checking its thirteen known IDs it falls back to a regular expression tested against the extension ID and the display name, description and keywords in the extension's locally installed manifest. All three off-list extensions were caught this way at medium confidence, off their real published metadata - Sourcery only by its keywords, which genuinely include ai and copilot. On that detector geiger does better than its own data file suggests.

The npm detector has no fallback. It iterates seven package names and stops. Amp - still published as @sourcegraph/amp, now canonically @ampcode/cli - is a real agent CLI, and it is invisible under either name, as is any other global agent install not among the seven. Command-line agents are precisely the category carrying EXECUTES.

The second miss is the MCP server inside Codex's TOML config. Geiger flags that file as partially parsed and says to review it by hand, which is honest, but the server never becomes a finding anyone could count.

Credit on the other side too: Claude Code's per-project MCP servers are found without --path, because they sit centrally in ~/.claude.json and the detector reads that projects map. The gap is not "project configuration is invisible." It is narrower, and easier to walk into.

What the author already says

None of this is concealed, and writing it as though it were would be unfair.

Geiger ships a Limitations section that opens with a line most tools do not write about themselves - "Stated up front, because a scanner you overtrust is worse than no scanner:" - and leads with:

Geiger reads known config locations. Agents installed in nonstandard paths, other user accounts, containers, or WSL (from the Windows side) are not seen.

It says TOML configs are scanned by shape and flagged with reduced confidence rather than skipped, which covers the Codex miss exactly. It says plainly that "The ecosystem this tool audits changes weekly."

So list-boundedness is disclosed. The seam is narrower, and it is why this piece exists: a repository's .mcp.json is not a nonstandard path. It is the documented standard one, and geiger parses it correctly the moment it is pointed there. Neither the Limitations list nor either usage recommendation mentions that the alarm's scope is the set of directories named. The disclosure and the recommendation do not quite meet.

How to actually use it

Two corrections, applied to the output.

Read a zero as "nothing on the list, at the paths named." Not "nothing there." That is the difference between an inventory and a clean bill of health, and only one is on offer. It matters most when someone senior asks what AI tooling the company is running: the number from a default scan is a floor, and it looks equally confident either way.

Pass --path for every repository in play, and put that expansion in the scheduled job, not just the one-off scan. If the repositories cannot be enumerated, the honest drift alarm is one that walks a directory of checkouts rather than one baseline standing in for a whole machine.

Worth your afternoon?

Worth the two minutes, for anyone. Not yet the thing to build a control on.

Geiger agrees, for the record. Its own FAQ answers "Is this a security audit?" with "No. It's an inventory with honest exposure labels - the thing you need before any audit means anything." The tool does not overclaim; it is careful not to. The problem is that a clean count in a terminal gets quoted upward as an answer whether or not a README two scrolls above said it was not one.

The rest of the reasons are ones the author would give. It is four days old - first commit and first npm publish both on 6 September, twelve commits from a single git author, currently v0.2.1. Not a weekend project by an unknown: it is Atomburst's, published under the company's npm account with signed provenance on every release but the first. But it identifies things from lists maintained by hand against, in its own words, an ecosystem that changes weekly. Reasonable design for a tool this small. Poor foundation for a compliance answer.

Which is a fine place for it to sit. As a Tuesday-afternoon look at what is actually installed on a machine, it answers a question most operators cannot otherwise answer without an afternoon of grepping dotfiles. As the artifact handed to whoever asks what AI tooling the company is running, it will quietly under-report - and the report will look exactly as confident either way.


Bench boundary: the machine was synthetic, built on Linux, and no agent was run against it - nothing here speaks to runtime behavior, which geiger does not claim to see. Path tables differ per operating system; macOS and Windows were not tested. The plant was authored after reading the detector source, which biases it toward gaps of a shape already known to exist. Geiger is MIT-licensed and free; VarOps has no relationship with Atomburst.

Add VarOps on Google