Gate /api/ai/triage on admin or operator role#13
Merged
Conversation
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>
This was referenced May 20, 2026
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>
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 join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Summary
Closes the highest-priority RBAC gap flagged in the earlier AI-features review:
/api/ai/triagewas reachable by any authenticated identity, includingreadonlyconsumers and per-VPSagentAPI 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 extractsCurrentUserand callsrequire_role(&user, &[\"admin\", \"operator\"])before any LLM dispatch. OpenAPI response set gains a403.sigma-api/tests/ai_triage_test.rs(new) — four role assertions: admin/operator → 200 (degraded since the test env has noLLM_API_KEY, which is sufficient to prove RBAC passed); readonly/agent → 403.sigma-api/tests/common/mod.rs— pre-existing breakage fixed: the integration-testAppStatebuilder was missing thellm_providerandllm_api_keyfields added in Add docs/ai-triage — end-to-end AI flow (EN + ZH) #10 / earlier, and the test router didn't mergeai_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 --testspasses locally — already verified in the worktreereadonlyJWT should get 403 fromPOST /api/ai/triageoperatorJWT still works (returnsavailable: falseifLLM_API_KEYunset, real triage if set)🤖 Generated with Claude Code