Repository navigation
feat(extension-core): serve binary fetch from the background for hosted file downloads - #122
Open
chrischall wants to merge 1 commit into
Open
chrischall wants to merge 1 commit into
chrischall wants to merge 1 commit into
Conversation
…ed file downloads Implements the extension half of chrischall/fetchproxy#460. A `fetch` with `init.responseType: 'base64'` now returns the response BYTES instead of `response.text()`'s lossy decode, so a hosted MCP can read a PDF, an office file or an image through the user's browser. - handlers/fetch-binary.ts: runs from the background service worker (event page on Safari), where host_permissions make CORS irrelevant, so a signed file host that sends no CORS headers is readable. Follows redirects. Scope: the request URL (checked by handleFetchRequest, as for text) AND the final URL after redirects must be on the MCP's declared domains; an off-domain final URL is cancelled unread and named by host only. Caps the body at MAX_BINARY_RESPONSE_BYTES (16 MiB) early on content-length and again while streaming, refusing with code 'response_too_large'. Relays content-type / content-length / content-disposition. 120 s transfer timeout. Chunked base64 encoding (JSC argument limit safe). - handlers/fetch.ts: routes responseType 'base64' there after the existing url / tabUrl domain checks; refuses inPage in this mode. Text mode is untouched. - socket.ts: the hello advertises `features: ['fetch_base64']` on every platform (unsigned, advisory; servers trust the answer's bodyEncoding marker). - docs: PRIVACY.md names the new network path, the host_permissions justification and extension-chrome README cover it, CLAUDE.md gotcha. - deps: @fetchproxy/protocol ^3.6.3 -> ^3.7.0 (first-party; the release carrying responseType, bodyEncoding, features and the size-refusal helpers). CI stays red until 3.7.0 is on npm. The lock entry has no integrity until then; `npm install` after the publish records it. No new permission. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012aZHZ6pnrdzrECCUaQW3gY
Contributor
|
✅ Auto-review verdict: pass — The background binary-fetch path checks scope correctly: the request URL and tabUrl are checked first, then the final URL after redirects, before any of the body is read. The size cap applies both up front and while streaming, and the tests cover these paths well. I found no blocking issues. The only open question is whether Reviewed commit No issues found. What I checked
Open questions (not counted toward the verdict)
Verdict: pass |
chrischall
enabled auto-merge (squash)
November 7, 2026 12:45
This branch has not been deployed
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.
This is the extension half of chrischall/fetchproxy#460. The fetchproxy half is chrischall/fetchproxy#461.
A
fetchwithinit.responseType: 'base64'now returns the response bytes, so a hosted MCP can read a PDF, an office file or an image through the user's browser. Before this,response.text()decoded every body lossily.What changes
background/handlers/fetch-binary.ts(new)host_permissionsmake CORS irrelevant there, so a signed file host that sends no CORS headers is readable. It follows redirects.handleFetchRequest, as for text) and the final URL after redirects must be on the MCP's declared domains. An off-domain final URL is cancelled before any of its body is read, and the error names its host only, so no token from the URL is echoed.MAX_BINARY_RESPONSE_BYTES(16 MiB), applied early oncontent-lengthand again while streaming. The refusal usescode: 'response_too_large'.content-type,content-lengthandcontent-disposition.handlers/fetch.ts: routesresponseType: 'base64'to the new handler after the existing url and tabUrl domain checks, and refusesinPagein that mode. Text mode is untouched.socket.ts: the hello advertisesfeatures: ['fetch_base64']on every platform. This is the one intended change to Chrome's hello bytes.host_permissionsjustification, the extension-chrome README and CLAUDE.md are updated too.@fetchproxy/protocolgoes from^3.6.3to^3.7.0, the release that will carry chrischall/fetchproxy#461. CI stays red until 3.7.0 is on npm. The lock entry has no integrity until then. Runnpm installafter the publish to record it, as was done for #25.Release order: merge fetchproxy#461 → release fetchproxy 3.7.0 (
npm view @fetchproxy/protocol version) → refresh this lockfile → merge this → release ContextMint Bridge. After that, submit to the Chrome Web Store and the App Store; Safari reaches users viaapple/'s TestFlight/App Store build.Tests
runBinaryFetch:redirect: 'follow', defaultcredentials: 'include', and'omit'are honoured;content-length(body never read);handleFetchRequest:validateInnerFrame;inPagewith base64 is refused;featuresis advertised on Chrome and Safari.Run locally against the fetchproxy#461 protocol build: typecheck,
npm test(117 files, 1642 tests) andnpm run build(Chrome and Safari) all pass.🤖 Generated with Claude Code
https://claude.ai/code/session_012aZHZ6pnrdzrECCUaQW3gY