Follow-up to the traceback gate: client-facing exception detail is now gated too.
- safe_error(e) (app/utils/security.py): returns the exception string only when
FLASK_DEBUG, otherwise a generic message. Full detail stays in server logs.
- All catch-all `except Exception` handlers in api/{graph,report,simulation}.py now
return safe_error(e) instead of str(e); same for the persisted error fields
(project.error / state.error / task fail messages). Typed `except ValueError`
validation handlers (404/400) keep str(e) — those are intentional, actionable
user messages that echo only user-supplied ids.
- graph_builder no longer puts a full traceback into the task error (logs it
server-side with exc_info, surfaces safe_error to the client); the batch-failure
progress message is gated too.
- README documents the CSP connect-src ↔ VITE_API_BASE_URL coupling.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
H5 (traceback leak, completing the prior log-level fix): new app/utils/security.py
safe_traceback() logs the full stack server-side and returns it to clients only when
FLASK_DEBUG; all 53 traceback.format_exc() in api/{graph,report,simulation}.py now call
it (import traceback removed).
Upload content sniff: upload_content_ok() magic-byte check — pdf must start %PDF-,
txt/md/markdown rejected if they contain NUL bytes (BOM-prefixed UTF-16/32/8 text
allowed). Wired into the graph.py upload loop so a renamed binary can't pass the
extension whitelist.
Path validation: validate_id() (^[A-Za-z0-9_-]{1,64}$) blocks traversal before every
id->filesystem sink — ProjectManager._get_project_dir, SimulationManager._get_simulation_dir,
ReportManager._get_report_folder + the two Report*Logger __init__, and a new
SimulationRunner._run_dir() that all RUN_STATE_DIR joins route through.
CSP / security headers: CSP <meta> in index.html, vite preview.headers
(X-Frame-Options/nosniff/Referrer-Policy + CSP frame-ancestors), and a Flask
after_request that sets the same headers on API responses.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
H1 — stored XSS via v-html / innerHTML of unsanitized LLM/agent/interview/report
content: add DOMPurify; both renderMarkdown() now return DOMPurify.sanitize(html)
(Step4Report.vue, Step5Interaction.vue), and the formatAnswer innerHTML sink is
wrapped in DOMPurify.sanitize. All 8 HTML-injection sinks now sanitized; markdown
rendering preserved (DOMPurify secure defaults keep the md-* tags/classes).
H4 — wildcard CORS: CORS origins now Config.ALLOWED_ORIGINS (comma-separated env,
default localhost:3000) instead of '*'.
H5 — request bodies written to disk in cleartext: logger file level now follows
FLASK_DEBUG (INFO in prod), so the before_request body-debug log is suppressed in
production. (traceback-in-response across 53 handlers deferred to a separate refactor.)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Closes the four CRITICAL findings from the 2026-06-13 review.
C1 — Werkzeug debug RCE / dev server in prod:
- FLASK_DEBUG defaults False (kills interactive-debugger network RCE)
- production runs gunicorn (-w 1 --threads 8) via `npm run start`; Dockerfile
builds the frontend and serves it with `vite preview` (host 0.0.0.0); gunicorn
+ uv.lock updated
- Config.validate() now runs inside create_app() so the gunicorn path enforces
SECRET_KEY (prod) / API_KEY / LLM / ZEP at boot
C2 — zero auth on all /api/* routes:
- before_request API-key guard (X-API-Key / Bearer), constant-time bytes compare,
/health + OPTIONS exempt
- AUTH_ENABLED fail-closed parse (only explicit false/0/no/off disables)
- frontend axios injects X-API-Key from build-time VITE_API_KEY, wired through
docker compose build-arg -> Dockerfile ARG -> vite build (+ frontend/.env.example)
C3 — denial-of-wallet (no cost ceiling; OASIS_DEFAULT_MAX_ROUNDS was dead config):
- OASIS_DEFAULT_MAX_ROUNDS now applied when max_rounds omitted (default 150, covers
the 144-round demo); hard ceilings OASIS_MAX_ROUNDS_CAP / OASIS_MAX_AGENTS_CAP;
runner always forwards the clamped rounds to the subprocess
C4 — no simulation deadline; env.step could wedge forever:
- every env.step (initial / round-loop / interview) wrapped in asyncio.wait_for
(OASIS_ROUND_TIMEOUT_SEC) across all 3 run scripts; per-loop total-deadline
(OASIS_RUN_TIMEOUT_SEC); gather(return_exceptions=True) + single-platform
try/except so one platform's failure can't skip env.close
New env vars documented in .env.example + README security section.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Add return type annotation (list[str]) to Config.validate()
- Add type annotations (msg: str, -> None) to logger convenience functions
- Add FileParser.is_supported() classmethod for checking file format support
Background threads (graph building, simulation prep, report generation,
profile generation) now inherit the requesting user's locale preference.
Previously these fell back to 'zh' because Flask request context was
unavailable in spawned threads.
Ensure poster_type stays PascalCase English and stance stays English enum
values regardless of language setting. Only natural language fields follow
the user's language preference.
The language instruction was causing LLM to change entity/relation naming
conventions. Now explicitly enforce PascalCase/UPPER_SNAKE_CASE for technical
identifiers while only applying language preference to description fields.
- Implemented `_get_report_id_for_simulation` to find the most recent report ID associated with a simulation ID by scanning the reports directory.
- Updated `get_simulation_history` to include the retrieved report ID in the response, enhancing the simulation data returned to the client.
- Updated simulation history retrieval to read project details directly from the Simulation file.
- Improved simulation configuration handling by reading simulation requirements from JSON.
- Added project file listing to the simulation history, displaying up to three associated files.
- Refined card layout in HistoryDatabase.vue to accommodate new file display features and improved responsiveness.
- Deleted docker-compose.yml, backend Dockerfile, frontend Dockerfile, and nginx configuration to streamline project setup.
- Updated .env.example to reorganize LLM and ZEP API configurations for clarity and ease of use.
- Enhanced README.md to reflect changes in project structure and provide clearer setup instructions.
- Created package-lock.json for dependency management.
- Updated package.json and frontend package.json to version 0.1.0.
- Adjusted backend pyproject.toml to reflect version 0.1.0.
- Introduced uv.lock for Python dependency resolution.
- Modified the backend setup script to clear the virtual environment before installation.
- Improved README.md by restructuring the prerequisites section into a table for better readability.
- Added installation instructions for the `uv` package and clarified terminal requirements post-installation.