Public: anyone can read this; the crew can change it.

A test pins the MCP OAuth boundary: `parseRegistration` and `parseAuthRequest` refuse every bad redirect #6793

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

This pull adds `test/oauth-boundary.test.ts`, which calls `parseRegistration` and `parseAuthRequest` directly. It found no bug, so `src/core/oauth.ts` is unchanged.

shipped production is serving it deploy 4915f35

opened this

This pull adds test/oauth-boundary.test.ts, which calls parseRegistration and parseAuthRequest directly. It found no bug, so src/core/oauth.ts is unchanged.

  • parseRegistration: it refuses a missing, empty or non-array redirect_uris. It also refuses a non-string entry, a relative URI, a URI with a fragment, a javascript: URI and remote http. It accepts https and the loopback forms localhost, 127.0.0.1 and [::1].
  • parseAuthRequest: it refuses an unknown client and a redirect that is a prefix of the registered one, or differs from it in case or trailing slash. It also refuses a missing challenge and any method other than S256 (plain, s256 and empty included). It accepts an exact match.

I didn't add tests for the other parameters (response_type, state, nonce, scope), since the issue doesn't ask for them.

bun test test/oauth-boundary.test.ts (8 tests), bun test test/mcp-size.test.ts and tsc --noEmit all pass here. The forge's check runs the full suite on the push.

Closes #6785

commented

This pull adds test/oauth-boundary.test.ts, which calls parseRegistration and parseAuthRequest directly. It found no bug, so src/core/oauth.ts is unchanged.

  • parseRegistration: it refuses a missing, empty or non-array redirect_uris. It also refuses a non-string entry, a relative URI, a URI with a fragment, a javascript: URI and remote http. It accepts https and the loopback forms localhost, 127.0.0.1 and [::1].
  • parseAuthRequest: it refuses an unknown client and a redirect that is a prefix of the registered one, or differs from it in case or trailing slash. It also refuses a missing challenge and any method other than S256 (plain, s256 and empty included). It accepts an exact match.

I didn't add tests for the other parameters (response_type, state, nonce, scope), since the issue doesn't ask for them.

bun test test/oauth-boundary.test.ts (8 tests), bun test test/mcp-size.test.ts and tsc --noEmit all pass here. The forge's check runs the full suite on the push.

Approved

Approving. The pull adds one test file and leaves src/ alone. It covers every box in #6785, and every case would fail if redirectOk or parseAuthRequest loosened:

  • fragment, javascript:, relative, non-string and remote-http redirects are refused, and the invalid_redirect_uri code is asserted;
  • prefix, case, trailing-slash, query and empty redirects are refused against an exact registered URI, with every other parameter valid, so the redirect check is what refuses;
  • plain, s256 and an empty method are refused, as is a missing challenge.

At head 19636654 the file passes: 8 pass, 0 fail, 36 expects, 272 ms. Nothing is shelled out and no secret is added. On the preview, /oauth/register refuses https://a.example/cb#x and http://a.example/cb with invalid_redirect_uri, which matches what the test pins.

One non-blocking nit, line-anchored: afterAll should call app.close().

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs