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.
Files changed (55) hide show
  1. valvur-0.1.0rc1/.gitattributes +12 -0
  2. valvur-0.1.0rc1/.gitignore +28 -0
  3. valvur-0.1.0rc1/CLAUDE.md +225 -0
  4. valvur-0.1.0rc1/CONTEXT.md +96 -0
  5. valvur-0.1.0rc1/Dockerfile +60 -0
  6. valvur-0.1.0rc1/PKG-INFO +276 -0
  7. valvur-0.1.0rc1/README.md +262 -0
  8. valvur-0.1.0rc1/pyproject.toml +65 -0
  9. valvur-0.1.0rc1/rules/llm-output-sinks.yaml +64 -0
  10. valvur-0.1.0rc1/rules/pinning-hygiene.yaml +17 -0
  11. valvur-0.1.0rc1/rules/python-security.yaml +54 -0
  12. valvur-0.1.0rc1/src/valvur/__init__.py +4 -0
  13. valvur-0.1.0rc1/src/valvur/adapters/__init__.py +38 -0
  14. valvur-0.1.0rc1/src/valvur/adapters/base.py +41 -0
  15. valvur-0.1.0rc1/src/valvur/adapters/check.py +48 -0
  16. valvur-0.1.0rc1/src/valvur/adapters/checkov.py +45 -0
  17. valvur-0.1.0rc1/src/valvur/adapters/gitleaks.py +41 -0
  18. valvur-0.1.0rc1/src/valvur/adapters/opengrep.py +75 -0
  19. valvur-0.1.0rc1/src/valvur/adapters/osv.py +82 -0
  20. valvur-0.1.0rc1/src/valvur/adapters/syft.py +24 -0
  21. valvur-0.1.0rc1/src/valvur/adapters/trivy.py +137 -0
  22. valvur-0.1.0rc1/src/valvur/api.py +172 -0
  23. valvur-0.1.0rc1/src/valvur/artifacts.py +105 -0
  24. valvur-0.1.0rc1/src/valvur/cache.py +33 -0
  25. valvur-0.1.0rc1/src/valvur/checks/__init__.py +16 -0
  26. valvur-0.1.0rc1/src/valvur/checks/__main__.py +32 -0
  27. valvur-0.1.0rc1/src/valvur/checks/ai_artifact.py +134 -0
  28. valvur-0.1.0rc1/src/valvur/checks/base.py +28 -0
  29. valvur-0.1.0rc1/src/valvur/checks/dependency_reality.py +180 -0
  30. valvur-0.1.0rc1/src/valvur/checks/licence_file.py +111 -0
  31. valvur-0.1.0rc1/src/valvur/cli.py +188 -0
  32. valvur-0.1.0rc1/src/valvur/compat.py +68 -0
  33. valvur-0.1.0rc1/src/valvur/data/kev.json +1 -0
  34. valvur-0.1.0rc1/src/valvur/data/popular-pypi.json +3009 -0
  35. valvur-0.1.0rc1/src/valvur/defang.py +89 -0
  36. valvur-0.1.0rc1/src/valvur/enrichment.py +124 -0
  37. valvur-0.1.0rc1/src/valvur/findings.py +102 -0
  38. valvur-0.1.0rc1/src/valvur/fingerprint.py +64 -0
  39. valvur-0.1.0rc1/src/valvur/licence_policy.py +90 -0
  40. valvur-0.1.0rc1/src/valvur/mcp/__init__.py +8 -0
  41. valvur-0.1.0rc1/src/valvur/mcp/jobs.py +77 -0
  42. valvur-0.1.0rc1/src/valvur/mcp/protocol.py +122 -0
  43. valvur-0.1.0rc1/src/valvur/mcp/server.py +110 -0
  44. valvur-0.1.0rc1/src/valvur/mcp/tools.py +63 -0
  45. valvur-0.1.0rc1/src/valvur/operations.py +229 -0
  46. valvur-0.1.0rc1/src/valvur/profiles.py +44 -0
  47. valvur-0.1.0rc1/src/valvur/provenance.py +22 -0
  48. valvur-0.1.0rc1/src/valvur/ranking.py +91 -0
  49. valvur-0.1.0rc1/src/valvur/rawoutput.py +72 -0
  50. valvur-0.1.0rc1/src/valvur/redact.py +22 -0
  51. valvur-0.1.0rc1/src/valvur/remediation.py +106 -0
  52. valvur-0.1.0rc1/src/valvur/results.py +239 -0
  53. valvur-0.1.0rc1/src/valvur/runner.py +375 -0
  54. valvur-0.1.0rc1/src/valvur/state.py +63 -0
  55. 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.