Repository navigation
fix: validate mcp.issuer is a full origin at config load (#139) - #152
Merged
Merged
Conversation
The existing check only caught a missing issuer when mcp.enabled. A schemeless value like `as.example.com` or a path-bearing one like `https://host/base` would pass the check but produce malformed JWT `iss` claims and broken endpoint URLs downstream (resolveIssuer returns it verbatim; buildAuthorizationServerMetadata prefixes every endpoint URL with it; protectedResourceMetadataUrl calls new URL() on the composed resource URI). Add a fast-fail validation: when mcp.issuer is present, parse it with new URL(), assert the protocol is http/https, and assert there is no path beyond a trailing slash, no query, and no fragment. Trailing slash is tolerated (resolveIssuer strips it). Error message names the bad value and the requirement. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Code Review
This pull request adds validation for the mcpConfig.issuer option to ensure it is a valid absolute http(s) origin without paths, queries, or fragments, along with corresponding unit tests. The reviewer suggested refactoring the validation logic to avoid duplicating the error message across multiple checks.
Contributor
|
Reviewed; no blockers found. |
Contributor
|
Reviewed; no blockers found. Prior 1 finding resolved by e0c11cd. |
Addresses Gemini review on #152 — parse once and evaluate all origin constraints together, throwing one error, instead of three duplicated messages. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The issuer-format check ran whenever mcp.issuer was present, so enabled: false with a stale/invalid issuer would throw at startup and leave stale MCP state on live reload — a regression of the enabled master switch. Gate the format validation on mcpConfig.enabled and add a regression test (disabled + invalid issuer must start cleanly). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…entials Gemini review on #152: add explicit tests for query-string and fragment rejection (previously only schemeless/path were tested), and reject userinfo (user:pass@host) which is not part of an origin and would leak into iss / endpoint URLs. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
heskew
marked this pull request as ready for review
July 1, 2026 18:39
This was referenced Jul 2, 2026
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Turns the "pin a full origin" documentation convention for
mcp.issuerinto an enforced invariant. Whenmcp.issueris set,updateConfigurationnow validates it is an absolutehttp(s)origin — parseable bynew URL(),http:orhttps:scheme, no path (beyond a trailing slash), no query, no fragment — and throws a clear error otherwise.The existing presence check (
mcp.enabled && !mcp.issuer) is unchanged.Why origin-only
resolveIssuerreturnsmcpConfig.issuerverbatim (minus trailing slash).buildAuthorizationServerMetadataprefixes every endpoint URL with it (${issuer}/oauth/mcp/authorize, etc.).protectedResourceMetadataUrlcallsnew URL(resolveResource(...)). A path-bearing value likehttps://host/basewould silently producehttps://host/base/oauth/mcp/authorize— not the correct path shape — and a schemeless value likeas.example.comwould throw insidenew URL()at request time rather than at startup.Validation logic
Trailing slash tolerated —
resolveIssueralready strips it.Tests added (
test/options-watcher.test.js)https://app.example.com→ passeshttp://localhost:9926→ passeshttps://app.example.com/(trailing slash) → passesas.example.com(schemeless) → throws withmcp.issuer must be an absolute http(s) originhttps://app.example.com/base(path-bearing) → throwsPre-existing cases (enabled+no-issuer, resource-only-pinned) unchanged.
Closes #139
🤖 Generated with Claude Code — Claude Sonnet 4.6