fix(mv2): [temporary workaround] privileged api leakage due to same-origin#1469
Open
cyfung1031 wants to merge 2 commits into
Open
fix(mv2): [temporary workaround] privileged api leakage due to same-origin#1469cyfung1031 wants to merge 2 commits into
cyfung1031 wants to merge 2 commits into
Conversation
Contributor
There was a problem hiding this comment.
Pull request overview
This PR introduces a Firefox MV2-focused hardening path for background/scheduled userscripts (script types 2 and 3) to mitigate privileged WebExtensions API leakage that can occur in same-origin sandbox execution contexts.
Changes:
- Adds an
isSandboxmode toproxyContext(...)to prevent escaping viatop,parent, andframeElement. - For script types
2/3, rewrites the compiled script source to inject a prelude that attempts to shadow common escape hatches (chrome,browser,window.*, etc.). - Wires the sandbox flag from
ExecScriptinto the proxied execution context.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
src/runtime/content/exec_script.ts |
Detects background/scheduled scripts and injects a defensive prelude; passes isSandbox into proxyContext. |
src/runtime/content/utils.ts |
Extends proxyContext with isSandbox behavior for top/parent/frameElement get/has/set semantics. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
126
to
146
| get(_, name): any { | ||
| switch (name) { | ||
| case "window": | ||
| case "self": | ||
| case "globalThis": | ||
| // eslint-disable-next-line @typescript-eslint/no-unsafe-return | ||
| return proxy; | ||
| case "top": | ||
| case "parent": | ||
| if (isSandbox) return proxy; | ||
| if (global[name] === global.self) { | ||
| return special.global || proxy; | ||
| } | ||
| return global[name]; | ||
| // eslint-disable-next-line no-fallthrough | ||
| case "frameElement": | ||
| if (isSandbox) return null; | ||
| // eslint-disable-next-line no-fallthrough | ||
| default: | ||
| break; | ||
| } |
Comment on lines
+59
to
+78
| const scriptType = this.scriptRes.type; | ||
| let scriptCode = this.scriptRes.code; | ||
| if ((scriptType === 2 || scriptType === 3) && scriptCode.length > 0) { | ||
| isSandbox = true; | ||
| // background script / scheduled script | ||
| // we need to remove the access to chrome or browser in FirefoxMV2 | ||
| // since sandbox manifest support is still missing. | ||
| const arr = scriptCode.split("\n"); | ||
| for (let i = 0, l = arr.length; i < l; i++) { | ||
| const t = arr[i].trim(); | ||
| if (!t || t.startsWith("//")) continue; | ||
| const codeGen = (target: string, property: string, value: string): string => { | ||
| return `try { Object.defineProperty(${target}, "${property}", { get() { return ${value}; }, configurable: false }); } catch {}`; | ||
| }; | ||
| arr.splice(i, 0, `let chrome = {}, browser = undefined, frameElement = null, parent = window, top = window; try { window.parent = window; } catch {} ${codeGen("window", "parent", "this")} ${codeGen("window", "top", "this")} ${codeGen("window", "chrome", "{}")} ${codeGen("window", "browser", "undefined")} ${codeGen("window", "frameElement", "null")}`) | ||
| scriptCode = arr.join("\n"); | ||
| break; | ||
| } | ||
| } | ||
| this.scriptFunc = compileScript(scriptCode); |
CodFrm
reviewed
May 27, 2026
| } else { | ||
| // 构建脚本资源 | ||
| this.scriptFunc = compileScript(this.scriptRes.code); | ||
| const scriptType = this.scriptRes.type; |
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.
See #1470 and #1448
We have no choice in MV2....
Summary
This PR adds a temporary Firefox MV2 workaround to reduce privileged API leakage caused by the same-origin sandbox setup used for background/scheduled scripts.
After #1448, Firefox MV2 needs
sandbox="allow-same-origin allow-scripts"for compatibility, but that means the userscript execution context can still reach extension-origin globals. In particular, background scripts may access privileged APIs throughchrome,browser,parent,top, orframeElement.This change hardens the MV2 background/scheduled script runtime by shadowing and proxying those escape hatches.
Changes
src/runtime/content/exec_script.tsscriptRes.type === 2 || scriptRes.type === 3.chromeas{}browserasundefinedframeElementasnullparentaswindowtopaswindowwindow.*properties with guardedObject.defineProperty(...)calls.proxyContext(...).src/runtime/content/utils.tsisSandboxargument toproxyContext(...).topandparentresolve to the sandbox proxy instead of the real parent/top window.frameElementresolves tonull.frameElementis reported as present by thehastrap.top,parent, andframeElementare rejected.Motivation
Firefox MV2 currently lacks the sandbox manifest support needed for a cleaner isolation model. Because ScriptCat still needs to support Firefox MV2, this PR adds a runtime-level mitigation for the privileged API leakage described in #1470.
Security impact
This reduces accidental or direct access from background/scheduled userscripts to privileged extension APIs in Firefox MV2 same-origin execution contexts.
The workaround covers common access paths:
chrome,browser,parent,top,frameElementwindow.*accessinchecks for sandboxed globalsScope
This workaround applies only to script types
2and3, which are treated here as background/scheduled scripts.Normal content/page script execution should keep the previous proxy behavior.
Limitations
This is a temporary workaround, not a full browser-level sandbox replacement.
It relies on source rewriting and proxy traps, so future changes to script compilation or execution context creation should verify that these protections still run before userscript code executes.
Related
implement manifest sandbox support
Verification Background Script