Context
Device authorization flow (RFC 8628) was published in v1.21.0 (9f8ce7c) and is live on dev-api.garnet.ai. This issue captures the production readiness assessment, test results, and actionable follow-ups based on hands-on testing of the dev deployment.
cc @nicolasparada — great work on the implementation. Architecture is correct, code is clean. Below are findings from a dedicated test session to help get this to prod.
Test Summary (dev-api.garnet.ai)
What works well ✓
- Auth enforcement: Approve/reject correctly require user JWT — project tokens, unauthenticated callers, and cross-env tokens all get 401
- Input validation: XSS/SQLi in deviceCode path → clean 404 (hash-based lookup prevents injection)
- Expiry: 10-min TTL works correctly, transitions to
expired status on poll
- Device code entropy: Crockford Base32, 26 chars = 130 bits — brute-force infeasible
- Method enforcement: PUT → 405, empty path → 404
- State machine: Expired codes can't be approved/rejected (auth still enforced)
What couldn't be tested
- One-time token reveal after approval (dev-app has Vercel deployment protection blocking external approval)
- Project membership enforcement on approve
- Second poll returns approved without token
→ Ask: Add integration tests in CI that exercise approve → poll → token-reveal → second-poll-no-token with a test user JWT.
Production Blockers
1. No rate limiting on unauthenticated endpoints
Tested 20 rapid POST /device-authorizations — all returned 201. Tested 20 rapid polls — all 200. No throttling observed.
Risk:
- Table flooding (unlimited pending records)
- Code-generation for phishing campaigns
- Poll-based load amplification
Fix:
- Per-IP rate limit on
POST /device-authorizations (suggest: 10/min/IP)
- Per-deviceCode rate limit on poll with RFC 8628
slow_down error (HTTP 400 {"error": "slow_down"}) when client polls faster than pollIntervalSeconds
429 Too Many Requests with Retry-After header on burst
Note: This compounds existing audit finding #8 (no backend rate limiting).
2. CORS allows any origin
< Access-Control-Allow-Origin: *
< Access-Control-Allow-Methods: GET, POST, PUT, DELETE, PATCH, OPTIONS, HEAD
While create/poll are public by design, the wildcard combined with unauthenticated create means any malicious site can silently create device authorizations and poll for approved tokens from the user's browser.
Fix: Restrict device-auth CORS to https://app.garnet.ai (and dev-app for staging). Create/poll don't need browser-origin access from arbitrary sites — only the Garnet UI calls approve.
Note: Related to existing audit finding #15/#25 (CORS wildcard).
3. No client context on consent screen
CreateDeviceAuthorization has an empty request body. The approval page can only show "approve code X for project Y" — the user can't see what is requesting access. This is the classic device-flow phishing vector.
Fix: Add optional metadata fields:
type CreateDeviceAuthorization struct {
ClientName string `json:"clientName,omitempty"` // e.g. "Devin", "Claude Code"
Environment string `json:"environment,omitempty"` // e.g. "github.com/org/repo"
RequestReason string `json:"requestReason,omitempty"` // e.g. "Configure Jibril for CI"
}
Render on /device consent page so users can make informed approve/reject decisions.
Non-Blocking Follow-ups
Scoped device-flow tokens
Currently issues a standard project token (unlimited registrations, no TTL, no audit trail to the device-auth). Recommend minting a dedicated token per device authorization with: TTL, registration cap (e.g. 1 agent), and metadata linking to the device-auth ID + approving user.
PKCE-style client binding
Anyone who intercepts the deviceCode can poll and steal the project token after approval. While 130-bit entropy makes brute force impossible, adding a code_verifier at create time (must present at poll time) closes interception attacks.
/device page route
verificationURI points to dev-app.garnet.ai/device, but garnet-ui has no /device route. Confirm whether this is served by the control-plane directly or needs a garnet-ui route (preferred for UX consistency).
?code= preservation through login redirect
When an unauthenticated user visits /device?code=X, the ?code= param must survive the Auth0 OAuth redirect chain back to the device page.
Approve must check project membership
Given audit finding #462 (cross-project authorization leak via empty resourceID), explicitly test that a user cannot approve a device-auth for a project they don't belong to.
Expired record garbage collection
Expired device authorizations should be periodically purged. Especially important given the rate-limit gap — without cleanup, an attacker could accumulate millions of records.
Integration Roadmap
| Phase |
Scope |
Depends on |
| 1 — Ship to prod |
Fix 3 blockers above, deploy to api.garnet.ai, confirm /device page |
This issue |
| 2 — CLI + MCP |
garnetctl auth device-login command, platform-mcp device-auth support |
Phase 1 |
| 3 — Agent SDK |
Devin/Claude/Copilot integration playbooks, scoped tokens, integration tests |
Phase 2 |
| 4 — OIDC federation |
GH Actions OIDC → short-lived credential exchange, deprecate static secrets for CI |
Phase 3 |
Full test report with raw curl output available on request. Tested 2026-06-12 from Devin session.
Context
Device authorization flow (RFC 8628) was published in
v1.21.0(9f8ce7c) and is live ondev-api.garnet.ai. This issue captures the production readiness assessment, test results, and actionable follow-ups based on hands-on testing of the dev deployment.cc @nicolasparada — great work on the implementation. Architecture is correct, code is clean. Below are findings from a dedicated test session to help get this to prod.
Test Summary (dev-api.garnet.ai)
What works well ✓
expiredstatus on pollWhat couldn't be tested
→ Ask: Add integration tests in CI that exercise
approve → poll → token-reveal → second-poll-no-tokenwith a test user JWT.Production Blockers
1. No rate limiting on unauthenticated endpoints
Tested 20 rapid
POST /device-authorizations— all returned 201. Tested 20 rapid polls — all 200. No throttling observed.Risk:
Fix:
POST /device-authorizations(suggest: 10/min/IP)slow_downerror (HTTP 400 {"error": "slow_down"}) when client polls faster thanpollIntervalSeconds429 Too Many RequestswithRetry-Afterheader on burstNote: This compounds existing audit finding #8 (no backend rate limiting).
2. CORS allows any origin
While create/poll are public by design, the wildcard combined with unauthenticated create means any malicious site can silently create device authorizations and poll for approved tokens from the user's browser.
Fix: Restrict device-auth CORS to
https://app.garnet.ai(and dev-app for staging). Create/poll don't need browser-origin access from arbitrary sites — only the Garnet UI calls approve.Note: Related to existing audit finding #15/#25 (CORS wildcard).
3. No client context on consent screen
CreateDeviceAuthorizationhas an empty request body. The approval page can only show "approve code X for project Y" — the user can't see what is requesting access. This is the classic device-flow phishing vector.Fix: Add optional metadata fields:
Render on
/deviceconsent page so users can make informed approve/reject decisions.Non-Blocking Follow-ups
Scoped device-flow tokens
Currently issues a standard project token (unlimited registrations, no TTL, no audit trail to the device-auth). Recommend minting a dedicated token per device authorization with: TTL, registration cap (e.g. 1 agent), and metadata linking to the device-auth ID + approving user.
PKCE-style client binding
Anyone who intercepts the deviceCode can poll and steal the project token after approval. While 130-bit entropy makes brute force impossible, adding a
code_verifierat create time (must present at poll time) closes interception attacks./devicepage routeverificationURIpoints todev-app.garnet.ai/device, but garnet-ui has no/deviceroute. Confirm whether this is served by the control-plane directly or needs a garnet-ui route (preferred for UX consistency).?code=preservation through login redirectWhen an unauthenticated user visits
/device?code=X, the?code=param must survive the Auth0 OAuth redirect chain back to the device page.Approve must check project membership
Given audit finding #462 (cross-project authorization leak via empty resourceID), explicitly test that a user cannot approve a device-auth for a project they don't belong to.
Expired record garbage collection
Expired device authorizations should be periodically purged. Especially important given the rate-limit gap — without cleanup, an attacker could accumulate millions of records.
Integration Roadmap
api.garnet.ai, confirm/devicepagegarnetctl auth device-logincommand,platform-mcpdevice-auth supportFull test report with raw curl output available on request. Tested 2026-06-12 from Devin session.