[Phase 1/scan] Auth & security exposure hardening (0.19.0)#315
Merged
rennf93 merged 134 commits intoJul 7, 2026
Conversation
A DB-durable PM-respawn counter (migration 051 / e2f7097) wedges forever once tripped: the only reset was a task status change, which can't happen while the breaker blocks the spawn. So a deploy that fixes the underlying loop (auth/prompt/schema) couldn't clear the wedge without manual DELETE surgery on respawn_tracker — the 2026-07-06 pr-reviewer-1 loop, where the auth fix cleared the 401 but count=63 survived restart and kept skipping the dispatcher spawn for an external-PR task. Freeze last_check at the trip tick and, after pm_respawn_trip_cooldown_seconds (default 300), let ONE spawn through. A still-wedged task re-trips after the threshold (bounded re-burn ~3 spawns per window); a fixed one advances and the status-change path fully resets. Restore re-stamps last_check to now, so a freshly restored row still trips immediately — durability preserved, which is why the migration-051 persistence tests still pass.
… /api/secretary/* 401 (L31-class)
…wn fail-open test nginx is the single entry point; request.client.host is the nginx peer IP, collapsing every external client into one limiter bucket (self-DoS amp). Read the downstream client IP from X-Forwarded-For (first hop) / X-Real-IP, falling back to the peer. Adds coverage for the XFF keying, the redis-down fail-open branch, and drops a redundant asyncio marker on a sync-TestClient test.
…n get_current_agent_slug
login_max_attempts was the only bare cloud-auth field; add a Field
description matching the surrounding idiom. Replace cast('str', ctx.slug)
with a runtime assert that fails loud if the cloud-auth ctx invariant
breaks, and drop the now-unused cast import.
…shutdown mock team (0dfd45c followup)
|
Thanks for opening your first pull request on RoboCo! Quick checklist before review (most of these are enforced by CI, but worth a glance):
See CONTRIBUTING.md for the full workflow and the Code of Conduct for the community standards we follow. Welcome aboard — a maintainer will review shortly. |
…/usage/rate-limits)
…ep so live cost reflects Anthropic cache spend
…ud 429 parks instead of crash-respawning
…ens roll into usage/cost summaries
…r's revisit counter survives a restart
…rates after 2026-08-31
…ilently falling back to a zero-usage env id
…ken-sweep test signatures)
…ck stays reachable
… tokens are bounded
…ry so a stolen cookie's exp is fixed
…t revokes the current jti
…w + jti revocation
[phase9] Auth hardening: bound agent tokens (iat/exp) + JWT jti revocation
[scan-fix] Phase 8: LLM providers / usage / billing
[Phase 7] schemas/conventions/MegaTask/API fixes (15 findings, scan-fix program)
Phase 6 — Panel frontend hardening (rides 0.19.0)
Phase 5 — Background-engine hardening (rides 0.19.0)
Phase 4 — Git / workflow hardening (rides 0.19.0)
Phase 3 — Data-integrity fixes (rides 0.19.0)
Phase 2 — State-machine & gateway integrity (rides 0.19.0)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Phase 1 — Auth & security exposure hardening (rides 0.19.0)
First phase of the 2026-07-06 full-codebase-scan fix plan. Six verified-real auth/security fixes plus two related fixes, all under the existing
## [0.19.0]CHANGELOG section. Dev mode (cloud auth off) is byte-for-byte unchanged — every exposure hole here is cloud-auth-on only.Fixes
9522cc01, on the base) — agent HMAC token was signed over the slug but MCP servers sendX-Agent-ID=UUID→ "signature mismatch" 401 loop. Sign over UUID + set containerROBOCO_AGENT_ID=UUID+ startup heal resolves slug→UUID before verify.dd381a67) — routeget_current_agent_slug/get_current_agent_id/get_optional_agent_idthrough the cloud-auth dual-path gate. Closes the A2A/notification/stream header-spoof hole.1a16a633) — omit UNSIGNED sentinel inmcp/utils.py6th header builder (dev-mode RAG/docs/search 401).fc7fb958) — startup guard:cloud_auth_enabled and panel_agent_token_set→ fail loud (nginx CEO-token injection footgun).74be4428) — panelproxy.tsauth-probe last-known-good cache (fail-open leaked dashboard chrome during a backend hiccup).49021be8+504b46bc) — per-IP rate limit onPOST /auth/login, keyed offX-Forwarded-Forfirst hop (nginx is the entry point); fail-open on redis-down.0dfd45ca+a3733076) — secretary token was signed over team""but the driver sendsX-Agent-Team=board→/api/secretary/*401. Sign overget_agent_team(SECRETARY_AGENT_ID).504b46bc+3e403a6d) — whole-branch opus review found the login limiter collapsed to a global throttle behind nginx (self-DoS amplifier) and the redis-down fail-open path was untested; both fixed + tested.Scope note (acknowledged scope creep)
7b28aecd(tripped respawn-breaker self-heals after a cooldown) is NOT a scan fix — it's a dev-loop wedge fix that landed on this branch. It's well-tested and root-cause (thepr_reviewer-1count=63 wedge was a downstream symptom of the auth 401 loop the slug→UUID fix addresses), so it rides here. Flagging so it's a conscious sign-off, not an accidental merge.Verification
uv run ruff format+ruff check— clean.uv run mypy roboco/— clean (357 files).uv run pytest tests/unit/— 4847 passed.uv run pytest tests/integration/(in isolation) — 206 passed.make e2e-smoke(with local DB) — 9 passed.pnpm typecheck/lint/test— exit 0.504b46bc).Known pre-existing issue (not introduced here)
Running
pytest tests/unit/ tests/integration/in ONE invocation produces ~11 integration failures from DB-state contamination (the suites share the local :55432 DB). Each suite passes cleanly in isolation. This is a pre-existing test-isolation gap, not a Phase-1 regression — flagged for a separate cleanup.Deploy note
The secretary token-team fix is NOT yet live — a redeploy (rebuild orchestrator image + restart) is needed for
/api/secretary/*401 to clear.Model note
Implementers ran on glm-5.2 (the session model); the whole-branch review ran on opus. kimi-k2.7-code:cloud is not routable in this harness.
🤖 Generated with Claude Code
Phase 1b — Auth gate coverage under cloud_auth (appended to this PR)
CEO flagged the auth scan was "missing a couple." Verified root cause: several auth guards check
_auth_required()(ROBOCO_AGENT_AUTH_REQUIRED, default false) instead ofsettings.cloud_auth_enabled(default true indocker-compose.yml). Under the default compose deploy (cloud_auth on, agent_auth_required off) these guards header-trust any forgedX-Agent-Rolewith no token. Six fixes, all dev-mode (cloud_auth off) byte-for-byte unchanged:ed6d3483) —_check_agent_auth_token(the shared chokepoint) now requires a verifiable HMAC token whencloud_auth_enabledOR_auth_required(). Closes the v1/api/v1/flow/*+/api/v1/do/*role-guard header-spoof hole (every flow/do endpoint). Agents always carry a token, so they're unaffected; only forged no-token headers are rejected.451a38bf) —_require_ceo(orchestrator control routes/api/orchestrator/*) async dual path: valid CEO HMAC token OR (under cloud_auth) a valid CEO session cookie. The panel is cookie-only (client.tssendswithCredentials+X-Agent-ID/Rolebut NOX-Agent-Token), so the panel reaches/api/orchestrator/*via the cookie. Mirrors the WS gate (websocket.py:_require_panel_token).68275e65) — HTTPrequire_panel_token(the live-route gate for/api/prompter/live/*+/api/secretary/live/*) async dual path: token OR session cookie. This was the gap the original audit missed — HTTPrequire_panel_tokenwas token-only and never consultedcloud_auth_enabled, so the prompter/secretary live routes were ungated under the default cloud-auth deploy (the WS gate was already cloud-auth-aware; HTTP was not).4d57702a) —/api/settings(GET settings, GET feature-flags, PUT) gated behindrequire_panel_token. Closes unauthenticated settings read+write.caec8b6f) — unauthenticated read routes gated:/api/agents,/api/kanban,/api/usage,/api/system/rate-limitsbehindrequire_panel_token;/api/a2a/tasks[/{id}]behindget_agent_context(agents still reach it via their HMAC token; the CEO cookie works too via the dual path)./.well-known/agent.json+/a2a/agents[/{id}/card]left ungated by design (agent discovery surface).3d280369) — hoisted 19 deferred test imports to top-level (cleared PLC0415 surfaced after a correct ruff).a103d2ba) — e2e smoketests/e2e_smoke/test_auth_gate_coverage.py(4 cross-layer cases: forged ceo header → 401 on/api/orchestrator/status; forged developer → 401 on/api/v1/flow/give-me-work; no-cred → 401 on/api/settings; real CEO session cookie → 200) + CHANGELOG append under## [0.19.0]### Security. The e2e harness (tests/e2e_smoke/harness.py) gained twoinclude_routercalls mounting the orchestrator + settings routers on the e2e app (matchingapp.pyprefixes) — additive, inert for the existing 9 e2e tests (full suite 13/13 green).Verification
uv run ruff format+ruff check— clean.uv run mypy roboco/— clean (357 files).uv run pytest tests/unit/api/— 124 passed (the Phase 1b auth-gate suite); 517 unit tests pass overall.make e2e-smoke(with local DB) — 13 passed (9 existing + 4 new auth-gate coverage)./tasksdev-modeCurrentAgentContextdep is an intentional additive gate (agent-only route, agents always sendX-Agent-ID); (2) the new dual-path cookie branches don't call_slide_session_cookie— mirrors the WS gate, and the CEO inevitably hitsget_agent_contextroutes that do slide; (3) the cookie path is user-not-role-scoped — safe today because registration is disabled +seed.pyupserts exactly one CEO row (same property as the WS gate), flagged for a future multi-user deployment. All 7 commits signed, no co-author lines, AGPL-3.0 only.Model note
Implementer ran on sonnet; whole-branch review on opus. kimi-k2.7-code:cloud is not routable in this harness.