fix(browser-runtime): block SSRF via SKILL_API.fetch bridge#222
Open
sebastiondev wants to merge 2 commits intoAIPexStudio:mainfrom
Open
fix(browser-runtime): block SSRF via SKILL_API.fetch bridge#222sebastiondev wants to merge 2 commits intoAIPexStudio:mainfrom
sebastiondev wants to merge 2 commits intoAIPexStudio:mainfrom
Conversation
Skills run as untrusted user-supplied code in a QuickJS VM but their fetch() bridge previously forwarded any URL to host fetch() in the extension service worker. A malicious skill could request internal addresses (cloud metadata 169.254.169.254, localhost services, RFC1918 ranges) and read the response. Add a URL guard that: - restricts schemes to http/https - rejects loopback, link-local, RFC1918, ULA, multicast, and reserved IPv4/IPv6 ranges (incl. ::ffff: IPv4-mapped forms) - rejects 'localhost' and .local/.internal/.localhost suffixes - forces redirect: 'error' so a public 3xx cannot escape the guard Includes unit tests covering the guard and the bridge integration.
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.
Summary
Block server-side request forgery (SSRF) via the
SKILL_API.fetchbridge that user-installed skills use to make HTTP requests from the host extension context.packages/browser-runtime/src/lib/vm/skill-api.ts(createSkillAPIBridge().fetch)Vulnerability
Skills run inside a QuickJS VM sandbox with a virtualized filesystem (ZenFS). The VM itself has no network primitives — the only way a skill can reach the network is the
SKILL_API.fetchbridge, which forwards the request to the hostfetchrunning in the extension service worker.Before this change, the bridge passed the URL to
fetch()unchanged. A malicious or compromised skill could therefore reach hosts that should never be reachable from skill code:http://169.254.169.254/latest/meta-data/...http://192.168.1.1/,http://10.0.0.1/http://127.0.0.1:<port>/,http://localhost/.local/.internal/.localhostsuffixes::ffff:127.0.0.1)file://The skill could then exfiltrate the response by calling
SKILL_API.fetchagain to an attacker-controlled origin.Data flow: skill JS in QuickJS VM →
SKILL_API.fetch(url, …)(attacker-controlledurl) → hostfetch()in the service worker → arbitrary internal endpoint.Fix
Add a single chokepoint in the bridge that validates URLs before they reach the host
fetch:url-guard.tsexportsassertSkillFetchUrlAllowed(url)which:http:orhttps:scheme.BLOCKED_HOSTNAMESset (localhost,ip6-localhost, etc.) and the.local/.internal/.localhostsuffixes.127.0.0.0/8,0.0.0.0/8,169.254.0.0/16,224.0.0.0/4,240.0.0.0/4.::1,::,fe80::/10,fc00::/7,ff00::/8, and IPv4-mapped IPv6 in both dotted (::ffff:127.0.0.1) and hex (::ffff:7f00:1) forms.skill-api.tscalls the guard, then setsredirect: "error"on the request so a public host cannot 3xx-redirect into the private range.The guard is placed at the VM↔host boundary — the only path skill code has to the network — so it cannot be bypassed within the existing architecture.
Tests
Added
packages/browser-runtime/src/lib/vm/skill-api.test.tswith coverage for both layers:assertSkillFetchUrlAllowed:file:,ftp:,chrome:, …)169.254.169.254cloud metadata10/8,172.16/12,192.168/16).local/.internal/.localhostsuffixes1.1.1.1,8.8.8.8)SKILL_API.fetchSSRF guard (with mocked hostfetch):file://scheme attemptsredirect: "error"(defense-in-depth)Repo
npm run preflightpasses (lint + typecheck + tests, includingnoUncheckedIndexedAccess).Why this is exploitable
The precondition is "user installs a malicious skill". That precondition alone does not grant network access to private ranges: the QuickJS VM has no networking, ZenFS is fully virtual, and
SKILL_API.downloadFiledefaults to a Save-As dialog. The fetch bridge is the unique path from skill code to the network, and before this fix it had no host/scheme allow-list. The fix therefore provides a real privilege reduction beyond what the precondition already grants.The known residual risk is DNS rebinding (a hostname that resolves to a public IP at validation time and to
127.0.0.1at connect time). That would require an additional DNS-rebinding control in the host environment to fully mitigate and is out of scope for this change.Adversarial review
Before submitting, we tried to disprove this. The QuickJS sandbox does block code execution and direct socket access, but it explicitly does not mediate the host-side
fetchbridge — that's the only network primitive exposed to skills, and prior to this patch it was unfiltered. We checked whether browser-level protections (CORS, mixed-content) would help: they don't, because thefetchruns in the extension service worker with extension privileges and bypasses CORS for permitted origins. The redirect-based bypass is closed byredirect: "error". The fix is at the right boundary and is the minimum change needed.cc @lewiswigmore