Repository navigation
fix(api): enforce user API key scoped permissions in requirePermission - #1226
Conversation
|
Warning Review limit reachedYou’ve reached a temporary PR review limit under our Fair Usage Limits Policy. Next review available in: 23 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (7)
📝 WalkthroughWalkthroughUser API keys now undergo effective scoped-permission checks during request authorization. The handler returns the owning user session on success, denies insufficient permissions, and falls back to bearer authentication when key validation fails. ChangesUser API key authorization
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant Request as requirePermission
participant Adapter as authServiceAdapter
participant Auth as auth.Service
Request->>Adapter: Check API key permission
Adapter->>Auth: Validate key and compute effective permissions
Auth-->>Adapter: Return user ID and authorization result
Adapter-->>Request: Return result
Request-->>Request: Create session, deny, or use bearer token
Possibly related issues
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
|
@coderabbitai review |
✅ Action performedReview finished.
|
5bfe068 to
f8bd10b
Compare
|
@coderabbitai review |
✅ Action performedReview finished.
|
Adversarial-review follow-ups for SEC-02 (issue #1142). Pure additions, no behavior change. - internal/api/handler_apikeys_test.go: when a valid scoped key is present, requirePermission must 403 immediately even if a valid bearer token is also sent. The frontend sends one or the other, but the documented key-precedence invariant deserves a regression guard so a future refactor that flips the fall-through order cannot silently widen a scoped key to the owner's full session perms. Asserts ValidateSession and HasPermissionAPI are never called. - internal/auth/service_apikeys_test.go: explicit fail-closed coverage for HasAPIKeyPermissionAPI on revoked keys, expired keys, and keys whose owner has been deactivated. These all error out today through ValidateUserAPIKey, but the existing test only covered "key not found"; the security boundary an attacker probes is the request-time entry point, so each rejection path is asserted directly there. All 2207 internal/api + internal/auth tests pass. Closes #1142
Adversarial-review passRan the playbook against every risk surface in the brief plus the matrix of In-scope hardening pushed (9ae02e5)Two new regression guards added; pure additions, no behavior change. All 2207
Confirmed clean
Out-of-scope follow-ups filed with full triage labels
CI / merge gatePR is UNSTABLE for reasons unrelated to this PR's logic: the pre-existing gocyclo violation in Re-pinging CR. |
|
@coderabbitai review |
✅ Action performedReview finished.
|
9ae02e5 to
678bafa
Compare
|
@coderabbitai review |
✅ Action performedReview finished.
|
678bafa to
8746b29
Compare
|
Rebased onto origin/main (73166c2). Clean rebase. Lint fix: named return values (userID string, allowed bool, err error) added to HasAPIKeyPermissionAPI in service_apikeys_api.go and app.go adapter to satisfy gocritic/unnamedResult. Gates: go build, go vet, gocyclo -over 10, golangci-lint (0 new issues). Tests: internal/auth (591 passed), internal/api (1668 passed). |
|
@coderabbitai review |
✅ Action performedReview finished.
|
User API keys arrive in x-api-key with no bearer token, so every requirePermission-gated endpoint returned 401 for them, and the per-key scoped permissions persisted at key-creation time were never enforced: ComputeEffectivePermissions had no caller on the request path (SEC-02). Wire a real user-API-key authorization path into requirePermission: - Add auth.Service.HasAPIKeyPermissionAPI, which validates the key and checks the requested action/resource against the key's effective permissions (intersection of key scope and the owner's group-derived permissions, via ComputeEffectivePermissions). Fail closed on any validation or lookup error. - Expose it on AuthServiceInterface and the server adapter. - In requirePermission, authorize a non-admin x-api-key against the key's effective permissions: in-scope returns a session for the owning user, out-of-scope returns 403, and a key that fails validation falls through to bearer-token auth (matching resolveAuthenticatedUserID). Permission matching reuses AuthContext.HasPermission, so the admin:* wildcard money-spending carve-outs (issue #923) apply to API keys too. Regression tests replicate the real failing scenario (x-api-key header, no bearer token) at both layers and were confirmed to fail against the pre-fix code: a scoped key cannot exceed its scope, an in-scope key is authorized as its owner, and GET recommendations works end to end with a view:recommendations-scoped key. Closes #1142
8746b29 to
fda1209
Compare
|
Rebased onto origin/main@27fdb06b5 (50 commits ahead of the original branch point). Resolved handler.go cleanly (main's redactURL change in HandleRequest is in a different function from the PR's requirePermission additions). Flake-fixed files (validator_test.go, fanout.go, fanout_test.go) now match main exactly. Gates: go build ./... (0), go vet ./... (0), tests for touched packages (0), scheduledauth -count=5 (0), TestFanOut -count=10 (0), gocyclo -over 10 (0), golangci-lint (0). New HEAD: fda1209. |
|
Merged to main on the substantive CI signal (all of Lint Code/Unit/Integration/Security/Build/E2E/Validate-Terraform green; the only red check is the non-blocking tflint/trivy pre-commit infra flake, which this repo's unprotected main does not gate on) closes #1142. enforce user API-key scoped permissions in requirePermission (fails closed with 403 on scope miss). |
Problem
Closes #1142 (SEC-02, P2).
requirePermissionrecognized only the stateless admin API key and bearer-token sessions. A user API key arrives inx-api-keywith no bearer token, so everyrequirePermission-gated endpoint returned401 no authorization token providedfor it. As a result the per-key scoped permissions persisted at key-creation time were never enforced anywhere:ComputeEffectivePermissions, which intersects the key's scope with the owning user's group-derived permissions, had no caller on the request path (dead code).Fix
Wires a real user-API-key authorization path into
requirePermission, per the report's first recommended option:internal/auth/service_apikeys_api.go: newHasAPIKeyPermissionAPI(ctx, apiKey, action, resource)validates the key (ValidateUserAPIKey) and checks the requested action/resource against the key's effective permissions (ComputeEffectivePermissions). Matching reusesAuthContext.HasPermission, so theadmin:*wildcard money-spending carve-outs (issue Add Purchaser group + remove implicit admin grant for execute/approve-any/retry-any purchases (separation of duties) #923) apply to API keys exactly as to sessions. Fail closed: any validation or lookup failure returns an error and the caller denies.internal/api/types.go+internal/server/app.go: method added toAuthServiceInterfaceand the server adapter.internal/api/handler.go:requirePermissionnow authorizes a non-adminx-api-keyagainst the key's effective permissions. In-scope: session for the owning user (account scoping then resolves against that user as before). Out-of-scope: 403 (fail closed). A key that fails validation falls through to bearer-token auth, matchingresolveAuthenticatedUserID, so a stale key header cannot lock out a caller that also presents a valid session. The frontend sends either the bearer token or the API key, never both, so existing UI behavior is unchanged.requireAdminintentionally still accepts only the admin API key or an admin session; user API keys remain unable to reach AuthAdmin routes.Test evidence
Regression tests replicate the real failing scenario (request with only an
x-api-keyheader, no bearer token) and were run against the pre-fix code (handler.go reverted) to confirm they fail there: in-scope authorization failed with 401, out-of-scope expected 403 but got 401, and the end-to-end recommendations call 401'd.internal/auth:TestService_HasAPIKeyPermissionAPI- scoped key grants in-scope action; scoped key cannot exceed its scope even when the owner holds the permission; key permission the owner lacks is denied (intersection); unscoped key inherits owner permissions; invalid key fails closed.internal/api:TestRequirePermission_UserAPIKey- in-scope key authorized as its owner; out-of-scope key gets 403; invalid key with no bearer gets 401; invalid key falls through to a valid bearer session; admin API key still bypasses the per-key lookup.TestGetRecommendations_UserAPIKey- GET recommendations end to end with aview:recommendations-scoped key.Ran:
go build ./...andgo test ./internal/api/... ./internal/auth/... ./internal/server/...- 2564 tests passed,go vetclean.Summary by CodeRabbit
Bug Fixes
Tests