From cf269d6b6017d76e82c366ff0d787df139c256df Mon Sep 17 00:00:00 2001 From: daijro Date: Mon, 3 Feb 2025 20:49:42 -0600 Subject: [PATCH] feat: Force system principal scope access (forceScopeAccess) A workaround to restore some original Playwright functionality. Forces Playwright to run in "God mode", allowing it to bypass CORS restrictions, access shadow roots (with element.shadowRootUnl), access undocumented JS browser methods, modify DOM without `allowMainWorld`, & restore some unsupported Playwright functionality. Note: `forceScopeAccess` is not detectable/visible to the page unless the dom is directly modified (elements are added or removed). --- additions/juggler/content/FrameTree.js | 49 ++++++++++++++++++++------ patches/shadow-root-bypass.patch | 14 ++++++++ settings/camoucfg.jvv | 1 + settings/properties.json | 1 + 4 files changed, 54 insertions(+), 11 deletions(-) create mode 100644 patches/shadow-root-bypass.patch diff --git a/additions/juggler/content/FrameTree.js b/additions/juggler/content/FrameTree.js index 65672be..c67beb4 100644 --- a/additions/juggler/content/FrameTree.js +++ b/additions/juggler/content/FrameTree.js @@ -411,7 +411,11 @@ class Frame { this._parentFrame = parentFrame; parentFrame._children.add(this); } + + this.allowMW = ChromeUtils.camouGetBool('allowMainWorld', false); + this.forceScopeAccess = ChromeUtils.camouGetBool('forceScopeAccess', false); + this.masterSandbox = undefined; this._lastCommittedNavigationId = null; this._pendingNavigationId = null; @@ -505,21 +509,44 @@ class Frame { }; } - _createIsolatedContext(name) { - const principal = [this.domWindow()]; // extended principal - const sandbox = Cu.Sandbox(principal, { - sandboxPrototype: this.domWindow(), - wantComponents: false, - wantExportHelpers: false, - wantXrays: true, - }); + // Camoufox: Add a "God mode" master sandbox with it's own compartment + getMasterSandbox() { + if (!this.masterSandbox) { + this.masterSandbox = Cu.Sandbox( + Services.scriptSecurityManager.getSystemPrincipal(), + { + sandboxPrototype: this.domWindow(), + wantComponents: false, + wantExportHelpers: false, + wantXrays: true, + freshCompartment: true, + } + ); + } + return this.masterSandbox; + } + + _createIsolatedContext(name, useMaster=false) { + let sandbox; + // Camoufox: Use the master sandbox (with system principle scope access) + if (useMaster && this.forceScopeAccess) { + sandbox = this.getMasterSandbox(); + } else { + // Standard access (run in domWindow principal) + sandbox = Cu.Sandbox([this.domWindow()], { + sandboxPrototype: this.domWindow(), + wantComponents: false, + wantExportHelpers: false, + wantXrays: true, + }); + } const world = this._runtime.createExecutionContext(this.domWindow(), sandbox, { frameId: this.id(), name, }); // Camoufox: Create a main world for the isolated context - if (ChromeUtils.camouGetBool('allowMainWorld', false)) { - const mainWorld = this._runtime.createMW(this.domWindow(), this.domWindow()); + if (this.allowMW) { + const mainWorld = this._runtime.createMW(this.domWindow(), sandbox); world.mainEquivalent = mainWorld; } this._worldNameToContext.set(name, world); @@ -559,7 +586,7 @@ class Frame { this._runtime.destroyExecutionContext(context); this._worldNameToContext.clear(); // Camoufox: Scope the initial execution context to prevent leaks - this._createIsolatedContext(''); + this._createIsolatedContext('', true); for (const [name, world] of this._frameTree._isolatedWorlds) { if (name) this._createIsolatedContext(name); diff --git a/patches/shadow-root-bypass.patch b/patches/shadow-root-bypass.patch new file mode 100644 index 0000000..b8a9ac1 --- /dev/null +++ b/patches/shadow-root-bypass.patch @@ -0,0 +1,14 @@ +diff --git a/dom/webidl/Element.webidl b/dom/webidl/Element.webidl +index 7163fac826..c0c201e29d 100644 +--- a/dom/webidl/Element.webidl ++++ b/dom/webidl/Element.webidl +@@ -288,6 +288,9 @@ partial interface Element { + [BinaryName="shadowRootByMode"] + readonly attribute ShadowRoot? shadowRoot; + ++ [Func="Document::IsCallerChromeOrAddon", BinaryName="shadowRoot"] ++ readonly attribute ShadowRoot? shadowRootUnl; ++ + [Func="Document::IsCallerChromeOrAddon", BinaryName="shadowRoot"] + readonly attribute ShadowRoot? openOrClosedShadowRoot; + diff --git a/settings/camoucfg.jvv b/settings/camoucfg.jvv index 4ac015f..f87844f 100644 --- a/settings/camoucfg.jvv +++ b/settings/camoucfg.jvv @@ -292,6 +292,7 @@ "showcursor": "bool", "allowMainWorld": "bool", + "forceScopeAccess": "bool", "memorysaver": "bool", "addons": "array[str]", "debug": "bool" diff --git a/settings/properties.json b/settings/properties.json index 22262ed..f8bd8a2 100644 --- a/settings/properties.json +++ b/settings/properties.json @@ -94,6 +94,7 @@ { "property": "mediaDevices:speakers", "type": "uint" }, { "property": "mediaDevices:enabled", "type": "bool" }, { "property": "allowMainWorld", "type": "bool" }, + { "property": "forceScopeAccess", "type": "bool" }, { "property": "memorysaver", "type": "bool" }, { "property": "addons", "type": "array" }, { "property": "debug", "type": "bool" }