Skip to content

Route OAuth token HTTP through hosted egress guard#1101

Closed
RhysSullivan wants to merge 1 commit into
mainfrom
fix/oauth-hosted-egress-guard
Closed

Route OAuth token HTTP through hosted egress guard#1101
RhysSullivan wants to merge 1 commit into
mainfrom
fix/oauth-hosted-egress-guard

Conversation

@RhysSullivan

@RhysSullivan RhysSullivan commented Jun 23, 2026

Copy link
Copy Markdown
Owner

What changed

  • Add a shared hosted Fetch API adapter for libraries that require fetch, using the same hosted outbound URL guard as the hosted HttpClient layer.
  • Pass that adapter into createExecutor as the generic fetch boundary while normal SDK and plugin HTTP stays on httpClientLayer.
  • Route OAuth token exchange, client-credentials, and refresh requests through that guarded fetch boundary.

Why

OAuth4WebAPI requires a fetch-compatible hook for token endpoint calls. Hosted executors need that forced boundary to use the same private-network guard as the rest of hosted outbound HTTP.

Validation

  • bun --bun vitest run src/oauth-helpers.test.ts src/oauth-flow.test.ts src/hosted-http-client.test.ts from packages/core/sdk
  • bun --bun run typecheck from packages/core/sdk
  • bun --bun run typecheck from packages/core/api

Stack

Base: fix/hosted-egress-dns-guard

Previous: #1100

Next: fix/mcp-hosted-egress-guard

@github-actions

github-actions Bot commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

Cloudflare preview

Torn down — the PR is closed.

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jun 23, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
executor-marketing f5906e3 Commit Preview URL

Branch Preview URL
Jun 23 2026, 06:12 PM

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jun 23, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
executor-cloud f5906e3 Jun 23 2026, 06:13 PM

@RhysSullivan RhysSullivan force-pushed the fix/hosted-egress-dns-guard branch from a575a3e to a186b3f Compare June 23, 2026 16:58
@pkg-pr-new

pkg-pr-new Bot commented Jun 23, 2026

Copy link
Copy Markdown

Open in StackBlitz

@executor-js/cli

npm i https://pkg.pr.new/@executor-js/cli@1101

@executor-js/config

npm i https://pkg.pr.new/@executor-js/config@1101

@executor-js/execution

npm i https://pkg.pr.new/@executor-js/execution@1101

@executor-js/sdk

npm i https://pkg.pr.new/@executor-js/sdk@1101

@executor-js/codemode-core

npm i https://pkg.pr.new/@executor-js/codemode-core@1101

@executor-js/runtime-quickjs

npm i https://pkg.pr.new/@executor-js/runtime-quickjs@1101

@executor-js/plugin-file-secrets

npm i https://pkg.pr.new/@executor-js/plugin-file-secrets@1101

@executor-js/plugin-graphql

npm i https://pkg.pr.new/@executor-js/plugin-graphql@1101

@executor-js/plugin-keychain

npm i https://pkg.pr.new/@executor-js/plugin-keychain@1101

@executor-js/plugin-mcp

npm i https://pkg.pr.new/@executor-js/plugin-mcp@1101

@executor-js/plugin-onepassword

npm i https://pkg.pr.new/@executor-js/plugin-onepassword@1101

@executor-js/plugin-openapi

npm i https://pkg.pr.new/@executor-js/plugin-openapi@1101

executor

npm i https://pkg.pr.new/executor@1101

commit: 26b8f48

@RhysSullivan RhysSullivan force-pushed the fix/oauth-hosted-egress-guard branch 2 times, most recently from fe019f2 to 02ee1b1 Compare June 23, 2026 17:35
@RhysSullivan RhysSullivan changed the title Route OAuth fetches through hosted egress guard Route OAuth token HTTP through hosted egress guard Jun 23, 2026
@RhysSullivan RhysSullivan force-pushed the fix/hosted-egress-dns-guard branch from a186b3f to f3d7be5 Compare June 23, 2026 18:00
@RhysSullivan RhysSullivan force-pushed the fix/oauth-hosted-egress-guard branch from 02ee1b1 to 26b8f48 Compare June 23, 2026 18:00
@RhysSullivan RhysSullivan marked this pull request as ready for review June 23, 2026 18:02
@RhysSullivan RhysSullivan force-pushed the fix/hosted-egress-dns-guard branch from f3d7be5 to 14f2dbb Compare June 23, 2026 18:05
@greptile-apps

greptile-apps Bot commented Jun 23, 2026

Copy link
Copy Markdown

Greptile Summary

This PR routes OAuth token-endpoint HTTP (authorization-code exchange, client-credentials, and token refresh) through the same DNS/SSRF guard that already protects the hosted HttpClient layer, by introducing makeHostedFetch — a thin guardFetch-backed adapter for libraries that consume a raw fetch function (here, oauth4webapi via its customFetch symbol).

  • New makeHostedFetch adapter (hosted-http-client.ts): reuses guardFetch and validateHostedOutboundUrl so both the HttpClient layer and the new fetch boundary share identical redirect validation and private-IP blocking logic.
  • fetch threaded through ExecutorConfigexecutor.ts → oauth helpers: all three grant helpers (exchangeAuthorizationCode, exchangeClientCredentials, refreshAccessToken) now accept an optional fetch field forwarded to oauth4webapi's customFetch option.
  • OAuthServiceDeps updated (oauth-service.ts): the fetch boundary propagates into start/complete paths so hosted service-level OAuth calls are also guarded.

Confidence Score: 5/5

Safe to merge — the change is additive, narrows the attack surface for OAuth token-endpoint SSRF, and does not alter any non-hosted code paths.

All three OAuth grant helpers now receive the guarded fetch on the hosted path, and none of the existing non-hosted paths are affected. The makeHostedFetch adapter reuses the same guardFetch/validateHostedOutboundUrl logic already exercised by the HTTP client layer, so there is no new trust boundary being created. The DNS-block test in hosted-http-client.test.ts confirms the guard fires before the underlying fetch is called. No call-sites were missed — exchangeAuthorizationCode goes only through OAuthService, and both direct call-sites in executor.ts are updated.

No files require special attention.

Important Files Changed

Filename Overview
packages/core/sdk/src/hosted-http-client.ts Adds makeHostedFetch, a two-line wrapper over the existing guardFetch that exposes a guarded Fetch API adapter; consistent with makeHostedHttpClientLayer and reuses the same DNS-resolution default.
packages/core/sdk/src/oauth-helpers.ts Adds optional fetch field to all three grant-helper input types and forwards it to oauth4webapi via oauth.customFetch symbol in oauth4webapiRequestOptions; type-cast approach is idiomatic given Record<string, unknown> cannot accept Symbol keys directly.
packages/core/sdk/src/oauth-service.ts Adds fetch to OAuthServiceDeps and threads it into both exchangeClientCredentials (start path) and exchangeAuthorizationCode (complete path); refreshAccessToken is handled upstream in executor.ts.
packages/core/sdk/src/executor.ts Adds fetch to ExecutorConfig, threads it to both direct token-exchange call-sites (exchangeClientCredentials, refreshAccessToken) and into makeOAuthService; coverage is complete for all OAuth code paths in this file.
packages/core/api/src/server/scoped-executor.ts Extracts hostedHttpOptions into a shared object so both makeHostedHttpClientLayer and makeHostedFetch receive the same allowLocalNetwork setting; passes the resulting hostedFetch to createExecutor.
packages/core/sdk/src/hosted-http-client.test.ts Adds a targeted test verifying that makeHostedFetch blocks requests resolving to a private IP before the underlying fetch is called; correctly uses a custom resolveHostname to avoid real DNS lookups.
packages/core/sdk/src/oauth-helpers.test.ts Adds a test verifying all three grant helpers route requests through an injected custom fetch; test is placed inside describe("exchangeClientCredentials") while exercising all three helpers, which can mislead readers about coverage scope.
packages/core/sdk/src/host-internal.ts Re-exports makeHostedFetch from the host-internal boundary module; comment updated to match.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant SE as scoped-executor
    participant CE as createExecutor
    participant OS as OAuthService
    participant OH as oauth-helpers
    participant GF as guardFetch (DNS guard)
    participant O4W as oauth4webapi
    participant TE as Token Endpoint

    SE->>SE: makeHostedFetch(hostedHttpOptions)
    SE->>CE: "createExecutor({ fetch: hostedFetch, httpClientLayer })"

    Note over CE: Token refresh / client-credentials path
    CE->>OH: "exchangeClientCredentials({ fetch: config.fetch })"
    CE->>OH: "refreshAccessToken({ fetch: config.fetch })"
    OH->>O4W: "customFetch = hostedFetch"
    O4W->>GF: hostedFetch(tokenUrl, init)
    GF->>GF: validateHostedOutboundUrl (DNS / private-IP check)
    GF->>TE: underlying fetch (redirect: manual)
    TE-->>GF: response
    GF-->>O4W: response

    Note over CE: Authorization-code path (via OAuthService)
    CE->>OS: "makeOAuthService({ fetch: config.fetch })"
    OS->>OH: "exchangeAuthorizationCode({ fetch })"
    OH->>O4W: "customFetch = hostedFetch"
    O4W->>GF: hostedFetch(tokenUrl, init)
    GF->>GF: validateHostedOutboundUrl
    GF->>TE: underlying fetch (redirect: manual)
    TE-->>GF: response
    GF-->>O4W: response
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant SE as scoped-executor
    participant CE as createExecutor
    participant OS as OAuthService
    participant OH as oauth-helpers
    participant GF as guardFetch (DNS guard)
    participant O4W as oauth4webapi
    participant TE as Token Endpoint

    SE->>SE: makeHostedFetch(hostedHttpOptions)
    SE->>CE: "createExecutor({ fetch: hostedFetch, httpClientLayer })"

    Note over CE: Token refresh / client-credentials path
    CE->>OH: "exchangeClientCredentials({ fetch: config.fetch })"
    CE->>OH: "refreshAccessToken({ fetch: config.fetch })"
    OH->>O4W: "customFetch = hostedFetch"
    O4W->>GF: hostedFetch(tokenUrl, init)
    GF->>GF: validateHostedOutboundUrl (DNS / private-IP check)
    GF->>TE: underlying fetch (redirect: manual)
    TE-->>GF: response
    GF-->>O4W: response

    Note over CE: Authorization-code path (via OAuthService)
    CE->>OS: "makeOAuthService({ fetch: config.fetch })"
    OS->>OH: "exchangeAuthorizationCode({ fetch })"
    OH->>O4W: "customFetch = hostedFetch"
    O4W->>GF: hostedFetch(tokenUrl, init)
    GF->>GF: validateHostedOutboundUrl
    GF->>TE: underlying fetch (redirect: manual)
    TE-->>GF: response
    GF-->>O4W: response
Loading

Reviews (2): Last reviewed commit: "Route OAuth token fetches through hosted..." | Re-trigger Greptile

Comment thread packages/core/sdk/src/oauth-helpers.test.ts
@RhysSullivan RhysSullivan changed the base branch from fix/hosted-egress-dns-guard to main June 23, 2026 18:09
@RhysSullivan RhysSullivan force-pushed the fix/oauth-hosted-egress-guard branch from 26b8f48 to f5906e3 Compare June 23, 2026 18:09
@RhysSullivan

Copy link
Copy Markdown
Owner Author

Superseded by batch merge #1106.

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