Skip to content

Do not follow redirects when renewing an OAuth access token - #425

Merged
davidmckayv merged 1 commit into
CopilotKit:mainfrom
Ayush7614:fix/oauth-refresh-redirect-manual
Sep 8, 2026
Merged

Do not follow redirects when renewing an OAuth access token#425
davidmckayv merged 1 commit into
CopilotKit:mainfrom
Ayush7614:fix/oauth-refresh-redirect-manual

Conversation

@Ayush7614

Copy link
Copy Markdown
Contributor

What

exchangeRefreshTokenOverHttp in server/src/plugins/store.ts carries the deployment client secret and a user's refresh token to a catalogue-pinned tokenUrl. fetch follows redirects by default, so a compromised or misbehaving vendor answering 302 would hand both secrets to whatever address the answer named.

This sends the renewal with redirect: manual, exactly like the authorization-code redemption and the dynamic-client registration in server/src/plugins/oauth.ts already do, so a 3xx lands in the existing refusal branch instead of being followed.

Why it matters

Security boundary, not style: the third and last secret-carrying token path that followed redirects. A redirecting token endpoint is now a refusal (The vendor would not renew this access (302)) and only ever contacts the pinned endpoint.

Verification

  • New unit suite server/tests/oauth-refresh-redirect.test.ts: a 302 becomes a refusal with exactly one fetch to the pinned URL carrying redirect: manual; a 200 renewal still succeeds with redirect: manual.
  • bun test server/tests/oauth-refresh-redirect.test.ts — 2 pass.
  • bun test server/tests/plugin-oauth.test.ts — 42 pass (sibling redirect guards unchanged).
  • bun run --filter server typecheck — clean. Biome format + lint — clean.

exchangeRefreshTokenOverHttp carries the deployment client secret and
a user refresh token to a catalogue-pinned tokenUrl. fetch follows
redirects by default, so a compromised or misbehaving vendor 302 would
hand both secrets to whatever address the answer named.

Send the renewal with redirect manual, like the authorization-code
redemption and the dynamic-client registration already do, so a 3xx
lands in the refusal branch instead of being followed.

@davidmckayv davidmckayv 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.

Deep-reviewed against live code (correctness, governance, no vendor/secret/scale issues). Composed build+tests green. CHANGELOG/format rebase on CI-validated substance.

@davidmckayv
davidmckayv merged commit b3c69f4 into CopilotKit:main Sep 8, 2026
14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants