From a632a7df7d31b5d4f34e65df8149662fa38d42a5 Mon Sep 17 00:00:00 2001 From: Adam Moussa Date: Tue, 23 Jun 2026 17:28:22 -0400 Subject: [PATCH] fix(agent-team): scrub exception text from /api/state + /api/topology errors /sh-security-review confirmed SEC-DASH-001 (low): build_snapshot embedded str(exc) of a sqlite/OS error into the /api/state payload, leaking the absolute DB path / table names to the unauthenticated LAN surface. Return only type(exc).__name__ (matching the task_detail hardening); apply the same to /api/topology's error branch (SEC-DASH-003). Full exception detail stays in server-side logs. --- agent-team/agent_team/dashboard.py | 9 ++++++++- agent-team/agent_team/status_page.py | 10 +++++++--- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/agent-team/agent_team/dashboard.py b/agent-team/agent_team/dashboard.py index d5a8d86..69f5711 100644 --- a/agent-team/agent_team/dashboard.py +++ b/agent-team/agent_team/dashboard.py @@ -258,7 +258,14 @@ def make_dashboard_app(db_path: str | Path | None = None) -> Any: try: payload = build_topology() except Exception as exc: # pragma: no cover - defensive - payload = {"trees": [], "nodes": [], "edges": [], "error": str(exc)} + # Type only, never str(exc) — keep internal LangGraph/import detail + # off the unauthenticated LAN surface (SEC-DASH-003). + payload = { + "trees": [], + "nodes": [], + "edges": [], + "error": type(exc).__name__, + } return JSONResponse(payload, headers={"Cache-Control": "no-store"}) @app.get("/api/task/{thread_id}") diff --git a/agent-team/agent_team/status_page.py b/agent-team/agent_team/status_page.py index 1d57d78..e5b8a67 100644 --- a/agent-team/agent_team/status_page.py +++ b/agent-team/agent_team/status_page.py @@ -298,19 +298,23 @@ def build_snapshot(db_path: Path | str | None = None) -> Snapshot: tasks = _read_tasks(resolved, thread_ids, open_by_thread) spend, spend_total, budget_available = _read_recent_spend(conn) except sqlite3.OperationalError as exc: - # Locked / busy / unreadable — never crash, just say so. + # Locked / busy / unreadable — never crash, just say so. Return only the + # exception TYPE, never str(exc): a SQLite message can carry the DB path / + # table names, and this payload is served unauthenticated on the LAN + # (matches the task_detail hardening). [sh-security-review SEC-DASH-001] return Snapshot( generated_at=generated_at, db_path=str(resolved), ok=False, - error=f"could not read ledger (busy or unreadable): {exc}", + error=f"could not read ledger (busy or unreadable): {type(exc).__name__}", ) except Exception as exc: # defensive: any unexpected read failure + # Type only, never str(exc) — see SEC-DASH-001 note above. return Snapshot( generated_at=generated_at, db_path=str(resolved), ok=False, - error=f"unexpected error reading ledger: {exc}", + error=f"unexpected error reading ledger: {type(exc).__name__}", ) finally: if conn is not None: