fix(auth): gate the four unauthenticated run/policy sub-resource GET routes - #18
Merged
Merged
Conversation
…routes GET /api/runs/[id]/agents, /api/runs/[id]/metrics, /api/runs/[id]/policies, and /api/policies/[id]/results shipped with no requireUser and no ownership check while every sibling route enforced both — any request with any (even invalid) credential could read any run's cost metrics, full event timeline, and policy results across all users. - The three run sub-resources now apply the same rules as GET /api/runs/[id]: authenticated, members see only their own runs, pre-auth (userId=null) runs are admin-only, and non-owners get 404 (not 403) so run IDs can't be enumerated. - /api/policies/[id]/results now requires auth and scopes results to the member's own runs via a join against the runs table (getPolicyResultsForPolicy gained an optional ownedByUserId filter); admins keep the cross-user view. - auth-gaps.test.ts extended with 8 cases covering anon/non-owner/owner/ admin and null-owner behavior for all four routes — these were exactly the routes the earlier auth sweep missed because they had no UI callers. Co-Authored-By: Claude Fable 5 <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
The July audit found four GET routes with no
requireUserand no ownership check, while every sibling route enforced both. Any request — including one with a garbage Bearer token — could read cross-tenant data:GET /api/runs/[id]/metrics— any run's cost/token metricsGET /api/runs/[id]/agents— any run's full event timeline (up to 500 events incl. payloads)GET /api/runs/[id]/policies— any run's policy resultsGET /api/policies/[id]/results— policy results across all users' runsThese were missed by the June auth sweep because they have no UI callers, so nothing exercised them.
Changes
GET /api/runs/[id]: authenticated; members see only their own runs; pre-auth (userId = null) runs are admin-only; non-owners get 404, not 403, so run IDs can't be enumerated.GET /api/policies/[id]/resultsrequires auth and scopes results to the member's own runs —getPolicyResultsForPolicygained an optionalownedByUserIdparameter that inner-joins therunstable. Admins keep the cross-user view (matchingresolveViewScopesemantics elsewhere).Tests
auth-gaps.test.tsextended with 8 cases: anon → 401, non-owner → 404, owner → 200, admin → 200, and null-owner-is-admin-only for each run route, plus member-vs-admin scoping of policy results. Full workspace suite green (1,278 tests) and web lint clean.🤖 Generated with Claude Code