valvur 0.1.0rc1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- valvur-0.1.0rc1/.gitattributes +12 -0
- valvur-0.1.0rc1/.gitignore +28 -0
- valvur-0.1.0rc1/CLAUDE.md +225 -0
- valvur-0.1.0rc1/CONTEXT.md +96 -0
- valvur-0.1.0rc1/Dockerfile +60 -0
- valvur-0.1.0rc1/PKG-INFO +276 -0
- valvur-0.1.0rc1/README.md +262 -0
- valvur-0.1.0rc1/pyproject.toml +65 -0
- valvur-0.1.0rc1/rules/llm-output-sinks.yaml +64 -0
- valvur-0.1.0rc1/rules/pinning-hygiene.yaml +17 -0
- valvur-0.1.0rc1/rules/python-security.yaml +54 -0
- valvur-0.1.0rc1/src/valvur/__init__.py +4 -0
- valvur-0.1.0rc1/src/valvur/adapters/__init__.py +38 -0
- valvur-0.1.0rc1/src/valvur/adapters/base.py +41 -0
- valvur-0.1.0rc1/src/valvur/adapters/check.py +48 -0
- valvur-0.1.0rc1/src/valvur/adapters/checkov.py +45 -0
- valvur-0.1.0rc1/src/valvur/adapters/gitleaks.py +41 -0
- valvur-0.1.0rc1/src/valvur/adapters/opengrep.py +75 -0
- valvur-0.1.0rc1/src/valvur/adapters/osv.py +82 -0
- valvur-0.1.0rc1/src/valvur/adapters/syft.py +24 -0
- valvur-0.1.0rc1/src/valvur/adapters/trivy.py +137 -0
- valvur-0.1.0rc1/src/valvur/api.py +172 -0
- valvur-0.1.0rc1/src/valvur/artifacts.py +105 -0
- valvur-0.1.0rc1/src/valvur/cache.py +33 -0
- valvur-0.1.0rc1/src/valvur/checks/__init__.py +16 -0
- valvur-0.1.0rc1/src/valvur/checks/__main__.py +32 -0
- valvur-0.1.0rc1/src/valvur/checks/ai_artifact.py +134 -0
- valvur-0.1.0rc1/src/valvur/checks/base.py +28 -0
- valvur-0.1.0rc1/src/valvur/checks/dependency_reality.py +180 -0
- valvur-0.1.0rc1/src/valvur/checks/licence_file.py +111 -0
- valvur-0.1.0rc1/src/valvur/cli.py +188 -0
- valvur-0.1.0rc1/src/valvur/compat.py +68 -0
- valvur-0.1.0rc1/src/valvur/data/kev.json +1 -0
- valvur-0.1.0rc1/src/valvur/data/popular-pypi.json +3009 -0
- valvur-0.1.0rc1/src/valvur/defang.py +89 -0
- valvur-0.1.0rc1/src/valvur/enrichment.py +124 -0
- valvur-0.1.0rc1/src/valvur/findings.py +102 -0
- valvur-0.1.0rc1/src/valvur/fingerprint.py +64 -0
- valvur-0.1.0rc1/src/valvur/licence_policy.py +90 -0
- valvur-0.1.0rc1/src/valvur/mcp/__init__.py +8 -0
- valvur-0.1.0rc1/src/valvur/mcp/jobs.py +77 -0
- valvur-0.1.0rc1/src/valvur/mcp/protocol.py +122 -0
- valvur-0.1.0rc1/src/valvur/mcp/server.py +110 -0
- valvur-0.1.0rc1/src/valvur/mcp/tools.py +63 -0
- valvur-0.1.0rc1/src/valvur/operations.py +229 -0
- valvur-0.1.0rc1/src/valvur/profiles.py +44 -0
- valvur-0.1.0rc1/src/valvur/provenance.py +22 -0
- valvur-0.1.0rc1/src/valvur/ranking.py +91 -0
- valvur-0.1.0rc1/src/valvur/rawoutput.py +72 -0
- valvur-0.1.0rc1/src/valvur/redact.py +22 -0
- valvur-0.1.0rc1/src/valvur/remediation.py +106 -0
- valvur-0.1.0rc1/src/valvur/results.py +239 -0
- valvur-0.1.0rc1/src/valvur/runner.py +375 -0
- valvur-0.1.0rc1/src/valvur/state.py +63 -0
- valvur-0.1.0rc1/src/valvur/suppressions.py +187 -0
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Normalise line endings in the repo so fingerprints computed on a Windows
|
|
2
|
+
# checkout match those on macOS/Linux. Guards requirement F5.4 at the VCS layer.
|
|
3
|
+
* text=auto eol=lf
|
|
4
|
+
|
|
5
|
+
# Binary / fixture files must not be munged.
|
|
6
|
+
*.png binary
|
|
7
|
+
*.gif binary
|
|
8
|
+
*.zip binary
|
|
9
|
+
*.gz binary
|
|
10
|
+
|
|
11
|
+
# Golden scanner fixtures are byte-exact by definition — never normalise.
|
|
12
|
+
tests/fixtures/golden/** -text
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# valvur results — never commit scan output.
|
|
2
|
+
# The Results Folder also self-ignores via .security-scan/.gitignore,
|
|
3
|
+
# but this entry is the visible signal to humans.
|
|
4
|
+
.security-scan/
|
|
5
|
+
|
|
6
|
+
# Python
|
|
7
|
+
.venv/
|
|
8
|
+
venv/
|
|
9
|
+
__pycache__/
|
|
10
|
+
*.py[cod]
|
|
11
|
+
*.egg-info/
|
|
12
|
+
.pytest_cache/
|
|
13
|
+
.mypy_cache/
|
|
14
|
+
.ruff_cache/
|
|
15
|
+
dist/
|
|
16
|
+
build/
|
|
17
|
+
|
|
18
|
+
# Secrets and local config
|
|
19
|
+
.env
|
|
20
|
+
.env.*
|
|
21
|
+
!.env.example
|
|
22
|
+
|
|
23
|
+
# OS / editor
|
|
24
|
+
.DS_Store
|
|
25
|
+
Thumbs.db
|
|
26
|
+
.idea/
|
|
27
|
+
.vscode/
|
|
28
|
+
*.swp
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
# CLAUDE.md — long-term context for this repository
|
|
2
|
+
|
|
3
|
+
> **Audience:** any AI agent or human joining this project with no prior context.
|
|
4
|
+
> Read this before proposing changes. Written 2026-08-29.
|
|
5
|
+
> **Working name:** `valvur` (Estonian: *guard, watchman*). Provisional until first
|
|
6
|
+
> publish — renaming is one `sed` away and stays cheap until a GitHub repo, PyPI
|
|
7
|
+
> release or Docker push exists.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## 1. What this is
|
|
12
|
+
|
|
13
|
+
A lean, **local-first, fully offline** security scanner for codebases, with a
|
|
14
|
+
first-class focus on **AI-generated code**. It orchestrates best-of-breed open
|
|
15
|
+
source scanners, normalises their findings, ranks them by real-world
|
|
16
|
+
exploitability, and writes an **agent-consumable** results folder into the
|
|
17
|
+
project being scanned.
|
|
18
|
+
|
|
19
|
+
Delivered primarily as an **MCP tool** the developer adds to their agent (Kiro,
|
|
20
|
+
Claude Code) and invokes deliberately; the CLI is the second way in. The MCP server
|
|
21
|
+
is hand-rolled over stdio with **zero dependencies** (ADR-0015).
|
|
22
|
+
Packaged as one OCI container. Runs on Docker or Podman, locally by default,
|
|
23
|
+
optionally on AWS (ECR/Fargate) using the identical image.
|
|
24
|
+
|
|
25
|
+
**Status:** spec phase. No application code yet.
|
|
26
|
+
|
|
27
|
+
## 2. What it is NOT
|
|
28
|
+
|
|
29
|
+
Be ruthless about this — scope creep here destroys the product:
|
|
30
|
+
|
|
31
|
+
- **Not a new scanning engine.** Detection is done by Trivy, Gitleaks, Opengrep,
|
|
32
|
+
Checkov, OSV-Scanner and Syft. We orchestrate, normalise, enrich and present.
|
|
33
|
+
Never imply proprietary detection. Always credit the scanners.
|
|
34
|
+
- **Not a reachability analyser.** We do not prove a vulnerable function is
|
|
35
|
+
called. That is a multi-year, per-language effort (Endor Labs / Semgrep Pro
|
|
36
|
+
territory). Never claim or imply it.
|
|
37
|
+
- **Not an autonomous fixer.** No scan→fix→rescan loop. See §4.
|
|
38
|
+
- **Not a pen-test tool.** No DAST, no exploitation, no network scanning of
|
|
39
|
+
deployed systems. That is a separate product with a different legal posture
|
|
40
|
+
(authorisation required) and a blast radius this tool must never have.
|
|
41
|
+
- **Not a code-quality platform.** SonarQube's territory. We do security only.
|
|
42
|
+
|
|
43
|
+
## 3. The moat — non-negotiable
|
|
44
|
+
|
|
45
|
+
**The product is defined by what it refuses to do.** Competitors cannot copy
|
|
46
|
+
this without breaking their own business model. Every one of these is a hard
|
|
47
|
+
constraint, not an aspiration. Any feature that trades one away must be
|
|
48
|
+
rejected, however useful it seems.
|
|
49
|
+
|
|
50
|
+
1. **It never phones home, and that is provable.** `--network=none` on the
|
|
51
|
+
quick profile. Source is mounted read-only. No account, no API key, no
|
|
52
|
+
telemetry, ever. A reviewer must be able to *verify* this themselves, not
|
|
53
|
+
take our word for it.
|
|
54
|
+
2. **The source tree is mounted read-only.** The scanner cannot modify the code
|
|
55
|
+
it scans — structurally, not by policy.
|
|
56
|
+
3. **Vulnerability data comes from auditable primary sources** (CISA KEV, FIRST
|
|
57
|
+
EPSS, OSV). No proprietary database, no lock-in, mirrorable for air-gapped
|
|
58
|
+
use.
|
|
59
|
+
4. **Results never leave the machine and are never committed.**
|
|
60
|
+
|
|
61
|
+
> If a future proposal improves results by sending data somewhere, that is the
|
|
62
|
+
> moat being traded away. Refuse it, or escalate to the owner explicitly.
|
|
63
|
+
|
|
64
|
+
## 4. Human-in-the-loop is a safety property, not a UX choice
|
|
65
|
+
|
|
66
|
+
The developer chooses **which** fixes to apply and **when** to rescan. There is
|
|
67
|
+
no autonomous remediation loop.
|
|
68
|
+
|
|
69
|
+
Why this is non-negotiable: an agent told to drive findings to zero has a
|
|
70
|
+
cheaper path via deleting code or writing suppressions than via correct fixes.
|
|
71
|
+
"The finding disappeared" is not the same claim as "the vulnerability is fixed"
|
|
72
|
+
— swapping a hash function satisfies the scanner and breaks every stored
|
|
73
|
+
credential. Only a human can distinguish those.
|
|
74
|
+
|
|
75
|
+
Consequences, already reflected in the architecture:
|
|
76
|
+
- No `scan_and_fix` MCP tool exists. The MCP surface is read-only w.r.t. source.
|
|
77
|
+
- `REMEDIATION.md` is a **proposal**, never an execution script. Each item is
|
|
78
|
+
independently applicable, because the developer will cherry-pick.
|
|
79
|
+
- Rescan is always an explicit call. No file watchers, no on-save hooks.
|
|
80
|
+
|
|
81
|
+
## 5. Target market
|
|
82
|
+
|
|
83
|
+
Primary, in priority order:
|
|
84
|
+
|
|
85
|
+
1. **Regulated industries that cannot send code to a vendor** — finance,
|
|
86
|
+
defence, healthcare, government, critical infrastructure. For them, "your
|
|
87
|
+
dependency manifest is analysed on our servers" ends the procurement
|
|
88
|
+
conversation.
|
|
89
|
+
2. **Jurisdictions with data-residency requirements** for AI and code-generation
|
|
90
|
+
tooling. Cloud code-analysis services are typically offered in a handful of
|
|
91
|
+
regions; anything outside them is non-compliant by construction. A fully
|
|
92
|
+
offline tool has no residency question to answer.
|
|
93
|
+
3. **Teams shipping AI-generated code** who need checks nobody else runs —
|
|
94
|
+
slopsquatting, agent-config auditing, hidden Unicode, LLM-output-to-sink
|
|
95
|
+
taint.
|
|
96
|
+
4. **Individual developers and OSS maintainers** who want one command, no
|
|
97
|
+
account, useful output in under 60 seconds.
|
|
98
|
+
|
|
99
|
+
## 6. Locked decisions
|
|
100
|
+
|
|
101
|
+
Full rationale lives in `docs/adr/`. Do not re-litigate these without a strong
|
|
102
|
+
new argument.
|
|
103
|
+
|
|
104
|
+
| # | Decision |
|
|
105
|
+
|---|---|
|
|
106
|
+
| [001](docs/adr/0001-thin-host-shim-read-only-container.md) | **Thin host shim + read-only container engine.** MCP server is a small host-side shim; all scanners live in the image; source mounted `:ro`; scratch dir mounted `:rw`; the shim writes results as the developer's own user. Chosen over a fat container (file-ownership chaos across Docker/Podman/rootless/SELinux) and host install (dependency hell). The mount **is** the workspace jail — structural, not validation code. |
|
|
107
|
+
| [002](docs/adr/0002-layered-results-contract.md) | **Layered results contract.** One in-memory findings model projected to several artifacts, each with exactly one consumer. Self-ignoring results folder. See §7. |
|
|
108
|
+
| [003](docs/adr/0003-per-class-finding-identity.md) | **Per-class finding identity.** Fingerprints keyed by each finding class's natural identity, not line numbers. Versioned (`fp_version`). See §8. |
|
|
109
|
+
| [004](docs/adr/0004-opengrep-not-semgrep.md) | **Opengrep, not Semgrep.** Semgrep moved its maintained rules to a licence permitting only internal, non-competing, non-SaaS use (Dec 2024). We publish a scanning tool — that is plausibly a competing use, and redistributing those rules in an image is legally murky. Opengrep is the LGPL-2.1 consortium fork with the same rule syntax. |
|
|
110
|
+
| [005](docs/adr/0005-no-gpl-tools-in-the-image.md) | **No GPL tools deliberately added.** hadolint is GPL-3.0; Checkov and Trivy cover Dockerfiles adequately. *Corrected 2026-08-30:* the original claim was "no GPL component in the image", which no Linux container can satisfy — ours has 12, all base-OS. F10.4 now constrains what we **add**, and requires an SBOM disclosing the rest. |
|
|
111
|
+
| [006](docs/adr/0006-no-graph-database.md) | **No graph database.** Findings are a flat table with predictable queries. The one graph-shaped thing (the dependency tree) arrives free in the SBOM. Visual comprehension is served by `SUMMARY.md` and `REMEDIATION.md`, which render natively everywhere (see ADR-0014). |
|
|
112
|
+
| [007](docs/adr/0007-internal-enrichment-no-external-platform.md) | **Enrichment is internal and has zero external prerequisites.** ~200 lines behind an `EnrichmentProvider` interface: KEV snapshot bundled in the image, EPSS fetched on demand for found CVEs only, degrading to KEV-only when offline. The sibling VulnGraph project is **parked** and must never become a dependency. |
|
|
113
|
+
| [008](docs/adr/0008-no-saas-coupled-dependencies.md) | **No SaaS-coupled dependencies.** `snyk/agent-scan` was rejected despite being credible (Apache-2.0, well-adopted) because it requires `SNYK_TOKEN` and transmits component data to Snyk. Applying this rule to Snyk and waiving it elsewhere would make the principle meaningless. |
|
|
114
|
+
| [009](docs/adr/0009-human-in-the-loop-remediation.md) | **Human-in-the-loop remediation.** valvur proposes, never remediates. No `scan_and_fix` tool, no watchers, no on-save hooks — and a test asserts no such tool *exists in the registry*, so adding one fails the build rather than merely failing review. An agent told to drive findings to zero has a cheaper path via deletion and suppression than via correct fixes. See §4. |
|
|
115
|
+
| [015](docs/adr/0015-hand-rolled-mcp-stdio-transport.md) | **MCP stdio is hand-rolled, zero dependencies.** The official SDK pulls 22 packages — an HTTP server, an OAuth stack and a crypto library — to support transports Kiro and Claude Code do not use. stdio has no listener: the process boundary is the trust boundary. MCP is on the **primary** install path; the CLI is second. |
|
|
116
|
+
| [014](docs/adr/0014-no-html-report.md) | **No `report.html`.** Cut before implementation. The Results Folder is deliberately unshareable (ADR-0011), which removes an HTML report's main advantage over Markdown; rendering untrusted content in a browser was the largest security surface in the contract; and it was the only artifact with no single identified consumer, which is ADR-0002's own rule. F7.8 deferred, F7.15 stays dormant. |
|
|
117
|
+
| [013](docs/adr/0013-checks-run-inside-the-container.md) | **valvur's own Checks run inside the container**, like Scanners. Host-side would put the Dependency Reality Check's registry calls outside `--network=none`, turning ADR-0010's guarantee back into a policy. No new orchestrator protocol was needed: Checks emit JSON and fit the existing adapter contract. |
|
|
118
|
+
| [011](docs/adr/0011-scan-output-never-enters-git.md) | **Scan output never enters git history, on any branch.** Self-ignoring folder + root `.gitignore` + a tracked `pre-commit` hook that refuses staged `.security-scan/` paths (`.gitignore` does not stop `git add -f`). A separate "clean publish branch" was rejected: git objects are repo-wide, so committing on any branch puts results on the remote. |
|
|
119
|
+
| [012](docs/adr/0012-vulnerability-db-lives-outside-the-image.md) | **The vulnerability DB lives outside the image.** Baking Trivy's DB in took the image from 187MB to 1.52GB *and* tied advisory freshness to image release cadence. It now lives in a host cache, mounted at scan time; scans run `--skip-db-update` so `quick` stays offline. |
|
|
120
|
+
| [010](docs/adr/0010-provable-non-exfiltration.md) | **Provable non-exfiltration is a hard constraint.** Not a policy — a testable property, with a regression test that fails if the quick profile touches a socket. This is the product; see §3. |
|
|
121
|
+
|
|
122
|
+
## 7. Results contract
|
|
123
|
+
|
|
124
|
+
Written into the scanned project:
|
|
125
|
+
|
|
126
|
+
```
|
|
127
|
+
.security-scan/
|
|
128
|
+
.gitignore # contains "*" — the folder ignores itself
|
|
129
|
+
SUMMARY.md # entry point, capped ~200 lines, leads with failures
|
|
130
|
+
REMEDIATION.md # ranked proposal: KEV/EPSS order, dependency paths
|
|
131
|
+
findings.json # normalised, schema-versioned, secrets redacted
|
|
132
|
+
results.sarif # SARIF 2.1.0 for IDEs and tooling
|
|
133
|
+
sbom.cdx.json # CycloneDX
|
|
134
|
+
run.json # provenance: tool + DB versions, skips, failures
|
|
135
|
+
state.json # previous run's fingerprints (local only)
|
|
136
|
+
raw/ # per-tool output, secrets redacted
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Plus `.security-scan.toml` at project root — **committed**, holds suppressions
|
|
140
|
+
with **mandatory expiry dates**.
|
|
141
|
+
|
|
142
|
+
Rules that must hold:
|
|
143
|
+
- **Self-ignoring folder** is the guarantee results are never committed; a root
|
|
144
|
+
`.gitignore` entry is added too, as a visible signal to humans.
|
|
145
|
+
- **Secrets are redacted** in every written artifact including `raw/`. Gitleaks
|
|
146
|
+
emits live credential values; writing those verbatim would have our security
|
|
147
|
+
tool copy your secrets to a second cleartext location on disk.
|
|
148
|
+
- **Progressive disclosure.** The agent always reads `SUMMARY.md` (bounded),
|
|
149
|
+
works from `REMEDIATION.md`, queries `findings.json` per finding, and never
|
|
150
|
+
reads `raw/`. A 40MB scan stays usable because the read path is bounded.
|
|
151
|
+
- **`SUMMARY.md` opens with a machine-facing block** explaining the folder. An
|
|
152
|
+
agent in someone else's repo meets the output before it ever sees our README.
|
|
153
|
+
- **Fail loudly.** If a scanner crashed, that appears at the top of
|
|
154
|
+
`SUMMARY.md`. A silent failure manufactures false confidence and is worse
|
|
155
|
+
than no scan.
|
|
156
|
+
- **Evidence is neutralised, never reproduced raw** (F3.13). An agent reads
|
|
157
|
+
`SUMMARY.md` first and by instruction. If we quote an injection payload
|
|
158
|
+
verbatim, we launder an attack out of a file the agent might never have
|
|
159
|
+
opened into one we tell it to read. Hidden Unicode is escaped; directive
|
|
160
|
+
text is fenced and labelled untrusted. **valvur must never become the
|
|
161
|
+
delivery mechanism.**
|
|
162
|
+
- **The same applies to MCP responses** (F9.9). A response reaches an agent's
|
|
163
|
+
context with no file in between — the most direct injection path valvur has,
|
|
164
|
+
and the only one the agent cannot decline to read.
|
|
165
|
+
- **In markup, escaping replaces fencing** (F7.15). The `[UNTRUSTED CONTENT]`
|
|
166
|
+
fence is a textual convention with no effect in HTML, where a payload can
|
|
167
|
+
execute or hide itself with styling while remaining in the file. Any
|
|
168
|
+
artifact that renders escapes workspace content so it cannot act as markup,
|
|
169
|
+
style or script. Dormant since ADR-0014 cut `report.html`; the guard stays in
|
|
170
|
+
force for whatever renders next.
|
|
171
|
+
- **Clean is explicit.** No findings still writes the folder, with
|
|
172
|
+
`"status": "clean"`, so an agent can tell "clean" from "never ran".
|
|
173
|
+
|
|
174
|
+
## 8. Finding identity
|
|
175
|
+
|
|
176
|
+
Identity is per finding class, keyed on what is naturally stable — never on
|
|
177
|
+
line numbers, which shift on every edit and would make the rescan diff useless.
|
|
178
|
+
|
|
179
|
+
| Class | Identity |
|
|
180
|
+
|---|---|
|
|
181
|
+
| Dependency CVE | `(ecosystem, package, version, vuln_id)` |
|
|
182
|
+
| Secret | `(rule, path, sha256(secret)[:16])` |
|
|
183
|
+
| IaC misconfig | `(rule, path, resource_address)` |
|
|
184
|
+
| Licence | `(package, license_id)` |
|
|
185
|
+
| Slopsquat / dep-reality | `(ecosystem, package_name)` |
|
|
186
|
+
| SAST / AI-artifact | `(rule, path, sha256(normalised_match), occurrence)` |
|
|
187
|
+
|
|
188
|
+
Only the last row needs a content hash. Paths are repo-relative so fingerprints
|
|
189
|
+
are byte-identical across machines — suppressions are shared in a committed
|
|
190
|
+
file and must match everywhere.
|
|
191
|
+
|
|
192
|
+
`fp_version` is a compatibility surface from the first commit: changing the
|
|
193
|
+
algorithm invalidates every suppression in every repo using the tool.
|
|
194
|
+
|
|
195
|
+
Status diff: `new` / `persisting` / `fixed` / `regressed`, computed against
|
|
196
|
+
`state.json`. A fresh clone has no history and reports everything as `new` —
|
|
197
|
+
correct and honest.
|
|
198
|
+
|
|
199
|
+
## 9. Working conventions
|
|
200
|
+
|
|
201
|
+
- **Spec-driven.** [`.kiro/specs/valvur/`](.kiro/specs/valvur/) holds
|
|
202
|
+
[requirements](.kiro/specs/valvur/requirements.md) →
|
|
203
|
+
[design](.kiro/specs/valvur/design.md) →
|
|
204
|
+
[tasks](.kiro/specs/valvur/tasks.md). Requirement IDs (F1.1, N2.1, P5 …) are
|
|
205
|
+
load-bearing — cited by the design, the tasks and the verification suite.
|
|
206
|
+
**Never renumber them.**
|
|
207
|
+
- **Vocabulary.** [CONTEXT.md](CONTEXT.md) is the glossary. Use its terms
|
|
208
|
+
exactly; the `_Avoid_` lists exist because the wrong word erodes a constraint.
|
|
209
|
+
- **Test-driven.** Tests before implementation. Fingerprinting, normalisation
|
|
210
|
+
and redaction are the highest-value units — write those first.
|
|
211
|
+
- **ADRs** in `docs/adr/`, numbered, with rejected alternatives recorded.
|
|
212
|
+
- **Dogfooding is a release gate.** The tool scans itself; a clean self-scan,
|
|
213
|
+
signed image, published SBOM and pinned dependencies gate every release.
|
|
214
|
+
For a security tool the repo is its own best test case, and anything we
|
|
215
|
+
preach but do not practise is the first thing a reviewer will notice.
|
|
216
|
+
|
|
217
|
+
## 10. Prohibited without explicit owner approval
|
|
218
|
+
|
|
219
|
+
- Any network call in the `quick` profile.
|
|
220
|
+
- Any dependency requiring an account, API key or token to function.
|
|
221
|
+
- Any feature that writes to the scanned source tree.
|
|
222
|
+
- Any autonomous remediation.
|
|
223
|
+
- Any claim of reachability analysis, proprietary detection, or coverage we do
|
|
224
|
+
not have.
|
|
225
|
+
- Bundling GPL-licensed tools into the distributed image.
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# valvur
|
|
2
|
+
|
|
3
|
+
A fully offline security scanner for codebases, with particular attention to how
|
|
4
|
+
AI-generated code fails. It orchestrates third-party scanners, normalises their
|
|
5
|
+
output into a single model, ranks it by real-world exploitability, and writes it
|
|
6
|
+
where a developer or a coding agent can act on it.
|
|
7
|
+
|
|
8
|
+
## Language
|
|
9
|
+
|
|
10
|
+
### Scanning
|
|
11
|
+
|
|
12
|
+
**Scan Run**:
|
|
13
|
+
One complete invocation of valvur against one Workspace, producing one Results Folder.
|
|
14
|
+
_Avoid_: scan, job, execution, analysis
|
|
15
|
+
|
|
16
|
+
**Workspace**:
|
|
17
|
+
The developer's project being examined. Always mounted read-only; valvur never writes to it.
|
|
18
|
+
_Avoid_: project, repo, target, source directory, codebase
|
|
19
|
+
|
|
20
|
+
**Profile**:
|
|
21
|
+
The named breadth of a Scan Run — `quick`, `standard` or `deep` — determining which Scanners run and whether any network access is permitted.
|
|
22
|
+
_Avoid_: mode, level, preset, tier
|
|
23
|
+
|
|
24
|
+
**Scanner**:
|
|
25
|
+
A third-party tool that performs actual detection (Trivy, Gitleaks, Opengrep, Checkov, OSV-Scanner, Syft). valvur never detects anything itself.
|
|
26
|
+
_Avoid_: tool, engine, plugin, analyser
|
|
27
|
+
|
|
28
|
+
**Check**:
|
|
29
|
+
A detection valvur performs itself rather than delegating to a Scanner — the Dependency Reality Check, AI Artifact scan, pinning hygiene, and Licence Hygiene.
|
|
30
|
+
_Avoid_: rule, test, custom scanner
|
|
31
|
+
|
|
32
|
+
### Findings
|
|
33
|
+
|
|
34
|
+
**Finding**:
|
|
35
|
+
One problem identified in the Workspace, attributed to the Scanner or Check that produced it.
|
|
36
|
+
_Avoid_: issue, vulnerability, alert, violation, error, result
|
|
37
|
+
|
|
38
|
+
**Finding Class**:
|
|
39
|
+
The category a Finding belongs to — dependency vulnerability, secret, IaC misconfiguration, licence, dependency reality, or static analysis. Determines how its Fingerprint is derived.
|
|
40
|
+
_Avoid_: type, kind, category
|
|
41
|
+
|
|
42
|
+
**Fingerprint**:
|
|
43
|
+
A Finding's stable identity across Scan Runs, derived from its Finding Class's natural key. Never derived from line numbers. Byte-identical across machines.
|
|
44
|
+
_Avoid_: id, hash, key, signature
|
|
45
|
+
|
|
46
|
+
**Status**:
|
|
47
|
+
A Finding's relationship to the previous Scan Run — `new`, `persisting`, `fixed`, or `regressed`.
|
|
48
|
+
_Avoid_: state, disposition
|
|
49
|
+
|
|
50
|
+
**Redaction**:
|
|
51
|
+
The replacement of a secret's value with its Fingerprint before any Finding is written to disk. Applies to every written artifact without exception.
|
|
52
|
+
_Avoid_: masking, scrubbing, sanitising
|
|
53
|
+
|
|
54
|
+
### Prioritisation
|
|
55
|
+
|
|
56
|
+
**Enrichment**:
|
|
57
|
+
Exploitability information attached to a Finding to rank it — distinct from the severity a Scanner assigns.
|
|
58
|
+
_Avoid_: metadata, context, intel, annotation
|
|
59
|
+
|
|
60
|
+
**Exploit Signal**:
|
|
61
|
+
Evidence that a vulnerability is exploited in reality rather than in theory: presence in CISA KEV, the KEV ransomware flag, and the FIRST EPSS probability.
|
|
62
|
+
_Avoid_: threat intel, exploit data
|
|
63
|
+
|
|
64
|
+
**Enrichment Provider**:
|
|
65
|
+
A source of Enrichment behind a common interface. The only Provider is local — a bundled KEV snapshot plus on-demand EPSS lookups.
|
|
66
|
+
_Avoid_: intel source, feed, backend
|
|
67
|
+
|
|
68
|
+
**Dependency Path**:
|
|
69
|
+
The chain from a direct dependency of the Workspace to a vulnerable transitive one, naming the direct package that must change.
|
|
70
|
+
_Avoid_: dependency chain, transitive path
|
|
71
|
+
|
|
72
|
+
**Slopsquat**:
|
|
73
|
+
A package registered by an attacker under a name that language models hallucinate. Detected by the Dependency Reality Check, not by any advisory database.
|
|
74
|
+
_Avoid_: typosquat, malicious package
|
|
75
|
+
|
|
76
|
+
### Output and disposition
|
|
77
|
+
|
|
78
|
+
**Results Folder**:
|
|
79
|
+
The `.security-scan/` directory written into the Workspace by the host, ignoring itself via its own `.gitignore`. Never committed.
|
|
80
|
+
_Avoid_: output directory, report directory, artifacts
|
|
81
|
+
|
|
82
|
+
**Provenance**:
|
|
83
|
+
The record of what a Scan Run actually did — Scanner versions, Enrichment age, what was skipped and why, what failed. Makes a clean result falsifiable.
|
|
84
|
+
_Avoid_: metadata, manifest, audit log
|
|
85
|
+
|
|
86
|
+
**Clean Scan**:
|
|
87
|
+
A Scan Run that completed with no Findings. Still writes a full Results Folder, so it is distinguishable from a Scan Run that never happened.
|
|
88
|
+
_Avoid_: pass, green, empty result
|
|
89
|
+
|
|
90
|
+
**Remediation Item**:
|
|
91
|
+
A proposed change addressing one or more Findings, ranked by Exploit Signal and independently applicable. A proposal for a human, never an instruction executed automatically.
|
|
92
|
+
_Avoid_: fix, recommendation, action, task
|
|
93
|
+
|
|
94
|
+
**Suppression**:
|
|
95
|
+
A recorded decision to accept a Finding, keyed by Fingerprint, carrying a mandatory expiry date. Lives in the Workspace and is committed — unlike the Results Folder.
|
|
96
|
+
_Avoid_: ignore, exception, waiver, mute, allowlist entry
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# valvur scanner image.
|
|
2
|
+
#
|
|
3
|
+
# Scanners are pinned to exact versions (F2.2). Non-root, read-only root filesystem,
|
|
4
|
+
# no capabilities (F10.2). No GPL/AGPL components (ADR-0005).
|
|
5
|
+
#
|
|
6
|
+
# The vulnerability DB is deliberately NOT baked in — it is 1.2GB and would tie
|
|
7
|
+
# advisory freshness to image release cadence (ADR-0012).
|
|
8
|
+
|
|
9
|
+
FROM zricethezav/gitleaks:v8.30.1 AS gitleaks
|
|
10
|
+
FROM aquasec/trivy:0.74.0 AS trivy
|
|
11
|
+
FROM ghcr.io/google/osv-scanner:v2.2.4 AS osv
|
|
12
|
+
FROM anchore/syft:v1.51.1 AS syft
|
|
13
|
+
|
|
14
|
+
FROM python:3.12-alpine3.22
|
|
15
|
+
ARG TARGETARCH
|
|
16
|
+
ARG VALVUR_VERSION=0.0.0-dev
|
|
17
|
+
LABEL org.opencontainers.image.source="https://github.com/MaverickHQ/valvur"
|
|
18
|
+
LABEL org.opencontainers.image.description="Fully offline security scanner for AI-generated code"
|
|
19
|
+
LABEL org.opencontainers.image.licenses="MIT"
|
|
20
|
+
# Read by the shim to refuse an incompatible pair (F1.9). ADR-0001 accepted two
|
|
21
|
+
# artifacts on condition this check existed.
|
|
22
|
+
LABEL org.opencontainers.image.version="${VALVUR_VERSION}"
|
|
23
|
+
|
|
24
|
+
COPY --from=gitleaks /usr/bin/gitleaks /usr/local/bin/gitleaks
|
|
25
|
+
COPY --from=trivy /usr/local/bin/trivy /usr/local/bin/trivy
|
|
26
|
+
COPY --from=osv /osv-scanner /usr/local/bin/osv-scanner
|
|
27
|
+
COPY --from=syft /syft /usr/local/bin/syft
|
|
28
|
+
|
|
29
|
+
# Opengrep publishes signed static musllinux binaries but no image. LGPL-2.1, and
|
|
30
|
+
# the consortium fork of Semgrep - see ADR-0004 for why not Semgrep itself.
|
|
31
|
+
ADD --chmod=755 https://github.com/opengrep/opengrep/releases/download/v1.29.0/opengrep_musllinux_x86 /tmp/opengrep_amd64
|
|
32
|
+
ADD --chmod=755 https://github.com/opengrep/opengrep/releases/download/v1.29.0/opengrep_musllinux_aarch64 /tmp/opengrep_arm64
|
|
33
|
+
RUN mv /tmp/opengrep_${TARGETARCH} /usr/local/bin/opengrep && rm -f /tmp/opengrep_*
|
|
34
|
+
|
|
35
|
+
# Our own rules, licensed with the project. Bundling the community registry would
|
|
36
|
+
# reintroduce exactly the licensing problem ADR-0004 exists to avoid.
|
|
37
|
+
COPY rules /opt/valvur-rules
|
|
38
|
+
|
|
39
|
+
# valvur's own Checks run in the container, like Scanners (ADR-0013), so the package
|
|
40
|
+
# ships in the image. Last layer: check code changes rebuild only this.
|
|
41
|
+
COPY src/valvur /usr/local/lib/python3.12/site-packages/valvur
|
|
42
|
+
|
|
43
|
+
# Checkov is Python. Installed into the system environment; no compiler is kept.
|
|
44
|
+
RUN apk add --no-cache --virtual .build gcc musl-dev libffi-dev \
|
|
45
|
+
&& pip install --no-cache-dir checkov==3.2.517 \
|
|
46
|
+
&& apk del .build \
|
|
47
|
+
&& find /usr/local -name '__pycache__' -type d -prune -exec rm -rf {} + 2>/dev/null || true
|
|
48
|
+
|
|
49
|
+
# Checkov ships an update checker that calls out at startup and writes a cache.
|
|
50
|
+
# Both are disabled explicitly: a scanner that phones home would break the central
|
|
51
|
+
# claim in ADR-0010, and our read-only rootfs caught it only by accident.
|
|
52
|
+
ENV TRIVY_CACHE_DIR=/cache/trivy \
|
|
53
|
+
PYTHONDONTWRITEBYTECODE=1 \
|
|
54
|
+
CHECKOV_DISABLE_UPDATE_CHECK=true \
|
|
55
|
+
HOME=/tmp
|
|
56
|
+
|
|
57
|
+
RUN adduser -D -u 10001 valvur
|
|
58
|
+
USER 10001:10001
|
|
59
|
+
WORKDIR /workspace
|
|
60
|
+
# No ENTRYPOINT: the shim names a specific scanner binary per adapter.
|