valvur
A fully offline security scanner for AI-generated code. Your source never leaves your machine — and you can prove it.
Status:
0.2.0— published and installable;v1.0.0follows the usability gate.pip install valvur·ghcr.io/maverickhq/valvur
Why this exists
Every serious code-security scanner sends your code — or metadata about it — to someone else's servers. For regulated industries and data-residency jurisdictions that is not a preference to negotiate; it ends the procurement conversation. valvur runs entirely on your machine, with networking switched off and your source mounted read-only.
At the same time, AI now writes a fast-growing share of production code, and it fails in ways classic scanners were never built to catch: hallucinated dependencies that attackers pre-register, poisoned agent instruction files, hidden Unicode directives.
Offline scanning, built for how AI-generated code actually breaks.
The three claims
This is the introduction. docs/EVALUATING.md is the audit —
the measured first run, the verification commands, the three statuses, and a plain
list of what valvur does not claim. Read that one sceptically.
1. It cannot exfiltrate your code — and you can verify it
No account, no API key, no telemetry: nothing to opt out of. On the default offline
profile the scanners run in containers with --network=none, and the host process
that launches them opens no socket either. Both halves are checked by one command:
python3 scripts/verify-offline.py /path/to/your/repo
On Linux the OS can deny the whole process tree the network, no privileges needed:
unshare -rn valvur scan --profile offline. Every run records in run.json exactly
what left the machine — on offline, the word nothing.
2. Security checks built for AI-generated code
- Hallucinated dependencies (slopsquatting). LLMs invent package names; attackers
register them. No advisory database can catch it — the package is new, not
known-bad. valvur checks that every declared dependency exists, offline, against
a local index of every name on PyPI, npm, RubyGems, Packagist and crates.io (6.3
million names, exact, published daily and signed). On
fullit also asks how old each one is, and whether it is one edit from something popular. - Agent-config auditing.
CLAUDE.md,AGENTS.md,.cursorrules,.mcp.json, skills and prompt files — scanned for injected directives, hidden Unicode (zero-width, bidi, tag characters), unpinned@mainMCP refs, blanketautoApproveand permission-bypass flags. - A small set of Opengrep rules for model output reaching
eval,exec, a shell, SQL orinnerHTML, and for unpinned actions and mutable git refs.
What is covered, and what is not, is stated on every scan rather than left to infer:
| ecosystem | exists? | known CVEs? |
|---|---|---|
| Python, npm, Ruby, PHP, Rust | offline, from the index | needs requirements*.txt, uv.lock, poetry.lock / a lockfile |
| JVM, Go | full only — no offline index exists for either registry |
pom.xml, go.mod on their own |
A manifest with no lockfile beside it, or a manifest nothing here reads (a lone
Pipfile, say), is a coverage note: the run reads inconclusive rather than
clean, and names why. That reporting is the part we consider non-optional.
3. Ten things that matter, not four hundred findings
Findings are ranked by whether attackers are actually exploiting them — CISA KEV (including the ransomware-campaign flag) and FIRST EPSS — not by CVSS theatre. Development-only dependencies are demoted; transitive vulnerabilities come with the path and the direct package to bump.
| Finding | CVSS | EPSS | KEV | Severity-sorted | Ranked here |
|---|---|---|---|---|---|
| CVE in a dev-only test library | 9.8 CRITICAL | 0.04% | No | #1 | #40 |
| CVE in your production web framework | 6.5 MEDIUM | 92% | Yes | #40 | #1 |
For AI coding agents — the primary way in
Add valvur to your agent's MCP configuration:
{ "mcpServers": { "valvur": { "command": "uvx", "args": ["--from", "valvur", "valvur-mcp"] } } }
Then ask it to scan. The server is stdio only — no listener, no port — and every tool it exposes is read-only: valvur can never change your code.
Add this to your project's CLAUDE.md or AGENTS.md, so the agent uses what it has:
## Security scanning
This project uses valvur. Scan with the `valvur` MCP tools if you have them:
call `scan`, then `scan_status` until it reports DONE. Otherwise run
`valvur scan`. Results appear in `.security-scan/`: read SUMMARY.md, then
REMEDIATION.md. Never commit `.security-scan/`. Never add suppressions without
explicit human approval. Propose fixes for approval — do not apply them and
rescan autonomously.
For developers
pip install valvur # or: uv tool install valvur
valvur update # the image, the vulnerability database and the name index, once
valvur scan # offline by default; --profile full adds the networked checks
The first valvur update pulls the image (about 240MB), the vulnerability database
(118MB) and the name index (34MB, one signed artifact, built daily) — a minute or
two. Later updates take seconds; run valvur update --if-stale from a hook or cron,
it costs one file read when current. A scan that finds the image missing pulls it
and says so — over MCP, scan_status reads "pulling ghcr.io/…" with the size —
rather than sitting silent. If the published index cannot be reached, the five
registries are walked directly instead, which takes about seven minutes once.
Results land in .security-scan/:
.security-scan/
├── SUMMARY.md ← start here. Bounded, leads with anything that failed
├── REMEDIATION.md ← ranked proposal, with dependency paths and upgrade targets
├── findings.json ← complete, normalised, schema-versioned
├── results.sarif ← SARIF 2.1.0 for your IDE
├── sbom.cdx.json ← CycloneDX SBOM
├── run.json ← what ran, which versions, what was skipped and why
└── raw/ ← untouched per-tool output, so you can verify us
The folder ignores itself, so results are never committed. Secrets are redacted in
every artifact, raw/ included. You decide which fixes to apply and when to
rescan — there is no autonomous loop. Suppressions (with mandatory expiry dates)
and [scan] exclude paths live in a committed .security-scan.toml, and every
exclusion is reported with what it cost.
What actually does the scanning
valvur builds no detection engine. Six open source scanners do that and deserve the credit; valvur adds orchestration, one findings model, exploit-aware ranking, and the checks under claim 2.
| Tool | Licence | Does |
|---|---|---|
| Trivy | Apache-2.0 | Dependency vulnerabilities |
| Gitleaks | MIT | Secrets, including git history |
| OSV-Scanner | Apache-2.0 | Dependencies against OSV.dev (full) |
| Opengrep | LGPL-2.1 | Static analysis |
| Checkov | Apache-2.0 | Infrastructure misconfiguration |
| Syft | Apache-2.0 | SBOM, and the dependency licences read from it |
Exploit intelligence comes from CISA KEV and FIRST EPSS — public primary sources, auditable and mirrorable. No proprietary database; nothing to lock you in.
What it deliberately does not do
- No reachability analysis. We do not prove a vulnerable function is called.
- No autonomous fixing. You choose the fixes.
- No penetration testing. No DAST, no exploitation, no scanning of deployed systems.
- No code-quality analysis. Security only.
- We will not out-detect commercial SAST. We win on trust, breadth in one artifact, and prioritisation — not on engine depth.
Platforms
| macOS, Linux — Docker or Podman | Supported, tested on every commit against both runtimes |
linux/amd64 and linux/arm64 |
Both, from 0.2.0. 0.1.0rc1 was published arm64 only — a defect, not a policy |
| Windows via WSL2 | Supported — inside WSL valvur is running on Linux |
| Native Windows | Not claimed. Untested, and valvur says so at startup |
| SELinux-enforcing hosts (RHEL, Fedora) | Supported, with one deliberate friction: valvur will not relabel your source tree unless you set VALVUR_SELINUX_RELABEL=1. Details in EVALUATING.md |
Air-gapped? The database, the name index and KEV all live outside the image and
each has a mirror setting, measured end to end. See docs/AIR-GAPPED.md.
Contributing, and reporting problems
A finding you disagree with — especially one valvur missed — is a bug worth reporting. CONTRIBUTING.md has the setup and the short list of things refused on principle; SECURITY.md is for suspected vulnerabilities, which for a security tool include a false clean result; CHANGELOG.md is what changed.
Licence
Apache-2.0 — see LICENSE; chosen over MIT for the explicit patent grant. Bundled scanners keep their own licences, listed above. valvur adds no GPL or AGPL component; the Alpine base carries GPL userland as every Linux container does, and the published SBOM discloses all of it (ADR-0005).
Release files for valvur 0.2.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 | |
|---|---|---|---|
| valvur-0.2.0.tar.gz | 747.6 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| valvur-0.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 935.2 kB
Release files / valvur-0.2.0.tar.gz
| Download URL | valvur-0.2.0.tar.gz |
|---|---|
| Size | 747.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
8fdb047a2934de89a3022bd7eb339fcc9304a6c028e7e039cf6a2ce54e10f4a3
|
|
BLAKE2b-256 checksum How to use checksums |
fdb29eb10e2ca9208e686695fc6f5c44fbca992693e21ebe07fdbd2c656867e0
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
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.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 13, 2026.
Transparency logRelease files / valvur-0.2.0-py3-none-any.whl
| Download URL | valvur-0.2.0-py3-none-any.whl |
|---|---|
| Size | 187.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
c6e89cc3e0433926337d8c6845f64ed599cd24e5b2fffcec5a6f3348c4dc3796
|
|
BLAKE2b-256 checksum How to use checksums |
ff862872543594af6b81ac59ac60ffa4d89f2d35c44c6dc594ffa584f2758491
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
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.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 13, 2026.
Transparency log