fix(engine): harden ffprobe parsing and command arguments - #2740
Conversation
939901e to
e740f1c
Compare
There was a problem hiding this comment.
Reviewed packages/engine/src/utils/ffprobe.ts end-to-end at HEAD (e740f1c) plus the new tests. No prior reviews/comments on the PR.
Strengths
parseFrameRate(fix #1, ~L239) is correct and thoroughly table-tested —Number.isFiniteon both operands +den !== 0, with"30/","30/0","0/0","abc/def", and bare"60"all pinned.0is the right fallback: downstreamavgFps || rFpsand the still-image path already toleratefps: 0, whereasNaNpoisoned every frame-count calc.- cICP ordering (fix #3, L184/L208) is correct — the color space is stashed in a local and only emitted once IHDR supplies valid dims. Handles cICP-before-IHDR, normal cICP-after-IHDR, and cICP-with-no-IHDR (→
null). All three covered by the new test.
Blocker
- Fix #2 is incomplete. The PR body says "Every
runFfprobeinvocation now inserts--before the user-suppliedfilePath," but the AAC packet-count probe misses it —ffprobe.ts:388passesfilePathbare (right after-print_format json). Three of the four call sites got--(L274, L366, L457); this fourth did not. So the same audio file at a--prefixed path is separator-protected on the main audio probe (L366) but not on the AAC sub-probe just below it — the exact arg-injection class this PR sets out to close, left open on one path. The"uses -- for audio and keyframe probes too"test can't catch it because the audio fixture ispcm_s16le, so theif (audioCodec === "aac")branch never executes (it asserts 2--, not 3).
Fix: add"--"beforefilePathat L388, and add anaac-codec case to that test so the count becomes 3.
Important — merge-readiness, not code
- CI has not actually run. Every real job — CI, regression, preview-regression, Player perf, CodeQL, Windows render verification — is in
action_required(0s, awaiting maintainer approval to run); the only completed check isWIP(pass). So the body's claim that the 3 LFS-fixture tests "pass in CI with the LFS object" is currently unverified — nothing green confirms it, and this is why the PR showsBLOCKED. A maintainer needs to approve the workflow runs and confirm the vitest CI job (with thehdr-photo-pq.pngLFS object) is green before merge.
Verdict: REQUEST CHANGES
Reasoning: Fixes #1 and #3 are correct and well-tested, but the headline command-arg hardening (#2) misses the AAC packet-count call site — so the "every invocation" claim is false and the injection path stays open there — and CI hasn't executed, so the test-pass claim is unconfirmed.
— Rames Jusso
| const probePromise = (async (): Promise<AudioMetadata> => { | ||
| const stdout = await runFfprobe( | ||
| ["-v", "quiet", "-print_format", "json", "-show_format", "-show_streams", filePath], | ||
| ["-v", "quiet", "-print_format", "json", "-show_format", "-show_streams", "--", filePath], |
There was a problem hiding this comment.
Fix is incomplete: the AAC packet-count sub-probe a few lines down (ffprobe.ts:388, inside if (audioCodec === "aac" && sampleRate > 0)) still passes filePath bare — no --. The same audio file at a --prefixed path is separator-protected on this probe but not on that one, which contradicts the PR body's "every runFfprobe invocation" claim. The "uses -- for audio and keyframe probes too" test can't catch it because its fixture is pcm_s16le (the aac branch never runs). Add "--" before filePath at L388, plus an aac case that bumps the assertion to 3 --.
b4a2a4a to
6575caf
Compare
|
Good catch — I missed the |
|
Thanks for the thorough review, @jrusso1020! A couple of factual corrections on the two points raised: 1. AAC packet-count probe —
|
|
Quick follow-up on the outstanding review: the current head () still includes the AAC sub-probe separator and the AAC regression assertion.\n\nSpecifically on current HEAD:\n- includes before in the AAC packet-count probe\n- uses an fixture for that path and asserts \n\nSo the requested command-arg hardening is present on the branch now. If you have a moment, could you please re-review the latest head? Happy to add more coverage if there is still a remaining gap. |
|
Quick follow-up on the outstanding review: the current head ( Specifically on current HEAD:
So the requested command-arg hardening is present on the branch now. If you have a moment, could you please re-review the latest head? Happy to add more coverage if there is still a remaining gap. |
- parseFrameRate now rejects malformed ratios (e.g. "30/", "30/0") instead of NaN. - Add "--" before file paths so names starting with "-" are not parsed as options. - cICP PNG chunk no longer returns before IHDR supplies width and height. - Add regression tests for option injection, frame rates, and cICP ordering.
2af3a11 to
6d0b397
Compare
|
@jrusso1020 Re-review request on rebased head Addressed:
Regression check: Status: Ready for re-review. No further code changes needed for the requested command-arg hardening. |
jrusso1020
left a comment
There was a problem hiding this comment.
Re-reviewed at 6d0b3972f. My blocker is resolved — dismissing it.
The -- fix is now structural rather than per-call-site. main had already centralized probing into runFfprobe(filePath, argsWithoutInput), and this PR adds the separator to that single spawn (packages/engine/src/utils/ffprobe.ts:51). There is exactly one spawn in the file, so no call site can omit it — that is stronger than the per-site patch I originally asked for, because it also can't regress when a fifth probe gets added later. And the audio fixture is now aac, so the packet-count sub-probe actually executes in the test rather than being skipped.
One note for the record, since it cost a couple of round-trips: my earlier review was pinned to e740f1c, where the AAC call site genuinely did pass filePath bare. Your "the -- IS present" correction was reading a later commit. We were each right about a different SHA — nothing to fix on your side.
What still blocks merge is on our side, not yours. All six required workflows at this head — CI, regression, preview-regression, Player perf, CodeQL, Windows render verification — are sitting at conclusion=action_required: queued but never executed, awaiting maintainer authorization for a fork PR. So the 29/32 local result is still unconfirmed by CI, and that is what makes the PR read BLOCKED. A maintainer needs to approve the runs. I'm holding at comment rather than approving until that comes back green; that is a CI-evidence gate, not a code concern.
Two non-blocking observations:
-
ffprobe.test.ts—expect(args.filter((arg) => arg === "--")).toHaveLength(3)counts across the flattened args of all three calls, so it would also pass if one probe carried two separators and another carried none. Asserting per call, the way the first test does (args[args.indexOf(filePath) - 1]), pins it exactly. Cosmetic now that the guarantee is structural. -
extractPngMetadataFromBuffer— replacing the earlyreturnat cICP with a stash means the walk now continues to IEND, CRC32-checking every chunk including IDAT, where previously it stopped at cICP. Two consequences, both specific to HDR PNGs (the only ones carrying cICP): every still-image probe now pays a full-buffer pass through the pure-JScrc32(L170), and a corrupt trailing chunk now turns a previously-successful probe intonulland falls through to the ffprobe path. Both fall out of a fix that is otherwise correct, and neither is wrong. If you want the old cost profile back,if (seenIdat && width > 0 && height > 0) break;is safe and consistent with the!seenIdatguard the cICP branch already relies on.
Worth noting the normal IHDR-then-cICP ordering is exercised only by the hdr-photo-pq.png LFS fixture — one of the three tests that can't run locally — so that path specifically needs the CI run to be confirmed.
Verdict: code concern cleared, prior blocker dismissed. Holding for a green CI run that a maintainer has to trigger.
— Rames Jusso
Resolved at 6d0b397 — the option separator is now enforced centrally in runFfprobe, so the AAC sub-probe gap is closed structurally. See my re-review comment. Remaining gate is the CI run, which needs maintainer authorization.
jrusso1020
left a comment
There was a problem hiding this comment.
Terminal-green at 6d0b3972f. All six required workflows (CI, regression, preview-regression, Player perf, CodeQL, Windows render verification) came back success. That was the only thing I was holding for; my earlier blocker was already dismissed once main's runFfprobe centralization made the separator structural rather than per-call-site.
Worth recording that the HDR path is genuinely covered here rather than skipped: the three hdr-photo-pq.png tests are declared unconditionally, and the run fetched the LFS objects, so they executed. That also explains the 29/32 you saw locally. Those three were reading the LFS pointer file instead of the actual PNG.
The two notes from my previous review stay non-blocking, yours to take or leave:
- The
--assertion counts across the flattened args of all three probes, so asserting per call would pin it exactly. extractPngMetadataFromBuffernow walks to IEND instead of returning early at cICP, so every still-image probe CRC32s the whole buffer in pure JS.if (seenIdat && width > 0 && height > 0) break;restores the old cost profile and is consistent with the!seenIdatguard the cICP branch already relies on.
Thanks for staying with this one. The week of silence was on our side, not yours: the required workflows were sitting queued awaiting authorization for a fork PR.
Approving.
— Rames Jusso
…site #2740 added `--` to one of nine independent ffprobe invocations, so the bug class it closed stayed open everywhere else while CI reported it fixed — the regression test asserts the argv of that single site. Reproduced on ffprobe 8.1.1: an asset named `-intro.mp4` probes fine through extractMediaMetadata but fails with "Missing argument for option 'intro.mp4'" in audio pad/trim (mid-render), `hyperframes init`, whisper duration probing and webmAlphaCheck. hevcPreviewLint catches and returns false, so a dash-prefixed HEVC preview silently passes the lint rule. Terminated at all of them: producer/services/render/audioPadTrim.ts (x2) producer/plan-parity-analysis.ts cli/commands/init.ts cli/utils/webmAlphaCheck.ts cli/whisper/transcribe.ts (x2) core/mediaGradeAnalyzer.ts lint/hevcPreviewLint.ts audioPadTrim's runFfprobeJson is a near-verbatim clone of the engine's runFfprobe and structurally cannot add the terminator itself, because callers bake the input path into `args`. It now asserts the terminator is present rather than letting a dash-prefixed path through, takes the same stdio ["ignore", ...] as the engine helper, and redacts its stderr — it was throwing raw ffprobe output, which echoes the input path, into logs and telemetry. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ontract The previous commit claimed "all nine now terminate their options". That was false: `producer/src/utils/audioRegression.ts:307` still passed the path bare, and it is production source used by the regression harness. A repo-wide audit found two more in studio-server (`mediaValidation.ts`, `mediaMetadata.ts`) — their current callers pass absolute paths, so they were defence-in-depth rather than live bugs, but the exhaustiveness claim should be true rather than narrowed. Eleven sites total, all terminated. Adds a SOURCE-level contract test, which is the gap that let this happen twice. #2740 fixed one of ten sites and shipped a regression asserting the argv of that single site, so CI reported the class closed while nine invocations still parsed `-intro.mp4` as an option. A per-site unit test has the same blind spot for site twelve; scanning the tree does not. The test also asserts its own coverage list has not shrunk. Verification: engine 1300, lint 511, core 1431, studio-server 398, cli init/webmAlphaCheck/whisper 146, producer utils 51, audioPadTrim 18. Removing any single terminator fails the contract test by name. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…site #2740 added `--` to one of nine independent ffprobe invocations, so the bug class it closed stayed open everywhere else while CI reported it fixed — the regression test asserts the argv of that single site. Reproduced on ffprobe 8.1.1: an asset named `-intro.mp4` probes fine through extractMediaMetadata but fails with "Missing argument for option 'intro.mp4'" in audio pad/trim (mid-render), `hyperframes init`, whisper duration probing and webmAlphaCheck. hevcPreviewLint catches and returns false, so a dash-prefixed HEVC preview silently passes the lint rule. Terminated at all of them: producer/services/render/audioPadTrim.ts (x2) producer/plan-parity-analysis.ts cli/commands/init.ts cli/utils/webmAlphaCheck.ts cli/whisper/transcribe.ts (x2) core/mediaGradeAnalyzer.ts lint/hevcPreviewLint.ts audioPadTrim's runFfprobeJson is a near-verbatim clone of the engine's runFfprobe and structurally cannot add the terminator itself, because callers bake the input path into `args`. It now asserts the terminator is present rather than letting a dash-prefixed path through, takes the same stdio ["ignore", ...] as the engine helper, and redacts its stderr — it was throwing raw ffprobe output, which echoes the input path, into logs and telemetry. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ontract The previous commit claimed "all nine now terminate their options". That was false: `producer/src/utils/audioRegression.ts:307` still passed the path bare, and it is production source used by the regression harness. A repo-wide audit found two more in studio-server (`mediaValidation.ts`, `mediaMetadata.ts`) — their current callers pass absolute paths, so they were defence-in-depth rather than live bugs, but the exhaustiveness claim should be true rather than narrowed. Eleven sites total, all terminated. Adds a SOURCE-level contract test, which is the gap that let this happen twice. #2740 fixed one of ten sites and shipped a regression asserting the argv of that single site, so CI reported the class closed while nine invocations still parsed `-intro.mp4` as an option. A per-site unit test has the same blind spot for site twelve; scanning the tree does not. The test also asserts its own coverage list has not shrunk. Verification: engine 1300, lint 511, core 1431, studio-server 398, cli init/webmAlphaCheck/whisper 146, producer utils 51, audioPadTrim 18. Removing any single terminator fails the contract test by name. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…site #2740 added `--` to one of nine independent ffprobe invocations, so the bug class it closed stayed open everywhere else while CI reported it fixed — the regression test asserts the argv of that single site. Reproduced on ffprobe 8.1.1: an asset named `-intro.mp4` probes fine through extractMediaMetadata but fails with "Missing argument for option 'intro.mp4'" in audio pad/trim (mid-render), `hyperframes init`, whisper duration probing and webmAlphaCheck. hevcPreviewLint catches and returns false, so a dash-prefixed HEVC preview silently passes the lint rule. Terminated at all of them: producer/services/render/audioPadTrim.ts (x2) producer/plan-parity-analysis.ts cli/commands/init.ts cli/utils/webmAlphaCheck.ts cli/whisper/transcribe.ts (x2) core/mediaGradeAnalyzer.ts lint/hevcPreviewLint.ts audioPadTrim's runFfprobeJson is a near-verbatim clone of the engine's runFfprobe and structurally cannot add the terminator itself, because callers bake the input path into `args`. It now asserts the terminator is present rather than letting a dash-prefixed path through, takes the same stdio ["ignore", ...] as the engine helper, and redacts its stderr — it was throwing raw ffprobe output, which echoes the input path, into logs and telemetry. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ontract The previous commit claimed "all nine now terminate their options". That was false: `producer/src/utils/audioRegression.ts:307` still passed the path bare, and it is production source used by the regression harness. A repo-wide audit found two more in studio-server (`mediaValidation.ts`, `mediaMetadata.ts`) — their current callers pass absolute paths, so they were defence-in-depth rather than live bugs, but the exhaustiveness claim should be true rather than narrowed. Eleven sites total, all terminated. Adds a SOURCE-level contract test, which is the gap that let this happen twice. #2740 fixed one of ten sites and shipped a regression asserting the argv of that single site, so CI reported the class closed while nine invocations still parsed `-intro.mp4` as an option. A per-site unit test has the same blind spot for site twelve; scanning the tree does not. The test also asserts its own coverage list has not shrunk. Verification: engine 1300, lint 511, core 1431, studio-server 398, cli init/webmAlphaCheck/whisper 146, producer utils 51, audioPadTrim 18. Removing any single terminator fails the contract test by name. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…#2740) - parseFrameRate now rejects malformed ratios (e.g. "30/", "30/0") instead of NaN. - Add "--" before file paths so names starting with "-" are not parsed as options. - cICP PNG chunk no longer returns before IHDR supplies width and height. - Add regression tests for option injection, frame rates, and cICP ordering.
What
Hardens
packages/engine/src/utils/ffprobe.tsagainst three edge cases:"30/","30/0","abc/def") were returningNaNfromparseFrameRate.-were passed as bare arguments to ffprobe, which can be misinterpreted as option flags.cICPcolor-space handler returned immediately, so acICPchunk seen beforeIHDRcould emitwidth: 0, height: 0.Why
These bugs caused downstream metadata (
fps,colorSpace) to becomeNaN/null or, in the path case, allowed accidental option injection when user-provided paths begin with a dash.How
parseFrameRatenow checksNumber.isFiniteon both numerator and denominator and requiresden !== 0; invalid ratios fall back to0instead ofNaN.runFfprobeinvocation now inserts--before the user-suppliedfilePath.extractPngMetadataFromBufferstores anycICPcolor space in a local variable and only emits it afterIHDRhas supplied valid width/height.Test plan
Ran
bunx vitest run src/utils/ffprobe.test.tsandbunx oxlint/bunx oxfmt --checkon the changed files. Three existing fixture-dependent tests fail locally becausehdr-photo-pq.pngis stored via Git LFS and is not present in this working tree; they pass in CI with the LFS object.