Skip to content

Gate /api/ai/triage on admin or operator role#13

Merged
lai3d merged 1 commit into
mainfrom
claude/triage-rbac
May 19, 2026
Merged

Gate /api/ai/triage on admin or operator role#13
lai3d merged 1 commit into
mainfrom
claude/triage-rbac

Conversation

@lai3d
Copy link
Copy Markdown
Owner

@lai3d lai3d commented May 19, 2026

Summary

Closes the highest-priority RBAC gap flagged in the earlier AI-features review: /api/ai/triage was reachable by any authenticated identity, including readonly consumers and per-VPS agent API keys — meaning a compromised low-privilege key could burn LLM tokens at will. Auto-remediation is already an explicit non-goal of this endpoint, so we don't lose any product capability by tightening the gate.

Change

  • sigma-api/src/routes/ai_triage.rs — handler now extracts CurrentUser and calls require_role(&user, &[\"admin\", \"operator\"]) before any LLM dispatch. OpenAPI response set gains a 403.
  • sigma-api/tests/ai_triage_test.rs (new) — four role assertions: admin/operator → 200 (degraded since the test env has no LLM_API_KEY, which is sufficient to prove RBAC passed); readonly/agent → 403.
  • sigma-api/tests/common/mod.rs — pre-existing breakage fixed: the integration-test AppState builder was missing the llm_provider and llm_api_key fields added in Add docs/ai-triage — end-to-end AI flow (EN + ZH) #10 / earlier, and the test router didn't merge ai_triage::router(). Both fixed so the new tests compile.
  • CLAUDE.md, docs/ai-triage.{en,zh}.md, docs/api-authentication.{en,zh}.md — RBAC matrix + Auth sections updated to reflect the new gate.

Why admin + operator (not just admin)

Matches the rest of the matrix: anything that mutates fleet state (VPS, tickets, costs, etc.) is admin + operator. AI Triage doesn't mutate but does spend money, which fits the same trust tier. Pure read-only consumers and per-VPS heartbeat keys never need to call it.

Test plan

  • cargo build --tests passes locally — already verified in the worktree
  • CI runs the new tests against the test DB + Redis
  • Manual smoke: a readonly JWT should get 403 from POST /api/ai/triage
  • Manual smoke: an operator JWT still works (returns available: false if LLM_API_KEY unset, real triage if set)

🤖 Generated with Claude Code

LLM calls cost real tokens; the endpoint must not be reachable from
`readonly` consumers (dashboards, monitoring) or per-VPS `agent` keys.
Adds `require_role(&user, &["admin", "operator"])` to the handler, plus
integration tests covering all four roles.

The pre-existing test setup was missing the `llm_provider` and
`llm_api_key` fields on AppState and didn't wire the ai_triage router;
fixed both so the new tests compile.

Docs (project CLAUDE.md, docs/ai-triage.{en,zh}.md, docs/api-authentication.{en,zh}.md)
updated to reflect the gate.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@lai3d lai3d merged commit cfdc11a into main May 19, 2026
1 check passed
@lai3d lai3d deleted the claude/triage-rbac branch May 19, 2026 12:59
lai3d added a commit that referenced this pull request May 20, 2026
Backend PR #13 made /api/ai/triage admin/operator-only (403 for readonly
and per-VPS agent keys), and PR #16 added a per-user LLM rate limit
(429 with Retry-After). The web UI silently routed both into a generic
red "Request failed" banner and still rendered the "Ask AI" button on
the VPS detail page for every authenticated role.

VpsDetail: gate the "Ask AI" button behind canMutate (admin|operator),
matching the other mutation actions on the page.

AiTriageDialog: branch on axios error status. 429 surfaces the
Retry-After header as "Try again in X minutes Y seconds" in an amber
panel (it's a recoverable hint, not a hard error). 403 renders a
friendly "your role doesn't permit AI triage" amber panel — readonly
users can still reach the standalone /ai-triage page from the sidebar,
so the dialog still needs this branch. Other errors keep the existing
red banner.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant