Skip to content

[phase9] Auth hardening: bound agent tokens (iat/exp) + JWT jti revocation#323

Merged
rennf93 merged 5 commits into
fix/scan-llm-providersfrom
fix/scan-auth-hardening
Jul 7, 2026
Merged

[phase9] Auth hardening: bound agent tokens (iat/exp) + JWT jti revocation#323
rennf93 merged 5 commits into
fix/scan-llm-providersfrom
fix/scan-auth-hardening

Conversation

@rennf93

@rennf93 rennf93 commented Jul 7, 2026

Copy link
Copy Markdown
Owner

Phase 9 — Auth hardening (structural)

Final phase of the 2026-07-06 full-codebase-scan fix program. Two findings, 5 commits, stacked on Phase 8 (fix/scan-llm-providers, PR #322). Merge bottom-up after #322.

M35 — Bound agent HMAC tokens with iat/exp (Tasks 1-2)

The agent token was a static HMAC(secret, "id:role:team") hex with no expiry — a leaked token was valid forever. issue_agent_token now mints a {base64url(payload)}.{sig} token carrying iat/exp (TTL agent_token_ttl_seconds, default 7d) when a TTL is passed, and the orchestrator passes one at spawn. Backward-compatible: verify_agent_token detects the format by the . separator; a caller passing no TTL still gets the static hex token, which still verifies byte-for-byte (issue_panel_token is unchanged). Expired tokens are rejected; the now param is the test seam (no global time monkeypatch).

M36 — Cloud-auth JWT revocation + sliding re-mint (Tasks 3-4, default-off ROBOCO_CLOUD_AUTH_ENABLED)

  • M36a: JWTs now carry a jti; _slide_session_cookie re-mints only when the current token is within cloud_auth_remint_threshold_seconds (default 1d) of expiry, so a stolen cookie's exp stays fixed instead of rolling with the legit user.
  • M36b: FastAPI Users' built-in /logout only cleared the cookie — a stolen copy stayed valid for cloud_auth_cookie_max_age. A custom /logout now adds the cookie's jti to a Redis revocation set (TTL = remaining life), and read_token rejects any token whose jti is in the set. Fail-OPEN on Redis unavailable — the pwd_fp password-rotation check remains the strong user-wide revocation; jti is the per-session logout kill and a Redis hiccup never locks out the CEO. The Redis check is short-circuited behind the pwd_fp check in read_token (a rotated-password token never touches Redis).

Task 5 — CHANGELOG

3 bullets appended to 0.19.0 ### Fixed.

Gate

  • ruff format . (1082 unchanged) + ruff check . clean
  • mypy roboco/ — no issues, 359 source files
  • pytest (DB :55432) — 12042 passed / 418 skipped / 95% (+17 over Phase 8: Tasks 1-4 new tests)
  • Opus whole-branch review (merge-base feccd41d): SHIP, no fix wave. Fail-open and backward-compat invariants verified adversarially; now test seam exercised for expired-token rejection; grok.py correctly untouched (delegates to the orchestrator injector).

Minor findings (list only, no fix required pre-merge)

  1. /auth/logout decodes the cookie without signature verification (plan-mandated verify_signature: False) — defense-in-depth: an unauthenticated caller can revoke a forged jti, but jtis are 128-bit uuid4 (unguessable) and impact is logout-DoS only.
  2. revoke_jti write-error fail-open branch uncovered by tests.
  3. _should_remint decode-failure → True safe-default branch uncovered.
  4. Operational note: with the default 7d TTL, an agent running >7d without respawn has an expired token (401s between restarts); _heal_stale_agent_tokens kills it at the next restart — the documented M35 tradeoff.

Implementation note

The brief's verbatim test monkeypatch target revocation.redis.asyncio was wrong — revocation does import redis.asyncio as redis, so revocation.redis IS the redis.asyncio module and .asyncio resolved to stdlib asyncio (AttributeError: module 'redis.asyncio' has no attribute 'asyncio'). Corrected to revocation.redis. The Task 4 sonnet implementer 429-failed on the glm-5.2:cloud ollama.com session cap; the controller completed the wiring inline.

🤖 Generated with Claude Code

@github-project-automation github-project-automation Bot moved this to Backlog in RoboCo Kanban Jul 7, 2026
@github-actions github-actions Bot added documentation Docs, README, CHANGELOG, governance files area: api Touches roboco/api/ (FastAPI routes, schemas, app) tests Test suite changes area: orchestrator Touches roboco/runtime/ (agent spawner, dispatch loops) labels Jul 7, 2026
@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown

Thanks for opening your first pull request on RoboCo!

Quick checklist before review (most of these are enforced by CI, but worth a glance):

  • make quality — ruff format check, ruff check, mypy, pytest (≥80% coverage), and the rest of the gate
  • Panel changes pass pnpm lint and pnpm exec tsc --noEmit (run from panel/)
  • No # noqa / # type: ignore shortcuts; pre-existing violations in touched files are fixed
  • Added an entry under ## [Unreleased] in CHANGELOG.md
  • Signed the CLA (the bot will prompt you on this PR)
  • Signed your commits — master requires verified signatures (SSH signing setup)
  • Updated any affected docs under docs/

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.

@rennf93 rennf93 self-assigned this Jul 7, 2026
@rennf93 rennf93 moved this from Backlog to In review in RoboCo Kanban Jul 7, 2026
@rennf93 rennf93 merged commit e628897 into fix/scan-llm-providers Jul 7, 2026
4 checks passed
@github-project-automation github-project-automation Bot moved this from In review to Done in RoboCo Kanban Jul 7, 2026
@github-actions github-actions Bot locked and limited conversation to collaborators Jul 7, 2026
@rennf93 rennf93 deleted the fix/scan-auth-hardening branch July 7, 2026 08:10
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area: api Touches roboco/api/ (FastAPI routes, schemas, app) area: orchestrator Touches roboco/runtime/ (agent spawner, dispatch loops) documentation Docs, README, CHANGELOG, governance files tests Test suite changes

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

1 participant