Skip to content

Commit 1f42201

Browse files
chore: trim verbose APS-19009 comments
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 8df7a40 commit 1f42201

2 files changed

Lines changed: 10 additions & 30 deletions

File tree

‎bin/helpers/packageInstaller.js‎

Lines changed: 7 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -33,20 +33,10 @@ const setupPackageFolder = (runSettings, directoryPath) => {
3333

3434
// Combine win and mac specific dependencies if present
3535
const combinedDependencies = combineMacWinNpmDependencies(runSettings);
36-
// APS-19009: the primary RCE fix is `--ignore-scripts` at install time (see packageInstall
37-
// below), which neutralises lifecycle-script (postinstall etc.) execution for EVERY
38-
// dependency spec — registry, git, file: or tarball-url alike. That closes the documented
39-
// vulnerability without affecting any legitimate flow.
40-
//
41-
// As defence-in-depth we additionally drop any dependency whose *name* is not a valid npm
42-
// package name. This strips shell-metacharacter / command-injection payloads (e.g.
43-
// "left-pad; cat /flag", "$(sleep 6)") that a poisoned browserstack.json could try to smuggle.
44-
//
45-
// We deliberately DO NOT validate the version spec, and we SKIP a bad entry rather than
46-
// aborting the run: git / file: / tarball-url / private-registry version specs are legitimate
47-
// and widely used (BrowserStack's own SDK CI and real enterprise customers depend on them —
48-
// rejecting them would have broken ~7.3k legitimate builds over 90 days per BQ analysis). A
49-
// genuine customer session must never be blocked by this control.
36+
// APS-19009: drop any dependency whose name is not a valid npm package name (strips
37+
// shell-metacharacter payloads like "left-pad; cat /flag"). Version specs are NOT validated
38+
// (git / file: / tarball-url are legitimate), and a bad entry is skipped, not aborted, so a
39+
// customer session is never blocked. RCE itself is closed by --ignore-scripts (see below).
5040
const NPM_NAME_RE = /^(@[a-zA-Z0-9-~][a-zA-Z0-9-._~]*\/)?[a-zA-Z0-9-~][a-zA-Z0-9-._~]*$/;
5141
const safeDependencies = {};
5242
for (const depName of Object.keys(combinedDependencies || {})) {
@@ -121,12 +111,9 @@ const packageInstall = (packageDir, bsConfig) => {
121111

122112
// add --legacy-peer-deps flag while installing dependencies for npm v7+
123113
// For more info please read "Peer Dependencies" section here -> https://github.blog/2021-02-02-npm-7-is-now-generally-available/
124-
// APS-19009: --ignore-scripts prevents a user-supplied npm_dependencies package from
125-
// executing lifecycle scripts (postinstall etc.) during this install, which was an RCE
126-
// on CI. npm_dependencies is documented as pure-JS only. shell:true is retained on
127-
// purpose: the command line is fully static (package names live in package.json, never
128-
// on the command line, so there is no injection surface) and it is required for the
129-
// output redirection and for invoking npm.cmd on Windows.
114+
// APS-19009: --ignore-scripts blocks lifecycle-script (postinstall) RCE. shell:true is kept
115+
// on purpose — the command line is static (package names live in package.json, not on the CLI)
116+
// and shell:true is needed for the output redirection and for npm.cmd on Windows.
130117
if (parseInt(npm_major_version) >= 7) {
131118
logger.debug(`Running NPM install command: npm install --legacy-peer-deps --ignore-scripts --loglevel verbose > ../npm_install_debug.log`);
132119
nodeProcess = spawn(/^win/.test(process.platform) ? 'npm.cmd' : 'npm', ['install', '--legacy-peer-deps', '--ignore-scripts', '--loglevel', 'verbose', '>', '../npm_install_debug.log', '2>&1'], {cwd: packageDir, shell: true}); // nosemgrep: javascript.lang.security.audit.spawn-shell-true.spawn-shell-true

‎test/unit/bin/helpers/packageInstaller.js‎

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -212,9 +212,7 @@ describe("packageInstaller", () => {
212212
});
213213

214214
it("should ALLOW a non-registry version spec (git/file/tarball are legitimate) and still write package.json (APS-19009)", () => {
215-
// APS-19009: git / file: / tarball-url specs are legitimate and widely used (BrowserStack's own
216-
// SDK CI and real customers rely on them). They must NOT abort the run — the RCE is closed by
217-
// --ignore-scripts, not by rejecting the spec.
215+
// APS-19009: git / file: / tarball-url specs are legitimate — must be written, not rejected.
218216
packageInstaller.__set__({
219217
fileHelpers: {deletePackageArchieve: fileHelpersStub},
220218
fs: {
@@ -250,9 +248,7 @@ describe("packageInstaller", () => {
250248
});
251249

252250
it("should SKIP an invalid package name (shell-metacharacter payload) but keep valid deps and NOT abort the run (APS-19009)", () => {
253-
// APS-19009: a poisoned browserstack.json name like "left-pad; cat /flag" must be dropped from
254-
// the generated package.json (defence-in-depth on top of --ignore-scripts), but the session must
255-
// still run with the remaining valid dependencies — never a hard abort.
251+
// APS-19009: a bad name like "left-pad; cat /flag" is dropped, valid deps kept, run continues.
256252
packageInstaller.__set__({
257253
fileHelpers: {deletePackageArchieve: fileHelpersStub},
258254
fs: {
@@ -598,10 +594,7 @@ describe("packageInstaller", () => {
598594
});
599595

600596
it("should RESOLVE (fall back to runtime install), never reject, when setupPackageFolder throws — APS-19009: a folder/setup failure must never block a customer session", () => {
601-
// APS-19009 regression guard: the earlier fix hard-aborted the run on a rejected dependency
602-
// spec. BQ analysis showed that broke ~7.3k legitimate builds/90d (our own SDK CI + real
603-
// customers using git/file/private-registry specs). packageSetupAndInstaller must therefore
604-
// always resolve — the security control is dep-level skipping + --ignore-scripts, not a run abort.
597+
// APS-19009: a setup failure must never block a session — always resolve, defer to runtime.
605598
const setupError = new Error('some setup failure');
606599
let setupPackageFolderErrorStub = sandbox.stub().returns(Promise.reject(setupError));
607600
packageInstaller.__set__({

0 commit comments

Comments
 (0)