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
+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, '"');