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: <all_urls>, 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 <carfeii@users.noreply.github.com>
This commit is contained in:
carfeiiandcarfeii authored and GitHub committed 2026-10-18 07:12:53 +01:00
1 parent 9f6effd4a6
commit c96b3c85ab
1 file changed
+14 -10
+14 -10
View File
@@ -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()