vaporcheck
Fail-closed existence verification for model-emitted identifiers. Catch hallucinated and slop-squatted package names, dead file paths, and nonexistent API/tool names before an agent acts on them — because vaporware you pip install is somebody else's payload.
Why
LLM coding agents confidently emit identifiers that don't exist: a USENIX Security 2025 study found 19.7% of LLM-recommended packages were hallucinated — an open door for slopsquatting (an attacker registers the hallucinated name). The same failure shows up as dead file paths, deprecated/removed APIs, and calls to tools that were never registered.
The root cause — model overconfidence — is not fixable by a wrapper. So vaporcheck doesn't try. It replaces the unanswerable question "is the model confident?" with a deterministic one:
"Does the referent actually exist in its authoritative source?"
That's a binary lookup (a registry, the filesystem, a symbol table) — testable, cheap, and hard to argue with.
How it works
A small resolver core answers exists / not-found / deprecated / cannot-verify for one identifier, shipped through two delivery vehicles:
| Vehicle | What it is | Guarantee |
|---|---|---|
MCP server (verify_identifier tool) |
Any MCP client can ask "does this exist?" | Agent-callable, advisory |
| PreToolUse hook | A Claude Code hook that denies the tool call | Fail-closed — the agent can't proceed |
Both reuse the same resolvers. Standalone, the MCP is advisory (the model may ignore it); paired with the hook (or any gateway that can block), it becomes a hard gate.
The honest boundary
A hook sees tool calls, not generated text. So today this gates the two classes that surface at the tool boundary:
- package installs (
pip install,npm install, …) — verified against PyPI / npm - file paths (
Editof a missing file) — verified against the filesystem
Tool-names and API-symbols buried in generated code are verify-on-write / verify-on-run (gate the Edit or test that introduces them), not mid-sentence interception. This is a deliberate, documented limit — see docs/SPIKE-FINDINGS.md.
Install
Requires Python 3.10+. Zero runtime dependencies (stdlib only — fitting for an anti-supply-chain-risk tool).
Option A — pip (adds the vaporcheck-mcp console command)
pip install vaporcheck
Option B — clone (if you want the hook/MCP config to point at source files)
git clone https://github.com/cdmx-in/vaporcheck.git
cd vaporcheck
python vaporcheck/test_parse.py # offline smoke test — should print 10/10 PASS
Configure
As an MCP server (any MCP client)
// .mcp.json (project scope) — adjust the path to where you cloned
{
"mcpServers": {
"vaporcheck": { "command": "python", "args": ["/path/to/vaporcheck/vaporcheck/server.py"] }
}
}
(On Windows, "command": "py", "args": ["-3", ...] also works. If you installed via pip, use "command": "vaporcheck-mcp" with no args.)
Then call the verify_identifier tool:
{ "kind": "pypi", "value": "reqeusts" }
// -> { "status": "not-found", ... } (a slopsquat — blocked)
As a fail-closed Claude Code hook
// .claude/settings.json (or settings.local.json)
{
"hooks": {
"PreToolUse": [
{ "matcher": "Bash",
"hooks": [{ "type": "command", "command": "python /path/to/vaporcheck/vaporcheck/hook.py", "timeout": 15 }] }
]
}
}
Restart Claude Code to load the hook. Now pip install <hallucinated-package> is denied before it runs, with a reason the model can self-correct from. On network failure the hook degrades to ask instead of falsely blocking.
Status
MVP — working, tested, dogfooded. Resolvers: PyPI, npm, filesystem paths. Shared SQLite cache (warm lookups ~1 ms) with status-aware TTLs and a token-bucket throttle.
Roadmap
- More package ecosystems: crates.io, Go, RubyGems, Maven
- Tool-name resolver (against the live tool registry) — greenfield, no incumbent
- API-symbol resolver (via LSP / compiler) — greenfield
- Citations resolver (last; the crowded class — breadth, not headline)
- Namespace the modules under a package; publish to PyPI
Tests
38 checks across 4 suites — all hitting real registries / filesystem, no mocks:
python vaporcheck/run_spike.py # hook behavior (10)
python vaporcheck/test_parse.py # shell-aware install parser (10)
python vaporcheck/test_mcp.py # MCP client handshake (9)
python vaporcheck/test_cache.py # cache latency / TTL / errors (9)
Provenance
This repo is the product of a structured research → validation → build effort. The full record — pain-point research, web-verified go/no-go, and the live-dogfooding spike that found and fixed two real parser bugs — is in docs/.
License
Metadata
Release files for vaporcheck 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| vaporcheck-0.1.0.tar.gz | 21.4 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| vaporcheck-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 44.3 kB
Release files / vaporcheck-0.1.0.tar.gz
| Download URL | vaporcheck-0.1.0.tar.gz |
|---|---|
| Size | 21.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
7761d94a7e3021fe69e4e60c466c43e5c26e457fb297985208fac2fdb037fc02
|
|
BLAKE2b-256 checksum How to use checksums |
d920744007c8c2f0990c442bd5aa5604a67eae0beb423d526812f8456c6e2966
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Release files / vaporcheck-0.1.0-py3-none-any.whl
| Download URL | vaporcheck-0.1.0-py3-none-any.whl |
|---|---|
| Size | 22.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
15fbf68d6337d9586b8927195d61d8d26ea6ca2118b8eb7e86c71ca77b576bab
|
|
BLAKE2b-256 checksum How to use checksums |
3db88ec05f0d2ff87be0e09ec40bb9c53342e79f443bcf7ce8ce0fd2a3bfa87b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Security for vaporcheck 0.1.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.