ci: Clean up Template Injection surface in Actions (#29354)

This commit is contained in:
Matsu
2026-04-28 07:44:44 +00:00
committed by GitHub
parent ffef9c9c48
commit 4cf26bb70b
11 changed files with 111 additions and 46 deletions
+32 -13
View File
@@ -2,16 +2,18 @@
/**
* Retry a shell command with configurable attempts and delay.
*
* Usage: node retry.mjs [--attempts N] [--delay N] '<command>'
* Usage (safe): node retry.mjs [--attempts N] [--delay N] -- <cmd> [args...]
* Usage (legacy): node retry.mjs [--attempts N] [--delay N] '<shell command>'
*
* Options:
* --attempts N Maximum number of attempts (default: 4)
* --delay N Seconds to wait between retries (default: 15)
*
* The command is executed via shell, so pipes and env-var expansion work.
* The -- form passes args directly to the process (no shell, safe for untrusted input).
* The legacy form executes via shell, so pipes and env-var expansion work but injection is possible.
* Exits 0 on first success, 1 if all attempts fail.
*/
import { execSync } from 'node:child_process';
import { execSync, spawnSync } from 'node:child_process';
const args = process.argv.slice(2);
@@ -29,23 +31,40 @@ function getFlag(name, defaultValue) {
const attempts = getFlag('attempts', 4);
const delay = getFlag('delay', 15);
// Command is the last positional arg (skip flags and their values)
const command = args
.filter((a, i) => {
if (a.startsWith('--')) return false;
if (i > 0 && args[i - 1].startsWith('--')) return false;
return true;
})
.pop();
// Preferred form: -- cmd arg1 arg2 ... (no shell, safe for untrusted input)
// Legacy form: '<shell command string>' (uses shell; kept for backwards compat)
const separatorIndex = args.indexOf('--');
let command;
let commandArgs = [];
const isSafeRetry = separatorIndex !== -1;
if (isSafeRetry) {
[command, ...commandArgs] = args.slice(separatorIndex + 1);
} else {
command = args
.filter((a, i) => {
if (a.startsWith('--')) return false;
if (i > 0 && args[i - 1].startsWith('--')) return false;
return true;
})
.pop();
}
if (!command) {
console.error("Usage: node retry.mjs [--attempts N] [--delay N] '<command>'");
console.error('Usage: node retry.mjs [--attempts N] [--delay N] -- <cmd> [args...]');
process.exit(1);
}
for (let i = 1; i <= attempts; i++) {
try {
execSync(command, { shell: true, stdio: 'inherit' });
if (isSafeRetry) {
const result = spawnSync(command, commandArgs, { stdio: 'inherit' });
if (result.status !== 0) throw new Error(`Exit code ${result.status}`);
} else {
execSync(command, { shell: true, stdio: 'inherit' });
}
process.exit(0);
} catch {
if (i < attempts) {