Skip to content

feat: block PAT from cross-org endpoints and fix principal.ID resolution#1516

Merged
AmanGIT07 merged 1 commit intomainfrom
fix/block-pat-cross-org-endpoints
Apr 7, 2026
Merged

feat: block PAT from cross-org endpoints and fix principal.ID resolution#1516
AmanGIT07 merged 1 commit intomainfrom
fix/block-pat-cross-org-endpoints

Conversation

@AmanGIT07
Copy link
Copy Markdown
Contributor

Description:

Summary

  • Block PAT from calling cross-org and session-only endpoints via interceptor deny list
  • Fix principal.ID usage in handlers where PAT ID was incorrectly used as user ID

Problem

Endpoints in authorizationSkipEndpoints bypass authorization entirely. When called with a PAT:

  • Cross-org operations like CreateOrganization may succeed even though PATs are scoped to one org
  • Session operations like RevokeSession fail because principal.ID is the PAT ID, not the user ID
  • UpdateCurrentUser and preference handlers use principal.ID directly, which is the PAT ID instead of the user ID

Changes

Interceptor deny list (authorization.go)

Added patDeniedEndpoints map checked before the authz logic. When principal is PAT and endpoint is in the deny list, returns PermissionDenied. Runs after AuthenticationInterceptor so principal is already cached in context.

Blocked endpoints:

Endpoint Reason
CreateOrganization Cross-org — PATs are scoped to one org
JoinOrganization Cross-org
AcceptOrganizationInvitation Cross-org — How can you create token if you weren't part of that org already
ListSessions Session management, not API automation
PingUserSession Session management
RevokeSession Session management
CreateCurrentUserPAT PATs shouldn't create other PATs, only by UI

Handler fixes

Replaced principal.ID with principal.ResolveSubject() which correctly returns user ID for PAT, user ID for user, and serviceuser ID for serviceuser:

  • UpdateCurrentUser (user.go) — was updating with PAT ID as user ID
  • CreateCurrentUserPreferences (preferences.go) — was storing preferences under PAT ID
  • ListCurrentUserPreferences (preferences.go) — was querying preferences by PAT ID

Test plan

  • go build ./... passes
  • make lint-fix — 0 issues
  • Manual: Auth with PAT → call CreateOrganization → PermissionDenied
  • Manual: Auth with PAT → call RevokeSession → PermissionDenied
  • Manual: Auth with PAT → call UpdateCurrentUser → updates correct user
  • Manual: Auth with user session → all blocked endpoints still work normally

@vercel
Copy link
Copy Markdown

vercel bot commented Apr 7, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
frontier Ready Ready Preview, Comment Apr 7, 2026 9:53am

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 7, 2026

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Introduced authorization checks to restrict personal access tokens from accessing specific API endpoints, strengthening security controls and preventing unauthorized operations on organization-scoped resources and sensitive areas.
  • Refactor

    • Improved user identity resolution mechanisms in preference management and user profile operations to ensure consistent and reliable handling of user identifiers across platform interactions and API calls.

Walkthrough

Changes introduce subject ID resolution via principal.ResolveSubject() in preferences and user update API handlers to use resolved identifiers instead of principal.ID for persistence operations. Additionally, PAT-specific authorization checks are added to deny org-scoped personal access tokens from calling certain endpoints.

Changes

Cohort / File(s) Summary
Subject ID Resolution
internal/api/v1beta1connect/preferences.go, internal/api/v1beta1connect/user.go
Changed resource and user identifiers from principal.ID to subjectID resolved via principal.ResolveSubject() in create/list preferences and user update operations.
PAT Authorization Checks
pkg/server/connect_interceptors/authorization.go
Added pre-authorization check in AuthorizationInterceptor.WrapUnary to deny PAT principals access to endpoints defined in new patDeniedEndpoints map with connect.CodePermissionDenied.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • whoAbhishekSah

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
internal/api/v1beta1connect/user.go (1)

346-353: ⚠️ Potential issue | 🟡 Minor

Validate subjectID is non-empty before update.

ResolveSubject() returns (p.ID, p.Type) when p.PAT is nil. If somehow a PAT principal has a nil PAT field, subjectID would be the PAT's ID (not the user ID), which is the bug this change aims to fix.

Additionally, if subjectID is empty (edge case), userService.Update would fall through to UpdateByName("") per the relevant code snippet at core/user/service.go:112-123, potentially causing unexpected behavior.

Consider adding validation:

🛡️ Proposed fix
 	subjectID, _ := principal.ResolveSubject()
+	if subjectID == "" {
+		return nil, connect.NewError(connect.CodeInternal, ErrInternalServerError)
+	}
 	updatedUser, err := h.userService.Update(ctx, user.User{
 		ID:       subjectID,
internal/api/v1beta1connect/preferences.go (1)

188-198: ⚠️ Potential issue | 🟡 Minor

Validate subjectID is non-empty before creating preferences.

Same concern as in user.go: if subjectID resolves to empty string (edge case), preferences would be created with an empty ResourceID. Per the relevant code snippet at internal/store/postgres/preference_repository.go:1-50, empty strings are stored directly without validation.

🛡️ Proposed fix
 	subjectID, _ := principal.ResolveSubject()
+	if subjectID == "" {
+		return nil, connect.NewError(connect.CodeInternal, ErrInternalServerError)
+	}
 	var createdPreferences []preference.Preference
 	for _, prefBody := range req.Msg.GetBodies() {
🧹 Nitpick comments (1)
pkg/server/connect_interceptors/authorization.go (1)

36-56: Consider PAT check consistency for streaming handlers.

The PAT denial check is only applied in WrapUnary (line 60-67) but not in WrapStreamingHandler. If any streaming endpoints should be blocked for PAT principals in the future, they won't be protected.

This is likely fine for now since the blocked endpoints (org/session operations) appear to be unary RPCs.


ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: d65d24f7-133d-48e5-83d0-50f1ccd851af

📥 Commits

Reviewing files that changed from the base of the PR and between 4e7b2be and c118567.

📒 Files selected for processing (3)
  • internal/api/v1beta1connect/preferences.go
  • internal/api/v1beta1connect/user.go
  • pkg/server/connect_interceptors/authorization.go

@coveralls
Copy link
Copy Markdown

Coverage Report for CI Build 24075403743

Coverage decreased (-0.001%) to 41.146%

Details

  • Coverage decreased (-0.001%) from the base build.
  • Patch coverage: 7 uncovered changes across 1 file (6 of 13 lines covered, 46.15%).
  • No coverage regressions found.

Uncovered Changes

File Changed Covered %
pkg/server/connect_interceptors/authorization.go 7 0 0.0%

Coverage Regressions

No coverage regressions found.


Coverage Stats

Coverage Status
Relevant Lines: 36298
Covered Lines: 14935
Line Coverage: 41.15%
Coverage Strength: 11.89 hits per line

💛 - Coveralls

@AmanGIT07 AmanGIT07 merged commit 862e133 into main Apr 7, 2026
8 checks passed
@AmanGIT07 AmanGIT07 deleted the fix/block-pat-cross-org-endpoints branch April 7, 2026 11:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants