Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ bs_local.start(bs_local_args, function() {

Apart from the key, all other BrowserStack Local modifiers are optional. For the full list of modifiers, refer [BrowserStack Local modifiers](https://www.browserstack.com/local-testing#modifiers). For examples, refer below -

Only documented modifiers are forwarded to the binary. An unrecognised option key, an option the wrapper sets itself (`daemon`, `log-file`), or a value beginning with `-` is refused with an error rather than passed through to the `BrowserStackLocal` argv — otherwise any code that merges untrusted input into the options object could inject arbitrary flags into the binary.

#### Verbose Logging
To enable verbose logging -
```js
Expand Down
139 changes: 130 additions & 9 deletions lib/Local.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,68 @@ var childProcess = require('child_process'),
version = require('../package.json').version,
treeKill = require('tree-kill');

// Option keys this wrapper is allowed to forward verbatim to the
// BrowserStackLocal daemon. Mirrors the binary's own CLI definition
// (COMMAND_CONFIGURATION in browserStackTunnel, extensions/node/config/constants.js)
// — long names and their aliases — so every documented modifier keeps working
// while an unrecognised key can no longer reach the daemon argv.
//
// Deliberately a COMPLETE mirror, so it can be diffed against the binary's CLI
// when that gains a flag. Some entries (key, folder, force, only, forcelocal,
// verbose, onlyAutomate, proxyHost/Port/User/Pass, localIdentifier, forceproxy,
// logFile, parallelRuns) are handled by an explicit case in addArgs and so never
// reach this list at runtime; they are listed for completeness, not effect.
var PASSTHROUGH_OPTIONS = [

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[for-human] Two scope calls the PR flags but can't settle on its own — both need someone to decide, not more code here:

  1. Sibling bindings. browserstack-local-python / -ruby / -java / -php / -csharp carry the same addArgs catch-all shape. If F-007 applies there it needs its own tickets — none exist, and none of those repos are in scope for this work item. Worth confirming and filing before this closes, otherwise the same primitive stays open in five packages while the chain ticket reads as resolved.

  2. index.d.ts still declares [key: string]: string | boolean. TypeScript users get no compile-time signal for a key that now throws at runtime. Removing the index signature would break the documented modifiers the interface doesn't enumerate (pac-file, localProxyHost, region, …), so the real fix is to enumerate them — a typings pass, reasonably a follow-up rather than this PR.

Also for the record, since it changes what a caller sees: an unknown key is now a hard error rather than a silent no-op forward. LOC-6786 offered "silently ignore and warn" as an alternative and this PR deliberately didn't take it. I agree with that choice — a silent drop hides caller bugs — but it's a breaking change for anyone currently passing a typo'd or stale key, so it wants a minor-version bump and a changelog line, not a patch release.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Acknowledged — both are genuinely human calls and I'm not deciding either here. They're carried in the Jira ticket's assessment under "Not tested" / follow-ups (sibling bindings not in this repo or work item; index.d.ts index signature left as-is because removing it would break the documented modifiers the interface doesn't enumerate). No code change this round.

'key', 'folder', 'help', 'version', 'force', 'only',
'forcelocal', 'force-local',
'verbose',
'onlyAutomate', 'only-automate',
'proxyHost', 'proxy-host',
'proxyPort', 'proxy-port',
'proxyUser', 'proxy-user',
'proxyPass', 'proxy-pass',
'localIdentifier', 'local-identifier',
'forceproxy', 'force-proxy',
'region',
'localProxyHost', 'local-proxy-host',
'localProxyPort', 'local-proxy-port',
'localProxyUser', 'local-proxy-user',
'localProxyPass', 'local-proxy-pass',
'enableLoggingForAPI', 'enable-logging-for-api',
'logFile',

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nit] 15 of the 55 entries here are unreachable — they're shadowed by an explicit switch case, so they never hit default:/addUserArg: key, folder, force, only, forcelocal, verbose, onlyAutomate, proxyHost, proxyPort, proxyUser, proxyPass, localIdentifier, forceproxy, logFile, parallelRuns.

logFile is the confusing one, because the RESERVED_OPTIONS comment right below says "logFile has its own supported option; only the raw binary alias is reserved" — which is correct, and is exactly why logFile shouldn't be in the passthrough list at all. Reads today as though logFile were a passthrough.

Harmless at runtime, but the list is the thing a maintainer will diff against COMMAND_CONFIGURATION next time the binary gains a flag, so it's worth it being honest about what it controls. Either drop the shadowed names, or keep them with a one-line note that they're listed for completeness against the binary's CLI and handled by explicit cases above.

Verified separately: against COMMAND_CONFIGURATION at browserStackTunnel@origin/master (with BsGlobal.SANDBOX_FLAG/DISABLE_SANDBOX_FLAG/TUNNEL_BIND_FLAG/TUNNEL_DISALLOW_FLAG resolved from extensions/common/global.js:207-211), the allowlist has no missing binary option and no extras once explicit cases and RESERVED_OPTIONS are accounted for. The compatibility claim holds.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Took the second option you offered — kept the entries, added the note — since a complete mirror is what makes the list diffable against COMMAND_CONFIGURATION when the binary gains a flag, which is the maintenance path you describe.

PASSTHROUGH_OPTIONS now carries a comment naming the 15 shadowed entries explicitly and saying they are listed for completeness, not effect.

The logFile contradiction is fixed at the other end: the RESERVED_OPTIONS comment no longer says "logFile has its own supported option" as if that made it a passthrough. It now spells out that the log file is settable only through the wrapper's logfile/logFile case, which routes into getBinaryArgs' single --log-file, and that the binary's raw log-file alias is reserved precisely so a second one cannot be added.

Thanks for the independent check that the allowlist has no missing binary option and no extras — that's the part I'd have had the hardest time proving to a reviewer myself.

'pacFile', 'pac-file',
'parallelRuns', 'parallel-runs',
'disableProxyDiscovery', 'disable-proxy-discovery',
'enableUTCLogging', 'enable-utc-logging',
'no-container',
'include-hosts', 'exclude-hosts',
'bsHost', 'bs-host',
'debug-utility', 'debug-url',
'customRepeater', 'custom-repeater',
'enterprise',
'use-system-installed-ca', 'use-ca-certificate',
'https-ports',
'ntlm-username', 'ntlm-password', 'ntlm-domain', 'ntlm-workstation',
'connect-timeout',
'public-interface-services',
'disableDashboard', 'disable-dashboard',
'config-file',
'client-protocol',
'identifier',
'trusted-hosts'
];

// Flags getBinaryArgs() always puts on the argv itself. Accepting them from
// the options object too would let a caller append a second, conflicting copy
// — e.g. '--daemon stop' after our '--daemon start'. The log file is settable,
// but only through the wrapper's own 'logfile'/'logFile' case above, which
// routes it into getBinaryArgs' single '--log-file'; the binary's raw
// 'log-file' alias is reserved so it cannot add a second one.
var RESERVED_OPTIONS = ['daemon', 'log-file', 'source'];

// Keys consumed by this wrapper and never meant for the binary.
var INTERNAL_OPTIONS = ['onlyCommand'];

function Local(){
this.sanitizePath = function(rawPath) {
var doubleQuoteIfRequired = this.windows && !rawPath.match(/"[^"]+"/) ? '"' : '';
Expand All @@ -30,7 +92,9 @@ function Local(){
this.startSync = function(options) {
this.userArgs = [];
var that = this;
this.addArgs(options);
const argsError = this.addArgs(options);
if(argsError)
return argsError;

if(typeof options['onlyCommand'] !== 'undefined')
return;
Expand Down Expand Up @@ -83,7 +147,9 @@ function Local(){
this.start = function(options, callback){
this.userArgs = [];
var that = this;
this.addArgs(options);
const argsError = this.addArgs(options);
if(argsError)
return callback(argsError);

if(typeof options['onlyCommand'] !== 'undefined')
return callback();
Expand Down Expand Up @@ -150,6 +216,15 @@ function Local(){
for(var key in options){
var value = options[key];

// Runs for EVERY key, including the ones with an explicit case below.
// A value is only ever safe as a value: the binary's parser will not
// consume one that begins with '-', it reads it as another flag — and it
// accepts the '--flag=value' form, so a value like '--log-file=/tmp/x'
// smuggles a complete flag in through an otherwise legitimate option.
var valueError = this.rejectFlagLikeValue(key, value);

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Round-1 confirmation — this is fixed, verified rather than assumed. I re-ran every round-0 vector plus new ones against head 4ab6262, reading the real getBinaryArgs() output: 37/37.

All 18 explicitly-cased keys now reject a flag-like value — localIdentifier, only, folder, key, verbose, force, forceLocal, forceProxy, onlyAutomate, logFile, parallelRuns, useCaCertificate, binarypath, proxyHost/Port/User/Pass — including the --log-file=/--daemon=/--config-file= forms that previously rode in as values and defeated RESERVED_OPTIONS. List elements are checked too, including a nested array (via toString coercion). startSync returns the LocalError rather than swallowing it.

Equally important, the accept path didn't get over-tightened: localProxyHost + pac-file, custom-repeater + bs-host, the full proxy set, include-hosts as both an array and a space-separated string, bare boolean flags, and numeric values all still produce exactly the argv the binary expects, every element a string.

Hoisting above the switch was the right shape — one check, no per-case duplication, and it can't be bypassed by adding a case later.

if(valueError)
return valueError;

switch(key){
case 'key':
if(value)
Expand Down Expand Up @@ -245,15 +320,61 @@ function Local(){
this.binaryPath = value;
break;

default:
if(value.toString().toLowerCase() == 'true'){
this.userArgs.push('--' + key);
} else {
this.userArgs.push('--' + key);
this.userArgs.push(value);
}
default: {
var error = this.addUserArg(key, value);
if(error)
return error;
break;
}
}
}
};

// Returns a LocalError if any part of the value would be read as a flag
// rather than as this option's value. List-typed options (--include-hosts,
// --exclude-hosts) take an array, so every element is checked.
this.rejectFlagLikeValue = function(key, value){
var values = Array.isArray(value) ? value : [value];
for(var i = 0; i < values.length; i++){
if(values[i] === undefined || values[i] === null)
continue;
if(values[i].toString().charAt(0) === '-')
return new LocalError('Invalid value for option \'' + key + '\': values starting with \'-\' are not allowed');
}
};

// Forwards one caller-supplied option to the daemon argv, or returns a
// LocalError describing why it was refused. Only documented modifiers get
// through: an unknown key used to be prefixed with '--' and pushed blindly,
// which let any caller inject arbitrary flags into the native binary.
// The value has already been checked by rejectFlagLikeValue in addArgs.
this.addUserArg = function(key, value){
if(INTERNAL_OPTIONS.indexOf(key) !== -1)
return;

if(RESERVED_OPTIONS.indexOf(key) !== -1)
return new LocalError('Option \'' + key + '\' is set by browserstack-local itself and cannot be passed in');

if(PASSTHROUGH_OPTIONS.indexOf(key) === -1)
return new LocalError('Unknown option \'' + key + '\'. Only documented BrowserStack Local modifiers are forwarded to the binary, see https://www.browserstack.com/local-testing#modifiers');

// Match the explicit cases above, which all guard with `if(value)`.
if(value === undefined || value === null)
return;

if(value.toString().toLowerCase() == 'true'){
this.userArgs.push('--' + key);
return;
}

// argv elements must be strings — execFile/spawnSync reject anything else.
this.userArgs.push('--' + key);
if(Array.isArray(value)){
for(var i = 0; i < value.length; i++){
this.userArgs.push(value[i].toString());

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nit] The scalar path above got the null/undefined guard I asked for in round 0, but this new array loop didn't — and rejectFlagLikeValue deliberately continues past null elements, so nothing stops one reaching here:

{'include-hosts': ['localhost', null]}
  -> TypeError: Cannot read properties of null (reading 'toString')   thrown out of start()
{'include-hosts': ['localhost', undefined]}
  -> TypeError: Cannot read properties of undefined (reading 'toString')

Thrown synchronously out of start(), so it bypasses the callback(err) contract the rest of this change is careful to honour. Not a regression — master accepted the same input and pushed the raw array, which then dies at execFile — but it's the identical defect class the scalar path just fixed, in the sibling branch added by the same commit.

if(Array.isArray(value)){
  for(var i = 0; i < value.length; i++){
    if(value[i] === undefined || value[i] === null)
      continue;
    this.userArgs.push(value[i].toString());
  }
}

Two cosmetic siblings while you're here: {'include-hosts': ['true']} stringifies to 'true' at the check above, so it pushes a bare --include-hosts and silently drops the element; {'include-hosts': []} pushes a dangling --include-hosts with nothing after it. Both benign — the binary's list parser reads an empty list — but a values.length === 0 early return would make the intent explicit.

}
} else {
this.userArgs.push(value.toString());
}
};

Expand Down
136 changes: 126 additions & 10 deletions test/local.js
Original file line number Diff line number Diff line change
Expand Up @@ -124,20 +124,136 @@ describe('Local', function () {
});
});

it('should enable custom boolean args', function (done) {
bsLocal.start({ 'key': process.env.BROWSERSTACK_ACCESS_KEY, onlyCommand: true, 'boolArg1': true, 'boolArg2': true }, function(){
expect(bsLocal.getBinaryArgs().indexOf('--boolArg1')).to.not.equal(-1);
expect(bsLocal.getBinaryArgs().indexOf('--boolArg2')).to.not.equal(-1);
// Argument injection (CWE-88): addArgs used to prefix ANY unknown option key

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[blocking] The tree is clean now — git grep -E '\b(LOC|SC|APPSEC)-[0-9]+|C-00[0-9]|F-0[0-9][0-9]' over head 4ab6262 returns zero hits, and the body no longer carries the Jira links or the exploit narrative. That's the part that mattered most; thank you.

The squash-merge note doesn't finish the job, though, for two reasons:

  1. GitHub's default squash-commit subject is the PR title, which is still LOC-6805: allowlist option keys forwarded to the BrowserStackLocal binary. So LOC-6805 lands in master's history unless the merger also edits the subject — and the note only asks them to squash. (Commit subjects carrying ticket ids do have precedent here, e.g. LOC-5083: Add support for Linux arm64 binary, so if you want to keep the title on that basis, say so and I'll drop this half.)
  2. Squashing doesn't retract commit 1964cb1. Its message is already rendered publicly on this PR's Commits tab and stays reachable by SHA afterwards. Squashing only shapes what enters master.

The substantive part isn't the bare ticket key — it's that 1964cb1's message names LOC-6790 (binarypath path traversal) and LOC-6777 (no binary integrity check) as still open and unfixed, with a one-line characterisation of each, on a public repo, for a package that ships to npm. That's a pointer to two live unpatched issues, which is a different thing from an internal ticket number.

This branch is a Draft with no other contributors, so amending is free:

git rebase -i --root   # or: git commit --amend on 1964cb1 via a soft reset
git push --force-with-lease

Rewrite 1964cb1's message with the CWE-88 framing this comment block now uses and drop the closing paragraph about the other two tickets. If your team's disclosure policy is comfortable leaving it, that's a legitimate call to make explicitly — but it shouldn't rest on a merge-time step that GitHub won't do by default.

// with '--' and push it onto the daemon argv, letting a caller — or upstream
// code merging untrusted input into `options` — inject arbitrary flags into
// the native binary. Only documented BrowserStackLocal modifiers may be
// forwarded now, and no value may pose as a flag.

it('should reject unknown boolean args', function (done) {
bsLocal.start({ 'key': process.env.BROWSERSTACK_ACCESS_KEY, onlyCommand: true, 'boolArg1': true, 'boolArg2': true }, function(error){
expect(error).to.be.an(Error);
expect(error.toString()).to.contain('Unknown option \'boolArg1\'');
expect(bsLocal.getBinaryArgs().indexOf('--boolArg1')).to.equal(-1);
expect(bsLocal.getBinaryArgs().indexOf('--boolArg2')).to.equal(-1);
done();
});
});

it('should enable custom keyval args', function (done) {
bsLocal.start({ 'key': process.env.BROWSERSTACK_ACCESS_KEY, onlyCommand: true, 'customKey1': 'custom value1', 'customKey2': 'custom value2' }, function(){
expect(bsLocal.getBinaryArgs().indexOf('--customKey1')).to.not.equal(-1);
expect(bsLocal.getBinaryArgs().indexOf('custom value1')).to.not.equal(-1);
expect(bsLocal.getBinaryArgs().indexOf('--customKey2')).to.not.equal(-1);
expect(bsLocal.getBinaryArgs().indexOf('custom value2')).to.not.equal(-1);
it('should reject unknown keyval args', function (done) {
bsLocal.start({ 'key': process.env.BROWSERSTACK_ACCESS_KEY, onlyCommand: true, 'customKey1': 'custom value1', 'customKey2': 'custom value2' }, function(error){
expect(error).to.be.an(Error);
expect(error.toString()).to.contain('Unknown option \'customKey1\'');
expect(bsLocal.getBinaryArgs().indexOf('--customKey1')).to.equal(-1);
expect(bsLocal.getBinaryArgs().indexOf('custom value1')).to.equal(-1);
done();
});
});

it('should reject the reported flag-injection payload', function (done) {
bsLocal.start({ 'key': process.env.BROWSERSTACK_ACCESS_KEY, onlyCommand: true, 'config': '/tmp/attacker.conf', 'daemon': 'stop' }, function(error){
expect(error).to.be.an(Error);
const args = bsLocal.getBinaryArgs();
expect(args.indexOf('--config')).to.equal(-1);
expect(args.indexOf('/tmp/attacker.conf')).to.equal(-1);
// the wrapper's own '--daemon start' must be the only daemon flag
expect(args.indexOf('stop')).to.equal(-1);
done();
});
});

it('should reject options the wrapper sets itself', function (done) {
bsLocal.start({ 'key': process.env.BROWSERSTACK_ACCESS_KEY, onlyCommand: true, 'log-file': '/tmp/attacker-owned' }, function(error){
expect(error).to.be.an(Error);
expect(error.toString()).to.contain('set by browserstack-local itself');
expect(bsLocal.getBinaryArgs().indexOf('/tmp/attacker-owned')).to.equal(-1);
done();
});
});

it('should reject a value that would be parsed as another flag', function (done) {
// bs-minimist does not consume a value beginning with '-'; it reads it as
// a separate flag, so a legitimate key can still smuggle one in.
bsLocal.start({ 'key': process.env.BROWSERSTACK_ACCESS_KEY, onlyCommand: true, 'region': '--pac-file' }, function(error){
expect(error).to.be.an(Error);
expect(error.toString()).to.contain('values starting with \'-\' are not allowed');
expect(bsLocal.getBinaryArgs().indexOf('--pac-file')).to.equal(-1);
done();
});
});

// The value check must cover keys that have an explicit case too, not just
// the ones reaching the allowlist. bs-minimist accepts '--flag=value', so a
// smuggled flag carries its own value and needs no following argv slot.
it('should reject a flag-like value on an explicitly handled option', function (done) {
bsLocal.start({ 'key': process.env.BROWSERSTACK_ACCESS_KEY, onlyCommand: true, 'localIdentifier': '--log-file=/tmp/attacker-owned' }, function(error){
expect(error).to.be.an(Error);
expect(error.toString()).to.contain('values starting with \'-\' are not allowed');
const args = bsLocal.getBinaryArgs();
expect(args.indexOf('--log-file=/tmp/attacker-owned')).to.equal(-1);
expect(args.indexOf('--local-identifier')).to.equal(-1);
done();
});
});

it('should reject a daemon-lifecycle smuggle through an explicitly handled option', function (done) {
bsLocal.start({ 'key': process.env.BROWSERSTACK_ACCESS_KEY, onlyCommand: true, 'only': '--daemon=stop' }, function(error){
expect(error).to.be.an(Error);
expect(bsLocal.getBinaryArgs().indexOf('--daemon=stop')).to.equal(-1);
done();
});
});

it('should reject a flag-like element inside a list-valued option', function (done) {
bsLocal.start({ 'key': process.env.BROWSERSTACK_ACCESS_KEY, onlyCommand: true, 'include-hosts': ['localhost', '--config-file=/tmp/attacker.yml'] }, function(error){
expect(error).to.be.an(Error);
expect(bsLocal.getBinaryArgs().indexOf('--config-file=/tmp/attacker.yml')).to.equal(-1);
done();
});
});

it('should forward a list-valued option as separate argv elements', function (done) {

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nit] Good addition — this test is what makes the new array handling a contract rather than an accident, and pairing it with the flag-like-element case above is the right split.

Two gaps worth closing while the file is open, both of which I had to find by hand rather than from the suite:

  • Nothing covers a null element inside a list value (see my comment on lib/Local.js:374 — it throws out of start()).
  • Nothing covers startSync on the reject path. It's the other function whose signature changed, and the round-0 report notes the Start sync block only runs in isolation because of the pre-existing should stop local abort. A one-liner asserting bsLocal.startSync({key: ..., onlyCommand: true, localIdentifier: '--daemon=stop'}) returns a LocalError would pin the return-value contract. I verified it does today.

Separately, on the PR description rather than this file: the test plan dropped the BrowserStack session id that round 0's body carried. The round-1 re-run is real — I verified session 1d95429466a4fcca4ce9aef3fb3afc20d5004f32 via the session-details API (build locsec-WI-549cf2ac, local active, created after the round-1 code) — but a reader of this PR now has no link to it. Worth putting back, since it's the only evidence that the hoisted guard doesn't falsely reject a real access key on a live tunnel.

bsLocal.start({ 'key': process.env.BROWSERSTACK_ACCESS_KEY, onlyCommand: true, 'include-hosts': ['localhost', '127.0.0.1'] }, function(error){
expect(error).to.equal(undefined);
const args = bsLocal.getBinaryArgs();
expect(args.indexOf('--include-hosts')).to.not.equal(-1);
expect(args.indexOf('localhost')).to.not.equal(-1);
expect(args.indexOf('127.0.0.1')).to.not.equal(-1);
done();
});
});

it('should skip a null or undefined passthrough value instead of pushing it raw', function (done) {
// execFile/spawnSync reject a non-string argv element, so a raw null here
// used to throw ERR_INVALID_ARG_TYPE out of start() instead of erroring.
bsLocal.start({ 'key': process.env.BROWSERSTACK_ACCESS_KEY, onlyCommand: true, 'region': null, 'connect-timeout': 30 }, function(error){
expect(error).to.equal(undefined);
const args = bsLocal.getBinaryArgs();
expect(args.indexOf('--region')).to.equal(-1);
expect(args.indexOf(null)).to.equal(-1);
// numbers are coerced, so every argv element is a string
expect(args.indexOf('30')).to.not.equal(-1);
expect(args.every(function(a){ return typeof a === 'string'; })).to.equal(true);
done();
});
});

it('should not forward wrapper-internal keys to the binary', function (done) {
bsLocal.start({ 'key': process.env.BROWSERSTACK_ACCESS_KEY, onlyCommand: true }, function(error){
expect(error).to.equal(undefined);
expect(bsLocal.getBinaryArgs().indexOf('--onlyCommand')).to.equal(-1);
done();
});
});

it('should still forward documented modifiers that have no explicit case', function (done) {
bsLocal.start({ 'key': process.env.BROWSERSTACK_ACCESS_KEY, onlyCommand: true, 'localProxyHost': '127.0.0.1', 'pac-file': '/tmp/proxy.pac' }, function(error){
expect(error).to.equal(undefined);
const args = bsLocal.getBinaryArgs();
expect(args.indexOf('--localProxyHost')).to.not.equal(-1);
expect(args.indexOf('127.0.0.1')).to.not.equal(-1);
expect(args.indexOf('--pac-file')).to.not.equal(-1);
expect(args.indexOf('/tmp/proxy.pac')).to.not.equal(-1);
done();
});
});
Expand Down
Loading