Skip to content

fix(sdk): add fetch timeouts to TokenVerifier JWKS and revocation requests#36

Merged
khaliqgant merged 1 commit intomainfrom
sdk/timeout-fix-fetch
Apr 24, 2026
Merged

fix(sdk): add fetch timeouts to TokenVerifier JWKS and revocation requests#36
khaliqgant merged 1 commit intomainfrom
sdk/timeout-fix-fetch

Conversation

@khaliqgant
Copy link
Copy Markdown
Member

@khaliqgant khaliqgant commented Apr 24, 2026

Summary

@relayauth/sdk TokenVerifier made its two HTTP fetches with no AbortSignal / timeout, so any slow or unresponsive relayauth endpoint would stall callers indefinitely. After cloud#329 migrated relayfile tokens from HS256 to RS256, every relayfile request verifies via JWKS — so JWKS hangs propagate everywhere.

This PR:

  • Wraps the JWKS fetch (packages/sdk/typescript/src/verify.ts) with AbortSignal.timeout() (default 5000ms).
  • Wraps the revocation fetch with AbortSignal.timeout() (default 5000ms).
  • Exposes both as optional VerifyOptions.jwksTimeoutMs and VerifyOptions.revocationTimeoutMs. Non-positive / non-finite values fall back to the default.
  • Adds packages/sdk/typescript/src/__tests__/verify-timeout.test.ts with three cases:
    • JWKS fetch aborts within the configured window when upstream stalls.
    • Default JWKS timeout is applied (an AbortSignal is attached) when no override is given.
    • Revocation fetch aborts within the configured window.

No version bumps in this PR — Khaliq is handling the publish step.

Test plan

  • cd packages/sdk/typescript && npm test — 141/141 pass (was 138 before, +3 new timeout tests).
  • npm run typecheck — clean.
  • New tests confirm timeout fires in <2s when upstream is slow; both fail-paths surface the existing RelayAuthError (jwks_fetch_failed / revocation_check_failed).

Notes

  • The behavior of the existing fail paths is unchanged — an aborted fetch still throws the same RelayAuthError that a network failure would. Callers that already handle jwks_fetch_failed / revocation_check_failed get the timeout case for free.
  • AbortSignal.timeout in Node uses an unref'd timer, so a naive test that relies solely on the abort signal can let the process exit before the timer fires. The new tests pair the abort signal with a refed setTimeout in the mock (cleared on abort) so the loop is held open and the test reliably asserts timing.

🤖 Generated with Claude Code


Open in Devin Review

…uests

The JWKS and revocation fetches in TokenVerifier had no AbortSignal /
timeout, so any slow or unresponsive relayauth endpoint could stall
callers indefinitely. After cloud migrated relayfile tokens to RS256,
every relayfile request verifies via JWKS, so a JWKS hang propagates
everywhere.

Add an AbortSignal.timeout() to both fetches, defaulting to 5000ms
each, plumbed through as optional `jwksTimeoutMs` and
`revocationTimeoutMs` on VerifyOptions. New unit tests cover:

- JWKS fetch aborts within the configured window when upstream stalls
- Default JWKS timeout is applied when no override is given (signal
  is still attached)
- Revocation fetch aborts within the configured window
@khaliqgant khaliqgant merged commit 987066b into main Apr 24, 2026
2 checks passed
@khaliqgant khaliqgant deleted the sdk/timeout-fix-fetch branch April 24, 2026 13:04
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 37409e41ba

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +118 to +120
signal: AbortSignal.timeout(
normalizeTimeoutMs(this.options?.jwksTimeoutMs, DEFAULT_JWKS_TIMEOUT_MS),
),
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Guard AbortSignal.timeout for runtimes without this API

This unconditionally calls AbortSignal.timeout(...), which throws in runtimes where fetch exists but the static timeout helper is not implemented (for example older browser/edge runtimes or polyfilled environments). In that case verification fails before any network request is made, so valid tokens are rejected with jwks_fetch_failed (and the same regression appears in #checkRevocation), which is a behavior break from the previous implementation that worked without this API. Add a feature check/fallback (e.g., AbortController + setTimeout, or omit the signal when timeout helpers are unavailable).

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

@devin-ai-integration devin-ai-integration Bot left a comment

Choose a reason for hiding this comment

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

Devin Review found 1 potential issue.

View 3 additional findings in Devin Review.

Open in Devin Review

Comment on lines +18 to +19
jwksTimeoutMs?: number;
revocationTimeoutMs?: number;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 New timeout options silently dropped by AI adapter's #buildVerifyOptions

The PR adds jwksTimeoutMs and revocationTimeoutMs to the exported VerifyOptions interface, but packages/ai/src/adapter.ts:507-527 (#buildVerifyOptions) explicitly enumerates which fields to forward to TokenVerifier and does not include the new fields. Since AdapterOptions extends Partial<VerifyOptions> (packages/ai/src/types.ts:29), TypeScript allows users to pass jwksTimeoutMs/revocationTimeoutMs through the adapter config, but at runtime these values are silently dropped — the verifier always uses the 5000ms defaults. Additionally, areVerifyOptionsEqual at packages/ai/src/adapter.ts:661-674 doesn't compare the new fields, so changing timeout options on a live adapter won't trigger verifier re-creation.

Prompt for agents
The new jwksTimeoutMs and revocationTimeoutMs fields were added to VerifyOptions in packages/sdk/typescript/src/verify.ts, but the downstream consumer in packages/ai/src/adapter.ts was not updated. Two places need changes:

1. #buildVerifyOptions() at packages/ai/src/adapter.ts:507-527 — add jwksTimeoutMs and revocationTimeoutMs to both the destructuring and the return object so they are forwarded to TokenVerifier.

2. areVerifyOptionsEqual() at packages/ai/src/adapter.ts:661-674 — add comparisons for previous.jwksTimeoutMs === next.jwksTimeoutMs and previous.revocationTimeoutMs === next.revocationTimeoutMs so that changing these options triggers verifier re-creation.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

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.

1 participant