dashboard server: hardening + better default UX#4
Conversation
Three small improvements to tools/su2_dashboard_server.py based on the review of the live setup: 1. Block directory listings + dev-artefact paths Default Python http.server exposes the repo root: hitting http://localhost:8001/ used to print the contents of .git/, .venv/, build/, etc. Override list_directory() to refuse, and add a denylist in send_head() (shared by GET and HEAD) that rejects any path under .git/, .venv/, build/, __pycache__/, dotfile-prefixed components, and .. traversals. The dashboard only legitimately needs paths under tools/ and results/, both still allowed. 2. Redirect / -> /dashboard Pasting a bare host:port URL now lands on the dashboard instead of 404'ing on the disabled listing. 3. Always-on hardening headers X-Content-Type-Options: nosniff, X-Frame-Options: DENY, Referrer-Policy: no-referrer applied to every response. Conservative set; the vanilla-JS dashboard uses no inline eval / no remote scripts so it is unaffected. Useful if the bind ever moves off 127.0.0.1. 4. Startup readability check on --root exists() / is_dir() can both pass on a symlink whose target is gone (e.g. results/ pointing at an unmounted external drive). Try a real os.scandir read so the operator gets an immediate startup failure instead of silent empty data later. No behaviour change for the dashboard's actual API endpoints (/events, /chat, /thread_control, /thread_telemetry, /results/* JSON serving). All authentication / CORS handling unchanged. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: cd45c56118
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| def _path_is_denied(self, path): | ||
| if path.startswith("/"): | ||
| path = path[1:] | ||
| if not path: |
There was a problem hiding this comment.
Decode URL paths before denylist checks
The new denylist is bypassable because _path_is_denied evaluates parsed.path without URL-decoding it first. A request such as /%2egit/config (or encoded .. segments) will pass this check, but SimpleHTTPRequestHandler later decodes/normalizes the path in file resolution, so .git and traversal-like targets can still be reached despite the hardening logic. Normalize and decode the path before checking components/prefixes so encoded dotfiles and dot-segments are rejected consistently.
Useful? React with 👍 / 👎.
Summary
Four small improvements to
tools/su2_dashboard_server.pybased on a review of the live setup:http.serverexposes the repo root: hittinghttp://localhost:8001/previously rendered the contents of.git/,.venv/,build/, etc. New:list_directory()returns 404, and a denylist insend_head()(shared by GET and HEAD) refuses any path under.git/,.venv/,build/,__pycache__/, dotfile-prefixed components, and..traversals. Verified with curl:/.git/configand/build/source.shnow 404;/tools/su2_dashboard.htmlstill 200./to/dashboardso a bare host:port URL lands on the dashboard instead of 404'ing.X-Content-Type-Options: nosniff,X-Frame-Options: DENY,Referrer-Policy: no-referreron every response. Conservative set; the vanilla-JS dashboard uses no inline eval / no remote scripts so unaffected. Useful if the bind ever moves off127.0.0.1.--root.exists()/is_dir()both pass on a symlink whose target is gone (e.g.results/pointing at an unmounted external drive). Now also callsos.scandir(root)so the operator sees an immediate startup failure rather than silent empty data later.No behaviour change for the dashboard's API endpoints (
/events,/chat,/thread_control,/thread_telemetry,/results/*JSON serving). Auth / CORS handling unchanged.Cleanup also included
Grid/eigen-3.4.0.tar.bz2andGrid/eigen-3.4.0.tar.bz2.1were committed accidentally (4MB total).bootstrap.shdownloads them fresh on every run, so removing them from git is uncontroversial. Happy to split into a separate commit / PR if preferred.Test plan
curl -sSI http://127.0.0.1:8001/.git/config→ 404curl -sSI http://127.0.0.1:8001/build/source.sh→ 404curl -sSI http://127.0.0.1:8001/tools/su2_dashboard.html→ 200curl -s http://127.0.0.1:8001/→ 302 to/dashboardX-Content-Type-Options: nosniffetc.--rootpointing at a dangling symlink (T705 mount removed) → fails fast withRoot is not readable./events) still streams — should be unaffected since SSE was never gated by these checks.🤖 Generated with Claude Code