[miniflare] Authenticate remote bindings with Cloudflare Access service tokens#14198
Open
krys-cf wants to merge 2 commits into
Open
[miniflare] Authenticate remote bindings with Cloudflare Access service tokens#14198krys-cf wants to merge 2 commits into
krys-cf wants to merge 2 commits into
Conversation
…ce tokens When the workers.dev domain is behind Cloudflare Access, remote bindings (AI, Vectorize, Images, Artifacts, etc.) failed with 401/403 because the proxy client never sent Access credentials. Attach CF-Access-Client-Id / CF-Access-Client-Secret (from CLOUDFLARE_ACCESS_CLIENT_ID / CLOUDFLARE_ACCESS_CLIENT_SECRET env vars) to both: - the HTTP makeFetch path (wrapped-fetcher bindings: AI, Vectorize, Images) - the capnweb WebSocket path (RPC bindings: Artifacts) via a fetch()-based Upgrade so headers ride the handshake (new WebSocket(url) cannot set headers) Consistent with getAccessHeaders() which already reads these env vars for the realish-preview HTTP path. No behavior change when the env vars are unset.
🦋 Changeset detectedLatest commit: 4e7793c The changes in this PR will be included in the next version bump. This PR includes changesets to release 6 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
Contributor
|
Codeowners approval required for this PR:
Show detailed file reviewers
|
- Return undefined for 'then' while the capnweb WebSocket is connecting so the proxy is not treated as a thenable (an await would otherwise dispatch a bogus remote 'then' RPC). - Attach a no-op .catch() to stubPromise so a failed WS upgrade isn't an unhandled rejection when only the .fetch() path is used; the error still surfaces on RPC access via the awaited stubPromise.
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.
What
When
wrangler dev(or the Vite plugin) uses remote bindings against a Worker whose*.workers.devdomain is protected by Cloudflare Access, requests from the local remote-bindings proxy client to the remote proxy server are rejected with a401/403. This breaks every remote binding behind Access — Workers AI, AI Gateway, Vectorize, Images, Artifacts, etc.This builds on #14008 / #14011 (which fixed Access service-token auth for the realish-preview HTTP path and added a block warning) by also authenticating the binding proxy traffic itself. There are two distinct code paths and both needed fixing:
makeFetchCF-Access-Client-Id/CF-Access-Client-Secretheaders to the request to the proxy servermakeRemoteProxyStubfetch()upgrade (Upgrade: websocket) so the Access headers ride the handshake —new WebSocket(url)cannot set request headers in the Workers runtimeCredentials are read from
CLOUDFLARE_ACCESS_CLIENT_ID/CLOUDFLARE_ACCESS_CLIENT_SECRET— the same Service Token env vars thatgetAccessHeaders()already uses for the realish-preview HTTP path (packages/wrangler/src/user/access.ts), so this is consistent with existing wrangler conventions. They're forwarded to the proxy client worker as text bindings (accessClientId/accessClientSecret).When the env vars are unset, behaviour is unchanged.
Why the WebSocket change
capnweb'snewWebSocketRpcSessionaccepts either a URL string (which it upgrades withnew WebSocket(url)— no header support) or a pre-connectedWebSocket. When Access credentials are present we do the upgrade ourselves viafetch(httpUrl, { headers: { Upgrade: "websocket", ...accessHeaders } })and hand the resultingresponse.webSocketto capnweb. RPC methods are async over the network, so awaiting the authenticated upgrade is transparent to callers (e.g.await env.ARTIFACTS.<method>()).Testing
Verified end-to-end against a real internal app (D1/Vectorize/AI/Artifacts, all
remote: true) on aworkers.devaccount protected by an Access policy:✅ Workers AI + AI Gateway (course generation) — was failing with
InferenceUpstreamError: invalid_token, now succeeds✅ Vectorize query + AI embeddings (vector search) — returns ranked results
✅ Artifacts git read/write over capnweb RPC — commits succeed and content reads back
✅ Confirmed by elimination: patch + creds → works; creds only →
invalid_token; patch only →invalid_token✅
pnpm --filter miniflare buildpasses with 0 type errorsTests included/updated — not added (see note below)
Public documentation — the existing Service Token docs (added in [Workers] Document Access Service Auth setup for remote bindings cloudflare-docs#31021) already cover
CLOUDFLARE_ACCESS_CLIENT_ID/CLOUDFLARE_ACCESS_CLIENT_SECRET; this extends the same mechanism to all remote bindings.