Repository navigation
feat(dashboard): local web Hunt Dashboard (/dashboard) - #143
Conversation
33d8412 to
82805b5
Compare
A hunt's state is scattered across memory/leads/<target>.jsonl, recon/, findings/, reports/, screenshot gallery.html files, and hunt-memory/*.jsonl, so stale high-priority leads rot (Critical Rule 6) and the memory flywheel stays invisible (TODO-6). This adds one live view over all of it. - bughunter/tools/hunt_dashboard.py — pure-stdlib server (`serve`) + self- contained snapshot (`export`). collect_state()/render_dashboard() are pure given a root dir, so they're unit-tested without a running server. - Shows stat tiles, the lead board (untouched-first, stale HIGH alert), recon surface, findings/reports, screenshot galleries, memory flywheel. - Security: binds 127.0.0.1 only; every recon-derived value is HTML-escaped (recon data is attacker-controlled); /file endpoint is path-traversal guarded and extension-allowlisted. - Distinct from bughunter/tools/dashboard.py (the in-terminal ANSI progress TUI) — named hunt_dashboard.py to avoid collision. - 16 tests in tests/test_hunt_dashboard.py; /dashboard command; CLAUDE.md command table + tools list updated. Rebased onto the v6.x package layout: tool lives under bughunter/tools/, data root resolves to the package dir like lead_board.py and poc_bundler.py. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
shuvonsec
left a comment
There was a problem hiding this comment.
Nice work - clean zero-dep stdlib dashboard, and safe_path (realpath + commonpath + extension allowlist) is solid. Verified 16 tests pass. Two security asks before merge, one minor:
- (security)
/fileserves allow-listed.htmlraw astext/html. Any.htmlunder root (a recon/PoC-captured HTTP response saved as.html) then runs as live JS in the dashboard's own origin, which also exposes/api/state+/filesame-origin with no auth = data-exfil XSS. Please serve untrusted.htmlastext/plain(or escape it), not passthrough. - (security)
serveaccepts--host 0.0.0.0and does no Host-header check, so the unauthenticated server can be exposed / hit via DNS-rebinding from a malicious page. Add a loopback Host-header allowlist and warn loudly when--hostis non-loopback. - (minor)
collect_recon(root)runs 3x per page load (once for state, twice in the totals set-expr). Compute once and reuse.
|
Thanks for the sharp review, @shuvonsec — both security points are spot on. Fixed in 1. 2. DNS-rebinding via 3. 21 tests pass. Ready for another look |
…on once Resolves the change requests from @shuvonsec on the /dashboard PR. 1. (security) /file no longer serves .html as text/html. A recon/PoC-captured HTTP response saved as .html would otherwise run as live JS in the dashboard's own origin and exfil /api/state. It's now served as text/plain with X-Content-Type-Options: nosniff, so captured HTML is inert. Applies to all .html under root — no passthrough. 2. (security) serve now enforces a Host-header allow-list (loopback + the bind host + any --allow-host), returning 403 for anything else. This blocks DNS-rebinding: a malicious page pointing its domain at 127.0.0.1 still sends a foreign Host and is rejected. Binding to a non-loopback --host now prints a loud warning that the unauthenticated dashboard is being network-exposed. 3. (perf) collect_recon(root) is computed once in collect_state and reused for both the 'recon' field and the totals, instead of running per page load. Tests: +5 in tests/test_hunt_dashboard.py — host_allowed / build_allowed_hosts, a live server test asserting a hostile <script> .html comes back as text/plain + nosniff (never parsed), and a foreign Host → 403. 21 tests pass. Docs updated. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
What & why
A hunt's state is scattered across
memory/leads/<target>.jsonl,recon/,findings/,reports/, screenshotgallery.htmlfiles, andhunt-memory/*.jsonl. Nothing shows it all at once, so stale high-priority leads rot (Critical Rule 6) and the memory flywheel stays invisible (TODO-6). This adds one live web view over all of it.What it adds
tools/hunt_dashboard.py— pure-stdlib (zero new deps, same tech asdemo/app.py):serve— live local server (/,/api/stateJSON, safe/fileviewer), auto-refresh.export— a single self-contained HTML snapshot (shareable as report evidence).collect_state()/render_dashboard()are pure given a root dir, so they're unit-tested without a running server./dashboardslash command +CLAUDE.mdcommand-table and tools-list entries.Security
127.0.0.1only./fileis path-traversal guarded (resolves under repo root only) and extension-allowlisted.Tests
tests/test_hunt_dashboard.py— collection, rendering, XSS-escaping, live/static modes, traversal guard, CLI export. All green.4 files changed, 824 insertions(+), nothing deleted; the existing TUI and its tests are byte-untouched.Try it