Skip to content

Commit 6238d1e

Browse files
committed
added allowlist of args/flags
1 parent cb76139 commit 6238d1e

1 file changed

Lines changed: 67 additions & 0 deletions

File tree

browserstack/local.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,71 @@
1010
except:
1111
import pkg_resources
1212

13+
# LOC-6719 / INJ-002 (CWE-88): kwargs are forwarded to the BrowserStackLocal
14+
# binary as flags. Without this allowlist an attacker-influenced kwarg can
15+
# inject arbitrary flags — notably proxyHost / proxyPort / forceproxy to
16+
# redirect tunnel traffic through an attacker-controlled proxy.
17+
#
18+
# Cross-referenced against the binary's COMMAND_CONFIGURATION table
19+
# (browserStackTunnel/extensions/node/config/constants.js) and the public docs
20+
# at https://www.browserstack.com/docs/local-testing/binary-params. Both the
21+
# camelCase form ('name') and the kebab-case form ('alias') are accepted
22+
# because user code passes both shapes. Internal-only flags (visible:false:
23+
# region, bsHost, customRepeater, enterprise, public-interface-services,
24+
# identifier, trusted-hosts), help/version flags, and undocumented internal
25+
# flags (-r, -skipCheck, -daemonInstance, -tunnelIdentifier, -uniqueIdentifier)
26+
# are intentionally excluded.
27+
#
28+
# Wrapper-managed keys (key, binarypath, logfile, source) are stripped from
29+
# self.options in start() before this check runs and are not listed here.
30+
# 'daemon' / 'logFile' are also omitted because they are emitted unconditionally
31+
# by _generate_cmd and a user-supplied duplicate would conflict.
32+
ALLOWED_OPTIONS = frozenset({
33+
# Verbose / logging
34+
'v', 'vv', 'vvv', 'verbose',
35+
'enableLoggingForAPI', 'enable-logging-for-api',
36+
'enableUTCLogging', 'enable-utc-logging',
37+
# Folder testing
38+
'f', 'folder',
39+
# Force / start behaviour
40+
'force', 'F',
41+
'forcelocal', 'force-local', 'forceLocal',
42+
'forceproxy', 'force-proxy', 'forceProxy',
43+
'onlyAutomate', 'only-automate',
44+
# Host targeting / restriction
45+
'only',
46+
'include-hosts', 'exclude-hosts',
47+
'localIdentifier', 'local-identifier',
48+
'parallelRuns', 'parallel-runs',
49+
# Corporate proxy
50+
'proxyHost', 'proxy-host',
51+
'proxyPort', 'proxy-port',
52+
'proxyUser', 'proxy-user',
53+
'proxyPass', 'proxy-pass',
54+
'disableProxyDiscovery', 'disable-proxy-discovery',
55+
# Local proxy
56+
'localProxyHost', 'local-proxy-host',
57+
'localProxyPort', 'local-proxy-port',
58+
'localProxyUser', 'local-proxy-user',
59+
'localProxyPass', 'local-proxy-pass',
60+
# PAC / HTTPS / protocol
61+
'pacFile', 'pac-file',
62+
'https-ports',
63+
'client-protocol',
64+
# CA certificates
65+
'useCaCertificate', 'use-ca-certificate',
66+
'useSystemInstalledCa', 'use-system-installed-ca',
67+
# NTLM proxy
68+
'ntlm-username', 'ntlm-password', 'ntlm-domain', 'ntlm-workstation',
69+
# Dashboard / misc
70+
'disableDashboard', 'disable-dashboard',
71+
'config-file',
72+
'no-container',
73+
'connect-timeout',
74+
'timeout',
75+
'debug-utility', 'debug-url',
76+
})
77+
1378
class Local:
1479
def __init__(self, key=None, binary_path=None, **kwargs):
1580
self.key = os.environ['BROWSERSTACK_ACCESS_KEY'] if 'BROWSERSTACK_ACCESS_KEY' in os.environ else key
@@ -52,6 +117,8 @@ def get_package_version(self):
52117
def _generate_cmd(self):
53118
cmd = [self.binary_path, '-d', 'start', '-logFile', self.local_logfile_path, "-k", self.key, '--source', 'python:' + self.get_package_version()]
54119
for o in self.options.keys():
120+
if o not in ALLOWED_OPTIONS:
121+
raise BrowserStackLocalError('Unknown option: {}'.format(o))
55122
if self.options.get(o) is not None:
56123
cmd = cmd + self.__xstr(o, self.options.get(o))
57124
return cmd

0 commit comments

Comments
 (0)