feat: allow device keys (agent_user) on risk.createPolicyBypassRequest - #5082
Conversation
|
There was a problem hiding this comment.
cubic analysis
1 issue found across 13 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="server/internal/risk/policy_bypass_test.go">
<violation number="1" location="server/internal/risk/policy_bypass_test.go:918">
P3: The new tests verify the owner/requester binding but never the new ByKey `agent_user` scope gate. Because the handler performs no APIKeyScopes check and these tests call it directly, removing the `agent_user` scope requirement from the security design would leave all three tests green — the feature's actual authorization boundary has no coverage. Consider an integration test that drives the ByKey middleware (scope-missing key → 403), or assert `authCtx.APIKeyScopes` inside the handler so the scope gate is testable here.</violation>
</file>
Linked issue analysis
Linked issue: DNO-803: feat(server): allow device keys (agent_user) on risk.createPolicyBypassRequest
| Status | Acceptance criteria | Notes |
|---|---|---|
| ✅ | Accept Gram-Key (API key) header with agent_user scope alongside session for risk.createPolicyBypassRequest | Design and generated code add a ByKey scheme and header handling; OpenAPI/SDK/CLI were updated to accept Gram-Key in addition to Gram-Session. |
| ✅ | Permit an agent_user key owned by the requester to create a policy bypass request attributed to that user | Unit test exercises signing/redeeming with an agent_user key and asserts created request RequesterUserID equals key owner and request is created. |
| ✅ | Reject a key owned by a different user (403) | Unit test demonstrates that using another user's agent_user key to redeem the token returns forbidden. |
| ✅ | Reject org install (shared) key with an attributed token (403) | Unit test asserts that an org/shared key (agent scope / attributed token) cannot file requests for the named developer — returns forbidden. |
| ✅ | Preserve existing session-based dashboard path (no regression) | The design still includes Session security and the generated code continues to handle Gram-Session; the PR explicitly states and tests for session path regression. |
| Audit the create action and record the key owner as the actor | Tests verify an audit event was created (count increment), and the request is attributed to the key owner, but the diff does not show an explicit assertion that the audit record's actor field equals the key owner. |
Reply with feedback, questions, or to request a fix.
Fix all with cubic | Re-trigger cubic
| // authenticated device agent: no session, the given key scopes, and the key | ||
| // owner as the caller. Mirrors what internal/auth/key.go builds for a | ||
| // Gram-Key request. | ||
| func withAgentKeyAuth(t *testing.T, ctx context.Context, scopes []string, ownerUserID string) context.Context { |
There was a problem hiding this comment.
P3: The new tests verify the owner/requester binding but never the new ByKey agent_user scope gate. Because the handler performs no APIKeyScopes check and these tests call it directly, removing the agent_user scope requirement from the security design would leave all three tests green — the feature's actual authorization boundary has no coverage. Consider an integration test that drives the ByKey middleware (scope-missing key → 403), or assert authCtx.APIKeyScopes inside the handler so the scope gate is testable here.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At server/internal/risk/policy_bypass_test.go, line 918:
<comment>The new tests verify the owner/requester binding but never the new ByKey `agent_user` scope gate. Because the handler performs no APIKeyScopes check and these tests call it directly, removing the `agent_user` scope requirement from the security design would leave all three tests green — the feature's actual authorization boundary has no coverage. Consider an integration test that drives the ByKey middleware (scope-missing key → 403), or assert `authCtx.APIKeyScopes` inside the handler so the scope gate is testable here.</comment>
<file context>
@@ -908,3 +910,112 @@ func TestDenyPolicyBypassRequest_AllowAllLeavesBlockedListUntouched(t *testing.T
+// authenticated device agent: no session, the given key scopes, and the key
+// owner as the caller. Mirrors what internal/auth/key.go builds for a
+// Gram-Key request.
+func withAgentKeyAuth(t *testing.T, ctx context.Context, scopes []string, ownerUserID string) context.Context {
+ t.Helper()
+ authCtx, ok := contextvalues.GetAuthContext(ctx)
</file context>
d9536e3 to
a009fc3
Compare
…ssRequest (DNO-803) The device agent files bypass requests on the user's behalf with the per-user agent_user key minted by token-exchange. The key owner IS the enrolled user, so the handler's existing org + requester binding applies unchanged — no handler logic change. Deliberately no vouched-email param (unlike agent.getPlugins): this endpoint feeds an admin-approval workflow, and a leaked shared org install key must not be able to file requests as arbitrary users. An org key redeeming an attributed token 403s via the same requester binding; the daemon only ever calls with the per-user key. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LqNdUoJxcz85saAAyPodoW
a009fc3 to
e49fbc6
Compare
Part 2 of Request access for blocked AI tool calls (DNO-803). Stacked on #5081.
Why
The device agent files bypass requests on the user's behalf with the per-user
agent_userkey minted by token-exchange, sorisk.createPolicyBypassRequestneeds to accept key auth alongside the dashboard session.What
Security(ByKey, agent_user)added to the Goa design; session auth unchanged; codegen + TS SDK regenerated.authCtx.UserIDto the key owner — for anagent_userkey that IS the enrolled user — so the existing org + requester binding applies unchanged.emailparam (unlikeagent.getPlugins): this feeds an admin-approval workflow, and a leaked shared org install key must not be able to file requests as arbitrary users. An org key redeeming an attributed token 403s via the same requester binding.Test plan
policy_bypass_test.go:agent_userkey of the requester succeeds (and audits); another user's key 403s; the org install key with an attributed token 403s.go test ./internal/risk/green; dashboardtscclean against the regenerated SDK.🤖 Generated with Claude Code
https://claude.ai/code/session_01LqNdUoJxcz85saAAyPodoW
Summary by cubic
Allow per-user device keys to create policy bypass requests via
risk.createPolicyBypassRequest. Addsagent_userAPI key auth with theGram-Keyheader alongside session so the device agent can submit for the enrolled user (DNO-803).Security(ByKey)withagent_userscope; session path unchanged.Gram-Keyheader; OpenAPI and server codegen regenerated; dashboard SDK supportsgramKey; CLI adds--apikey-token.Written for commit e49fbc6. Summary will update on new commits.