AAE-40427 Replace shelljs with native node api (#11404)

* Replace shelljs with native node api

* Refactor command execution in audit and changelog scripts to use spawnSync for improved security and error handling
This commit is contained in:
Denys Vuika
2025-11-28 12:57:49 +00:00
committed by GitHub
parent 6bece85060
commit a9efe36e5f
6 changed files with 87 additions and 1076 deletions
+10 -975
View File
File diff suppressed because it is too large Load Diff
-2
View File
@@ -26,7 +26,6 @@
"ejs": "^3.1.10",
"license-checker": "^25.0.1",
"node-fetch": "^2.7.0",
"shelljs": "^0.10.0",
"spdx-license-list": "^5.0.0"
},
"keywords": [
@@ -36,7 +35,6 @@
"devDependencies": {
"@types/ejs": "^3.1.2",
"@types/node": "^20.1.7",
"@types/shelljs": "^0.8.12",
"typescript": "^4.9.5"
}
}
+40 -3
View File
@@ -17,7 +17,7 @@
* limitations under the License.
*/
import * as shell from 'shelljs';
import { spawnSync } from 'node:child_process';
import * as ejs from 'ejs';
import * as path from 'path';
import * as fs from 'fs';
@@ -93,8 +93,45 @@ Options:
console.log(`Running audit on ${packagePath}`);
const packageJson = JSON.parse(fs.readFileSync(packagePath).toString());
const cmd = 'npm audit --json --prod';
const jsonAudit = JSON.parse(shell.exec(cmd, { silent: true }));
// Run in the directory containing the package.json
const packageDir = path.dirname(packagePath);
// Use spawnSync with array arguments for safer command execution (prevents shell injection)
// Cross-platform: npm is available on PATH on all platforms (Windows, macOS, Linux)
const result = spawnSync('npm', ['audit', '--json', '--prod'], {
cwd: packageDir,
encoding: 'utf-8',
// shell: false is the default and more secure (no shell interpretation)
shell: false,
// Set maxBuffer to handle large audit outputs
maxBuffer: 10 * 1024 * 1024 // 10MB
});
let jsonAudit;
// npm audit returns non-zero exit code when vulnerabilities are found
// We still want to parse the JSON output in this case
if (result.error) {
console.error('Failed to run npm audit:', result.error.message);
reject(result.error);
return;
}
const auditOutput = result.stdout;
if (!auditOutput) {
console.error('npm audit produced no output');
reject(new Error('npm audit produced no output'));
return;
}
try {
jsonAudit = JSON.parse(auditOutput);
} catch (parseError) {
console.error('Failed to parse npm audit output');
reject(parseError);
return;
}
ejs.renderFile(
templatePath,
+37 -11
View File
@@ -21,7 +21,7 @@
import { argv, exit } from 'node:process';
import { parseArgs } from 'node:util';
import * as shell from 'shelljs';
import { spawnSync } from 'node:child_process';
import * as path from 'path';
import { logger } from './logger';
import * as fs from 'fs';
@@ -65,10 +65,22 @@ interface DiffOptions {
* @returns URL pointing to the git remote
*/
function getRemote(workingDir: string): string {
const command = 'git config --get remote.origin.url';
const remote = shell.exec(command, { cwd: workingDir, silent: true }).toString();
// Use spawnSync with array arguments for safer command execution (prevents shell injection)
const result = spawnSync('git', ['config', '--get', 'remote.origin.url'], {
cwd: workingDir,
encoding: 'utf-8',
shell: false
});
return remote.trim();
if (result.error) {
throw new Error(`Failed to get git remote: ${result.error.message}`);
}
if (result.status !== 0) {
throw new Error(`git config command failed with exit code ${result.status}: ${result.stderr}`);
}
return result.stdout.trim();
}
/**
@@ -87,14 +99,14 @@ function getCommits(options: DiffOptions): Array<Commit> {
authorFilter = `bot|Alfresco Build User`;
}
// Build git command arguments array for safe execution (prevents shell injection)
const args = [
`git`,
`log`,
'log',
options.range,
`--no-merges`,
`--first-parent`,
'--no-merges',
'--first-parent',
// this format is needed to allow parsing all characters in the commit message and safely convert to JSON
`--format="{ ^@^hash^@^: ^@^%h^@^, ^@^author^@^: ^@^%an^@^, ^@^author_email^@^: ^@^%ae^@^, ^@^date^@^: ^@^%ad^@^, ^@^subject^@^: ^@^%s^@^ }"`
'--format={ ^@^hash^@^: ^@^%h^@^, ^@^author^@^: ^@^%an^@^, ^@^author_email^@^: ^@^%ae^@^, ^@^date^@^: ^@^%ad^@^, ^@^subject^@^: ^@^%s^@^ }'
];
if (options.max !== undefined) {
@@ -105,9 +117,23 @@ function getCommits(options: DiffOptions): Array<Commit> {
args.push(`--skip=${options.skip}`);
}
const command = args.join(' ');
// Use spawnSync with array arguments for safer command execution
const result = spawnSync('git', args, {
cwd: options.dir,
encoding: 'utf-8',
shell: false,
maxBuffer: 10 * 1024 * 1024 // 10MB to handle large git logs
});
let log = shell.exec(command, { cwd: options.dir, silent: true }).toString();
if (result.error) {
throw new Error(`Failed to get git commits: ${result.error.message}`);
}
if (result.status !== 0) {
throw new Error(`git log command failed with exit code ${result.status}: ${result.stderr}`);
}
let log = result.stdout;
// https://stackoverflow.com/a/13928240/14644447
log = JSON.stringify(log.trim()).slice(1, -1).replace(/\^@\^/gm, '"');
-83
View File
@@ -70,7 +70,6 @@
"@types/minimatch": "5.1.2",
"@types/node": "^18.16.9",
"@types/pdfjs-dist": "2.10.378",
"@types/shelljs": "^0.8.17",
"@types/superagent": "^4.1.22",
"@typescript-eslint/eslint-plugin": "6.21.0",
"@typescript-eslint/parser": "6.21.0",
@@ -116,7 +115,6 @@
"prettier": "3.6.2",
"rimraf": "^6.1.2",
"sass-loader": "16.0.5",
"shelljs": "^0.10.0",
"spdx-license-list": "^6.9.0",
"stylelint": "16.20.0",
"stylelint-config-standard-scss": "^13.1.0",
@@ -14682,57 +14680,6 @@
"@types/node": "*"
}
},
"node_modules/@types/shelljs": {
"version": "0.8.17",
"resolved": "https://registry.npmjs.org/@types/shelljs/-/shelljs-0.8.17.tgz",
"integrity": "sha512-IDksKYmQA2W9MkQjiyptbMmcQx+8+Ol6b7h6dPU5S05JyiQDSb/nZKnrMrZqGwgV6VkVdl6/SPCKPDlMRvqECg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/node": "*",
"glob": "^11.0.3"
}
},
"node_modules/@types/shelljs/node_modules/glob": {
"version": "11.1.0",
"resolved": "https://registry.npmjs.org/glob/-/glob-11.1.0.tgz",
"integrity": "sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw==",
"dev": true,
"license": "BlueOak-1.0.0",
"dependencies": {
"foreground-child": "^3.3.1",
"jackspeak": "^4.1.1",
"minimatch": "^10.1.1",
"minipass": "^7.1.2",
"package-json-from-dist": "^1.0.0",
"path-scurry": "^2.0.0"
},
"bin": {
"glob": "dist/esm/bin.mjs"
},
"engines": {
"node": "20 || >=22"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/@types/shelljs/node_modules/minimatch": {
"version": "10.1.1",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.1.1.tgz",
"integrity": "sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==",
"dev": true,
"license": "BlueOak-1.0.0",
"dependencies": {
"@isaacs/brace-expansion": "^5.0.0"
},
"engines": {
"node": "20 || >=22"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/@types/sockjs": {
"version": "0.3.36",
"resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.36.tgz",
@@ -23478,22 +23425,6 @@
"node": ">=8"
}
},
"node_modules/jackspeak": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.1.1.tgz",
"integrity": "sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ==",
"dev": true,
"license": "BlueOak-1.0.0",
"dependencies": {
"@isaacs/cliui": "^8.0.2"
},
"engines": {
"node": "20 || >=22"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/jake": {
"version": "10.9.4",
"resolved": "https://registry.npmjs.org/jake/-/jake-10.9.4.tgz",
@@ -31599,20 +31530,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/shelljs": {
"version": "0.10.0",
"resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.10.0.tgz",
"integrity": "sha512-Jex+xw5Mg2qMZL3qnzXIfaxEtBaC4n7xifqaqtrZDdlheR70OGkydrPJWT0V1cA1k3nanC86x9FwAmQl6w3Klw==",
"dev": true,
"license": "BSD-3-Clause",
"dependencies": {
"execa": "^5.1.1",
"fast-glob": "^3.3.2"
},
"engines": {
"node": ">=18"
}
},
"node_modules/side-channel": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
-2
View File
@@ -97,7 +97,6 @@
"@types/minimatch": "5.1.2",
"@types/node": "^18.16.9",
"@types/pdfjs-dist": "2.10.378",
"@types/shelljs": "^0.8.17",
"@types/superagent": "^4.1.22",
"@typescript-eslint/eslint-plugin": "6.21.0",
"@typescript-eslint/parser": "6.21.0",
@@ -143,7 +142,6 @@
"prettier": "3.6.2",
"rimraf": "^6.1.2",
"sass-loader": "16.0.5",
"shelljs": "^0.10.0",
"spdx-license-list": "^6.9.0",
"stylelint": "16.20.0",
"stylelint-config-standard-scss": "^13.1.0",