fix(media-use): kill shell command injection in probe/heygen-search/eval

Swap execSync(<shell-string>) → execFileSync(file, [argv]) in probe.mjs, heygen-search.mjs, and eval.mjs so hostile filenames / queries / manifest metadata can't inject shell. Adds probe.test.mjs regression guard and a CI Test (skills) job so it actually runs. Closes the media-use High/Critical scanner alert.
This commit is contained in:
Miguel Ángel
2026-06-25 15:16:52 -04:00
committed by GitHub
parent f7bc0384f0
commit 041f2fa196
6 changed files with 95 additions and 21 deletions
+14 -8
View File
@@ -1,20 +1,26 @@
import { execSync } from "node:child_process";
import { execFileSync } from "node:child_process";
export function heygenSearch(subcommand, query, { type, limit = 5, minScore } = {}) {
const q = query.replace(/'/g, "'\\''");
// execFileSync with an argv array (no shell), so query/type/etc. are passed as
// literal arguments — no quoting tricks, no command injection. subcommand is a
// hardcoded multi-word string (e.g. "audio sounds list"), split into tokens.
// Tag the caller via the CLI's allowlisted attribution header (heygen >= v0.1.6).
const parts = [
`heygen --headers 'X-HeyGen-Client-Source: media-use' ${subcommand} --query '${q}'`,
const args = [
"--headers",
"X-HeyGen-Client-Source: media-use",
...subcommand.split(" "),
"--query",
query,
];
if (type) parts.push(`--type ${type}`);
parts.push(`--limit ${limit}`);
if (type) args.push("--type", type);
args.push("--limit", String(limit));
// Server-side score floor. Honored by `audio sounds list`; the `asset search`
// backend rejects it, so only audio providers pass minScore (see image-provider).
if (minScore != null) parts.push(`--min-score ${minScore}`);
if (minScore != null) args.push("--min-score", String(minScore));
let out;
try {
out = execSync(parts.join(" "), {
out = execFileSync("heygen", args, {
encoding: "utf8",
timeout: 15000,
stdio: ["pipe", "pipe", "pipe"],