chore(web): Validate OAuth scopes for MCP access - #1396

Merged
brendan-kellam merged 11 commits into
mainfrom
brendan/sou-948-access-token-scope-validation-in-resource-server
Jul 1, 2026
Merged

chore(web): Validate OAuth scopes for MCP access#1396
brendan-kellam merged 11 commits into
mainfrom
brendan/sou-948-access-token-scope-validation-in-resource-server

Conversation

@brendan-kellam

@brendan-kellambrendan-kellam commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Add the groundwork for validating OAuth scopes per endpoint.

@coderabbitai

coderabbitaiBot commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds mcp OAuth scope support across OAuth discovery, authorization, token issuance, auth middleware, and the MCP route. It also stores scope on authorization codes and updates tests, mocks, and changelog entries.

Changes

MCP OAuth Scope Enforcement

Layer / File(s)Summary
Scope constants and utilities
packages/web/src/ee/features/oauth/constants.ts, packages/web/src/ee/features/oauth/utils.ts, packages/web/src/lib/errorCodes.ts, packages/web/src/lib/serviceError.ts, packages/web/src/ee/features/oauth/utils.test.ts
Adds the Sourcebot OAuth scope constants and type, plus scope parsing, formatting, validation, redirect checks, the insufficient-scope error code, and the matching service error helper.
Authorization code scope storage
packages/db/prisma/schema.prisma, packages/db/prisma/migrations/20260630005335_add_oauth_scope_to_authorization_code/migration.sql
Adds a scope field to OAuthAuthorizationCode in Prisma and the matching migration.
Token issuance: scope persistence and return
packages/web/src/ee/features/oauth/server.ts, packages/web/src/app/api/(server)/ee/oauth/token/route.ts, packages/web/src/ee/features/oauth/server.test.ts
generateAndStoreAuthCode now stores scope, token exchange/rotation return and persist scope, and the token endpoint returns helper-provided scope and expiry values.
Auth middleware: scope parsing and enforcement
packages/web/src/middleware/withAuth.ts, packages/web/src/middleware/withAuth.test.ts
withAuth, withOptionalAuth, and getAuthContext accept requiredOAuthScopes; bearer tokens expose parsed oauthScopes, and missing scopes return insufficientOAuthScope.
Consent and authorization flow: scope propagation
packages/web/src/app/oauth/authorize/page.tsx, packages/web/src/app/oauth/authorize/components/consentScreen.tsx, packages/web/src/ee/features/oauth/actions.ts
The authorize page reads and validates the scope query parameter, passes it into consent, and approveAuthorization validates it before continuing.
MCP route: scope enforcement and WWW-Authenticate challenges
packages/web/src/app/api/(server)/ee/mcp/route.ts
The MCP POST and DELETE routes require the mcp scope, and error responses add scope-aware Bearer and DPoP WWW-Authenticate challenges.
Discovery metadata, mocks, and changelog
packages/web/src/app/api/(server)/ee/.well-known/oauth-authorization-server/route.ts, packages/web/src/app/api/(server)/ee/.well-known/oauth-protected-resource/[...path]/route.ts, packages/web/src/__mocks__/prisma.ts, packages/web/src/lib/apiHandler.test.ts, CHANGELOG.md
OAuth discovery routes advertise scopes_supported, Prisma mocks use mcp scope values, an API handler test callback signature is adjusted, and the changelog records the validation change.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • sourcebot-dev/sourcebot#977: Extends the OAuth token exchange and MCP authorization-server flow that this PR updates for scope persistence and enforcement.
  • sourcebot-dev/sourcebot#985: Also changes the OAuth discovery and protected-resource metadata routes that now publish supported scopes here.
  • sourcebot-dev/sourcebot#1395: Modifies the MCP WWW-Authenticate response path that this PR extends for insufficient-scope handling.

Suggested reviewers

  • jsourcebot
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 35.29% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Title check✅ PassedThe title clearly summarizes the main change: validating OAuth scopes for MCP access.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch brendan/sou-948-access-token-scope-validation-in-resource-server

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.

…s-token-scope-validation-in-resource-server
# Conflicts:
#	packages/web/src/app/api/(server)/ee/.well-known/oauth-protected-resource/[...path]/route.ts
#	packages/web/src/app/api/(server)/ee/mcp/route.ts
#	packages/web/src/app/api/(server)/ee/oauth/token/route.ts
#	packages/web/src/app/oauth/authorize/components/consentScreen.tsx
#	packages/web/src/app/oauth/authorize/page.tsx
#	packages/web/src/ee/features/oauth/actions.ts
#	packages/web/src/ee/features/oauth/server.ts
#	packages/web/src/middleware/withAuth.test.ts
#	packages/web/src/middleware/withAuth.ts
@brendan-kellam
brendan-kellam marked this pull request as ready for review June 30, 2026 00:04

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/web/src/app/oauth/authorize/page.tsx`:
- Line 20: Normalize the OAuth `scope` query param in `authorize/page.tsx`
before passing it to `resolveGrantedOAuthScopes()`, since Next.js 16 may provide
repeated values as `string[]` and that helper will fail on arrays. Update the
page’s query-param handling to treat `scope` the same way as `resource` and
`dpop_jkt`, and replace the unsafe `new URLSearchParams(params as Record<string,
string>)` usage for the callback URL with explicit string-safe construction so
array values are handled correctly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6369ba85-ed38-430b-9192-af298b6e071d

📥 Commits

Reviewing files that changed from the base of the PR and between 5d5a063 and 91d5959.

📒 Files selected for processing (20)
  • CHANGELOG.md
  • packages/db/prisma/migrations/20260629190000_backfill_sourcebot_mcp_oauth_scope/migration.sql
  • packages/db/prisma/schema.prisma
  • packages/web/src/__mocks__/prisma.ts
  • packages/web/src/app/api/(server)/ee/.well-known/oauth-authorization-server/route.ts
  • packages/web/src/app/api/(server)/ee/.well-known/oauth-protected-resource/[...path]/route.ts
  • packages/web/src/app/api/(server)/ee/mcp/route.ts
  • packages/web/src/app/api/(server)/ee/oauth/token/route.ts
  • packages/web/src/app/oauth/authorize/components/consentScreen.tsx
  • packages/web/src/app/oauth/authorize/page.tsx
  • packages/web/src/ee/features/oauth/actions.ts
  • packages/web/src/ee/features/oauth/constants.test.ts
  • packages/web/src/ee/features/oauth/constants.ts
  • packages/web/src/ee/features/oauth/server.test.ts
  • packages/web/src/ee/features/oauth/server.ts
  • packages/web/src/lib/apiHandler.test.ts
  • packages/web/src/lib/errorCodes.ts
  • packages/web/src/lib/serviceError.ts
  • packages/web/src/middleware/withAuth.test.ts
  • packages/web/src/middleware/withAuth.ts

Comment threadpackages/web/src/app/oauth/authorize/page.tsx

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@packages/db/prisma/migrations/20260630005335_add_oauth_scope_to_authorization_code/migration.sql`:
- Line 2: The new scope field for OAuth authorization codes is defaulting to an
empty string, which will propagate the wrong scope into tokens; update the
authorization-code migration to backfill with the canonical MCP scope instead,
and make the same default change in the Prisma schema so
`OAuthAuthorizationCode` consistently uses `mcp` for existing and future rows.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 0c8407ea-82ef-47b6-a4be-2bea5dd475d4

📥 Commits

Reviewing files that changed from the base of the PR and between 6e14b50 and 17f79b3.

📒 Files selected for processing (11)
  • packages/db/prisma/migrations/20260630005335_add_oauth_scope_to_authorization_code/migration.sql
  • packages/db/prisma/schema.prisma
  • packages/web/src/app/oauth/authorize/components/consentScreen.tsx
  • packages/web/src/app/oauth/authorize/page.tsx
  • packages/web/src/ee/features/oauth/actions.ts
  • packages/web/src/ee/features/oauth/constants.ts
  • packages/web/src/ee/features/oauth/server.test.ts
  • packages/web/src/ee/features/oauth/server.ts
  • packages/web/src/ee/features/oauth/utils.test.ts
  • packages/web/src/ee/features/oauth/utils.ts
  • packages/web/src/middleware/withAuth.ts
✅ Files skipped from review due to trivial changes (1)
  • packages/web/src/ee/features/oauth/constants.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • packages/web/src/ee/features/oauth/actions.ts
  • packages/web/src/app/oauth/authorize/page.tsx
  • packages/web/src/app/oauth/authorize/components/consentScreen.tsx
  • packages/web/src/middleware/withAuth.ts

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Caution

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

⚠️ Outside diff range comments (1)
packages/web/src/app/api/(server)/ee/mcp/route.ts (1)

34-45: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Escape quoted WWW-Authenticate parameter values.

Line 44 injects error.message inside a quoted auth-param without escaping quotes or backslashes, which can produce a malformed OAuth challenge.

Proposed fix
+function quoteAuthParam(value: string): string {+ return `"${value.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`;+}+
function mcpOAuthChallenge(scheme: 'Bearer' | 'DPoP', error: ServiceError): string {
const issuer = env.AUTH_URL.replace(/\/$/, '');
const params = [
- 'realm="Sourcebot"',- `resource_metadata_uri="${issuer}/.well-known/oauth-protected-resource/api/mcp"`,- `scope="${SOURCEBOT_MCP_OAUTH_SCOPE}"`,+ `realm=${quoteAuthParam('Sourcebot')}`,+ `resource_metadata_uri=${quoteAuthParam(`${issuer}/.well-known/oauth-protected-resource/api/mcp`)}`,+ `scope=${quoteAuthParam(SOURCEBOT_MCP_OAUTH_SCOPE)}`,
];
if (error.errorCode === ErrorCode.OAUTH_INSUFFICIENT_SCOPE) {
params.push('error="insufficient_scope"');
- params.push(`error_description="${error.message}"`);+ params.push(`error_description=${quoteAuthParam(error.message)}`);
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/web/src/app/api/`(server)/ee/mcp/route.ts around lines 34 - 45, The
mcpOAuthChallenge helper is building a quoted WWW-Authenticate challenge value
with raw error.message, which can break the header when the message contains
quotes or backslashes. Update mcpOAuthChallenge to escape auth-param values
before pushing error_description (and any other quoted params) into the params
array, using a small helper or inline escaping in the same function. Keep the
fix localized to mcpOAuthChallenge and ensure the resulting header string
remains valid for OAuth errors.
🧹 Nitpick comments (1)
packages/web/src/app/api/(server)/ee/mcp/route.ts (1)

115-115: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use the canonical MCP OAuth scope constant for enforcement.

The challenge advertises SOURCEBOT_MCP_OAUTH_SCOPE, but the route enforces a separate 'mcp' literal. Use the same constant to avoid contract drift.

Proposed fix
- }, { requiredOAuthScopes: ['mcp'] })+ }, { requiredOAuthScopes: [SOURCEBOT_MCP_OAUTH_SCOPE] })
...
- }, { requiredOAuthScopes: ['mcp'] })+ }, { requiredOAuthScopes: [SOURCEBOT_MCP_OAUTH_SCOPE] })

Also applies to: 159-159

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/web/src/app/api/`(server)/ee/mcp/route.ts at line 115, The MCP route
is enforcing a hardcoded 'mcp' OAuth scope instead of the canonical
SOURCEBOT_MCP_OAUTH_SCOPE constant, which can drift from the advertised
challenge scope. Update the requiredOAuthScopes configuration in the MCP route
handler to use SOURCEBOT_MCP_OAUTH_SCOPE everywhere the scope is enforced,
including the other matching location in this file, so the challenge and route
stay aligned.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@packages/web/src/app/api/`(server)/ee/mcp/route.ts:
- Around line 34-45: The mcpOAuthChallenge helper is building a quoted
WWW-Authenticate challenge value with raw error.message, which can break the
header when the message contains quotes or backslashes. Update mcpOAuthChallenge
to escape auth-param values before pushing error_description (and any other
quoted params) into the params array, using a small helper or inline escaping in
the same function. Keep the fix localized to mcpOAuthChallenge and ensure the
resulting header string remains valid for OAuth errors.
---
Nitpick comments:
In `@packages/web/src/app/api/`(server)/ee/mcp/route.ts:
- Line 115: The MCP route is enforcing a hardcoded 'mcp' OAuth scope instead of
the canonical SOURCEBOT_MCP_OAUTH_SCOPE constant, which can drift from the
advertised challenge scope. Update the requiredOAuthScopes configuration in the
MCP route handler to use SOURCEBOT_MCP_OAUTH_SCOPE everywhere the scope is
enforced, including the other matching location in this file, so the challenge
and route stay aligned.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6cd0d0ab-85c8-4807-b647-11fec66e2cb5

📥 Commits

Reviewing files that changed from the base of the PR and between 17f79b3 and 090a2ae.

📒 Files selected for processing (4)
  • packages/web/src/app/api/(server)/ee/mcp/route.ts
  • packages/web/src/ee/features/oauth/constants.ts
  • packages/web/src/ee/features/oauth/server.ts
  • packages/web/src/middleware/withAuth.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/web/src/ee/features/oauth/constants.ts
  • packages/web/src/middleware/withAuth.ts

@brendan-kellambrendan-kellam changed the title SOU-948: Validate OAuth scopes for MCP accesschore(web): Validate OAuth scopes for MCP accessJun 30, 2026
@github-actions

Copy link
Copy Markdown
Contributor

License Audit

Status: FAIL

MetricCount
Total packages2224
Resolved (non-standard)17
Unresolved4
Strong copyleft0
Weak copyleft39

Fail Reasons

  • 4 packages have unresolvable licenses: @react-grab/cli@0.1.23, @react-grab/cli@0.1.29, @react-grab/mcp@0.1.29, element-source@0.0.3

Unresolved Packages

PackageVersionLicenseReason
@react-grab/cli0.1.23UNKNOWNNo license field on npm registry, no repository or homepage URL, and no LICENSE file could be located.
@react-grab/cli0.1.29UNKNOWNNo license field on npm registry, no repository or homepage URL, and no LICENSE file could be located.
@react-grab/mcp0.1.29UNKNOWNNo license field on npm registry, no repository or homepage URL, and no LICENSE file could be located.
element-source0.0.3UNKNOWNNo license field on npm registry, no repository or homepage URL, and no LICENSE file could be located.

Weak Copyleft Packages (informational)

PackageVersionLicense
@img/sharp-libvips-darwin-arm641.0.4LGPL-3.0-or-later
@img/sharp-libvips-darwin-arm641.2.4LGPL-3.0-or-later
@img/sharp-libvips-darwin-x641.0.4LGPL-3.0-or-later
@img/sharp-libvips-darwin-x641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-arm1.0.5LGPL-3.0-or-later
@img/sharp-libvips-linux-arm1.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-arm641.0.4LGPL-3.0-or-later
@img/sharp-libvips-linux-arm641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-ppc641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-riscv641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-s390x1.0.4LGPL-3.0-or-later
@img/sharp-libvips-linux-s390x1.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-x641.0.4LGPL-3.0-or-later
@img/sharp-libvips-linux-x641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linuxmusl-arm641.0.4LGPL-3.0-or-later
@img/sharp-libvips-linuxmusl-arm641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linuxmusl-x641.0.4LGPL-3.0-or-later
@img/sharp-libvips-linuxmusl-x641.2.4LGPL-3.0-or-later
@img/sharp-wasm320.33.5Apache-2.0 AND LGPL-3.0-or-later AND MIT
@img/sharp-wasm320.34.5Apache-2.0 AND LGPL-3.0-or-later AND MIT
@img/sharp-win32-arm640.34.5Apache-2.0 AND LGPL-3.0-or-later
@img/sharp-win32-ia320.33.5Apache-2.0 AND LGPL-3.0-or-later
@img/sharp-win32-ia320.34.5Apache-2.0 AND LGPL-3.0-or-later
@img/sharp-win32-x640.33.5Apache-2.0 AND LGPL-3.0-or-later
@img/sharp-win32-x640.34.5Apache-2.0 AND LGPL-3.0-or-later
axe-core4.10.3MPL-2.0
dompurify3.4.11(MPL-2.0 OR Apache-2.0)
lightningcss1.32.0MPL-2.0
lightningcss-android-arm641.32.0MPL-2.0
lightningcss-darwin-arm641.32.0MPL-2.0
lightningcss-darwin-x641.32.0MPL-2.0
lightningcss-freebsd-x641.32.0MPL-2.0
lightningcss-linux-arm-gnueabihf1.32.0MPL-2.0
lightningcss-linux-arm64-gnu1.32.0MPL-2.0
lightningcss-linux-arm64-musl1.32.0MPL-2.0
lightningcss-linux-x64-gnu1.32.0MPL-2.0
lightningcss-linux-x64-musl1.32.0MPL-2.0
lightningcss-win32-arm64-msvc1.32.0MPL-2.0
lightningcss-win32-x64-msvc1.32.0MPL-2.0
Resolved Packages (17)
PackageVersionOriginalResolvedSource
khroma2.1.0UNKNOWNMITGitHub repo (fabiospampinato/khroma LICENSE)
valid-url1.0.9UNKNOWNMITGitHub repo (ogt/valid-url LICENSE file states MIT)
map-stream0.1.0UNKNOWNMITGitHub repo (dominictarr/map-stream LICENSE)
codemirror-lang-elixir4.0.0UNKNOWNApache-2.0GitHub repo (livebook-dev/codemirror-lang-elixir LICENSE)
lezer-elixir1.1.2UNKNOWNApache-2.0GitHub repo (livebook-dev/lezer-elixir LICENSE)
memorystream0.3.1UNKNOWNMITextracted from object (npm 'licenses' field: [{type:'MIT',...}])
pause-stream0.0.11["MIT","Apache2"]MIT OR Apache-2.0extracted from object (npm 'license' array ["MIT","Apache2"])
posthog-js1.369.0SEE LICENSE IN LICENSEApache-2.0GitHub repo (PostHog/posthog-js LICENSE; primary Apache-2.0 with MIT for vendored code)
@sentry/cli2.58.5FSL-1.1-MITFSL-1.1-MITnpm registry (confirmed FSL-1.1-MIT, Functional Source License)
@sentry/cli-darwin2.58.5FSL-1.1-MITFSL-1.1-MITnpm registry (confirmed FSL-1.1-MIT)
@sentry/cli-linux-arm2.58.5FSL-1.1-MITFSL-1.1-MITnpm registry (confirmed FSL-1.1-MIT)
@sentry/cli-linux-arm642.58.5FSL-1.1-MITFSL-1.1-MITnpm registry (confirmed FSL-1.1-MIT)
@sentry/cli-linux-i6862.58.5FSL-1.1-MITFSL-1.1-MITnpm registry (confirmed FSL-1.1-MIT)
@sentry/cli-linux-x642.58.5FSL-1.1-MITFSL-1.1-MITnpm registry (confirmed FSL-1.1-MIT)
@sentry/cli-win32-arm642.58.5FSL-1.1-MITFSL-1.1-MITnpm registry (confirmed FSL-1.1-MIT)
@sentry/cli-win32-i6862.58.5FSL-1.1-MITFSL-1.1-MITnpm registry (confirmed FSL-1.1-MIT)
@sentry/cli-win32-x642.58.5FSL-1.1-MITFSL-1.1-MITnpm registry (confirmed FSL-1.1-MIT)

@brendan-kellam
brendan-kellam merged commit ff4b389 into mainJul 1, 2026
10 of 11 checks passed
@brendan-kellam
brendan-kellam deleted the brendan/sou-948-access-token-scope-validation-in-resource-server branch July 1, 2026 03:11
@github-actionsgithub-actionsBot mentioned this pull request Jul 1, 2026
Sign up for freeto 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.

1 participant

@brendan-kellam
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

chore(web): Validate OAuth scopes for MCP access - #1396

Merged
brendan-kellam merged 11 commits into
mainfrom
brendan/sou-948-access-token-scope-validation-in-resource-server
Jul 1, 2026
Merged

chore(web): Validate OAuth scopes for MCP access#1396
brendan-kellam merged 11 commits into
mainfrom
brendan/sou-948-access-token-scope-validation-in-resource-server

Conversation

@brendan-kellam

@brendan-kellambrendan-kellam commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Add the groundwork for validating OAuth scopes per endpoint.

@coderabbitai

coderabbitaiBot commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds mcp OAuth scope support across OAuth discovery, authorization, token issuance, auth middleware, and the MCP route. It also stores scope on authorization codes and updates tests, mocks, and changelog entries.

Changes

MCP OAuth Scope Enforcement

Layer / File(s)Summary
Scope constants and utilities
packages/web/src/ee/features/oauth/constants.ts, packages/web/src/ee/features/oauth/utils.ts, packages/web/src/lib/errorCodes.ts, packages/web/src/lib/serviceError.ts, packages/web/src/ee/features/oauth/utils.test.ts
Adds the Sourcebot OAuth scope constants and type, plus scope parsing, formatting, validation, redirect checks, the insufficient-scope error code, and the matching service error helper.
Authorization code scope storage
packages/db/prisma/schema.prisma, packages/db/prisma/migrations/20260630005335_add_oauth_scope_to_authorization_code/migration.sql
Adds a scope field to OAuthAuthorizationCode in Prisma and the matching migration.
Token issuance: scope persistence and return
packages/web/src/ee/features/oauth/server.ts, packages/web/src/app/api/(server)/ee/oauth/token/route.ts, packages/web/src/ee/features/oauth/server.test.ts
generateAndStoreAuthCode now stores scope, token exchange/rotation return and persist scope, and the token endpoint returns helper-provided scope and expiry values.
Auth middleware: scope parsing and enforcement
packages/web/src/middleware/withAuth.ts, packages/web/src/middleware/withAuth.test.ts
withAuth, withOptionalAuth, and getAuthContext accept requiredOAuthScopes; bearer tokens expose parsed oauthScopes, and missing scopes return insufficientOAuthScope.
Consent and authorization flow: scope propagation
packages/web/src/app/oauth/authorize/page.tsx, packages/web/src/app/oauth/authorize/components/consentScreen.tsx, packages/web/src/ee/features/oauth/actions.ts
The authorize page reads and validates the scope query parameter, passes it into consent, and approveAuthorization validates it before continuing.
MCP route: scope enforcement and WWW-Authenticate challenges
packages/web/src/app/api/(server)/ee/mcp/route.ts
The MCP POST and DELETE routes require the mcp scope, and error responses add scope-aware Bearer and DPoP WWW-Authenticate challenges.
Discovery metadata, mocks, and changelog
packages/web/src/app/api/(server)/ee/.well-known/oauth-authorization-server/route.ts, packages/web/src/app/api/(server)/ee/.well-known/oauth-protected-resource/[...path]/route.ts, packages/web/src/__mocks__/prisma.ts, packages/web/src/lib/apiHandler.test.ts, CHANGELOG.md
OAuth discovery routes advertise scopes_supported, Prisma mocks use mcp scope values, an API handler test callback signature is adjusted, and the changelog records the validation change.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • sourcebot-dev/sourcebot#977: Extends the OAuth token exchange and MCP authorization-server flow that this PR updates for scope persistence and enforcement.
  • sourcebot-dev/sourcebot#985: Also changes the OAuth discovery and protected-resource metadata routes that now publish supported scopes here.
  • sourcebot-dev/sourcebot#1395: Modifies the MCP WWW-Authenticate response path that this PR extends for insufficient-scope handling.

Suggested reviewers

  • jsourcebot
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 35.29% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Title check✅ PassedThe title clearly summarizes the main change: validating OAuth scopes for MCP access.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch brendan/sou-948-access-token-scope-validation-in-resource-server

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.

…s-token-scope-validation-in-resource-server
# Conflicts:
#	packages/web/src/app/api/(server)/ee/.well-known/oauth-protected-resource/[...path]/route.ts
#	packages/web/src/app/api/(server)/ee/mcp/route.ts
#	packages/web/src/app/api/(server)/ee/oauth/token/route.ts
#	packages/web/src/app/oauth/authorize/components/consentScreen.tsx
#	packages/web/src/app/oauth/authorize/page.tsx
#	packages/web/src/ee/features/oauth/actions.ts
#	packages/web/src/ee/features/oauth/server.ts
#	packages/web/src/middleware/withAuth.test.ts
#	packages/web/src/middleware/withAuth.ts
@brendan-kellam
brendan-kellam marked this pull request as ready for review June 30, 2026 00:04

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/web/src/app/oauth/authorize/page.tsx`:
- Line 20: Normalize the OAuth `scope` query param in `authorize/page.tsx`
before passing it to `resolveGrantedOAuthScopes()`, since Next.js 16 may provide
repeated values as `string[]` and that helper will fail on arrays. Update the
page’s query-param handling to treat `scope` the same way as `resource` and
`dpop_jkt`, and replace the unsafe `new URLSearchParams(params as Record<string,
string>)` usage for the callback URL with explicit string-safe construction so
array values are handled correctly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6369ba85-ed38-430b-9192-af298b6e071d

📥 Commits

Reviewing files that changed from the base of the PR and between 5d5a063 and 91d5959.

📒 Files selected for processing (20)
  • CHANGELOG.md
  • packages/db/prisma/migrations/20260629190000_backfill_sourcebot_mcp_oauth_scope/migration.sql
  • packages/db/prisma/schema.prisma
  • packages/web/src/__mocks__/prisma.ts
  • packages/web/src/app/api/(server)/ee/.well-known/oauth-authorization-server/route.ts
  • packages/web/src/app/api/(server)/ee/.well-known/oauth-protected-resource/[...path]/route.ts
  • packages/web/src/app/api/(server)/ee/mcp/route.ts
  • packages/web/src/app/api/(server)/ee/oauth/token/route.ts
  • packages/web/src/app/oauth/authorize/components/consentScreen.tsx
  • packages/web/src/app/oauth/authorize/page.tsx
  • packages/web/src/ee/features/oauth/actions.ts
  • packages/web/src/ee/features/oauth/constants.test.ts
  • packages/web/src/ee/features/oauth/constants.ts
  • packages/web/src/ee/features/oauth/server.test.ts
  • packages/web/src/ee/features/oauth/server.ts
  • packages/web/src/lib/apiHandler.test.ts
  • packages/web/src/lib/errorCodes.ts
  • packages/web/src/lib/serviceError.ts
  • packages/web/src/middleware/withAuth.test.ts
  • packages/web/src/middleware/withAuth.ts

Comment threadpackages/web/src/app/oauth/authorize/page.tsx

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@packages/db/prisma/migrations/20260630005335_add_oauth_scope_to_authorization_code/migration.sql`:
- Line 2: The new scope field for OAuth authorization codes is defaulting to an
empty string, which will propagate the wrong scope into tokens; update the
authorization-code migration to backfill with the canonical MCP scope instead,
and make the same default change in the Prisma schema so
`OAuthAuthorizationCode` consistently uses `mcp` for existing and future rows.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 0c8407ea-82ef-47b6-a4be-2bea5dd475d4

📥 Commits

Reviewing files that changed from the base of the PR and between 6e14b50 and 17f79b3.

📒 Files selected for processing (11)
  • packages/db/prisma/migrations/20260630005335_add_oauth_scope_to_authorization_code/migration.sql
  • packages/db/prisma/schema.prisma
  • packages/web/src/app/oauth/authorize/components/consentScreen.tsx
  • packages/web/src/app/oauth/authorize/page.tsx
  • packages/web/src/ee/features/oauth/actions.ts
  • packages/web/src/ee/features/oauth/constants.ts
  • packages/web/src/ee/features/oauth/server.test.ts
  • packages/web/src/ee/features/oauth/server.ts
  • packages/web/src/ee/features/oauth/utils.test.ts
  • packages/web/src/ee/features/oauth/utils.ts
  • packages/web/src/middleware/withAuth.ts
✅ Files skipped from review due to trivial changes (1)
  • packages/web/src/ee/features/oauth/constants.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • packages/web/src/ee/features/oauth/actions.ts
  • packages/web/src/app/oauth/authorize/page.tsx
  • packages/web/src/app/oauth/authorize/components/consentScreen.tsx
  • packages/web/src/middleware/withAuth.ts

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Caution

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

⚠️ Outside diff range comments (1)
packages/web/src/app/api/(server)/ee/mcp/route.ts (1)

34-45: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Escape quoted WWW-Authenticate parameter values.

Line 44 injects error.message inside a quoted auth-param without escaping quotes or backslashes, which can produce a malformed OAuth challenge.

Proposed fix
+function quoteAuthParam(value: string): string {+ return `"${value.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`;+}+
function mcpOAuthChallenge(scheme: 'Bearer' | 'DPoP', error: ServiceError): string {
const issuer = env.AUTH_URL.replace(/\/$/, '');
const params = [
- 'realm="Sourcebot"',- `resource_metadata_uri="${issuer}/.well-known/oauth-protected-resource/api/mcp"`,- `scope="${SOURCEBOT_MCP_OAUTH_SCOPE}"`,+ `realm=${quoteAuthParam('Sourcebot')}`,+ `resource_metadata_uri=${quoteAuthParam(`${issuer}/.well-known/oauth-protected-resource/api/mcp`)}`,+ `scope=${quoteAuthParam(SOURCEBOT_MCP_OAUTH_SCOPE)}`,
];
if (error.errorCode === ErrorCode.OAUTH_INSUFFICIENT_SCOPE) {
params.push('error="insufficient_scope"');
- params.push(`error_description="${error.message}"`);+ params.push(`error_description=${quoteAuthParam(error.message)}`);
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/web/src/app/api/`(server)/ee/mcp/route.ts around lines 34 - 45, The
mcpOAuthChallenge helper is building a quoted WWW-Authenticate challenge value
with raw error.message, which can break the header when the message contains
quotes or backslashes. Update mcpOAuthChallenge to escape auth-param values
before pushing error_description (and any other quoted params) into the params
array, using a small helper or inline escaping in the same function. Keep the
fix localized to mcpOAuthChallenge and ensure the resulting header string
remains valid for OAuth errors.
🧹 Nitpick comments (1)
packages/web/src/app/api/(server)/ee/mcp/route.ts (1)

115-115: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use the canonical MCP OAuth scope constant for enforcement.

The challenge advertises SOURCEBOT_MCP_OAUTH_SCOPE, but the route enforces a separate 'mcp' literal. Use the same constant to avoid contract drift.

Proposed fix
- }, { requiredOAuthScopes: ['mcp'] })+ }, { requiredOAuthScopes: [SOURCEBOT_MCP_OAUTH_SCOPE] })
...
- }, { requiredOAuthScopes: ['mcp'] })+ }, { requiredOAuthScopes: [SOURCEBOT_MCP_OAUTH_SCOPE] })

Also applies to: 159-159

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/web/src/app/api/`(server)/ee/mcp/route.ts at line 115, The MCP route
is enforcing a hardcoded 'mcp' OAuth scope instead of the canonical
SOURCEBOT_MCP_OAUTH_SCOPE constant, which can drift from the advertised
challenge scope. Update the requiredOAuthScopes configuration in the MCP route
handler to use SOURCEBOT_MCP_OAUTH_SCOPE everywhere the scope is enforced,
including the other matching location in this file, so the challenge and route
stay aligned.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@packages/web/src/app/api/`(server)/ee/mcp/route.ts:
- Around line 34-45: The mcpOAuthChallenge helper is building a quoted
WWW-Authenticate challenge value with raw error.message, which can break the
header when the message contains quotes or backslashes. Update mcpOAuthChallenge
to escape auth-param values before pushing error_description (and any other
quoted params) into the params array, using a small helper or inline escaping in
the same function. Keep the fix localized to mcpOAuthChallenge and ensure the
resulting header string remains valid for OAuth errors.
---
Nitpick comments:
In `@packages/web/src/app/api/`(server)/ee/mcp/route.ts:
- Line 115: The MCP route is enforcing a hardcoded 'mcp' OAuth scope instead of
the canonical SOURCEBOT_MCP_OAUTH_SCOPE constant, which can drift from the
advertised challenge scope. Update the requiredOAuthScopes configuration in the
MCP route handler to use SOURCEBOT_MCP_OAUTH_SCOPE everywhere the scope is
enforced, including the other matching location in this file, so the challenge
and route stay aligned.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6cd0d0ab-85c8-4807-b647-11fec66e2cb5

📥 Commits

Reviewing files that changed from the base of the PR and between 17f79b3 and 090a2ae.

📒 Files selected for processing (4)
  • packages/web/src/app/api/(server)/ee/mcp/route.ts
  • packages/web/src/ee/features/oauth/constants.ts
  • packages/web/src/ee/features/oauth/server.ts
  • packages/web/src/middleware/withAuth.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/web/src/ee/features/oauth/constants.ts
  • packages/web/src/middleware/withAuth.ts

@brendan-kellambrendan-kellam changed the title SOU-948: Validate OAuth scopes for MCP accesschore(web): Validate OAuth scopes for MCP accessJun 30, 2026
@github-actions

Copy link
Copy Markdown
Contributor

License Audit

Status: FAIL

MetricCount
Total packages2224
Resolved (non-standard)17
Unresolved4
Strong copyleft0
Weak copyleft39

Fail Reasons

  • 4 packages have unresolvable licenses: @react-grab/cli@0.1.23, @react-grab/cli@0.1.29, @react-grab/mcp@0.1.29, element-source@0.0.3

Unresolved Packages

PackageVersionLicenseReason
@react-grab/cli0.1.23UNKNOWNNo license field on npm registry, no repository or homepage URL, and no LICENSE file could be located.
@react-grab/cli0.1.29UNKNOWNNo license field on npm registry, no repository or homepage URL, and no LICENSE file could be located.
@react-grab/mcp0.1.29UNKNOWNNo license field on npm registry, no repository or homepage URL, and no LICENSE file could be located.
element-source0.0.3UNKNOWNNo license field on npm registry, no repository or homepage URL, and no LICENSE file could be located.

Weak Copyleft Packages (informational)

PackageVersionLicense
@img/sharp-libvips-darwin-arm641.0.4LGPL-3.0-or-later
@img/sharp-libvips-darwin-arm641.2.4LGPL-3.0-or-later
@img/sharp-libvips-darwin-x641.0.4LGPL-3.0-or-later
@img/sharp-libvips-darwin-x641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-arm1.0.5LGPL-3.0-or-later
@img/sharp-libvips-linux-arm1.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-arm641.0.4LGPL-3.0-or-later
@img/sharp-libvips-linux-arm641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-ppc641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-riscv641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-s390x1.0.4LGPL-3.0-or-later
@img/sharp-libvips-linux-s390x1.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-x641.0.4LGPL-3.0-or-later
@img/sharp-libvips-linux-x641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linuxmusl-arm641.0.4LGPL-3.0-or-later
@img/sharp-libvips-linuxmusl-arm641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linuxmusl-x641.0.4LGPL-3.0-or-later
@img/sharp-libvips-linuxmusl-x641.2.4LGPL-3.0-or-later
@img/sharp-wasm320.33.5Apache-2.0 AND LGPL-3.0-or-later AND MIT
@img/sharp-wasm320.34.5Apache-2.0 AND LGPL-3.0-or-later AND MIT
@img/sharp-win32-arm640.34.5Apache-2.0 AND LGPL-3.0-or-later
@img/sharp-win32-ia320.33.5Apache-2.0 AND LGPL-3.0-or-later
@img/sharp-win32-ia320.34.5Apache-2.0 AND LGPL-3.0-or-later
@img/sharp-win32-x640.33.5Apache-2.0 AND LGPL-3.0-or-later
@img/sharp-win32-x640.34.5Apache-2.0 AND LGPL-3.0-or-later
axe-core4.10.3MPL-2.0
dompurify3.4.11(MPL-2.0 OR Apache-2.0)
lightningcss1.32.0MPL-2.0
lightningcss-android-arm641.32.0MPL-2.0
lightningcss-darwin-arm641.32.0MPL-2.0
lightningcss-darwin-x641.32.0MPL-2.0
lightningcss-freebsd-x641.32.0MPL-2.0
lightningcss-linux-arm-gnueabihf1.32.0MPL-2.0
lightningcss-linux-arm64-gnu1.32.0MPL-2.0
lightningcss-linux-arm64-musl1.32.0MPL-2.0
lightningcss-linux-x64-gnu1.32.0MPL-2.0
lightningcss-linux-x64-musl1.32.0MPL-2.0
lightningcss-win32-arm64-msvc1.32.0MPL-2.0
lightningcss-win32-x64-msvc1.32.0MPL-2.0
Resolved Packages (17)
PackageVersionOriginalResolvedSource
khroma2.1.0UNKNOWNMITGitHub repo (fabiospampinato/khroma LICENSE)
valid-url1.0.9UNKNOWNMITGitHub repo (ogt/valid-url LICENSE file states MIT)
map-stream0.1.0UNKNOWNMITGitHub repo (dominictarr/map-stream LICENSE)
codemirror-lang-elixir4.0.0UNKNOWNApache-2.0GitHub repo (livebook-dev/codemirror-lang-elixir LICENSE)
lezer-elixir1.1.2UNKNOWNApache-2.0GitHub repo (livebook-dev/lezer-elixir LICENSE)
memorystream0.3.1UNKNOWNMITextracted from object (npm 'licenses' field: [{type:'MIT',...}])
pause-stream0.0.11["MIT","Apache2"]MIT OR Apache-2.0extracted from object (npm 'license' array ["MIT","Apache2"])
posthog-js1.369.0SEE LICENSE IN LICENSEApache-2.0GitHub repo (PostHog/posthog-js LICENSE; primary Apache-2.0 with MIT for vendored code)
@sentry/cli2.58.5FSL-1.1-MITFSL-1.1-MITnpm registry (confirmed FSL-1.1-MIT, Functional Source License)
@sentry/cli-darwin2.58.5FSL-1.1-MITFSL-1.1-MITnpm registry (confirmed FSL-1.1-MIT)
@sentry/cli-linux-arm2.58.5FSL-1.1-MITFSL-1.1-MITnpm registry (confirmed FSL-1.1-MIT)
@sentry/cli-linux-arm642.58.5FSL-1.1-MITFSL-1.1-MITnpm registry (confirmed FSL-1.1-MIT)
@sentry/cli-linux-i6862.58.5FSL-1.1-MITFSL-1.1-MITnpm registry (confirmed FSL-1.1-MIT)
@sentry/cli-linux-x642.58.5FSL-1.1-MITFSL-1.1-MITnpm registry (confirmed FSL-1.1-MIT)
@sentry/cli-win32-arm642.58.5FSL-1.1-MITFSL-1.1-MITnpm registry (confirmed FSL-1.1-MIT)
@sentry/cli-win32-i6862.58.5FSL-1.1-MITFSL-1.1-MITnpm registry (confirmed FSL-1.1-MIT)
@sentry/cli-win32-x642.58.5FSL-1.1-MITFSL-1.1-MITnpm registry (confirmed FSL-1.1-MIT)

@brendan-kellam
brendan-kellam merged commit ff4b389 into mainJul 1, 2026
10 of 11 checks passed
@brendan-kellam
brendan-kellam deleted the brendan/sou-948-access-token-scope-validation-in-resource-server branch July 1, 2026 03:11
@github-actionsgithub-actionsBot mentioned this pull request Jul 1, 2026
Sign up for freeto 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.

1 participant

@brendan-kellam
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

chore(web): Validate OAuth scopes for MCP access - #1396

Merged
brendan-kellam merged 11 commits into
mainfrom
brendan/sou-948-access-token-scope-validation-in-resource-server
Jul 1, 2026
Merged

chore(web): Validate OAuth scopes for MCP access#1396
brendan-kellam merged 11 commits into
mainfrom
brendan/sou-948-access-token-scope-validation-in-resource-server

Conversation

@brendan-kellam

@brendan-kellambrendan-kellam commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Add the groundwork for validating OAuth scopes per endpoint.

@coderabbitai

coderabbitaiBot commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds mcp OAuth scope support across OAuth discovery, authorization, token issuance, auth middleware, and the MCP route. It also stores scope on authorization codes and updates tests, mocks, and changelog entries.

Changes

MCP OAuth Scope Enforcement

Layer / File(s)Summary
Scope constants and utilities
packages/web/src/ee/features/oauth/constants.ts, packages/web/src/ee/features/oauth/utils.ts, packages/web/src/lib/errorCodes.ts, packages/web/src/lib/serviceError.ts, packages/web/src/ee/features/oauth/utils.test.ts
Adds the Sourcebot OAuth scope constants and type, plus scope parsing, formatting, validation, redirect checks, the insufficient-scope error code, and the matching service error helper.
Authorization code scope storage
packages/db/prisma/schema.prisma, packages/db/prisma/migrations/20260630005335_add_oauth_scope_to_authorization_code/migration.sql
Adds a scope field to OAuthAuthorizationCode in Prisma and the matching migration.
Token issuance: scope persistence and return
packages/web/src/ee/features/oauth/server.ts, packages/web/src/app/api/(server)/ee/oauth/token/route.ts, packages/web/src/ee/features/oauth/server.test.ts
generateAndStoreAuthCode now stores scope, token exchange/rotation return and persist scope, and the token endpoint returns helper-provided scope and expiry values.
Auth middleware: scope parsing and enforcement
packages/web/src/middleware/withAuth.ts, packages/web/src/middleware/withAuth.test.ts
withAuth, withOptionalAuth, and getAuthContext accept requiredOAuthScopes; bearer tokens expose parsed oauthScopes, and missing scopes return insufficientOAuthScope.
Consent and authorization flow: scope propagation
packages/web/src/app/oauth/authorize/page.tsx, packages/web/src/app/oauth/authorize/components/consentScreen.tsx, packages/web/src/ee/features/oauth/actions.ts
The authorize page reads and validates the scope query parameter, passes it into consent, and approveAuthorization validates it before continuing.
MCP route: scope enforcement and WWW-Authenticate challenges
packages/web/src/app/api/(server)/ee/mcp/route.ts
The MCP POST and DELETE routes require the mcp scope, and error responses add scope-aware Bearer and DPoP WWW-Authenticate challenges.
Discovery metadata, mocks, and changelog
packages/web/src/app/api/(server)/ee/.well-known/oauth-authorization-server/route.ts, packages/web/src/app/api/(server)/ee/.well-known/oauth-protected-resource/[...path]/route.ts, packages/web/src/__mocks__/prisma.ts, packages/web/src/lib/apiHandler.test.ts, CHANGELOG.md
OAuth discovery routes advertise scopes_supported, Prisma mocks use mcp scope values, an API handler test callback signature is adjusted, and the changelog records the validation change.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • sourcebot-dev/sourcebot#977: Extends the OAuth token exchange and MCP authorization-server flow that this PR updates for scope persistence and enforcement.
  • sourcebot-dev/sourcebot#985: Also changes the OAuth discovery and protected-resource metadata routes that now publish supported scopes here.
  • sourcebot-dev/sourcebot#1395: Modifies the MCP WWW-Authenticate response path that this PR extends for insufficient-scope handling.

Suggested reviewers

  • jsourcebot
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 35.29% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Title check✅ PassedThe title clearly summarizes the main change: validating OAuth scopes for MCP access.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch brendan/sou-948-access-token-scope-validation-in-resource-server

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.

…s-token-scope-validation-in-resource-server
# Conflicts:
#	packages/web/src/app/api/(server)/ee/.well-known/oauth-protected-resource/[...path]/route.ts
#	packages/web/src/app/api/(server)/ee/mcp/route.ts
#	packages/web/src/app/api/(server)/ee/oauth/token/route.ts
#	packages/web/src/app/oauth/authorize/components/consentScreen.tsx
#	packages/web/src/app/oauth/authorize/page.tsx
#	packages/web/src/ee/features/oauth/actions.ts
#	packages/web/src/ee/features/oauth/server.ts
#	packages/web/src/middleware/withAuth.test.ts
#	packages/web/src/middleware/withAuth.ts
@brendan-kellam
brendan-kellam marked this pull request as ready for review June 30, 2026 00:04

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/web/src/app/oauth/authorize/page.tsx`:
- Line 20: Normalize the OAuth `scope` query param in `authorize/page.tsx`
before passing it to `resolveGrantedOAuthScopes()`, since Next.js 16 may provide
repeated values as `string[]` and that helper will fail on arrays. Update the
page’s query-param handling to treat `scope` the same way as `resource` and
`dpop_jkt`, and replace the unsafe `new URLSearchParams(params as Record<string,
string>)` usage for the callback URL with explicit string-safe construction so
array values are handled correctly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6369ba85-ed38-430b-9192-af298b6e071d

📥 Commits

Reviewing files that changed from the base of the PR and between 5d5a063 and 91d5959.

📒 Files selected for processing (20)
  • CHANGELOG.md
  • packages/db/prisma/migrations/20260629190000_backfill_sourcebot_mcp_oauth_scope/migration.sql
  • packages/db/prisma/schema.prisma
  • packages/web/src/__mocks__/prisma.ts
  • packages/web/src/app/api/(server)/ee/.well-known/oauth-authorization-server/route.ts
  • packages/web/src/app/api/(server)/ee/.well-known/oauth-protected-resource/[...path]/route.ts
  • packages/web/src/app/api/(server)/ee/mcp/route.ts
  • packages/web/src/app/api/(server)/ee/oauth/token/route.ts
  • packages/web/src/app/oauth/authorize/components/consentScreen.tsx
  • packages/web/src/app/oauth/authorize/page.tsx
  • packages/web/src/ee/features/oauth/actions.ts
  • packages/web/src/ee/features/oauth/constants.test.ts
  • packages/web/src/ee/features/oauth/constants.ts
  • packages/web/src/ee/features/oauth/server.test.ts
  • packages/web/src/ee/features/oauth/server.ts
  • packages/web/src/lib/apiHandler.test.ts
  • packages/web/src/lib/errorCodes.ts
  • packages/web/src/lib/serviceError.ts
  • packages/web/src/middleware/withAuth.test.ts
  • packages/web/src/middleware/withAuth.ts

Comment threadpackages/web/src/app/oauth/authorize/page.tsx

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@packages/db/prisma/migrations/20260630005335_add_oauth_scope_to_authorization_code/migration.sql`:
- Line 2: The new scope field for OAuth authorization codes is defaulting to an
empty string, which will propagate the wrong scope into tokens; update the
authorization-code migration to backfill with the canonical MCP scope instead,
and make the same default change in the Prisma schema so
`OAuthAuthorizationCode` consistently uses `mcp` for existing and future rows.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 0c8407ea-82ef-47b6-a4be-2bea5dd475d4

📥 Commits

Reviewing files that changed from the base of the PR and between 6e14b50 and 17f79b3.

📒 Files selected for processing (11)
  • packages/db/prisma/migrations/20260630005335_add_oauth_scope_to_authorization_code/migration.sql
  • packages/db/prisma/schema.prisma
  • packages/web/src/app/oauth/authorize/components/consentScreen.tsx
  • packages/web/src/app/oauth/authorize/page.tsx
  • packages/web/src/ee/features/oauth/actions.ts
  • packages/web/src/ee/features/oauth/constants.ts
  • packages/web/src/ee/features/oauth/server.test.ts
  • packages/web/src/ee/features/oauth/server.ts
  • packages/web/src/ee/features/oauth/utils.test.ts
  • packages/web/src/ee/features/oauth/utils.ts
  • packages/web/src/middleware/withAuth.ts
✅ Files skipped from review due to trivial changes (1)
  • packages/web/src/ee/features/oauth/constants.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • packages/web/src/ee/features/oauth/actions.ts
  • packages/web/src/app/oauth/authorize/page.tsx
  • packages/web/src/app/oauth/authorize/components/consentScreen.tsx
  • packages/web/src/middleware/withAuth.ts

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Caution

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

⚠️ Outside diff range comments (1)
packages/web/src/app/api/(server)/ee/mcp/route.ts (1)

34-45: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Escape quoted WWW-Authenticate parameter values.

Line 44 injects error.message inside a quoted auth-param without escaping quotes or backslashes, which can produce a malformed OAuth challenge.

Proposed fix
+function quoteAuthParam(value: string): string {+ return `"${value.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`;+}+
function mcpOAuthChallenge(scheme: 'Bearer' | 'DPoP', error: ServiceError): string {
const issuer = env.AUTH_URL.replace(/\/$/, '');
const params = [
- 'realm="Sourcebot"',- `resource_metadata_uri="${issuer}/.well-known/oauth-protected-resource/api/mcp"`,- `scope="${SOURCEBOT_MCP_OAUTH_SCOPE}"`,+ `realm=${quoteAuthParam('Sourcebot')}`,+ `resource_metadata_uri=${quoteAuthParam(`${issuer}/.well-known/oauth-protected-resource/api/mcp`)}`,+ `scope=${quoteAuthParam(SOURCEBOT_MCP_OAUTH_SCOPE)}`,
];
if (error.errorCode === ErrorCode.OAUTH_INSUFFICIENT_SCOPE) {
params.push('error="insufficient_scope"');
- params.push(`error_description="${error.message}"`);+ params.push(`error_description=${quoteAuthParam(error.message)}`);
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/web/src/app/api/`(server)/ee/mcp/route.ts around lines 34 - 45, The
mcpOAuthChallenge helper is building a quoted WWW-Authenticate challenge value
with raw error.message, which can break the header when the message contains
quotes or backslashes. Update mcpOAuthChallenge to escape auth-param values
before pushing error_description (and any other quoted params) into the params
array, using a small helper or inline escaping in the same function. Keep the
fix localized to mcpOAuthChallenge and ensure the resulting header string
remains valid for OAuth errors.
🧹 Nitpick comments (1)
packages/web/src/app/api/(server)/ee/mcp/route.ts (1)

115-115: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use the canonical MCP OAuth scope constant for enforcement.

The challenge advertises SOURCEBOT_MCP_OAUTH_SCOPE, but the route enforces a separate 'mcp' literal. Use the same constant to avoid contract drift.

Proposed fix
- }, { requiredOAuthScopes: ['mcp'] })+ }, { requiredOAuthScopes: [SOURCEBOT_MCP_OAUTH_SCOPE] })
...
- }, { requiredOAuthScopes: ['mcp'] })+ }, { requiredOAuthScopes: [SOURCEBOT_MCP_OAUTH_SCOPE] })

Also applies to: 159-159

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/web/src/app/api/`(server)/ee/mcp/route.ts at line 115, The MCP route
is enforcing a hardcoded 'mcp' OAuth scope instead of the canonical
SOURCEBOT_MCP_OAUTH_SCOPE constant, which can drift from the advertised
challenge scope. Update the requiredOAuthScopes configuration in the MCP route
handler to use SOURCEBOT_MCP_OAUTH_SCOPE everywhere the scope is enforced,
including the other matching location in this file, so the challenge and route
stay aligned.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@packages/web/src/app/api/`(server)/ee/mcp/route.ts:
- Around line 34-45: The mcpOAuthChallenge helper is building a quoted
WWW-Authenticate challenge value with raw error.message, which can break the
header when the message contains quotes or backslashes. Update mcpOAuthChallenge
to escape auth-param values before pushing error_description (and any other
quoted params) into the params array, using a small helper or inline escaping in
the same function. Keep the fix localized to mcpOAuthChallenge and ensure the
resulting header string remains valid for OAuth errors.
---
Nitpick comments:
In `@packages/web/src/app/api/`(server)/ee/mcp/route.ts:
- Line 115: The MCP route is enforcing a hardcoded 'mcp' OAuth scope instead of
the canonical SOURCEBOT_MCP_OAUTH_SCOPE constant, which can drift from the
advertised challenge scope. Update the requiredOAuthScopes configuration in the
MCP route handler to use SOURCEBOT_MCP_OAUTH_SCOPE everywhere the scope is
enforced, including the other matching location in this file, so the challenge
and route stay aligned.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6cd0d0ab-85c8-4807-b647-11fec66e2cb5

📥 Commits

Reviewing files that changed from the base of the PR and between 17f79b3 and 090a2ae.

📒 Files selected for processing (4)
  • packages/web/src/app/api/(server)/ee/mcp/route.ts
  • packages/web/src/ee/features/oauth/constants.ts
  • packages/web/src/ee/features/oauth/server.ts
  • packages/web/src/middleware/withAuth.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/web/src/ee/features/oauth/constants.ts
  • packages/web/src/middleware/withAuth.ts

@brendan-kellambrendan-kellam changed the title SOU-948: Validate OAuth scopes for MCP accesschore(web): Validate OAuth scopes for MCP accessJun 30, 2026
@github-actions

Copy link
Copy Markdown
Contributor

License Audit

Status: FAIL

MetricCount
Total packages2224
Resolved (non-standard)17
Unresolved4
Strong copyleft0
Weak copyleft39

Fail Reasons

  • 4 packages have unresolvable licenses: @react-grab/cli@0.1.23, @react-grab/cli@0.1.29, @react-grab/mcp@0.1.29, element-source@0.0.3

Unresolved Packages

PackageVersionLicenseReason
@react-grab/cli0.1.23UNKNOWNNo license field on npm registry, no repository or homepage URL, and no LICENSE file could be located.
@react-grab/cli0.1.29UNKNOWNNo license field on npm registry, no repository or homepage URL, and no LICENSE file could be located.
@react-grab/mcp0.1.29UNKNOWNNo license field on npm registry, no repository or homepage URL, and no LICENSE file could be located.
element-source0.0.3UNKNOWNNo license field on npm registry, no repository or homepage URL, and no LICENSE file could be located.

Weak Copyleft Packages (informational)

PackageVersionLicense
@img/sharp-libvips-darwin-arm641.0.4LGPL-3.0-or-later
@img/sharp-libvips-darwin-arm641.2.4LGPL-3.0-or-later
@img/sharp-libvips-darwin-x641.0.4LGPL-3.0-or-later
@img/sharp-libvips-darwin-x641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-arm1.0.5LGPL-3.0-or-later
@img/sharp-libvips-linux-arm1.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-arm641.0.4LGPL-3.0-or-later
@img/sharp-libvips-linux-arm641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-ppc641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-riscv641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-s390x1.0.4LGPL-3.0-or-later
@img/sharp-libvips-linux-s390x1.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-x641.0.4LGPL-3.0-or-later
@img/sharp-libvips-linux-x641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linuxmusl-arm641.0.4LGPL-3.0-or-later
@img/sharp-libvips-linuxmusl-arm641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linuxmusl-x641.0.4LGPL-3.0-or-later
@img/sharp-libvips-linuxmusl-x641.2.4LGPL-3.0-or-later
@img/sharp-wasm320.33.5Apache-2.0 AND LGPL-3.0-or-later AND MIT
@img/sharp-wasm320.34.5Apache-2.0 AND LGPL-3.0-or-later AND MIT
@img/sharp-win32-arm640.34.5Apache-2.0 AND LGPL-3.0-or-later
@img/sharp-win32-ia320.33.5Apache-2.0 AND LGPL-3.0-or-later
@img/sharp-win32-ia320.34.5Apache-2.0 AND LGPL-3.0-or-later
@img/sharp-win32-x640.33.5Apache-2.0 AND LGPL-3.0-or-later
@img/sharp-win32-x640.34.5Apache-2.0 AND LGPL-3.0-or-later
axe-core4.10.3MPL-2.0
dompurify3.4.11(MPL-2.0 OR Apache-2.0)
lightningcss1.32.0MPL-2.0
lightningcss-android-arm641.32.0MPL-2.0
lightningcss-darwin-arm641.32.0MPL-2.0
lightningcss-darwin-x641.32.0MPL-2.0
lightningcss-freebsd-x641.32.0MPL-2.0
lightningcss-linux-arm-gnueabihf1.32.0MPL-2.0
lightningcss-linux-arm64-gnu1.32.0MPL-2.0
lightningcss-linux-arm64-musl1.32.0MPL-2.0
lightningcss-linux-x64-gnu1.32.0MPL-2.0
lightningcss-linux-x64-musl1.32.0MPL-2.0
lightningcss-win32-arm64-msvc1.32.0MPL-2.0
lightningcss-win32-x64-msvc1.32.0MPL-2.0
Resolved Packages (17)
PackageVersionOriginalResolvedSource
khroma2.1.0UNKNOWNMITGitHub repo (fabiospampinato/khroma LICENSE)
valid-url1.0.9UNKNOWNMITGitHub repo (ogt/valid-url LICENSE file states MIT)
map-stream0.1.0UNKNOWNMITGitHub repo (dominictarr/map-stream LICENSE)
codemirror-lang-elixir4.0.0UNKNOWNApache-2.0GitHub repo (livebook-dev/codemirror-lang-elixir LICENSE)
lezer-elixir1.1.2UNKNOWNApache-2.0GitHub repo (livebook-dev/lezer-elixir LICENSE)
memorystream0.3.1UNKNOWNMITextracted from object (npm 'licenses' field: [{type:'MIT',...}])
pause-stream0.0.11["MIT","Apache2"]MIT OR Apache-2.0extracted from object (npm 'license' array ["MIT","Apache2"])
posthog-js1.369.0SEE LICENSE IN LICENSEApache-2.0GitHub repo (PostHog/posthog-js LICENSE; primary Apache-2.0 with MIT for vendored code)
@sentry/cli2.58.5FSL-1.1-MITFSL-1.1-MITnpm registry (confirmed FSL-1.1-MIT, Functional Source License)
@sentry/cli-darwin2.58.5FSL-1.1-MITFSL-1.1-MITnpm registry (confirmed FSL-1.1-MIT)
@sentry/cli-linux-arm2.58.5FSL-1.1-MITFSL-1.1-MITnpm registry (confirmed FSL-1.1-MIT)
@sentry/cli-linux-arm642.58.5FSL-1.1-MITFSL-1.1-MITnpm registry (confirmed FSL-1.1-MIT)
@sentry/cli-linux-i6862.58.5FSL-1.1-MITFSL-1.1-MITnpm registry (confirmed FSL-1.1-MIT)
@sentry/cli-linux-x642.58.5FSL-1.1-MITFSL-1.1-MITnpm registry (confirmed FSL-1.1-MIT)
@sentry/cli-win32-arm642.58.5FSL-1.1-MITFSL-1.1-MITnpm registry (confirmed FSL-1.1-MIT)
@sentry/cli-win32-i6862.58.5FSL-1.1-MITFSL-1.1-MITnpm registry (confirmed FSL-1.1-MIT)
@sentry/cli-win32-x642.58.5FSL-1.1-MITFSL-1.1-MITnpm registry (confirmed FSL-1.1-MIT)

@brendan-kellam
brendan-kellam merged commit ff4b389 into mainJul 1, 2026
10 of 11 checks passed
@brendan-kellam
brendan-kellam deleted the brendan/sou-948-access-token-scope-validation-in-resource-server branch July 1, 2026 03:11
@github-actionsgithub-actionsBot mentioned this pull request Jul 1, 2026
Sign up for freeto 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.

1 participant

@brendan-kellam
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

chore(web): Validate OAuth scopes for MCP access - #1396

Merged
brendan-kellam merged 11 commits into
mainfrom
brendan/sou-948-access-token-scope-validation-in-resource-server
Jul 1, 2026
Merged

chore(web): Validate OAuth scopes for MCP access#1396
brendan-kellam merged 11 commits into
mainfrom
brendan/sou-948-access-token-scope-validation-in-resource-server

Conversation

@brendan-kellam

@brendan-kellambrendan-kellam commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Add the groundwork for validating OAuth scopes per endpoint.

@coderabbitai

coderabbitaiBot commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds mcp OAuth scope support across OAuth discovery, authorization, token issuance, auth middleware, and the MCP route. It also stores scope on authorization codes and updates tests, mocks, and changelog entries.

Changes

MCP OAuth Scope Enforcement

Layer / File(s)Summary
Scope constants and utilities
packages/web/src/ee/features/oauth/constants.ts, packages/web/src/ee/features/oauth/utils.ts, packages/web/src/lib/errorCodes.ts, packages/web/src/lib/serviceError.ts, packages/web/src/ee/features/oauth/utils.test.ts
Adds the Sourcebot OAuth scope constants and type, plus scope parsing, formatting, validation, redirect checks, the insufficient-scope error code, and the matching service error helper.
Authorization code scope storage
packages/db/prisma/schema.prisma, packages/db/prisma/migrations/20260630005335_add_oauth_scope_to_authorization_code/migration.sql
Adds a scope field to OAuthAuthorizationCode in Prisma and the matching migration.
Token issuance: scope persistence and return
packages/web/src/ee/features/oauth/server.ts, packages/web/src/app/api/(server)/ee/oauth/token/route.ts, packages/web/src/ee/features/oauth/server.test.ts
generateAndStoreAuthCode now stores scope, token exchange/rotation return and persist scope, and the token endpoint returns helper-provided scope and expiry values.
Auth middleware: scope parsing and enforcement
packages/web/src/middleware/withAuth.ts, packages/web/src/middleware/withAuth.test.ts
withAuth, withOptionalAuth, and getAuthContext accept requiredOAuthScopes; bearer tokens expose parsed oauthScopes, and missing scopes return insufficientOAuthScope.
Consent and authorization flow: scope propagation
packages/web/src/app/oauth/authorize/page.tsx, packages/web/src/app/oauth/authorize/components/consentScreen.tsx, packages/web/src/ee/features/oauth/actions.ts
The authorize page reads and validates the scope query parameter, passes it into consent, and approveAuthorization validates it before continuing.
MCP route: scope enforcement and WWW-Authenticate challenges
packages/web/src/app/api/(server)/ee/mcp/route.ts
The MCP POST and DELETE routes require the mcp scope, and error responses add scope-aware Bearer and DPoP WWW-Authenticate challenges.
Discovery metadata, mocks, and changelog
packages/web/src/app/api/(server)/ee/.well-known/oauth-authorization-server/route.ts, packages/web/src/app/api/(server)/ee/.well-known/oauth-protected-resource/[...path]/route.ts, packages/web/src/__mocks__/prisma.ts, packages/web/src/lib/apiHandler.test.ts, CHANGELOG.md
OAuth discovery routes advertise scopes_supported, Prisma mocks use mcp scope values, an API handler test callback signature is adjusted, and the changelog records the validation change.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • sourcebot-dev/sourcebot#977: Extends the OAuth token exchange and MCP authorization-server flow that this PR updates for scope persistence and enforcement.
  • sourcebot-dev/sourcebot#985: Also changes the OAuth discovery and protected-resource metadata routes that now publish supported scopes here.
  • sourcebot-dev/sourcebot#1395: Modifies the MCP WWW-Authenticate response path that this PR extends for insufficient-scope handling.

Suggested reviewers

  • jsourcebot
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 35.29% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Title check✅ PassedThe title clearly summarizes the main change: validating OAuth scopes for MCP access.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch brendan/sou-948-access-token-scope-validation-in-resource-server

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.

…s-token-scope-validation-in-resource-server
# Conflicts:
#	packages/web/src/app/api/(server)/ee/.well-known/oauth-protected-resource/[...path]/route.ts
#	packages/web/src/app/api/(server)/ee/mcp/route.ts
#	packages/web/src/app/api/(server)/ee/oauth/token/route.ts
#	packages/web/src/app/oauth/authorize/components/consentScreen.tsx
#	packages/web/src/app/oauth/authorize/page.tsx
#	packages/web/src/ee/features/oauth/actions.ts
#	packages/web/src/ee/features/oauth/server.ts
#	packages/web/src/middleware/withAuth.test.ts
#	packages/web/src/middleware/withAuth.ts
@brendan-kellam
brendan-kellam marked this pull request as ready for review June 30, 2026 00:04

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/web/src/app/oauth/authorize/page.tsx`:
- Line 20: Normalize the OAuth `scope` query param in `authorize/page.tsx`
before passing it to `resolveGrantedOAuthScopes()`, since Next.js 16 may provide
repeated values as `string[]` and that helper will fail on arrays. Update the
page’s query-param handling to treat `scope` the same way as `resource` and
`dpop_jkt`, and replace the unsafe `new URLSearchParams(params as Record<string,
string>)` usage for the callback URL with explicit string-safe construction so
array values are handled correctly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6369ba85-ed38-430b-9192-af298b6e071d

📥 Commits

Reviewing files that changed from the base of the PR and between 5d5a063 and 91d5959.

📒 Files selected for processing (20)
  • CHANGELOG.md
  • packages/db/prisma/migrations/20260629190000_backfill_sourcebot_mcp_oauth_scope/migration.sql
  • packages/db/prisma/schema.prisma
  • packages/web/src/__mocks__/prisma.ts
  • packages/web/src/app/api/(server)/ee/.well-known/oauth-authorization-server/route.ts
  • packages/web/src/app/api/(server)/ee/.well-known/oauth-protected-resource/[...path]/route.ts
  • packages/web/src/app/api/(server)/ee/mcp/route.ts
  • packages/web/src/app/api/(server)/ee/oauth/token/route.ts
  • packages/web/src/app/oauth/authorize/components/consentScreen.tsx
  • packages/web/src/app/oauth/authorize/page.tsx
  • packages/web/src/ee/features/oauth/actions.ts
  • packages/web/src/ee/features/oauth/constants.test.ts
  • packages/web/src/ee/features/oauth/constants.ts
  • packages/web/src/ee/features/oauth/server.test.ts
  • packages/web/src/ee/features/oauth/server.ts
  • packages/web/src/lib/apiHandler.test.ts
  • packages/web/src/lib/errorCodes.ts
  • packages/web/src/lib/serviceError.ts
  • packages/web/src/middleware/withAuth.test.ts
  • packages/web/src/middleware/withAuth.ts

Comment threadpackages/web/src/app/oauth/authorize/page.tsx

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@packages/db/prisma/migrations/20260630005335_add_oauth_scope_to_authorization_code/migration.sql`:
- Line 2: The new scope field for OAuth authorization codes is defaulting to an
empty string, which will propagate the wrong scope into tokens; update the
authorization-code migration to backfill with the canonical MCP scope instead,
and make the same default change in the Prisma schema so
`OAuthAuthorizationCode` consistently uses `mcp` for existing and future rows.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 0c8407ea-82ef-47b6-a4be-2bea5dd475d4

📥 Commits

Reviewing files that changed from the base of the PR and between 6e14b50 and 17f79b3.

📒 Files selected for processing (11)
  • packages/db/prisma/migrations/20260630005335_add_oauth_scope_to_authorization_code/migration.sql
  • packages/db/prisma/schema.prisma
  • packages/web/src/app/oauth/authorize/components/consentScreen.tsx
  • packages/web/src/app/oauth/authorize/page.tsx
  • packages/web/src/ee/features/oauth/actions.ts
  • packages/web/src/ee/features/oauth/constants.ts
  • packages/web/src/ee/features/oauth/server.test.ts
  • packages/web/src/ee/features/oauth/server.ts
  • packages/web/src/ee/features/oauth/utils.test.ts
  • packages/web/src/ee/features/oauth/utils.ts
  • packages/web/src/middleware/withAuth.ts
✅ Files skipped from review due to trivial changes (1)
  • packages/web/src/ee/features/oauth/constants.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • packages/web/src/ee/features/oauth/actions.ts
  • packages/web/src/app/oauth/authorize/page.tsx
  • packages/web/src/app/oauth/authorize/components/consentScreen.tsx
  • packages/web/src/middleware/withAuth.ts

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Caution

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

⚠️ Outside diff range comments (1)
packages/web/src/app/api/(server)/ee/mcp/route.ts (1)

34-45: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Escape quoted WWW-Authenticate parameter values.

Line 44 injects error.message inside a quoted auth-param without escaping quotes or backslashes, which can produce a malformed OAuth challenge.

Proposed fix
+function quoteAuthParam(value: string): string {+ return `"${value.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`;+}+
function mcpOAuthChallenge(scheme: 'Bearer' | 'DPoP', error: ServiceError): string {
const issuer = env.AUTH_URL.replace(/\/$/, '');
const params = [
- 'realm="Sourcebot"',- `resource_metadata_uri="${issuer}/.well-known/oauth-protected-resource/api/mcp"`,- `scope="${SOURCEBOT_MCP_OAUTH_SCOPE}"`,+ `realm=${quoteAuthParam('Sourcebot')}`,+ `resource_metadata_uri=${quoteAuthParam(`${issuer}/.well-known/oauth-protected-resource/api/mcp`)}`,+ `scope=${quoteAuthParam(SOURCEBOT_MCP_OAUTH_SCOPE)}`,
];
if (error.errorCode === ErrorCode.OAUTH_INSUFFICIENT_SCOPE) {
params.push('error="insufficient_scope"');
- params.push(`error_description="${error.message}"`);+ params.push(`error_description=${quoteAuthParam(error.message)}`);
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/web/src/app/api/`(server)/ee/mcp/route.ts around lines 34 - 45, The
mcpOAuthChallenge helper is building a quoted WWW-Authenticate challenge value
with raw error.message, which can break the header when the message contains
quotes or backslashes. Update mcpOAuthChallenge to escape auth-param values
before pushing error_description (and any other quoted params) into the params
array, using a small helper or inline escaping in the same function. Keep the
fix localized to mcpOAuthChallenge and ensure the resulting header string
remains valid for OAuth errors.
🧹 Nitpick comments (1)
packages/web/src/app/api/(server)/ee/mcp/route.ts (1)

115-115: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use the canonical MCP OAuth scope constant for enforcement.

The challenge advertises SOURCEBOT_MCP_OAUTH_SCOPE, but the route enforces a separate 'mcp' literal. Use the same constant to avoid contract drift.

Proposed fix
- }, { requiredOAuthScopes: ['mcp'] })+ }, { requiredOAuthScopes: [SOURCEBOT_MCP_OAUTH_SCOPE] })
...
- }, { requiredOAuthScopes: ['mcp'] })+ }, { requiredOAuthScopes: [SOURCEBOT_MCP_OAUTH_SCOPE] })

Also applies to: 159-159

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/web/src/app/api/`(server)/ee/mcp/route.ts at line 115, The MCP route
is enforcing a hardcoded 'mcp' OAuth scope instead of the canonical
SOURCEBOT_MCP_OAUTH_SCOPE constant, which can drift from the advertised
challenge scope. Update the requiredOAuthScopes configuration in the MCP route
handler to use SOURCEBOT_MCP_OAUTH_SCOPE everywhere the scope is enforced,
including the other matching location in this file, so the challenge and route
stay aligned.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@packages/web/src/app/api/`(server)/ee/mcp/route.ts:
- Around line 34-45: The mcpOAuthChallenge helper is building a quoted
WWW-Authenticate challenge value with raw error.message, which can break the
header when the message contains quotes or backslashes. Update mcpOAuthChallenge
to escape auth-param values before pushing error_description (and any other
quoted params) into the params array, using a small helper or inline escaping in
the same function. Keep the fix localized to mcpOAuthChallenge and ensure the
resulting header string remains valid for OAuth errors.
---
Nitpick comments:
In `@packages/web/src/app/api/`(server)/ee/mcp/route.ts:
- Line 115: The MCP route is enforcing a hardcoded 'mcp' OAuth scope instead of
the canonical SOURCEBOT_MCP_OAUTH_SCOPE constant, which can drift from the
advertised challenge scope. Update the requiredOAuthScopes configuration in the
MCP route handler to use SOURCEBOT_MCP_OAUTH_SCOPE everywhere the scope is
enforced, including the other matching location in this file, so the challenge
and route stay aligned.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6cd0d0ab-85c8-4807-b647-11fec66e2cb5

📥 Commits

Reviewing files that changed from the base of the PR and between 17f79b3 and 090a2ae.

📒 Files selected for processing (4)
  • packages/web/src/app/api/(server)/ee/mcp/route.ts
  • packages/web/src/ee/features/oauth/constants.ts
  • packages/web/src/ee/features/oauth/server.ts
  • packages/web/src/middleware/withAuth.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/web/src/ee/features/oauth/constants.ts
  • packages/web/src/middleware/withAuth.ts

@brendan-kellambrendan-kellam changed the title SOU-948: Validate OAuth scopes for MCP accesschore(web): Validate OAuth scopes for MCP accessJun 30, 2026
@github-actions

Copy link
Copy Markdown
Contributor

License Audit

Status: FAIL

MetricCount
Total packages2224
Resolved (non-standard)17
Unresolved4
Strong copyleft0
Weak copyleft39

Fail Reasons

  • 4 packages have unresolvable licenses: @react-grab/cli@0.1.23, @react-grab/cli@0.1.29, @react-grab/mcp@0.1.29, element-source@0.0.3

Unresolved Packages

PackageVersionLicenseReason
@react-grab/cli0.1.23UNKNOWNNo license field on npm registry, no repository or homepage URL, and no LICENSE file could be located.
@react-grab/cli0.1.29UNKNOWNNo license field on npm registry, no repository or homepage URL, and no LICENSE file could be located.
@react-grab/mcp0.1.29UNKNOWNNo license field on npm registry, no repository or homepage URL, and no LICENSE file could be located.
element-source0.0.3UNKNOWNNo license field on npm registry, no repository or homepage URL, and no LICENSE file could be located.

Weak Copyleft Packages (informational)

PackageVersionLicense
@img/sharp-libvips-darwin-arm641.0.4LGPL-3.0-or-later
@img/sharp-libvips-darwin-arm641.2.4LGPL-3.0-or-later
@img/sharp-libvips-darwin-x641.0.4LGPL-3.0-or-later
@img/sharp-libvips-darwin-x641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-arm1.0.5LGPL-3.0-or-later
@img/sharp-libvips-linux-arm1.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-arm641.0.4LGPL-3.0-or-later
@img/sharp-libvips-linux-arm641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-ppc641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-riscv641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-s390x1.0.4LGPL-3.0-or-later
@img/sharp-libvips-linux-s390x1.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-x641.0.4LGPL-3.0-or-later
@img/sharp-libvips-linux-x641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linuxmusl-arm641.0.4LGPL-3.0-or-later
@img/sharp-libvips-linuxmusl-arm641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linuxmusl-x641.0.4LGPL-3.0-or-later
@img/sharp-libvips-linuxmusl-x641.2.4LGPL-3.0-or-later
@img/sharp-wasm320.33.5Apache-2.0 AND LGPL-3.0-or-later AND MIT
@img/sharp-wasm320.34.5Apache-2.0 AND LGPL-3.0-or-later AND MIT
@img/sharp-win32-arm640.34.5Apache-2.0 AND LGPL-3.0-or-later
@img/sharp-win32-ia320.33.5Apache-2.0 AND LGPL-3.0-or-later
@img/sharp-win32-ia320.34.5Apache-2.0 AND LGPL-3.0-or-later
@img/sharp-win32-x640.33.5Apache-2.0 AND LGPL-3.0-or-later
@img/sharp-win32-x640.34.5Apache-2.0 AND LGPL-3.0-or-later
axe-core4.10.3MPL-2.0
dompurify3.4.11(MPL-2.0 OR Apache-2.0)
lightningcss1.32.0MPL-2.0
lightningcss-android-arm641.32.0MPL-2.0
lightningcss-darwin-arm641.32.0MPL-2.0
lightningcss-darwin-x641.32.0MPL-2.0
lightningcss-freebsd-x641.32.0MPL-2.0
lightningcss-linux-arm-gnueabihf1.32.0MPL-2.0
lightningcss-linux-arm64-gnu1.32.0MPL-2.0
lightningcss-linux-arm64-musl1.32.0MPL-2.0
lightningcss-linux-x64-gnu1.32.0MPL-2.0
lightningcss-linux-x64-musl1.32.0MPL-2.0
lightningcss-win32-arm64-msvc1.32.0MPL-2.0
lightningcss-win32-x64-msvc1.32.0MPL-2.0
Resolved Packages (17)
PackageVersionOriginalResolvedSource
khroma2.1.0UNKNOWNMITGitHub repo (fabiospampinato/khroma LICENSE)
valid-url1.0.9UNKNOWNMITGitHub repo (ogt/valid-url LICENSE file states MIT)
map-stream0.1.0UNKNOWNMITGitHub repo (dominictarr/map-stream LICENSE)
codemirror-lang-elixir4.0.0UNKNOWNApache-2.0GitHub repo (livebook-dev/codemirror-lang-elixir LICENSE)
lezer-elixir1.1.2UNKNOWNApache-2.0GitHub repo (livebook-dev/lezer-elixir LICENSE)
memorystream0.3.1UNKNOWNMITextracted from object (npm 'licenses' field: [{type:'MIT',...}])
pause-stream0.0.11["MIT","Apache2"]MIT OR Apache-2.0extracted from object (npm 'license' array ["MIT","Apache2"])
posthog-js1.369.0SEE LICENSE IN LICENSEApache-2.0GitHub repo (PostHog/posthog-js LICENSE; primary Apache-2.0 with MIT for vendored code)
@sentry/cli2.58.5FSL-1.1-MITFSL-1.1-MITnpm registry (confirmed FSL-1.1-MIT, Functional Source License)
@sentry/cli-darwin2.58.5FSL-1.1-MITFSL-1.1-MITnpm registry (confirmed FSL-1.1-MIT)
@sentry/cli-linux-arm2.58.5FSL-1.1-MITFSL-1.1-MITnpm registry (confirmed FSL-1.1-MIT)
@sentry/cli-linux-arm642.58.5FSL-1.1-MITFSL-1.1-MITnpm registry (confirmed FSL-1.1-MIT)
@sentry/cli-linux-i6862.58.5FSL-1.1-MITFSL-1.1-MITnpm registry (confirmed FSL-1.1-MIT)
@sentry/cli-linux-x642.58.5FSL-1.1-MITFSL-1.1-MITnpm registry (confirmed FSL-1.1-MIT)
@sentry/cli-win32-arm642.58.5FSL-1.1-MITFSL-1.1-MITnpm registry (confirmed FSL-1.1-MIT)
@sentry/cli-win32-i6862.58.5FSL-1.1-MITFSL-1.1-MITnpm registry (confirmed FSL-1.1-MIT)
@sentry/cli-win32-x642.58.5FSL-1.1-MITFSL-1.1-MITnpm registry (confirmed FSL-1.1-MIT)

@brendan-kellam
brendan-kellam merged commit ff4b389 into mainJul 1, 2026
10 of 11 checks passed
@brendan-kellam
brendan-kellam deleted the brendan/sou-948-access-token-scope-validation-in-resource-server branch July 1, 2026 03:11
@github-actionsgithub-actionsBot mentioned this pull request Jul 1, 2026
Sign up for freeto 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.

1 participant

@brendan-kellam
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

chore(web): Validate OAuth scopes for MCP access - #1396

Merged
brendan-kellam merged 11 commits into
mainfrom
brendan/sou-948-access-token-scope-validation-in-resource-server
Jul 1, 2026
Merged

chore(web): Validate OAuth scopes for MCP access#1396
brendan-kellam merged 11 commits into
mainfrom
brendan/sou-948-access-token-scope-validation-in-resource-server

Conversation

@brendan-kellam

@brendan-kellambrendan-kellam commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Add the groundwork for validating OAuth scopes per endpoint.

@coderabbitai

coderabbitaiBot commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds mcp OAuth scope support across OAuth discovery, authorization, token issuance, auth middleware, and the MCP route. It also stores scope on authorization codes and updates tests, mocks, and changelog entries.

Changes

MCP OAuth Scope Enforcement

Layer / File(s)Summary
Scope constants and utilities
packages/web/src/ee/features/oauth/constants.ts, packages/web/src/ee/features/oauth/utils.ts, packages/web/src/lib/errorCodes.ts, packages/web/src/lib/serviceError.ts, packages/web/src/ee/features/oauth/utils.test.ts
Adds the Sourcebot OAuth scope constants and type, plus scope parsing, formatting, validation, redirect checks, the insufficient-scope error code, and the matching service error helper.
Authorization code scope storage
packages/db/prisma/schema.prisma, packages/db/prisma/migrations/20260630005335_add_oauth_scope_to_authorization_code/migration.sql
Adds a scope field to OAuthAuthorizationCode in Prisma and the matching migration.
Token issuance: scope persistence and return
packages/web/src/ee/features/oauth/server.ts, packages/web/src/app/api/(server)/ee/oauth/token/route.ts, packages/web/src/ee/features/oauth/server.test.ts
generateAndStoreAuthCode now stores scope, token exchange/rotation return and persist scope, and the token endpoint returns helper-provided scope and expiry values.
Auth middleware: scope parsing and enforcement
packages/web/src/middleware/withAuth.ts, packages/web/src/middleware/withAuth.test.ts
withAuth, withOptionalAuth, and getAuthContext accept requiredOAuthScopes; bearer tokens expose parsed oauthScopes, and missing scopes return insufficientOAuthScope.
Consent and authorization flow: scope propagation
packages/web/src/app/oauth/authorize/page.tsx, packages/web/src/app/oauth/authorize/components/consentScreen.tsx, packages/web/src/ee/features/oauth/actions.ts
The authorize page reads and validates the scope query parameter, passes it into consent, and approveAuthorization validates it before continuing.
MCP route: scope enforcement and WWW-Authenticate challenges
packages/web/src/app/api/(server)/ee/mcp/route.ts
The MCP POST and DELETE routes require the mcp scope, and error responses add scope-aware Bearer and DPoP WWW-Authenticate challenges.
Discovery metadata, mocks, and changelog
packages/web/src/app/api/(server)/ee/.well-known/oauth-authorization-server/route.ts, packages/web/src/app/api/(server)/ee/.well-known/oauth-protected-resource/[...path]/route.ts, packages/web/src/__mocks__/prisma.ts, packages/web/src/lib/apiHandler.test.ts, CHANGELOG.md
OAuth discovery routes advertise scopes_supported, Prisma mocks use mcp scope values, an API handler test callback signature is adjusted, and the changelog records the validation change.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • sourcebot-dev/sourcebot#977: Extends the OAuth token exchange and MCP authorization-server flow that this PR updates for scope persistence and enforcement.
  • sourcebot-dev/sourcebot#985: Also changes the OAuth discovery and protected-resource metadata routes that now publish supported scopes here.
  • sourcebot-dev/sourcebot#1395: Modifies the MCP WWW-Authenticate response path that this PR extends for insufficient-scope handling.

Suggested reviewers

  • jsourcebot
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 35.29% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Title check✅ PassedThe title clearly summarizes the main change: validating OAuth scopes for MCP access.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch brendan/sou-948-access-token-scope-validation-in-resource-server

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.

…s-token-scope-validation-in-resource-server
# Conflicts:
#	packages/web/src/app/api/(server)/ee/.well-known/oauth-protected-resource/[...path]/route.ts
#	packages/web/src/app/api/(server)/ee/mcp/route.ts
#	packages/web/src/app/api/(server)/ee/oauth/token/route.ts
#	packages/web/src/app/oauth/authorize/components/consentScreen.tsx
#	packages/web/src/app/oauth/authorize/page.tsx
#	packages/web/src/ee/features/oauth/actions.ts
#	packages/web/src/ee/features/oauth/server.ts
#	packages/web/src/middleware/withAuth.test.ts
#	packages/web/src/middleware/withAuth.ts
@brendan-kellam
brendan-kellam marked this pull request as ready for review June 30, 2026 00:04

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/web/src/app/oauth/authorize/page.tsx`:
- Line 20: Normalize the OAuth `scope` query param in `authorize/page.tsx`
before passing it to `resolveGrantedOAuthScopes()`, since Next.js 16 may provide
repeated values as `string[]` and that helper will fail on arrays. Update the
page’s query-param handling to treat `scope` the same way as `resource` and
`dpop_jkt`, and replace the unsafe `new URLSearchParams(params as Record<string,
string>)` usage for the callback URL with explicit string-safe construction so
array values are handled correctly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6369ba85-ed38-430b-9192-af298b6e071d

📥 Commits

Reviewing files that changed from the base of the PR and between 5d5a063 and 91d5959.

📒 Files selected for processing (20)
  • CHANGELOG.md
  • packages/db/prisma/migrations/20260629190000_backfill_sourcebot_mcp_oauth_scope/migration.sql
  • packages/db/prisma/schema.prisma
  • packages/web/src/__mocks__/prisma.ts
  • packages/web/src/app/api/(server)/ee/.well-known/oauth-authorization-server/route.ts
  • packages/web/src/app/api/(server)/ee/.well-known/oauth-protected-resource/[...path]/route.ts
  • packages/web/src/app/api/(server)/ee/mcp/route.ts
  • packages/web/src/app/api/(server)/ee/oauth/token/route.ts
  • packages/web/src/app/oauth/authorize/components/consentScreen.tsx
  • packages/web/src/app/oauth/authorize/page.tsx
  • packages/web/src/ee/features/oauth/actions.ts
  • packages/web/src/ee/features/oauth/constants.test.ts
  • packages/web/src/ee/features/oauth/constants.ts
  • packages/web/src/ee/features/oauth/server.test.ts
  • packages/web/src/ee/features/oauth/server.ts
  • packages/web/src/lib/apiHandler.test.ts
  • packages/web/src/lib/errorCodes.ts
  • packages/web/src/lib/serviceError.ts
  • packages/web/src/middleware/withAuth.test.ts
  • packages/web/src/middleware/withAuth.ts

Comment threadpackages/web/src/app/oauth/authorize/page.tsx

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@packages/db/prisma/migrations/20260630005335_add_oauth_scope_to_authorization_code/migration.sql`:
- Line 2: The new scope field for OAuth authorization codes is defaulting to an
empty string, which will propagate the wrong scope into tokens; update the
authorization-code migration to backfill with the canonical MCP scope instead,
and make the same default change in the Prisma schema so
`OAuthAuthorizationCode` consistently uses `mcp` for existing and future rows.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 0c8407ea-82ef-47b6-a4be-2bea5dd475d4

📥 Commits

Reviewing files that changed from the base of the PR and between 6e14b50 and 17f79b3.

📒 Files selected for processing (11)
  • packages/db/prisma/migrations/20260630005335_add_oauth_scope_to_authorization_code/migration.sql
  • packages/db/prisma/schema.prisma
  • packages/web/src/app/oauth/authorize/components/consentScreen.tsx
  • packages/web/src/app/oauth/authorize/page.tsx
  • packages/web/src/ee/features/oauth/actions.ts
  • packages/web/src/ee/features/oauth/constants.ts
  • packages/web/src/ee/features/oauth/server.test.ts
  • packages/web/src/ee/features/oauth/server.ts
  • packages/web/src/ee/features/oauth/utils.test.ts
  • packages/web/src/ee/features/oauth/utils.ts
  • packages/web/src/middleware/withAuth.ts
✅ Files skipped from review due to trivial changes (1)
  • packages/web/src/ee/features/oauth/constants.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • packages/web/src/ee/features/oauth/actions.ts
  • packages/web/src/app/oauth/authorize/page.tsx
  • packages/web/src/app/oauth/authorize/components/consentScreen.tsx
  • packages/web/src/middleware/withAuth.ts

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Caution

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

⚠️ Outside diff range comments (1)
packages/web/src/app/api/(server)/ee/mcp/route.ts (1)

34-45: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Escape quoted WWW-Authenticate parameter values.

Line 44 injects error.message inside a quoted auth-param without escaping quotes or backslashes, which can produce a malformed OAuth challenge.

Proposed fix
+function quoteAuthParam(value: string): string {+ return `"${value.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`;+}+
function mcpOAuthChallenge(scheme: 'Bearer' | 'DPoP', error: ServiceError): string {
const issuer = env.AUTH_URL.replace(/\/$/, '');
const params = [
- 'realm="Sourcebot"',- `resource_metadata_uri="${issuer}/.well-known/oauth-protected-resource/api/mcp"`,- `scope="${SOURCEBOT_MCP_OAUTH_SCOPE}"`,+ `realm=${quoteAuthParam('Sourcebot')}`,+ `resource_metadata_uri=${quoteAuthParam(`${issuer}/.well-known/oauth-protected-resource/api/mcp`)}`,+ `scope=${quoteAuthParam(SOURCEBOT_MCP_OAUTH_SCOPE)}`,
];
if (error.errorCode === ErrorCode.OAUTH_INSUFFICIENT_SCOPE) {
params.push('error="insufficient_scope"');
- params.push(`error_description="${error.message}"`);+ params.push(`error_description=${quoteAuthParam(error.message)}`);
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/web/src/app/api/`(server)/ee/mcp/route.ts around lines 34 - 45, The
mcpOAuthChallenge helper is building a quoted WWW-Authenticate challenge value
with raw error.message, which can break the header when the message contains
quotes or backslashes. Update mcpOAuthChallenge to escape auth-param values
before pushing error_description (and any other quoted params) into the params
array, using a small helper or inline escaping in the same function. Keep the
fix localized to mcpOAuthChallenge and ensure the resulting header string
remains valid for OAuth errors.
🧹 Nitpick comments (1)
packages/web/src/app/api/(server)/ee/mcp/route.ts (1)

115-115: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use the canonical MCP OAuth scope constant for enforcement.

The challenge advertises SOURCEBOT_MCP_OAUTH_SCOPE, but the route enforces a separate 'mcp' literal. Use the same constant to avoid contract drift.

Proposed fix
- }, { requiredOAuthScopes: ['mcp'] })+ }, { requiredOAuthScopes: [SOURCEBOT_MCP_OAUTH_SCOPE] })
...
- }, { requiredOAuthScopes: ['mcp'] })+ }, { requiredOAuthScopes: [SOURCEBOT_MCP_OAUTH_SCOPE] })

Also applies to: 159-159

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/web/src/app/api/`(server)/ee/mcp/route.ts at line 115, The MCP route
is enforcing a hardcoded 'mcp' OAuth scope instead of the canonical
SOURCEBOT_MCP_OAUTH_SCOPE constant, which can drift from the advertised
challenge scope. Update the requiredOAuthScopes configuration in the MCP route
handler to use SOURCEBOT_MCP_OAUTH_SCOPE everywhere the scope is enforced,
including the other matching location in this file, so the challenge and route
stay aligned.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@packages/web/src/app/api/`(server)/ee/mcp/route.ts:
- Around line 34-45: The mcpOAuthChallenge helper is building a quoted
WWW-Authenticate challenge value with raw error.message, which can break the
header when the message contains quotes or backslashes. Update mcpOAuthChallenge
to escape auth-param values before pushing error_description (and any other
quoted params) into the params array, using a small helper or inline escaping in
the same function. Keep the fix localized to mcpOAuthChallenge and ensure the
resulting header string remains valid for OAuth errors.
---
Nitpick comments:
In `@packages/web/src/app/api/`(server)/ee/mcp/route.ts:
- Line 115: The MCP route is enforcing a hardcoded 'mcp' OAuth scope instead of
the canonical SOURCEBOT_MCP_OAUTH_SCOPE constant, which can drift from the
advertised challenge scope. Update the requiredOAuthScopes configuration in the
MCP route handler to use SOURCEBOT_MCP_OAUTH_SCOPE everywhere the scope is
enforced, including the other matching location in this file, so the challenge
and route stay aligned.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6cd0d0ab-85c8-4807-b647-11fec66e2cb5

📥 Commits

Reviewing files that changed from the base of the PR and between 17f79b3 and 090a2ae.

📒 Files selected for processing (4)
  • packages/web/src/app/api/(server)/ee/mcp/route.ts
  • packages/web/src/ee/features/oauth/constants.ts
  • packages/web/src/ee/features/oauth/server.ts
  • packages/web/src/middleware/withAuth.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/web/src/ee/features/oauth/constants.ts
  • packages/web/src/middleware/withAuth.ts

@brendan-kellambrendan-kellam changed the title SOU-948: Validate OAuth scopes for MCP accesschore(web): Validate OAuth scopes for MCP accessJun 30, 2026
@github-actions

Copy link
Copy Markdown
Contributor

License Audit

Status: FAIL

MetricCount
Total packages2224
Resolved (non-standard)17
Unresolved4
Strong copyleft0
Weak copyleft39

Fail Reasons

  • 4 packages have unresolvable licenses: @react-grab/cli@0.1.23, @react-grab/cli@0.1.29, @react-grab/mcp@0.1.29, element-source@0.0.3

Unresolved Packages

PackageVersionLicenseReason
@react-grab/cli0.1.23UNKNOWNNo license field on npm registry, no repository or homepage URL, and no LICENSE file could be located.
@react-grab/cli0.1.29UNKNOWNNo license field on npm registry, no repository or homepage URL, and no LICENSE file could be located.
@react-grab/mcp0.1.29UNKNOWNNo license field on npm registry, no repository or homepage URL, and no LICENSE file could be located.
element-source0.0.3UNKNOWNNo license field on npm registry, no repository or homepage URL, and no LICENSE file could be located.

Weak Copyleft Packages (informational)

PackageVersionLicense
@img/sharp-libvips-darwin-arm641.0.4LGPL-3.0-or-later
@img/sharp-libvips-darwin-arm641.2.4LGPL-3.0-or-later
@img/sharp-libvips-darwin-x641.0.4LGPL-3.0-or-later
@img/sharp-libvips-darwin-x641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-arm1.0.5LGPL-3.0-or-later
@img/sharp-libvips-linux-arm1.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-arm641.0.4LGPL-3.0-or-later
@img/sharp-libvips-linux-arm641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-ppc641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-riscv641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-s390x1.0.4LGPL-3.0-or-later
@img/sharp-libvips-linux-s390x1.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-x641.0.4LGPL-3.0-or-later
@img/sharp-libvips-linux-x641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linuxmusl-arm641.0.4LGPL-3.0-or-later
@img/sharp-libvips-linuxmusl-arm641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linuxmusl-x641.0.4LGPL-3.0-or-later
@img/sharp-libvips-linuxmusl-x641.2.4LGPL-3.0-or-later
@img/sharp-wasm320.33.5Apache-2.0 AND LGPL-3.0-or-later AND MIT
@img/sharp-wasm320.34.5Apache-2.0 AND LGPL-3.0-or-later AND MIT
@img/sharp-win32-arm640.34.5Apache-2.0 AND LGPL-3.0-or-later
@img/sharp-win32-ia320.33.5Apache-2.0 AND LGPL-3.0-or-later
@img/sharp-win32-ia320.34.5Apache-2.0 AND LGPL-3.0-or-later
@img/sharp-win32-x640.33.5Apache-2.0 AND LGPL-3.0-or-later
@img/sharp-win32-x640.34.5Apache-2.0 AND LGPL-3.0-or-later
axe-core4.10.3MPL-2.0
dompurify3.4.11(MPL-2.0 OR Apache-2.0)
lightningcss1.32.0MPL-2.0
lightningcss-android-arm641.32.0MPL-2.0
lightningcss-darwin-arm641.32.0MPL-2.0
lightningcss-darwin-x641.32.0MPL-2.0
lightningcss-freebsd-x641.32.0MPL-2.0
lightningcss-linux-arm-gnueabihf1.32.0MPL-2.0
lightningcss-linux-arm64-gnu1.32.0MPL-2.0
lightningcss-linux-arm64-musl1.32.0MPL-2.0
lightningcss-linux-x64-gnu1.32.0MPL-2.0
lightningcss-linux-x64-musl1.32.0MPL-2.0
lightningcss-win32-arm64-msvc1.32.0MPL-2.0
lightningcss-win32-x64-msvc1.32.0MPL-2.0
Resolved Packages (17)
PackageVersionOriginalResolvedSource
khroma2.1.0UNKNOWNMITGitHub repo (fabiospampinato/khroma LICENSE)
valid-url1.0.9UNKNOWNMITGitHub repo (ogt/valid-url LICENSE file states MIT)
map-stream0.1.0UNKNOWNMITGitHub repo (dominictarr/map-stream LICENSE)
codemirror-lang-elixir4.0.0UNKNOWNApache-2.0GitHub repo (livebook-dev/codemirror-lang-elixir LICENSE)
lezer-elixir1.1.2UNKNOWNApache-2.0GitHub repo (livebook-dev/lezer-elixir LICENSE)
memorystream0.3.1UNKNOWNMITextracted from object (npm 'licenses' field: [{type:'MIT',...}])
pause-stream0.0.11["MIT","Apache2"]MIT OR Apache-2.0extracted from object (npm 'license' array ["MIT","Apache2"])
posthog-js1.369.0SEE LICENSE IN LICENSEApache-2.0GitHub repo (PostHog/posthog-js LICENSE; primary Apache-2.0 with MIT for vendored code)
@sentry/cli2.58.5FSL-1.1-MITFSL-1.1-MITnpm registry (confirmed FSL-1.1-MIT, Functional Source License)
@sentry/cli-darwin2.58.5FSL-1.1-MITFSL-1.1-MITnpm registry (confirmed FSL-1.1-MIT)
@sentry/cli-linux-arm2.58.5FSL-1.1-MITFSL-1.1-MITnpm registry (confirmed FSL-1.1-MIT)
@sentry/cli-linux-arm642.58.5FSL-1.1-MITFSL-1.1-MITnpm registry (confirmed FSL-1.1-MIT)
@sentry/cli-linux-i6862.58.5FSL-1.1-MITFSL-1.1-MITnpm registry (confirmed FSL-1.1-MIT)
@sentry/cli-linux-x642.58.5FSL-1.1-MITFSL-1.1-MITnpm registry (confirmed FSL-1.1-MIT)
@sentry/cli-win32-arm642.58.5FSL-1.1-MITFSL-1.1-MITnpm registry (confirmed FSL-1.1-MIT)
@sentry/cli-win32-i6862.58.5FSL-1.1-MITFSL-1.1-MITnpm registry (confirmed FSL-1.1-MIT)
@sentry/cli-win32-x642.58.5FSL-1.1-MITFSL-1.1-MITnpm registry (confirmed FSL-1.1-MIT)

@brendan-kellam
brendan-kellam merged commit ff4b389 into mainJul 1, 2026
10 of 11 checks passed
@brendan-kellam
brendan-kellam deleted the brendan/sou-948-access-token-scope-validation-in-resource-server branch July 1, 2026 03:11
@github-actionsgithub-actionsBot mentioned this pull request Jul 1, 2026
Sign up for freeto 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.

1 participant

@brendan-kellam
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

chore(web): Validate OAuth scopes for MCP access - #1396

Merged
brendan-kellam merged 11 commits into
mainfrom
brendan/sou-948-access-token-scope-validation-in-resource-server
Jul 1, 2026
Merged

chore(web): Validate OAuth scopes for MCP access#1396
brendan-kellam merged 11 commits into
mainfrom
brendan/sou-948-access-token-scope-validation-in-resource-server

Conversation

@brendan-kellam

@brendan-kellambrendan-kellam commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Add the groundwork for validating OAuth scopes per endpoint.

@coderabbitai

coderabbitaiBot commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds mcp OAuth scope support across OAuth discovery, authorization, token issuance, auth middleware, and the MCP route. It also stores scope on authorization codes and updates tests, mocks, and changelog entries.

Changes

MCP OAuth Scope Enforcement

Layer / File(s)Summary
Scope constants and utilities
packages/web/src/ee/features/oauth/constants.ts, packages/web/src/ee/features/oauth/utils.ts, packages/web/src/lib/errorCodes.ts, packages/web/src/lib/serviceError.ts, packages/web/src/ee/features/oauth/utils.test.ts
Adds the Sourcebot OAuth scope constants and type, plus scope parsing, formatting, validation, redirect checks, the insufficient-scope error code, and the matching service error helper.
Authorization code scope storage
packages/db/prisma/schema.prisma, packages/db/prisma/migrations/20260630005335_add_oauth_scope_to_authorization_code/migration.sql
Adds a scope field to OAuthAuthorizationCode in Prisma and the matching migration.
Token issuance: scope persistence and return
packages/web/src/ee/features/oauth/server.ts, packages/web/src/app/api/(server)/ee/oauth/token/route.ts, packages/web/src/ee/features/oauth/server.test.ts
generateAndStoreAuthCode now stores scope, token exchange/rotation return and persist scope, and the token endpoint returns helper-provided scope and expiry values.
Auth middleware: scope parsing and enforcement
packages/web/src/middleware/withAuth.ts, packages/web/src/middleware/withAuth.test.ts
withAuth, withOptionalAuth, and getAuthContext accept requiredOAuthScopes; bearer tokens expose parsed oauthScopes, and missing scopes return insufficientOAuthScope.
Consent and authorization flow: scope propagation
packages/web/src/app/oauth/authorize/page.tsx, packages/web/src/app/oauth/authorize/components/consentScreen.tsx, packages/web/src/ee/features/oauth/actions.ts
The authorize page reads and validates the scope query parameter, passes it into consent, and approveAuthorization validates it before continuing.
MCP route: scope enforcement and WWW-Authenticate challenges
packages/web/src/app/api/(server)/ee/mcp/route.ts
The MCP POST and DELETE routes require the mcp scope, and error responses add scope-aware Bearer and DPoP WWW-Authenticate challenges.
Discovery metadata, mocks, and changelog
packages/web/src/app/api/(server)/ee/.well-known/oauth-authorization-server/route.ts, packages/web/src/app/api/(server)/ee/.well-known/oauth-protected-resource/[...path]/route.ts, packages/web/src/__mocks__/prisma.ts, packages/web/src/lib/apiHandler.test.ts, CHANGELOG.md
OAuth discovery routes advertise scopes_supported, Prisma mocks use mcp scope values, an API handler test callback signature is adjusted, and the changelog records the validation change.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • sourcebot-dev/sourcebot#977: Extends the OAuth token exchange and MCP authorization-server flow that this PR updates for scope persistence and enforcement.
  • sourcebot-dev/sourcebot#985: Also changes the OAuth discovery and protected-resource metadata routes that now publish supported scopes here.
  • sourcebot-dev/sourcebot#1395: Modifies the MCP WWW-Authenticate response path that this PR extends for insufficient-scope handling.

Suggested reviewers

  • jsourcebot
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 35.29% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Title check✅ PassedThe title clearly summarizes the main change: validating OAuth scopes for MCP access.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch brendan/sou-948-access-token-scope-validation-in-resource-server

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.

…s-token-scope-validation-in-resource-server
# Conflicts:
#	packages/web/src/app/api/(server)/ee/.well-known/oauth-protected-resource/[...path]/route.ts
#	packages/web/src/app/api/(server)/ee/mcp/route.ts
#	packages/web/src/app/api/(server)/ee/oauth/token/route.ts
#	packages/web/src/app/oauth/authorize/components/consentScreen.tsx
#	packages/web/src/app/oauth/authorize/page.tsx
#	packages/web/src/ee/features/oauth/actions.ts
#	packages/web/src/ee/features/oauth/server.ts
#	packages/web/src/middleware/withAuth.test.ts
#	packages/web/src/middleware/withAuth.ts
@brendan-kellam
brendan-kellam marked this pull request as ready for review June 30, 2026 00:04

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/web/src/app/oauth/authorize/page.tsx`:
- Line 20: Normalize the OAuth `scope` query param in `authorize/page.tsx`
before passing it to `resolveGrantedOAuthScopes()`, since Next.js 16 may provide
repeated values as `string[]` and that helper will fail on arrays. Update the
page’s query-param handling to treat `scope` the same way as `resource` and
`dpop_jkt`, and replace the unsafe `new URLSearchParams(params as Record<string,
string>)` usage for the callback URL with explicit string-safe construction so
array values are handled correctly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6369ba85-ed38-430b-9192-af298b6e071d

📥 Commits

Reviewing files that changed from the base of the PR and between 5d5a063 and 91d5959.

📒 Files selected for processing (20)
  • CHANGELOG.md
  • packages/db/prisma/migrations/20260629190000_backfill_sourcebot_mcp_oauth_scope/migration.sql
  • packages/db/prisma/schema.prisma
  • packages/web/src/__mocks__/prisma.ts
  • packages/web/src/app/api/(server)/ee/.well-known/oauth-authorization-server/route.ts
  • packages/web/src/app/api/(server)/ee/.well-known/oauth-protected-resource/[...path]/route.ts
  • packages/web/src/app/api/(server)/ee/mcp/route.ts
  • packages/web/src/app/api/(server)/ee/oauth/token/route.ts
  • packages/web/src/app/oauth/authorize/components/consentScreen.tsx
  • packages/web/src/app/oauth/authorize/page.tsx
  • packages/web/src/ee/features/oauth/actions.ts
  • packages/web/src/ee/features/oauth/constants.test.ts
  • packages/web/src/ee/features/oauth/constants.ts
  • packages/web/src/ee/features/oauth/server.test.ts
  • packages/web/src/ee/features/oauth/server.ts
  • packages/web/src/lib/apiHandler.test.ts
  • packages/web/src/lib/errorCodes.ts
  • packages/web/src/lib/serviceError.ts
  • packages/web/src/middleware/withAuth.test.ts
  • packages/web/src/middleware/withAuth.ts

Comment threadpackages/web/src/app/oauth/authorize/page.tsx

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@packages/db/prisma/migrations/20260630005335_add_oauth_scope_to_authorization_code/migration.sql`:
- Line 2: The new scope field for OAuth authorization codes is defaulting to an
empty string, which will propagate the wrong scope into tokens; update the
authorization-code migration to backfill with the canonical MCP scope instead,
and make the same default change in the Prisma schema so
`OAuthAuthorizationCode` consistently uses `mcp` for existing and future rows.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 0c8407ea-82ef-47b6-a4be-2bea5dd475d4

📥 Commits

Reviewing files that changed from the base of the PR and between 6e14b50 and 17f79b3.

📒 Files selected for processing (11)
  • packages/db/prisma/migrations/20260630005335_add_oauth_scope_to_authorization_code/migration.sql
  • packages/db/prisma/schema.prisma
  • packages/web/src/app/oauth/authorize/components/consentScreen.tsx
  • packages/web/src/app/oauth/authorize/page.tsx
  • packages/web/src/ee/features/oauth/actions.ts
  • packages/web/src/ee/features/oauth/constants.ts
  • packages/web/src/ee/features/oauth/server.test.ts
  • packages/web/src/ee/features/oauth/server.ts
  • packages/web/src/ee/features/oauth/utils.test.ts
  • packages/web/src/ee/features/oauth/utils.ts
  • packages/web/src/middleware/withAuth.ts
✅ Files skipped from review due to trivial changes (1)
  • packages/web/src/ee/features/oauth/constants.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • packages/web/src/ee/features/oauth/actions.ts
  • packages/web/src/app/oauth/authorize/page.tsx
  • packages/web/src/app/oauth/authorize/components/consentScreen.tsx
  • packages/web/src/middleware/withAuth.ts

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Caution

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

⚠️ Outside diff range comments (1)
packages/web/src/app/api/(server)/ee/mcp/route.ts (1)

34-45: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Escape quoted WWW-Authenticate parameter values.

Line 44 injects error.message inside a quoted auth-param without escaping quotes or backslashes, which can produce a malformed OAuth challenge.

Proposed fix
+function quoteAuthParam(value: string): string {+ return `"${value.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`;+}+
function mcpOAuthChallenge(scheme: 'Bearer' | 'DPoP', error: ServiceError): string {
const issuer = env.AUTH_URL.replace(/\/$/, '');
const params = [
- 'realm="Sourcebot"',- `resource_metadata_uri="${issuer}/.well-known/oauth-protected-resource/api/mcp"`,- `scope="${SOURCEBOT_MCP_OAUTH_SCOPE}"`,+ `realm=${quoteAuthParam('Sourcebot')}`,+ `resource_metadata_uri=${quoteAuthParam(`${issuer}/.well-known/oauth-protected-resource/api/mcp`)}`,+ `scope=${quoteAuthParam(SOURCEBOT_MCP_OAUTH_SCOPE)}`,
];
if (error.errorCode === ErrorCode.OAUTH_INSUFFICIENT_SCOPE) {
params.push('error="insufficient_scope"');
- params.push(`error_description="${error.message}"`);+ params.push(`error_description=${quoteAuthParam(error.message)}`);
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/web/src/app/api/`(server)/ee/mcp/route.ts around lines 34 - 45, The
mcpOAuthChallenge helper is building a quoted WWW-Authenticate challenge value
with raw error.message, which can break the header when the message contains
quotes or backslashes. Update mcpOAuthChallenge to escape auth-param values
before pushing error_description (and any other quoted params) into the params
array, using a small helper or inline escaping in the same function. Keep the
fix localized to mcpOAuthChallenge and ensure the resulting header string
remains valid for OAuth errors.
🧹 Nitpick comments (1)
packages/web/src/app/api/(server)/ee/mcp/route.ts (1)

115-115: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use the canonical MCP OAuth scope constant for enforcement.

The challenge advertises SOURCEBOT_MCP_OAUTH_SCOPE, but the route enforces a separate 'mcp' literal. Use the same constant to avoid contract drift.

Proposed fix
- }, { requiredOAuthScopes: ['mcp'] })+ }, { requiredOAuthScopes: [SOURCEBOT_MCP_OAUTH_SCOPE] })
...
- }, { requiredOAuthScopes: ['mcp'] })+ }, { requiredOAuthScopes: [SOURCEBOT_MCP_OAUTH_SCOPE] })

Also applies to: 159-159

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/web/src/app/api/`(server)/ee/mcp/route.ts at line 115, The MCP route
is enforcing a hardcoded 'mcp' OAuth scope instead of the canonical
SOURCEBOT_MCP_OAUTH_SCOPE constant, which can drift from the advertised
challenge scope. Update the requiredOAuthScopes configuration in the MCP route
handler to use SOURCEBOT_MCP_OAUTH_SCOPE everywhere the scope is enforced,
including the other matching location in this file, so the challenge and route
stay aligned.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@packages/web/src/app/api/`(server)/ee/mcp/route.ts:
- Around line 34-45: The mcpOAuthChallenge helper is building a quoted
WWW-Authenticate challenge value with raw error.message, which can break the
header when the message contains quotes or backslashes. Update mcpOAuthChallenge
to escape auth-param values before pushing error_description (and any other
quoted params) into the params array, using a small helper or inline escaping in
the same function. Keep the fix localized to mcpOAuthChallenge and ensure the
resulting header string remains valid for OAuth errors.
---
Nitpick comments:
In `@packages/web/src/app/api/`(server)/ee/mcp/route.ts:
- Line 115: The MCP route is enforcing a hardcoded 'mcp' OAuth scope instead of
the canonical SOURCEBOT_MCP_OAUTH_SCOPE constant, which can drift from the
advertised challenge scope. Update the requiredOAuthScopes configuration in the
MCP route handler to use SOURCEBOT_MCP_OAUTH_SCOPE everywhere the scope is
enforced, including the other matching location in this file, so the challenge
and route stay aligned.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6cd0d0ab-85c8-4807-b647-11fec66e2cb5

📥 Commits

Reviewing files that changed from the base of the PR and between 17f79b3 and 090a2ae.

📒 Files selected for processing (4)
  • packages/web/src/app/api/(server)/ee/mcp/route.ts
  • packages/web/src/ee/features/oauth/constants.ts
  • packages/web/src/ee/features/oauth/server.ts
  • packages/web/src/middleware/withAuth.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/web/src/ee/features/oauth/constants.ts
  • packages/web/src/middleware/withAuth.ts

@brendan-kellambrendan-kellam changed the title SOU-948: Validate OAuth scopes for MCP accesschore(web): Validate OAuth scopes for MCP accessJun 30, 2026
@github-actions

Copy link
Copy Markdown
Contributor

License Audit

Status: FAIL

MetricCount
Total packages2224
Resolved (non-standard)17
Unresolved4
Strong copyleft0
Weak copyleft39

Fail Reasons

  • 4 packages have unresolvable licenses: @react-grab/cli@0.1.23, @react-grab/cli@0.1.29, @react-grab/mcp@0.1.29, element-source@0.0.3

Unresolved Packages

PackageVersionLicenseReason
@react-grab/cli0.1.23UNKNOWNNo license field on npm registry, no repository or homepage URL, and no LICENSE file could be located.
@react-grab/cli0.1.29UNKNOWNNo license field on npm registry, no repository or homepage URL, and no LICENSE file could be located.
@react-grab/mcp0.1.29UNKNOWNNo license field on npm registry, no repository or homepage URL, and no LICENSE file could be located.
element-source0.0.3UNKNOWNNo license field on npm registry, no repository or homepage URL, and no LICENSE file could be located.

Weak Copyleft Packages (informational)

PackageVersionLicense
@img/sharp-libvips-darwin-arm641.0.4LGPL-3.0-or-later
@img/sharp-libvips-darwin-arm641.2.4LGPL-3.0-or-later
@img/sharp-libvips-darwin-x641.0.4LGPL-3.0-or-later
@img/sharp-libvips-darwin-x641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-arm1.0.5LGPL-3.0-or-later
@img/sharp-libvips-linux-arm1.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-arm641.0.4LGPL-3.0-or-later
@img/sharp-libvips-linux-arm641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-ppc641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-riscv641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-s390x1.0.4LGPL-3.0-or-later
@img/sharp-libvips-linux-s390x1.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-x641.0.4LGPL-3.0-or-later
@img/sharp-libvips-linux-x641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linuxmusl-arm641.0.4LGPL-3.0-or-later
@img/sharp-libvips-linuxmusl-arm641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linuxmusl-x641.0.4LGPL-3.0-or-later
@img/sharp-libvips-linuxmusl-x641.2.4LGPL-3.0-or-later
@img/sharp-wasm320.33.5Apache-2.0 AND LGPL-3.0-or-later AND MIT
@img/sharp-wasm320.34.5Apache-2.0 AND LGPL-3.0-or-later AND MIT
@img/sharp-win32-arm640.34.5Apache-2.0 AND LGPL-3.0-or-later
@img/sharp-win32-ia320.33.5Apache-2.0 AND LGPL-3.0-or-later
@img/sharp-win32-ia320.34.5Apache-2.0 AND LGPL-3.0-or-later
@img/sharp-win32-x640.33.5Apache-2.0 AND LGPL-3.0-or-later
@img/sharp-win32-x640.34.5Apache-2.0 AND LGPL-3.0-or-later
axe-core4.10.3MPL-2.0
dompurify3.4.11(MPL-2.0 OR Apache-2.0)
lightningcss1.32.0MPL-2.0
lightningcss-android-arm641.32.0MPL-2.0
lightningcss-darwin-arm641.32.0MPL-2.0
lightningcss-darwin-x641.32.0MPL-2.0
lightningcss-freebsd-x641.32.0MPL-2.0
lightningcss-linux-arm-gnueabihf1.32.0MPL-2.0
lightningcss-linux-arm64-gnu1.32.0MPL-2.0
lightningcss-linux-arm64-musl1.32.0MPL-2.0
lightningcss-linux-x64-gnu1.32.0MPL-2.0
lightningcss-linux-x64-musl1.32.0MPL-2.0
lightningcss-win32-arm64-msvc1.32.0MPL-2.0
lightningcss-win32-x64-msvc1.32.0MPL-2.0
Resolved Packages (17)
PackageVersionOriginalResolvedSource
khroma2.1.0UNKNOWNMITGitHub repo (fabiospampinato/khroma LICENSE)
valid-url1.0.9UNKNOWNMITGitHub repo (ogt/valid-url LICENSE file states MIT)
map-stream0.1.0UNKNOWNMITGitHub repo (dominictarr/map-stream LICENSE)
codemirror-lang-elixir4.0.0UNKNOWNApache-2.0GitHub repo (livebook-dev/codemirror-lang-elixir LICENSE)
lezer-elixir1.1.2UNKNOWNApache-2.0GitHub repo (livebook-dev/lezer-elixir LICENSE)
memorystream0.3.1UNKNOWNMITextracted from object (npm 'licenses' field: [{type:'MIT',...}])
pause-stream0.0.11["MIT","Apache2"]MIT OR Apache-2.0extracted from object (npm 'license' array ["MIT","Apache2"])
posthog-js1.369.0SEE LICENSE IN LICENSEApache-2.0GitHub repo (PostHog/posthog-js LICENSE; primary Apache-2.0 with MIT for vendored code)
@sentry/cli2.58.5FSL-1.1-MITFSL-1.1-MITnpm registry (confirmed FSL-1.1-MIT, Functional Source License)
@sentry/cli-darwin2.58.5FSL-1.1-MITFSL-1.1-MITnpm registry (confirmed FSL-1.1-MIT)
@sentry/cli-linux-arm2.58.5FSL-1.1-MITFSL-1.1-MITnpm registry (confirmed FSL-1.1-MIT)
@sentry/cli-linux-arm642.58.5FSL-1.1-MITFSL-1.1-MITnpm registry (confirmed FSL-1.1-MIT)
@sentry/cli-linux-i6862.58.5FSL-1.1-MITFSL-1.1-MITnpm registry (confirmed FSL-1.1-MIT)
@sentry/cli-linux-x642.58.5FSL-1.1-MITFSL-1.1-MITnpm registry (confirmed FSL-1.1-MIT)
@sentry/cli-win32-arm642.58.5FSL-1.1-MITFSL-1.1-MITnpm registry (confirmed FSL-1.1-MIT)
@sentry/cli-win32-i6862.58.5FSL-1.1-MITFSL-1.1-MITnpm registry (confirmed FSL-1.1-MIT)
@sentry/cli-win32-x642.58.5FSL-1.1-MITFSL-1.1-MITnpm registry (confirmed FSL-1.1-MIT)

@brendan-kellam
brendan-kellam merged commit ff4b389 into mainJul 1, 2026
10 of 11 checks passed
@brendan-kellam
brendan-kellam deleted the brendan/sou-948-access-token-scope-validation-in-resource-server branch July 1, 2026 03:11
@github-actionsgithub-actionsBot mentioned this pull request Jul 1, 2026
Sign up for freeto 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.

1 participant

@brendan-kellam
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

chore(web): Validate OAuth scopes for MCP access - #1396

Merged
brendan-kellam merged 11 commits into
mainfrom
brendan/sou-948-access-token-scope-validation-in-resource-server
Jul 1, 2026
Merged

chore(web): Validate OAuth scopes for MCP access#1396
brendan-kellam merged 11 commits into
mainfrom
brendan/sou-948-access-token-scope-validation-in-resource-server

Conversation

@brendan-kellam

@brendan-kellambrendan-kellam commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Add the groundwork for validating OAuth scopes per endpoint.

@coderabbitai

coderabbitaiBot commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds mcp OAuth scope support across OAuth discovery, authorization, token issuance, auth middleware, and the MCP route. It also stores scope on authorization codes and updates tests, mocks, and changelog entries.

Changes

MCP OAuth Scope Enforcement

Layer / File(s)Summary
Scope constants and utilities
packages/web/src/ee/features/oauth/constants.ts, packages/web/src/ee/features/oauth/utils.ts, packages/web/src/lib/errorCodes.ts, packages/web/src/lib/serviceError.ts, packages/web/src/ee/features/oauth/utils.test.ts
Adds the Sourcebot OAuth scope constants and type, plus scope parsing, formatting, validation, redirect checks, the insufficient-scope error code, and the matching service error helper.
Authorization code scope storage
packages/db/prisma/schema.prisma, packages/db/prisma/migrations/20260630005335_add_oauth_scope_to_authorization_code/migration.sql
Adds a scope field to OAuthAuthorizationCode in Prisma and the matching migration.
Token issuance: scope persistence and return
packages/web/src/ee/features/oauth/server.ts, packages/web/src/app/api/(server)/ee/oauth/token/route.ts, packages/web/src/ee/features/oauth/server.test.ts
generateAndStoreAuthCode now stores scope, token exchange/rotation return and persist scope, and the token endpoint returns helper-provided scope and expiry values.
Auth middleware: scope parsing and enforcement
packages/web/src/middleware/withAuth.ts, packages/web/src/middleware/withAuth.test.ts
withAuth, withOptionalAuth, and getAuthContext accept requiredOAuthScopes; bearer tokens expose parsed oauthScopes, and missing scopes return insufficientOAuthScope.
Consent and authorization flow: scope propagation
packages/web/src/app/oauth/authorize/page.tsx, packages/web/src/app/oauth/authorize/components/consentScreen.tsx, packages/web/src/ee/features/oauth/actions.ts
The authorize page reads and validates the scope query parameter, passes it into consent, and approveAuthorization validates it before continuing.
MCP route: scope enforcement and WWW-Authenticate challenges
packages/web/src/app/api/(server)/ee/mcp/route.ts
The MCP POST and DELETE routes require the mcp scope, and error responses add scope-aware Bearer and DPoP WWW-Authenticate challenges.
Discovery metadata, mocks, and changelog
packages/web/src/app/api/(server)/ee/.well-known/oauth-authorization-server/route.ts, packages/web/src/app/api/(server)/ee/.well-known/oauth-protected-resource/[...path]/route.ts, packages/web/src/__mocks__/prisma.ts, packages/web/src/lib/apiHandler.test.ts, CHANGELOG.md
OAuth discovery routes advertise scopes_supported, Prisma mocks use mcp scope values, an API handler test callback signature is adjusted, and the changelog records the validation change.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • sourcebot-dev/sourcebot#977: Extends the OAuth token exchange and MCP authorization-server flow that this PR updates for scope persistence and enforcement.
  • sourcebot-dev/sourcebot#985: Also changes the OAuth discovery and protected-resource metadata routes that now publish supported scopes here.
  • sourcebot-dev/sourcebot#1395: Modifies the MCP WWW-Authenticate response path that this PR extends for insufficient-scope handling.

Suggested reviewers

  • jsourcebot
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 35.29% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Title check✅ PassedThe title clearly summarizes the main change: validating OAuth scopes for MCP access.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch brendan/sou-948-access-token-scope-validation-in-resource-server

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.

…s-token-scope-validation-in-resource-server
# Conflicts:
#	packages/web/src/app/api/(server)/ee/.well-known/oauth-protected-resource/[...path]/route.ts
#	packages/web/src/app/api/(server)/ee/mcp/route.ts
#	packages/web/src/app/api/(server)/ee/oauth/token/route.ts
#	packages/web/src/app/oauth/authorize/components/consentScreen.tsx
#	packages/web/src/app/oauth/authorize/page.tsx
#	packages/web/src/ee/features/oauth/actions.ts
#	packages/web/src/ee/features/oauth/server.ts
#	packages/web/src/middleware/withAuth.test.ts
#	packages/web/src/middleware/withAuth.ts
@brendan-kellam
brendan-kellam marked this pull request as ready for review June 30, 2026 00:04

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/web/src/app/oauth/authorize/page.tsx`:
- Line 20: Normalize the OAuth `scope` query param in `authorize/page.tsx`
before passing it to `resolveGrantedOAuthScopes()`, since Next.js 16 may provide
repeated values as `string[]` and that helper will fail on arrays. Update the
page’s query-param handling to treat `scope` the same way as `resource` and
`dpop_jkt`, and replace the unsafe `new URLSearchParams(params as Record<string,
string>)` usage for the callback URL with explicit string-safe construction so
array values are handled correctly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6369ba85-ed38-430b-9192-af298b6e071d

📥 Commits

Reviewing files that changed from the base of the PR and between 5d5a063 and 91d5959.

📒 Files selected for processing (20)
  • CHANGELOG.md
  • packages/db/prisma/migrations/20260629190000_backfill_sourcebot_mcp_oauth_scope/migration.sql
  • packages/db/prisma/schema.prisma
  • packages/web/src/__mocks__/prisma.ts
  • packages/web/src/app/api/(server)/ee/.well-known/oauth-authorization-server/route.ts
  • packages/web/src/app/api/(server)/ee/.well-known/oauth-protected-resource/[...path]/route.ts
  • packages/web/src/app/api/(server)/ee/mcp/route.ts
  • packages/web/src/app/api/(server)/ee/oauth/token/route.ts
  • packages/web/src/app/oauth/authorize/components/consentScreen.tsx
  • packages/web/src/app/oauth/authorize/page.tsx
  • packages/web/src/ee/features/oauth/actions.ts
  • packages/web/src/ee/features/oauth/constants.test.ts
  • packages/web/src/ee/features/oauth/constants.ts
  • packages/web/src/ee/features/oauth/server.test.ts
  • packages/web/src/ee/features/oauth/server.ts
  • packages/web/src/lib/apiHandler.test.ts
  • packages/web/src/lib/errorCodes.ts
  • packages/web/src/lib/serviceError.ts
  • packages/web/src/middleware/withAuth.test.ts
  • packages/web/src/middleware/withAuth.ts

Comment threadpackages/web/src/app/oauth/authorize/page.tsx

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@packages/db/prisma/migrations/20260630005335_add_oauth_scope_to_authorization_code/migration.sql`:
- Line 2: The new scope field for OAuth authorization codes is defaulting to an
empty string, which will propagate the wrong scope into tokens; update the
authorization-code migration to backfill with the canonical MCP scope instead,
and make the same default change in the Prisma schema so
`OAuthAuthorizationCode` consistently uses `mcp` for existing and future rows.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 0c8407ea-82ef-47b6-a4be-2bea5dd475d4

📥 Commits

Reviewing files that changed from the base of the PR and between 6e14b50 and 17f79b3.

📒 Files selected for processing (11)
  • packages/db/prisma/migrations/20260630005335_add_oauth_scope_to_authorization_code/migration.sql
  • packages/db/prisma/schema.prisma
  • packages/web/src/app/oauth/authorize/components/consentScreen.tsx
  • packages/web/src/app/oauth/authorize/page.tsx
  • packages/web/src/ee/features/oauth/actions.ts
  • packages/web/src/ee/features/oauth/constants.ts
  • packages/web/src/ee/features/oauth/server.test.ts
  • packages/web/src/ee/features/oauth/server.ts
  • packages/web/src/ee/features/oauth/utils.test.ts
  • packages/web/src/ee/features/oauth/utils.ts
  • packages/web/src/middleware/withAuth.ts
✅ Files skipped from review due to trivial changes (1)
  • packages/web/src/ee/features/oauth/constants.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • packages/web/src/ee/features/oauth/actions.ts
  • packages/web/src/app/oauth/authorize/page.tsx
  • packages/web/src/app/oauth/authorize/components/consentScreen.tsx
  • packages/web/src/middleware/withAuth.ts

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Caution

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

⚠️ Outside diff range comments (1)
packages/web/src/app/api/(server)/ee/mcp/route.ts (1)

34-45: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Escape quoted WWW-Authenticate parameter values.

Line 44 injects error.message inside a quoted auth-param without escaping quotes or backslashes, which can produce a malformed OAuth challenge.

Proposed fix
+function quoteAuthParam(value: string): string {+ return `"${value.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`;+}+
function mcpOAuthChallenge(scheme: 'Bearer' | 'DPoP', error: ServiceError): string {
const issuer = env.AUTH_URL.replace(/\/$/, '');
const params = [
- 'realm="Sourcebot"',- `resource_metadata_uri="${issuer}/.well-known/oauth-protected-resource/api/mcp"`,- `scope="${SOURCEBOT_MCP_OAUTH_SCOPE}"`,+ `realm=${quoteAuthParam('Sourcebot')}`,+ `resource_metadata_uri=${quoteAuthParam(`${issuer}/.well-known/oauth-protected-resource/api/mcp`)}`,+ `scope=${quoteAuthParam(SOURCEBOT_MCP_OAUTH_SCOPE)}`,
];
if (error.errorCode === ErrorCode.OAUTH_INSUFFICIENT_SCOPE) {
params.push('error="insufficient_scope"');
- params.push(`error_description="${error.message}"`);+ params.push(`error_description=${quoteAuthParam(error.message)}`);
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/web/src/app/api/`(server)/ee/mcp/route.ts around lines 34 - 45, The
mcpOAuthChallenge helper is building a quoted WWW-Authenticate challenge value
with raw error.message, which can break the header when the message contains
quotes or backslashes. Update mcpOAuthChallenge to escape auth-param values
before pushing error_description (and any other quoted params) into the params
array, using a small helper or inline escaping in the same function. Keep the
fix localized to mcpOAuthChallenge and ensure the resulting header string
remains valid for OAuth errors.
🧹 Nitpick comments (1)
packages/web/src/app/api/(server)/ee/mcp/route.ts (1)

115-115: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use the canonical MCP OAuth scope constant for enforcement.

The challenge advertises SOURCEBOT_MCP_OAUTH_SCOPE, but the route enforces a separate 'mcp' literal. Use the same constant to avoid contract drift.

Proposed fix
- }, { requiredOAuthScopes: ['mcp'] })+ }, { requiredOAuthScopes: [SOURCEBOT_MCP_OAUTH_SCOPE] })
...
- }, { requiredOAuthScopes: ['mcp'] })+ }, { requiredOAuthScopes: [SOURCEBOT_MCP_OAUTH_SCOPE] })

Also applies to: 159-159

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/web/src/app/api/`(server)/ee/mcp/route.ts at line 115, The MCP route
is enforcing a hardcoded 'mcp' OAuth scope instead of the canonical
SOURCEBOT_MCP_OAUTH_SCOPE constant, which can drift from the advertised
challenge scope. Update the requiredOAuthScopes configuration in the MCP route
handler to use SOURCEBOT_MCP_OAUTH_SCOPE everywhere the scope is enforced,
including the other matching location in this file, so the challenge and route
stay aligned.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@packages/web/src/app/api/`(server)/ee/mcp/route.ts:
- Around line 34-45: The mcpOAuthChallenge helper is building a quoted
WWW-Authenticate challenge value with raw error.message, which can break the
header when the message contains quotes or backslashes. Update mcpOAuthChallenge
to escape auth-param values before pushing error_description (and any other
quoted params) into the params array, using a small helper or inline escaping in
the same function. Keep the fix localized to mcpOAuthChallenge and ensure the
resulting header string remains valid for OAuth errors.
---
Nitpick comments:
In `@packages/web/src/app/api/`(server)/ee/mcp/route.ts:
- Line 115: The MCP route is enforcing a hardcoded 'mcp' OAuth scope instead of
the canonical SOURCEBOT_MCP_OAUTH_SCOPE constant, which can drift from the
advertised challenge scope. Update the requiredOAuthScopes configuration in the
MCP route handler to use SOURCEBOT_MCP_OAUTH_SCOPE everywhere the scope is
enforced, including the other matching location in this file, so the challenge
and route stay aligned.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6cd0d0ab-85c8-4807-b647-11fec66e2cb5

📥 Commits

Reviewing files that changed from the base of the PR and between 17f79b3 and 090a2ae.

📒 Files selected for processing (4)
  • packages/web/src/app/api/(server)/ee/mcp/route.ts
  • packages/web/src/ee/features/oauth/constants.ts
  • packages/web/src/ee/features/oauth/server.ts
  • packages/web/src/middleware/withAuth.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/web/src/ee/features/oauth/constants.ts
  • packages/web/src/middleware/withAuth.ts

@brendan-kellambrendan-kellam changed the title SOU-948: Validate OAuth scopes for MCP accesschore(web): Validate OAuth scopes for MCP accessJun 30, 2026
@github-actions

Copy link
Copy Markdown
Contributor

License Audit

Status: FAIL

MetricCount
Total packages2224
Resolved (non-standard)17
Unresolved4
Strong copyleft0
Weak copyleft39

Fail Reasons

  • 4 packages have unresolvable licenses: @react-grab/cli@0.1.23, @react-grab/cli@0.1.29, @react-grab/mcp@0.1.29, element-source@0.0.3

Unresolved Packages

PackageVersionLicenseReason
@react-grab/cli0.1.23UNKNOWNNo license field on npm registry, no repository or homepage URL, and no LICENSE file could be located.
@react-grab/cli0.1.29UNKNOWNNo license field on npm registry, no repository or homepage URL, and no LICENSE file could be located.
@react-grab/mcp0.1.29UNKNOWNNo license field on npm registry, no repository or homepage URL, and no LICENSE file could be located.
element-source0.0.3UNKNOWNNo license field on npm registry, no repository or homepage URL, and no LICENSE file could be located.

Weak Copyleft Packages (informational)

PackageVersionLicense
@img/sharp-libvips-darwin-arm641.0.4LGPL-3.0-or-later
@img/sharp-libvips-darwin-arm641.2.4LGPL-3.0-or-later
@img/sharp-libvips-darwin-x641.0.4LGPL-3.0-or-later
@img/sharp-libvips-darwin-x641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-arm1.0.5LGPL-3.0-or-later
@img/sharp-libvips-linux-arm1.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-arm641.0.4LGPL-3.0-or-later
@img/sharp-libvips-linux-arm641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-ppc641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-riscv641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-s390x1.0.4LGPL-3.0-or-later
@img/sharp-libvips-linux-s390x1.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-x641.0.4LGPL-3.0-or-later
@img/sharp-libvips-linux-x641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linuxmusl-arm641.0.4LGPL-3.0-or-later
@img/sharp-libvips-linuxmusl-arm641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linuxmusl-x641.0.4LGPL-3.0-or-later
@img/sharp-libvips-linuxmusl-x641.2.4LGPL-3.0-or-later
@img/sharp-wasm320.33.5Apache-2.0 AND LGPL-3.0-or-later AND MIT
@img/sharp-wasm320.34.5Apache-2.0 AND LGPL-3.0-or-later AND MIT
@img/sharp-win32-arm640.34.5Apache-2.0 AND LGPL-3.0-or-later
@img/sharp-win32-ia320.33.5Apache-2.0 AND LGPL-3.0-or-later
@img/sharp-win32-ia320.34.5Apache-2.0 AND LGPL-3.0-or-later
@img/sharp-win32-x640.33.5Apache-2.0 AND LGPL-3.0-or-later
@img/sharp-win32-x640.34.5Apache-2.0 AND LGPL-3.0-or-later
axe-core4.10.3MPL-2.0
dompurify3.4.11(MPL-2.0 OR Apache-2.0)
lightningcss1.32.0MPL-2.0
lightningcss-android-arm641.32.0MPL-2.0
lightningcss-darwin-arm641.32.0MPL-2.0
lightningcss-darwin-x641.32.0MPL-2.0
lightningcss-freebsd-x641.32.0MPL-2.0
lightningcss-linux-arm-gnueabihf1.32.0MPL-2.0
lightningcss-linux-arm64-gnu1.32.0MPL-2.0
lightningcss-linux-arm64-musl1.32.0MPL-2.0
lightningcss-linux-x64-gnu1.32.0MPL-2.0
lightningcss-linux-x64-musl1.32.0MPL-2.0
lightningcss-win32-arm64-msvc1.32.0MPL-2.0
lightningcss-win32-x64-msvc1.32.0MPL-2.0
Resolved Packages (17)
PackageVersionOriginalResolvedSource
khroma2.1.0UNKNOWNMITGitHub repo (fabiospampinato/khroma LICENSE)
valid-url1.0.9UNKNOWNMITGitHub repo (ogt/valid-url LICENSE file states MIT)
map-stream0.1.0UNKNOWNMITGitHub repo (dominictarr/map-stream LICENSE)
codemirror-lang-elixir4.0.0UNKNOWNApache-2.0GitHub repo (livebook-dev/codemirror-lang-elixir LICENSE)
lezer-elixir1.1.2UNKNOWNApache-2.0GitHub repo (livebook-dev/lezer-elixir LICENSE)
memorystream0.3.1UNKNOWNMITextracted from object (npm 'licenses' field: [{type:'MIT',...}])
pause-stream0.0.11["MIT","Apache2"]MIT OR Apache-2.0extracted from object (npm 'license' array ["MIT","Apache2"])
posthog-js1.369.0SEE LICENSE IN LICENSEApache-2.0GitHub repo (PostHog/posthog-js LICENSE; primary Apache-2.0 with MIT for vendored code)
@sentry/cli2.58.5FSL-1.1-MITFSL-1.1-MITnpm registry (confirmed FSL-1.1-MIT, Functional Source License)
@sentry/cli-darwin2.58.5FSL-1.1-MITFSL-1.1-MITnpm registry (confirmed FSL-1.1-MIT)
@sentry/cli-linux-arm2.58.5FSL-1.1-MITFSL-1.1-MITnpm registry (confirmed FSL-1.1-MIT)
@sentry/cli-linux-arm642.58.5FSL-1.1-MITFSL-1.1-MITnpm registry (confirmed FSL-1.1-MIT)
@sentry/cli-linux-i6862.58.5FSL-1.1-MITFSL-1.1-MITnpm registry (confirmed FSL-1.1-MIT)
@sentry/cli-linux-x642.58.5FSL-1.1-MITFSL-1.1-MITnpm registry (confirmed FSL-1.1-MIT)
@sentry/cli-win32-arm642.58.5FSL-1.1-MITFSL-1.1-MITnpm registry (confirmed FSL-1.1-MIT)
@sentry/cli-win32-i6862.58.5FSL-1.1-MITFSL-1.1-MITnpm registry (confirmed FSL-1.1-MIT)
@sentry/cli-win32-x642.58.5FSL-1.1-MITFSL-1.1-MITnpm registry (confirmed FSL-1.1-MIT)

@brendan-kellam
brendan-kellam merged commit ff4b389 into mainJul 1, 2026
10 of 11 checks passed
@brendan-kellam
brendan-kellam deleted the brendan/sou-948-access-token-scope-validation-in-resource-server branch July 1, 2026 03:11
@github-actionsgithub-actionsBot mentioned this pull request Jul 1, 2026
Sign up for freeto 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.

1 participant

@brendan-kellam
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

chore(web): Validate OAuth scopes for MCP access - #1396

Merged
brendan-kellam merged 11 commits into
mainfrom
brendan/sou-948-access-token-scope-validation-in-resource-server
Jul 1, 2026
Merged

chore(web): Validate OAuth scopes for MCP access#1396
brendan-kellam merged 11 commits into
mainfrom
brendan/sou-948-access-token-scope-validation-in-resource-server

Conversation

@brendan-kellam

@brendan-kellambrendan-kellam commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Add the groundwork for validating OAuth scopes per endpoint.

@coderabbitai

coderabbitaiBot commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds mcp OAuth scope support across OAuth discovery, authorization, token issuance, auth middleware, and the MCP route. It also stores scope on authorization codes and updates tests, mocks, and changelog entries.

Changes

MCP OAuth Scope Enforcement

Layer / File(s)Summary
Scope constants and utilities
packages/web/src/ee/features/oauth/constants.ts, packages/web/src/ee/features/oauth/utils.ts, packages/web/src/lib/errorCodes.ts, packages/web/src/lib/serviceError.ts, packages/web/src/ee/features/oauth/utils.test.ts
Adds the Sourcebot OAuth scope constants and type, plus scope parsing, formatting, validation, redirect checks, the insufficient-scope error code, and the matching service error helper.
Authorization code scope storage
packages/db/prisma/schema.prisma, packages/db/prisma/migrations/20260630005335_add_oauth_scope_to_authorization_code/migration.sql
Adds a scope field to OAuthAuthorizationCode in Prisma and the matching migration.
Token issuance: scope persistence and return
packages/web/src/ee/features/oauth/server.ts, packages/web/src/app/api/(server)/ee/oauth/token/route.ts, packages/web/src/ee/features/oauth/server.test.ts
generateAndStoreAuthCode now stores scope, token exchange/rotation return and persist scope, and the token endpoint returns helper-provided scope and expiry values.
Auth middleware: scope parsing and enforcement
packages/web/src/middleware/withAuth.ts, packages/web/src/middleware/withAuth.test.ts
withAuth, withOptionalAuth, and getAuthContext accept requiredOAuthScopes; bearer tokens expose parsed oauthScopes, and missing scopes return insufficientOAuthScope.
Consent and authorization flow: scope propagation
packages/web/src/app/oauth/authorize/page.tsx, packages/web/src/app/oauth/authorize/components/consentScreen.tsx, packages/web/src/ee/features/oauth/actions.ts
The authorize page reads and validates the scope query parameter, passes it into consent, and approveAuthorization validates it before continuing.
MCP route: scope enforcement and WWW-Authenticate challenges
packages/web/src/app/api/(server)/ee/mcp/route.ts
The MCP POST and DELETE routes require the mcp scope, and error responses add scope-aware Bearer and DPoP WWW-Authenticate challenges.
Discovery metadata, mocks, and changelog
packages/web/src/app/api/(server)/ee/.well-known/oauth-authorization-server/route.ts, packages/web/src/app/api/(server)/ee/.well-known/oauth-protected-resource/[...path]/route.ts, packages/web/src/__mocks__/prisma.ts, packages/web/src/lib/apiHandler.test.ts, CHANGELOG.md
OAuth discovery routes advertise scopes_supported, Prisma mocks use mcp scope values, an API handler test callback signature is adjusted, and the changelog records the validation change.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • sourcebot-dev/sourcebot#977: Extends the OAuth token exchange and MCP authorization-server flow that this PR updates for scope persistence and enforcement.
  • sourcebot-dev/sourcebot#985: Also changes the OAuth discovery and protected-resource metadata routes that now publish supported scopes here.
  • sourcebot-dev/sourcebot#1395: Modifies the MCP WWW-Authenticate response path that this PR extends for insufficient-scope handling.

Suggested reviewers

  • jsourcebot
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 35.29% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Title check✅ PassedThe title clearly summarizes the main change: validating OAuth scopes for MCP access.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch brendan/sou-948-access-token-scope-validation-in-resource-server

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.

…s-token-scope-validation-in-resource-server
# Conflicts:
#	packages/web/src/app/api/(server)/ee/.well-known/oauth-protected-resource/[...path]/route.ts
#	packages/web/src/app/api/(server)/ee/mcp/route.ts
#	packages/web/src/app/api/(server)/ee/oauth/token/route.ts
#	packages/web/src/app/oauth/authorize/components/consentScreen.tsx
#	packages/web/src/app/oauth/authorize/page.tsx
#	packages/web/src/ee/features/oauth/actions.ts
#	packages/web/src/ee/features/oauth/server.ts
#	packages/web/src/middleware/withAuth.test.ts
#	packages/web/src/middleware/withAuth.ts
@brendan-kellam
brendan-kellam marked this pull request as ready for review June 30, 2026 00:04

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/web/src/app/oauth/authorize/page.tsx`:
- Line 20: Normalize the OAuth `scope` query param in `authorize/page.tsx`
before passing it to `resolveGrantedOAuthScopes()`, since Next.js 16 may provide
repeated values as `string[]` and that helper will fail on arrays. Update the
page’s query-param handling to treat `scope` the same way as `resource` and
`dpop_jkt`, and replace the unsafe `new URLSearchParams(params as Record<string,
string>)` usage for the callback URL with explicit string-safe construction so
array values are handled correctly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6369ba85-ed38-430b-9192-af298b6e071d

📥 Commits

Reviewing files that changed from the base of the PR and between 5d5a063 and 91d5959.

📒 Files selected for processing (20)
  • CHANGELOG.md
  • packages/db/prisma/migrations/20260629190000_backfill_sourcebot_mcp_oauth_scope/migration.sql
  • packages/db/prisma/schema.prisma
  • packages/web/src/__mocks__/prisma.ts
  • packages/web/src/app/api/(server)/ee/.well-known/oauth-authorization-server/route.ts
  • packages/web/src/app/api/(server)/ee/.well-known/oauth-protected-resource/[...path]/route.ts
  • packages/web/src/app/api/(server)/ee/mcp/route.ts
  • packages/web/src/app/api/(server)/ee/oauth/token/route.ts
  • packages/web/src/app/oauth/authorize/components/consentScreen.tsx
  • packages/web/src/app/oauth/authorize/page.tsx
  • packages/web/src/ee/features/oauth/actions.ts
  • packages/web/src/ee/features/oauth/constants.test.ts
  • packages/web/src/ee/features/oauth/constants.ts
  • packages/web/src/ee/features/oauth/server.test.ts
  • packages/web/src/ee/features/oauth/server.ts
  • packages/web/src/lib/apiHandler.test.ts
  • packages/web/src/lib/errorCodes.ts
  • packages/web/src/lib/serviceError.ts
  • packages/web/src/middleware/withAuth.test.ts
  • packages/web/src/middleware/withAuth.ts

Comment threadpackages/web/src/app/oauth/authorize/page.tsx

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@packages/db/prisma/migrations/20260630005335_add_oauth_scope_to_authorization_code/migration.sql`:
- Line 2: The new scope field for OAuth authorization codes is defaulting to an
empty string, which will propagate the wrong scope into tokens; update the
authorization-code migration to backfill with the canonical MCP scope instead,
and make the same default change in the Prisma schema so
`OAuthAuthorizationCode` consistently uses `mcp` for existing and future rows.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 0c8407ea-82ef-47b6-a4be-2bea5dd475d4

📥 Commits

Reviewing files that changed from the base of the PR and between 6e14b50 and 17f79b3.

📒 Files selected for processing (11)
  • packages/db/prisma/migrations/20260630005335_add_oauth_scope_to_authorization_code/migration.sql
  • packages/db/prisma/schema.prisma
  • packages/web/src/app/oauth/authorize/components/consentScreen.tsx
  • packages/web/src/app/oauth/authorize/page.tsx
  • packages/web/src/ee/features/oauth/actions.ts
  • packages/web/src/ee/features/oauth/constants.ts
  • packages/web/src/ee/features/oauth/server.test.ts
  • packages/web/src/ee/features/oauth/server.ts
  • packages/web/src/ee/features/oauth/utils.test.ts
  • packages/web/src/ee/features/oauth/utils.ts
  • packages/web/src/middleware/withAuth.ts
✅ Files skipped from review due to trivial changes (1)
  • packages/web/src/ee/features/oauth/constants.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • packages/web/src/ee/features/oauth/actions.ts
  • packages/web/src/app/oauth/authorize/page.tsx
  • packages/web/src/app/oauth/authorize/components/consentScreen.tsx
  • packages/web/src/middleware/withAuth.ts

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Caution

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

⚠️ Outside diff range comments (1)
packages/web/src/app/api/(server)/ee/mcp/route.ts (1)

34-45: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Escape quoted WWW-Authenticate parameter values.

Line 44 injects error.message inside a quoted auth-param without escaping quotes or backslashes, which can produce a malformed OAuth challenge.

Proposed fix
+function quoteAuthParam(value: string): string {+ return `"${value.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`;+}+
function mcpOAuthChallenge(scheme: 'Bearer' | 'DPoP', error: ServiceError): string {
const issuer = env.AUTH_URL.replace(/\/$/, '');
const params = [
- 'realm="Sourcebot"',- `resource_metadata_uri="${issuer}/.well-known/oauth-protected-resource/api/mcp"`,- `scope="${SOURCEBOT_MCP_OAUTH_SCOPE}"`,+ `realm=${quoteAuthParam('Sourcebot')}`,+ `resource_metadata_uri=${quoteAuthParam(`${issuer}/.well-known/oauth-protected-resource/api/mcp`)}`,+ `scope=${quoteAuthParam(SOURCEBOT_MCP_OAUTH_SCOPE)}`,
];
if (error.errorCode === ErrorCode.OAUTH_INSUFFICIENT_SCOPE) {
params.push('error="insufficient_scope"');
- params.push(`error_description="${error.message}"`);+ params.push(`error_description=${quoteAuthParam(error.message)}`);
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/web/src/app/api/`(server)/ee/mcp/route.ts around lines 34 - 45, The
mcpOAuthChallenge helper is building a quoted WWW-Authenticate challenge value
with raw error.message, which can break the header when the message contains
quotes or backslashes. Update mcpOAuthChallenge to escape auth-param values
before pushing error_description (and any other quoted params) into the params
array, using a small helper or inline escaping in the same function. Keep the
fix localized to mcpOAuthChallenge and ensure the resulting header string
remains valid for OAuth errors.
🧹 Nitpick comments (1)
packages/web/src/app/api/(server)/ee/mcp/route.ts (1)

115-115: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use the canonical MCP OAuth scope constant for enforcement.

The challenge advertises SOURCEBOT_MCP_OAUTH_SCOPE, but the route enforces a separate 'mcp' literal. Use the same constant to avoid contract drift.

Proposed fix
- }, { requiredOAuthScopes: ['mcp'] })+ }, { requiredOAuthScopes: [SOURCEBOT_MCP_OAUTH_SCOPE] })
...
- }, { requiredOAuthScopes: ['mcp'] })+ }, { requiredOAuthScopes: [SOURCEBOT_MCP_OAUTH_SCOPE] })

Also applies to: 159-159

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/web/src/app/api/`(server)/ee/mcp/route.ts at line 115, The MCP route
is enforcing a hardcoded 'mcp' OAuth scope instead of the canonical
SOURCEBOT_MCP_OAUTH_SCOPE constant, which can drift from the advertised
challenge scope. Update the requiredOAuthScopes configuration in the MCP route
handler to use SOURCEBOT_MCP_OAUTH_SCOPE everywhere the scope is enforced,
including the other matching location in this file, so the challenge and route
stay aligned.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@packages/web/src/app/api/`(server)/ee/mcp/route.ts:
- Around line 34-45: The mcpOAuthChallenge helper is building a quoted
WWW-Authenticate challenge value with raw error.message, which can break the
header when the message contains quotes or backslashes. Update mcpOAuthChallenge
to escape auth-param values before pushing error_description (and any other
quoted params) into the params array, using a small helper or inline escaping in
the same function. Keep the fix localized to mcpOAuthChallenge and ensure the
resulting header string remains valid for OAuth errors.
---
Nitpick comments:
In `@packages/web/src/app/api/`(server)/ee/mcp/route.ts:
- Line 115: The MCP route is enforcing a hardcoded 'mcp' OAuth scope instead of
the canonical SOURCEBOT_MCP_OAUTH_SCOPE constant, which can drift from the
advertised challenge scope. Update the requiredOAuthScopes configuration in the
MCP route handler to use SOURCEBOT_MCP_OAUTH_SCOPE everywhere the scope is
enforced, including the other matching location in this file, so the challenge
and route stay aligned.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6cd0d0ab-85c8-4807-b647-11fec66e2cb5

📥 Commits

Reviewing files that changed from the base of the PR and between 17f79b3 and 090a2ae.

📒 Files selected for processing (4)
  • packages/web/src/app/api/(server)/ee/mcp/route.ts
  • packages/web/src/ee/features/oauth/constants.ts
  • packages/web/src/ee/features/oauth/server.ts
  • packages/web/src/middleware/withAuth.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/web/src/ee/features/oauth/constants.ts
  • packages/web/src/middleware/withAuth.ts

@brendan-kellambrendan-kellam changed the title SOU-948: Validate OAuth scopes for MCP accesschore(web): Validate OAuth scopes for MCP accessJun 30, 2026
@github-actions

Copy link
Copy Markdown
Contributor

License Audit

Status: FAIL

MetricCount
Total packages2224
Resolved (non-standard)17
Unresolved4
Strong copyleft0
Weak copyleft39

Fail Reasons

  • 4 packages have unresolvable licenses: @react-grab/cli@0.1.23, @react-grab/cli@0.1.29, @react-grab/mcp@0.1.29, element-source@0.0.3

Unresolved Packages

PackageVersionLicenseReason
@react-grab/cli0.1.23UNKNOWNNo license field on npm registry, no repository or homepage URL, and no LICENSE file could be located.
@react-grab/cli0.1.29UNKNOWNNo license field on npm registry, no repository or homepage URL, and no LICENSE file could be located.
@react-grab/mcp0.1.29UNKNOWNNo license field on npm registry, no repository or homepage URL, and no LICENSE file could be located.
element-source0.0.3UNKNOWNNo license field on npm registry, no repository or homepage URL, and no LICENSE file could be located.

Weak Copyleft Packages (informational)

PackageVersionLicense
@img/sharp-libvips-darwin-arm641.0.4LGPL-3.0-or-later
@img/sharp-libvips-darwin-arm641.2.4LGPL-3.0-or-later
@img/sharp-libvips-darwin-x641.0.4LGPL-3.0-or-later
@img/sharp-libvips-darwin-x641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-arm1.0.5LGPL-3.0-or-later
@img/sharp-libvips-linux-arm1.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-arm641.0.4LGPL-3.0-or-later
@img/sharp-libvips-linux-arm641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-ppc641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-riscv641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-s390x1.0.4LGPL-3.0-or-later
@img/sharp-libvips-linux-s390x1.2.4LGPL-3.0-or-later
@img/sharp-libvips-linux-x641.0.4LGPL-3.0-or-later
@img/sharp-libvips-linux-x641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linuxmusl-arm641.0.4LGPL-3.0-or-later
@img/sharp-libvips-linuxmusl-arm641.2.4LGPL-3.0-or-later
@img/sharp-libvips-linuxmusl-x641.0.4LGPL-3.0-or-later
@img/sharp-libvips-linuxmusl-x641.2.4LGPL-3.0-or-later
@img/sharp-wasm320.33.5Apache-2.0 AND LGPL-3.0-or-later AND MIT
@img/sharp-wasm320.34.5Apache-2.0 AND LGPL-3.0-or-later AND MIT
@img/sharp-win32-arm640.34.5Apache-2.0 AND LGPL-3.0-or-later
@img/sharp-win32-ia320.33.5Apache-2.0 AND LGPL-3.0-or-later
@img/sharp-win32-ia320.34.5Apache-2.0 AND LGPL-3.0-or-later
@img/sharp-win32-x640.33.5Apache-2.0 AND LGPL-3.0-or-later
@img/sharp-win32-x640.34.5Apache-2.0 AND LGPL-3.0-or-later
axe-core4.10.3MPL-2.0
dompurify3.4.11(MPL-2.0 OR Apache-2.0)
lightningcss1.32.0MPL-2.0
lightningcss-android-arm641.32.0MPL-2.0
lightningcss-darwin-arm641.32.0MPL-2.0
lightningcss-darwin-x641.32.0MPL-2.0
lightningcss-freebsd-x641.32.0MPL-2.0
lightningcss-linux-arm-gnueabihf1.32.0MPL-2.0
lightningcss-linux-arm64-gnu1.32.0MPL-2.0
lightningcss-linux-arm64-musl1.32.0MPL-2.0
lightningcss-linux-x64-gnu1.32.0MPL-2.0
lightningcss-linux-x64-musl1.32.0MPL-2.0
lightningcss-win32-arm64-msvc1.32.0MPL-2.0
lightningcss-win32-x64-msvc1.32.0MPL-2.0
Resolved Packages (17)
PackageVersionOriginalResolvedSource
khroma2.1.0UNKNOWNMITGitHub repo (fabiospampinato/khroma LICENSE)
valid-url1.0.9UNKNOWNMITGitHub repo (ogt/valid-url LICENSE file states MIT)
map-stream0.1.0UNKNOWNMITGitHub repo (dominictarr/map-stream LICENSE)
codemirror-lang-elixir4.0.0UNKNOWNApache-2.0GitHub repo (livebook-dev/codemirror-lang-elixir LICENSE)
lezer-elixir1.1.2UNKNOWNApache-2.0GitHub repo (livebook-dev/lezer-elixir LICENSE)
memorystream0.3.1UNKNOWNMITextracted from object (npm 'licenses' field: [{type:'MIT',...}])
pause-stream0.0.11["MIT","Apache2"]MIT OR Apache-2.0extracted from object (npm 'license' array ["MIT","Apache2"])
posthog-js1.369.0SEE LICENSE IN LICENSEApache-2.0GitHub repo (PostHog/posthog-js LICENSE; primary Apache-2.0 with MIT for vendored code)
@sentry/cli2.58.5FSL-1.1-MITFSL-1.1-MITnpm registry (confirmed FSL-1.1-MIT, Functional Source License)
@sentry/cli-darwin2.58.5FSL-1.1-MITFSL-1.1-MITnpm registry (confirmed FSL-1.1-MIT)
@sentry/cli-linux-arm2.58.5FSL-1.1-MITFSL-1.1-MITnpm registry (confirmed FSL-1.1-MIT)
@sentry/cli-linux-arm642.58.5FSL-1.1-MITFSL-1.1-MITnpm registry (confirmed FSL-1.1-MIT)
@sentry/cli-linux-i6862.58.5FSL-1.1-MITFSL-1.1-MITnpm registry (confirmed FSL-1.1-MIT)
@sentry/cli-linux-x642.58.5FSL-1.1-MITFSL-1.1-MITnpm registry (confirmed FSL-1.1-MIT)
@sentry/cli-win32-arm642.58.5FSL-1.1-MITFSL-1.1-MITnpm registry (confirmed FSL-1.1-MIT)
@sentry/cli-win32-i6862.58.5FSL-1.1-MITFSL-1.1-MITnpm registry (confirmed FSL-1.1-MIT)
@sentry/cli-win32-x642.58.5FSL-1.1-MITFSL-1.1-MITnpm registry (confirmed FSL-1.1-MIT)

@brendan-kellam
brendan-kellam merged commit ff4b389 into mainJul 1, 2026
10 of 11 checks passed
@brendan-kellam
brendan-kellam deleted the brendan/sou-948-access-token-scope-validation-in-resource-server branch July 1, 2026 03:11
@github-actionsgithub-actionsBot mentioned this pull request Jul 1, 2026
Sign up for freeto 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.

1 participant

@brendan-kellam