Three robustness/hardening fixes surfaced by /sh-security-review on the agent-team listener/coordinator surface. None alter AUTHZ-01 allowlist behavior or the first-answer-wins compare-and-set semantics. 1. Respawn close() leak (CWE-772). The watchdog _supervise_slack_listener respawned the inbound Slack listener without tearing down the dead one, leaking a Socket Mode WebSocket / SDK thread set per flap. Now the dead listener is closed before respawn (new _close_dead_listener, idempotent), AND _run_listener has a finally that always closes the listener so a crashed serve() releases its socket. SlackListener.close() is idempotent, so the belt-and-braces close stays a safe no-op. 2. Broadened exception guard in handle_event (CWE-248). The submit block only caught ValueError; the accept path (submit_answer -> _question_turn/_question_thread) can raise KeyError on a concurrently mutated row, and the CAS can raise sqlite3.Error. An uncaught exception would escape into the Bolt dispatch. Added a separate `except Exception` that logs at WARNING (not silent, not debug) and returns None. The existing ValueError-as-debug behavior is unchanged; authorization still runs first, so the trust boundary is not widened. 3. channel_ref partial-unique index (defense-in-depth). Added uq_pending_questions_open_channel_ref — a PARTIAL UNIQUE index on (channel_ref) WHERE channel_ref IS NOT NULL AND status='open' — so two OPEN rows can never share a non-null channel_ref (a thread_ts can never map to two open questions). Installed in init_db AND unconditionally in migrate (idempotent IF NOT EXISTS) so existing v1 DBs gain it. NULLs and closed rows are excluded; mirrored verbatim into schema.sql. Tests: +8 (was 960, now 968). New: schema partial-unique reject/null/closed/ migrate cases; handle_event KeyError + sqlite3.Error swallow cases; coordinator close-before-respawn + run_listener-closes-on-crash. Fixed the operator-cli test fixture to use a per-question channel_ref (it previously inserted multiple open rows sharing one ref, which the new index correctly rejects). |
||
|---|---|---|
| .. | ||
| sim | ||
| __init__.py | ||
| conftest.py | ||
| test_apply_verify_workflow_hardening.py | ||
| test_billing.py | ||
| test_build_verify_subgraph.py | ||
| test_builders.py | ||
| test_builders_llm.py | ||
| test_checker_intake.py | ||
| test_ci_fetcher.py | ||
| test_ci_gate.py | ||
| test_ci_gate_workflow.py | ||
| test_clarifier.py | ||
| test_clarifier_llm.py | ||
| test_claude_code_adapter.py | ||
| test_claude_code_live.py | ||
| test_coordinator.py | ||
| test_deadline_timer.py | ||
| test_fixer.py | ||
| test_github_adapter.py | ||
| test_github_intake.py | ||
| test_github_live.py | ||
| test_graph.py | ||
| test_invoker.py | ||
| test_ledger.py | ||
| test_operator_cli.py | ||
| test_planner.py | ||
| test_recovery.py | ||
| test_responder.py | ||
| test_resume_worker.py | ||
| test_review_loop.py | ||
| test_review_loop_llm.py | ||
| test_run_team.py | ||
| test_schema.py | ||
| test_slack_adapter.py | ||
| test_slack_listener.py | ||
| test_slack_live.py | ||
| test_state_store.py | ||
| test_task_model.py | ||
| test_transport_base.py | ||
| test_verifier.py | ||
| test_verifier_llm.py | ||