From c96b3c85abcff20b1d4f420aa8602a5029e2353a Mon Sep 17 00:00:00 2001 From: carfeii <277509339+carfeii@users.noreply.github.com> Date: Sun, 18 Oct 2026 14:12:53 +0800 Subject: [PATCH] Merge commit from fork proxy.username and proxy.password from the /v1 API were interpolated directly into the generated background.js via %s string formatting with no escaping, allowing a crafted value to break out of its string literal and execute arbitrary JavaScript inside the privileged MV3 extension service worker (host_permissions: , tabs, webRequest, storage, webRequestAuthProvider). Encode scheme/host/port/username/password with json.dumps() so none of them can escape their intended JS literal position. Co-authored-by: carfeii --- src/utils.py | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/src/utils.py b/src/utils.py index 242d45f..37640ac 100644 --- a/src/utils.py +++ b/src/utils.py @@ -85,14 +85,18 @@ def create_proxy_extension(proxy: dict) -> str: } """ + # Values below originate from the untrusted `proxy` object of the /v1 API + # request. They must be encoded as JSON string/number literals, never + # interpolated as raw text, so none of them can break out of their + # intended position in the generated JavaScript. background_js = """ var config = { mode: "fixed_servers", rules: { singleProxy: { - scheme: "%s", - host: "%s", - port: %d + scheme: %s, + host: %s, + port: %s }, bypassList: ["localhost"] } @@ -103,8 +107,8 @@ def create_proxy_extension(proxy: dict) -> str: function callbackFn(details) { return { authCredentials: { - username: "%s", - password: "%s" + username: %s, + password: %s } }; } @@ -115,11 +119,11 @@ def create_proxy_extension(proxy: dict) -> str: ['blocking'] ); """ % ( - scheme, - host, - port, - username, - password + json.dumps(scheme), + json.dumps(host), + json.dumps(port), + json.dumps(username), + json.dumps(password) ) proxy_extension_dir = tempfile.mkdtemp()