# Security audit — Agnes AI Data Analyst **Date:** 2026-04-22 **Branch audited:** `main` at commit `cbb7733` **Method:** parallel review passes over four scope areas — (1) secrets/SQLi/authz/SSRF, (2) auth flows & route wiring, (3) templates & UI wiring/XSS, (4) data layer & config & dead code. Findings deduped across the passes, severities adjusted to real-world exploitability. Known issues already in flight are marked with their tracking links so we do not re-open them. --- ## Top 5 — fix first ### 1. `[CRITICAL]` Script-API sandbox escape → RCE for the `analyst` role - **File:** `app/api/scripts.py:116–180` - **Required role:** `Role.ANALYST` (not admin) - **Trigger:** `POST /api/scripts/run` with body: ```python ().__class__.__base__.__subclasses__()[N].__init__.__globals__["system"]("id") ``` - **Why existing guards miss it:** the AST walker and the string allowlist block direct `import os` / `exec`, but neither stops attribute traversal through Python's class hierarchy (`__class__ → __base__ → __subclasses__() → __globals__`). - **Impact:** arbitrary OS commands under the FastAPI process uid. Gives access to `DATA_DIR` (DuckDB files, cached parquet), `.jwt_secret`, env vars, and any credentials mounted into the container. - **Fix (minimum):** add to the string-pattern blocklist: `__subclasses__`, `__globals__`, `__mro__`, `__bases__`, `__class__`, `__dict__`, `__code__`. In the AST walker, reject any `ast.Attribute` whose `attr` starts and ends with `__`. - **Fix (correct):** do not run untrusted Python in-process. Either drop server-side script execution entirely, or run the sandbox in `nsjail`/gVisor/Pyodide in isolation, or gate the endpoint behind `Role.ADMIN` if it must stay. - **Confidence:** broken (verified analytically). ### 2. `[HIGH]` `/auth/password/reset` endpoint missing — "Forgot Password?" returns 404 - **Template reference:** `app/web/templates/login_email.html:47` — `