fix(cli,core,lint,producer): terminate ffprobe options at every call site - #2917
fix(cli,core,lint,producer): terminate ffprobe options at every call site#2917vanceingalls wants to merge 0 commit into
Conversation
miga-heygen
left a comment
There was a problem hiding this comment.
Stack 6/6: Option terminator at every call site — Review
Good sweep. The audioPadTrim assertion and stderr redaction are solid additions on top of the mechanical -- insertion. One gap.
Blocking: missed call site in audioRegression.ts
packages/producer/src/utils/audioRegression.ts:307 — probeAudioDuration spawns ffprobe with a bare file argument and no -- terminator:
const proc = spawnSync("ffprobe", [
"-v", "error",
"-select_streams", "a:0",
"-show_entries", "stream=duration",
"-of", "default=noprint_wrappers=1:nokey=1",
file, // <-- no --
], { encoding: "utf-8" });This is used by regression-harness.ts — production regression-detection, not a test. Same vulnerability class as every other site this PR fixes. A file named -intro.mp4 would produce Missing argument for option 'intro.mp4' here too.
The title says "every call site" — this one makes that claim incomplete.
Verified (the 8 sites the PR does cover)
-
cli/commands/init.ts—--added beforefilePathinexecFileSync -
cli/utils/webmAlphaCheck.ts— same -
cli/whisper/transcribe.ts— both sites (duration probe + wav check) -
core/mediaGradeAnalyzer.ts— same -
lint/hevcPreviewLint.ts— same. Bonus: this one silently passed dash-prefixed HEVC previews before -
producer/plan-parity-analysis.ts— same -
producer/services/render/audioPadTrim.ts— both probe sites + assertion guard inrunFfprobeJson+stdio: ["ignore", ...]+ stderr redaction viaredactTelemetryString
Fix the audioRegression site and the "every" claim lands.
james-russo-rames-d-jusso
left a comment
There was a problem hiding this comment.
The mechanical -- addition across the eight sites is straightforward; the higher-value change here is runFfprobeJson's new posture — args.includes("--") assertion, stdio: ["ignore", "pipe", "pipe"], and redactTelemetryString on the error path (stderr echoes the input path into logs/telemetry — that redaction wasn't advertised in the title but is a real independent win). A few things worth surfacing.
Blockers
packages/producer/src/utils/audioRegression.ts:307— missed inside this PR's own scope.probeAudioDurationdoesspawnSync("ffprobe", ["-v", "error", "-select_streams", "a:0", "-show_entries", "stream=duration", "-of", "default=noprint_wrappers=1:nokey=1", file], ...)with no"--"beforefile. It's reached fromregression-harness.ts:1395viacomputeAudioResidualRmsDb(rendered, snapshot, ...). The paths are render outputs and snapshot references rather than raw user input, so the real-world exploit surface is thin — but this is exactly the argv-injection class of bug the PR is closing everywhere else, in producer, and the title ("at every call site") plus body ("nine independent ffprobe invocations") reads as an exhaustive fix. Please patch this one too, or explicitly carve it out.
Concerns
- Out-of-scope, same class —
packages/studio-server/src/helpers/mediaValidation.ts:29.validateUploadedMediarunsrunner("ffprobe", [..., filePath])with no"--", called fromvalidateUploadedMediaBuffer→routes/files.ts:2148against uploaded media filenames. This is the most user-controlled path input in the codebase and squarely the case--is meant to close. Outside the PR's statedfix(cli,core,lint,producer)scope, but understand you may want to bundle it into this stack or immediately follow up rather than leaving it in a state where "we fixed the argv injection everywhere except the upload endpoint" is technically true. - Out-of-scope, same class —
packages/studio-server/src/helpers/mediaMetadata.ts:196.probeMediaMetadataalso missing--. Reached fromproxyTranscoder.ts,mediaCodecMap.ts,routes/media.ts— same class, lower urgency than the upload validator but still there. - No regression tests locking
--in the eight patched sites.engine/utils/ffprobe.test.ts:557/:588assert the terminator's presence — but only for the engine helper. Nothing in cli/core/lint/producer has an argv-shape assertion. Given the argv layout is identical across all eight ([..., "--", filePath]), a shared helper or per-site assertion is cheap to add; without it, a future refactor of any of the eight can silently reintroduce the bug the same way it slipped past #2740. This bug class has already slipped once — worth locking down.
Nits
runFfprobeJson's new comment says "callers bake the input path intoargs(terminated with--)" — a pointer at the two callers (:355,:375) would help the next reader trace the contract.- With the assertion in place,
runFfprobeJsonreads structurally identical to the engine'srunFfprobemodulo the assertion and the redaction. Not a blocker for this PR — the body already notes the two have diverged in error-message prefix and caller contract — but as/when the divergence closes further, collapsing them behind a---terminator-owning wrapper would remove the sibling-drift risk. Follow-up material.
What I didn't verify
- Whether the
redactTelemetryString(..., 2000)cap is calibrated against real ffprobe stderr sizes on the failure modes that reach this helper. Probably fine — 2000 chars is generous for the "container/codec error" family — just noting the constant wasn't derived from a measured maximum.
— Review by Rames D Jusso
miguel-heygen
left a comment
There was a problem hiding this comment.
Reviewed exact head b46e97bcb9558832fae4227d8190abe5fa66090e against stack parent #2916. One blocker:
P1 — the producer sweep is incomplete and nothing pins the fixed argv contract. packages/producer/src/utils/audioRegression.ts:307-319 still invokes ffprobe with file directly after the options, without "--". This is production source used by the regression harness and sits inside the PR’s declared producer scope, so the PR body’s “all nine now terminate their options” / exhaustive fix claim is not true. The seven changed files also add no regression that inspects argv; that is the same test gap that let #2740 leave the bug class open.
Please add the missing terminator and a focused argv regression that exercises a dash-prefixed path (ideally table-driven across the patched seams where practical). A repo-wide audit also finds the same shape in studio-server/src/helpers/mediaValidation.ts:29-37 and mediaMetadata.ts:196-207; current primary callers appear to pass absolute paths, so I am treating those as follow-up defense-in-depth rather than an additional blocker here.
Exact-head CI is terminal green, the PR is mergeable, and there are no unresolved review threads.
b150c48 to
fbb2735
Compare
b46e97b to
83c720e
Compare
|
Fixed in The missed site. The two you filed as follow-up. I fixed The test gap — the more important half. A source-level contract test now scans every known ffprobe caller for a format flag followed directly by a bare identifier. A per-site unit test has exactly the blind spot that let this happen twice: #2740 asserted the argv of the one site it fixed, CI reported the class closed, and nine invocations stayed broken. The new test also asserts its own coverage list hasn't shrunk, so deleting an entry to make it pass is itself a failure. Removing any single terminator fails it by name — verified against Verification: engine 1300, lint 511, core 1431, studio-server 398, cli 146, producer utils 51, audioPadTrim 18. |
miguel-heygen
left a comment
There was a problem hiding this comment.
Re-review
Approved at exact head 83c720e1a0d2e541c6b721c0a4fb626ee690252d.
The missed producer harness site is fixed, both studio-server defense-in-depth sites are included, and the repo audit now finds all eleven current ffprobe input seams terminated with --. The source-level contract gives useful per-file coverage and exact-head CI is terminal green.
Non-blocking suggestion
expect(SCANNED.length).toBeGreaterThanOrEqual(11) prevents the hard-coded list from shrinking, but it cannot discover a twelfth ffprobe caller added outside SCANNED, despite the adjacent comment. A future follow-up should derive the caller-file set from the tree (or compare it against a generated manifest) so the guard actually detects new unlisted seams. This does not affect the correctness of the eleven sites fixed here.
fbb2735 to
9e27542
Compare
83c720e to
9e27542
Compare
Stack 6/6, on top of #2916. Completes #2740.
The gap
#2740 added
--to one of nine independent ffprobe invocations, so the bug class it closed stayed open everywhere else — while CI reported it fixed, because the regression test asserts the argv of that single site.Reproduced on ffprobe 8.1.1 with an asset named
-intro.mp4. It probes fine throughextractMediaMetadata, then:producer/services/render/audioPadTrim.ts×2Missing argument for option 'intro.mp4'— mid-rendercli/commands/init.tshyperframes initcli/whisper/transcribe.ts×2cli/utils/webmAlphaCheck.tscore/mediaGradeAnalyzer.tsproducer/plan-parity-analysis.tslint/hevcPreviewLint.tsAll nine now terminate their options.
The clone that couldn't take the fix
audioPadTrim.ts:436runFfprobeJsonis a near-verbatim copy of the engine'srunFfprobe, and structurally cannot add--itself: callers bake the input path intoargs. It now:stdio: ["ignore", ...]as the engine helper (see fix(engine): reject stdin input, decode stdout correctly, bound its size #2916 for why)That duplication is worth collapsing into the engine helper eventually; this PR does not attempt it, since the two have diverged in error-message prefix and caller contract.
Verification
engine 1280, lint 511, core 1431, cli (init/webmAlphaCheck/whisper) 146, producer audioPadTrim 18 — all pass. Typecheck and lint clean across all five packages.
🤖 Generated with Claude Code