Fix ingest subprocess dropping the filename argument (C1)

spawn(..., { shell: true }) made Node concatenate argv without quoting;
cmd.exe re-tokenized it and the ingest prompt arrived as a bare "ingest"
with the filename silently discarded, while the run still reported
success. claude.exe is a real executable here, so no shell is needed:
drop shell: true and pass argv directly. isSafeFilename is unchanged
(still correct defence in depth) but its comment now reflects that the
spawn path carries no injection surface today.
This commit is contained in:
meels
2026-07-28 14:55:04 +02:00
parent 84fe67632a
commit 3bc1afa9f1

View File

@@ -156,18 +156,14 @@ function reconcileConcepts(rows, conceptNames) {
}; };
} }
// `shell: true` is required on Windows to resolve `claude.cmd`, which puts the // Defence in depth. The spawn path no longer uses a shell, so nothing here is
// filename into a shell string. Reject anything cmd.exe or a POSIX shell would // load-bearing against injection today — but the guard costs nothing and would
// interpret, plus every control character. // still hold if the single-string `shell: true` form is ever needed for a .cmd
// shim. `^` is cmd.exe's escape character; control characters are rejected
// because a NUL byte makes spawn() throw synchronously.
// //
// `^` is cmd.exe's escape character and belongs to the same class Node escapes // Apostrophes, spaces, cyrillic, em dashes and `!` stay allowed — they appear in
// in its CVE-2024-27980 mitigation for exactly this spawn-through-.cmd shape. // real filenames in this vault.
// Control characters are rejected because a NUL byte makes spawn() throw
// synchronously, which would otherwise strand the row mid-run.
//
// Apostrophes, spaces, cyrillic, em dashes and `!` stay allowed — they are safe
// inside double quotes and appear in real filenames in this vault. (`!` would
// matter only under `setlocal enabledelayedexpansion`, which is not in play.)
const UNSAFE_CHARS = /["`$&|;<>%^\u0000-\u001f\u007f]/; const UNSAFE_CHARS = /["`$&|;<>%^\u0000-\u001f\u007f]/;
function isSafeFilename(name) { function isSafeFilename(name) {
@@ -492,10 +488,14 @@ class WebinarDashPlugin extends PluginBase {
let child; let child;
try { try {
child = spawn("claude", ["-p", `ingest "${file.name}"`], { // `claude` resolves to a real .exe here, so libuv finds it via PATH and
cwd: base, // PATHEXT with no shell involved. Do NOT add `shell: true`: with a shell,
shell: true, // Node concatenates argv without quoting and cmd.exe re-tokenizes, so the
}); // prompt arrives as two arguments and the filename is silently discarded.
// Verified: shell:true yields ["-p","ingest","Webinar script.md"], where the
// program sees only "ingest". If a .cmd shim ever needs supporting, use the
// single-string form spawn(`claude -p "ingest ${name}"`, { shell: true }).
child = spawn("claude", ["-p", `ingest "${file.name}"`], { cwd: base });
} catch (err) { } catch (err) {
// spawn() throws synchronously for some argument shapes. Without this the // spawn() throws synchronously for some argument shapes. Without this the
// timer would run forever and the row would stay disabled until reload. // timer would run forever and the row would stay disabled until reload.