Skip to content

Commit 8e761ff

Browse files
KK Mookheyclaude
authored andcommitted
HTML reports + OSV.dev CVE enrichment (v0.2)
Adds two CLI flags and one new module so Whitney can produce demo-quality artifacts that read better in a screen recording than the existing CLI table: whitney scan ./repo --html report.html whitney sbom ./repo --html sbom.html --enrich Both output a single self-contained HTML file (CSS inlined, no JS framework, no images, no web fonts, embedded raw JSON for jq extraction). The SBOM `--enrich` flag cross-references each SDK component against OSV.dev's public vulnerability API and folds matching CVE / GHSA records into the report. Built-in `VULNERABLE_SDK_VERSIONS` matches are merged additively, deduped by ID. Per the design doc (docs/superpowers/specs/2026-04-26-html-reports-design.md) the explicit non-choice was Transilience: that API belongs in Shasta's compliance pipeline, not in Whitney's standalone OSS scanner. OSV.dev needs no auth, covers PyPI/npm/Go/Maven/etc., and matches the same data Snyk / GitHub Advisory ultimately resolve to. Implementation: - `whitney/html_report.py` — pure renderer, ~580 lines, stdlib-only templating. Two public functions: render_scan_html(findings, scan_root) and render_sbom_html(sbom). All user-content fields pass through html.escape; URL fields through urllib.parse.quote. Light theme by default with prefers-color-scheme dark auto-flip. Severity-coded badges (Critical #dc2626 → Info #0891b2). Findings grouped by severity with sticky headers. SBOM grouped by AI provider (OpenAI / Anthropic / AWS / etc.) with stable colour assignment. Raw input JSON embedded in a <script type="application/json" id="whitney-data"> block, with </ → <\/ escaping to prevent script-tag breakout. - `whitney/sbom.py` — added enrich_with_osv() and scan_ai_sbom_code_only_enriched(). ThreadPoolExecutor max_workers=8; cache at ~/.whitney/osv_cache.json keyed by (eco, name, version, query_date) so re-runs same day are free; MAX_OSV_QUERIES_PER_RUN=200 cap; fail-open on any network or parse error (renders the report with whatever vulnerabilities were resolved). - `whitney/cli.py` — `--html PATH` added to scan and sbom subcommands; `--enrich` added to sbom only. Default behaviour preserved (table output for scan, JSON-to-stdout for sbom) when no new flag is set. Tests (`tests/test_html_report.py`): - 24 tests, no snapshot files. Three classes: TestRenderScanHtml (HTML5 parse + grouping + XSS hardening), TestRenderSbomHtml (parse + provider grouping + vulnerability panel + XSS), TestEnrichOsv (mock urlopen + cache hit + per-run cap + fail-open on URLError). - XSS test specifically: a finding with code_snippet = '<script>alert(1)</script>' produces escaped output and the rendered document contains zero executable script elements. Sample reports committed for showcase: docs/sample-reports/scan.html (7 findings against a small mix of corpus positives) and sbom.html (7 components against a synthetic requirements.txt with intentionally old AI SDKs; OSV.dev surfaces 52 vulnerabilities across them). Version bumped 0.1.0 → 0.2.0 (additive feature). Zero new runtime deps. Verification: - pytest tests/test_html_report.py → 24/24 pass - pytest tests/corpus/ → 17/17 pass (no regression) - python -m tests.corpus.eval → recall=1.000, fp_rate=0.200 (unchanged default-mode baseline) - whitney scan + sbom --html against synthetic demo dir → 34KB scan, 96KB sbom, both render correctly in Safari/Firefox/Chrome. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent ecc1416 commit 8e761ff

10 files changed

Lines changed: 4201 additions & 12 deletions

File tree

README.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ Whitney is a curated [Semgrep](https://semgrep.dev) ruleset plus a thin Python w
66

77
```bash
88
pip install whitney
9-
whitney scan ./your-repo
9+
whitney scan ./your-repo # CLI table output
10+
whitney scan ./your-repo --html report.html # standalone HTML report
11+
whitney sbom ./your-repo --html sbom.html --enrich # HTML SBOM + OSV.dev CVE enrichment
1012
```
1113

1214
```
@@ -17,6 +19,10 @@ high app/api.py :17 Flas
1719
high app/rag.py :55 WebBaseLoader fetched content flows into LLM chain unfiltered
1820
```
1921

22+
For demos and sharing: `--html` produces a single self-contained `.html` file (CSS inlined, no JS framework, no images, no web fonts — typically <100KB) that you can open in any browser, attach to email, or paste into Slack. For SBOM specifically, `--enrich` cross-references each SDK component against [OSV.dev](https://osv.dev) (Google's free public vulnerability database) and folds matching CVE / GHSA records into the report. Results are cached daily under `~/.whitney/osv_cache.json` so re-runs cost nothing.
23+
24+
**Sample reports:** see [`docs/sample-reports/scan.html`](docs/sample-reports/scan.html) and [`docs/sample-reports/sbom.html`](docs/sample-reports/sbom.html) for what the reports look like — generated by running Whitney against a small mix of corpus positives + a synthetic `requirements.txt` with intentionally-old AI SDK versions, so the SBOM lights up with real OSV.dev CVE matches.
25+
2026
## What it finds
2127

2228
Prompt injection across **16 source types**:

docs/SCANNER.md

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -86,20 +86,31 @@ On 5 blind-test repositories (`aimaster-dev/chatbot-using-rag-and-langchain`, `L
8686
```bash
8787
pip install semgrep
8888

89-
# Zero-LLM scan — default, reproducible, no API key required
90-
py -3.12 -c "from whitney.code.scanner import scan_repository; print(scan_repository('.'))"
89+
# Default CLI table
90+
whitney scan ./my-repo
91+
92+
# Standalone HTML report (single self-contained file, demo-ready)
93+
whitney scan ./my-repo --html report.html
94+
95+
# JSON for tooling integration
96+
whitney scan ./my-repo --json > findings.json
97+
98+
# AI dependency SBOM with OSV.dev CVE enrichment
99+
whitney sbom ./my-repo --html sbom.html --enrich
91100

92101
# Triage mode — Claude Opus classifies LLM-as-judge prompts
93102
export ANTHROPIC_API_KEY=sk-ant-...
94103
export WHITNEY_STRICT_JUDGE_PROMPTS=1
95-
py -3.12 -c "from whitney.code.scanner import scan_repository; print(scan_repository('.'))"
104+
whitney scan ./my-repo --html report.html
96105

97-
# CI / offline — mock heuristic mode
106+
# CI / offline — mock heuristic mode (no API calls)
98107
export WHITNEY_STRICT_JUDGE_PROMPTS=1
99108
export WHITNEY_TRIAGE_MOCK=1
100-
py -3.12 -m tests.test_whitney.corpus.eval
109+
py -3.12 -m tests.corpus.eval
101110
```
102111

112+
The `--html` flag (added in v0.2) writes a self-contained HTML file with CSS inlined and the raw findings JSON embedded in a `<script type="application/json">` block — power users can extract via `cat report.html | sed -n '/whitney-data/,/<\/script>/p' | head -n -1 | tail -n +2 | jq`. The `--enrich` flag on `sbom` cross-references each SDK against [OSV.dev](https://osv.dev) (Google's free public vulnerability database, no auth required); results cached daily under `~/.whitney/osv_cache.json`.
113+
103114
Path excludes are applied automatically: `venv`, `.venv`, `env`, `__pycache__`, `node_modules`, `tests`, `test_*`, `fixtures`, `examples`, `docs`, `dist`, `build`, `site-packages`. A finding inside a test fixture is a false positive from the developer's perspective, regardless of its technical correctness.
104115

105116
## Known limitations

0 commit comments

Comments
 (0)