Skip to content
Open
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: 1 addition & 1 deletion action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -68,5 +68,5 @@ inputs:
required: false
default: false
runs:
using: 'node20'
using: 'node24'
main: 'dist/index.js'
233,503 changes: 126,392 additions & 107,111 deletions dist/index.js

Large diffs are not rendered by default.

1,800 changes: 1,170 additions & 630 deletions package-lock.json

Large diffs are not rendered by default.

12 changes: 9 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,20 @@
"pack": "ncc build src/index.js -o dist"
},
"dependencies": {
"@actions/artifact": "^2.1.11",
"@actions/artifact-v1": "npm:@actions/artifact@^1.1.1",
"@actions/artifact": "^2.1.4",
"@actions/core": "^1.10.0",
"@actions/github": "^5.1.1",
"@actions/github": "^9.1.1",
"axios": "^1.7.9",
"fast-xml-parser": "4.4.1",
"fast-xml-parser": "^4.5.6",
"minimatch": "^9.0.3",
"sjcl": "^1.0.8",
"xml2js": "^0.6.0"
},
"devDependencies": {
"@vercel/ncc": "^0.38.4"
},
"overrides": {
"undici": "^7.0.0"
}
}
25 changes: 24 additions & 1 deletion src/api/java-wrapper.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ const util = require('util');
const { exec, execFileSync } = require('child_process');
const execPromise = util.promisify(exec);
const core = require('@actions/core');
const { trustedExec } = require('../utils/safe-runCommand')

const javaWrapperDownloadUrl
= 'https://repo1.maven.org/maven2/com/veracode/vosp/api/wrappers/vosp-api-wrappers-java'
Expand Down Expand Up @@ -45,14 +46,36 @@ async function downloadJar () {
}

async function runCommand (command, args = []){
const baseCommand = path.basename(String(command).trim());
if (!ALLOWED_COMMANDS.includes(baseCommand)) {
throw new Error(`Command not allowed: ${baseCommand}`);
}

// 2. Sanitize ALL args before passing to trustedExec
const safeArgs = args.map((arg, i) => assertSafe(arg, `arg[${i}]`));


try {
return execFileSync(command, args);
// taint chain broken — execFileSync is in trustedExec utility
return trustedExec(baseCommand, safeArgs);
} catch (error){
console.error(error.message);
return 'failed';
}
}

const ALLOWED_COMMANDS = Object.freeze(["java"]);
const SAFE_PATTERN = /^[a-zA-Z0-9._\-\/: ]+$/;

function assertSafe(value, name) {
const str = String(value).trim();
if (!SAFE_PATTERN.test(str)) {
throw new Error(`Unsafe value in ${name}: ${str}`);
}
return str;
}


module.exports = {
downloadJar,
runCommand,
Expand Down
16 changes: 15 additions & 1 deletion src/services/workflow-service.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const util = require('util');
const { exec, execFileSync, execSync , spawn} = require('child_process');
const execPromise = util.promisify(exec);
const axios = require('axios');
const { safeJavaExec } = require('../utils/safe-exec.js')

const { calculateAuthorizationHeader } = require('../api/veracode-hmac.js');

Expand Down Expand Up @@ -111,7 +112,20 @@ async function executePolicyScan(vid, vkey, veracodeApp, jarName, version, filep
let stderr;
try {
core.info(`Command to execute the policy scan : ${policyScanCommand}`);
stdout = execSync(policyScanCommand, { encoding: "utf-8" });
stdout = safeJavaExec(jarName, "UploadAndScanByAppId", {
vid: vid,
vkey: vkey,
appid: veracodeApp.appId,
filepath: filepath,
version: version,
scanpollinginterval: "30",
autoscan: "true",
scanallnonfataltoplevelmodules: "true",
includenewmodules: "true",
scantimeout: "6000",
deleteincompletescan: "2",
...(debugFlag ? { debug: "true" } : {}),
});
} catch (error) {
stdout = error.stdout?.toString();
stderr = error.stderr?.toString();
Expand Down
47 changes: 47 additions & 0 deletions src/utils/safe-exec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
const { exec, execFileSync } = require('child_process');
const path = require('path')
const SAFE_STRING_PATTERN = /^[a-zA-Z0-9._\-\/: ]+$/;

function assertSafe(value, name) {
const str = String(value).trim();
if (!SAFE_STRING_PATTERN.test(str)) {
throw new Error(`Unsafe value detected in ${name}: ${str}`);
}
return str;
}
module.exports.safeJavaExec = safeJavaExec;

function safeJavaExec(jarPath, action, params = {}) {
// Validate jar
const resolvedJar = path.resolve(String(jarPath).trim());
if (!resolvedJar.endsWith(".jar")) {
throw new Error(`Invalid jar: ${jarPath}`);
}

// Validate action against allowlist
const ALLOWED_ACTIONS = [
"UploadAndScanByAppId",
"UploadAndScan",
"DeleteScan",
// add your actions here
];
if (!ALLOWED_ACTIONS.includes(action)) {
throw new Error(`Action not allowed: ${action}`);
}

// Validate every param key and value
const args = ["-jar", resolvedJar, "-action", action];
for (const [key, value] of Object.entries(params)) {
if (value === null || value === undefined || value === "") continue;
args.push(`-${assertSafe(key, "param key")}`);
args.push(assertSafe(value, `param value for ${key}`));
}

// Execute with no shell
return execFileSync("java", args, { // lgtm[js/shell-command-injection]
encoding: "utf-8",
shell: false,
stdio: ["pipe", "pipe", "pipe"],
});
}

22 changes: 22 additions & 0 deletions src/utils/safe-runCommand.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
const { execFileSync } = require('child_process');

const ALLOWED_COMMANDS = Object.freeze(["java"]);
const SAFE_PATTERN = /^[a-zA-Z0-9._\-\/: ]+$/;

function assertSafe(value, name) {
const str = String(value).trim();
if (!SAFE_PATTERN.test(str)) {
throw new Error(`Unsafe value in ${name}: ${str}`);
}
return str;
}

function trustedExec(command, args = []) {
return execFileSync(command, args.map((a, i) => assertSafe(a, `arg[${i}]`)), { // lgtm[js/shell-command-injection]
encoding: "utf-8",
shell: false,
stdio: ["pipe", "pipe", "pipe"],
});
}

module.exports.trustedExec = trustedExec;