Skip to content

fix: take helper programs only from install locations, never the project folder - #7524

Merged
jrusso1020 merged 5 commits into
mainfrom
fix/trusted-helper-binaries
Nov 11, 2026
Merged

jrusso1020 merged 5 commits into
mainfrom
fix/trusted-helper-binaries

Conversation

@jrusso1020

@jrusso1020 jrusso1020 commented Nov 10, 2026 •

Copy link
Copy Markdown
Collaborator

The CLI, engine and Studio now find FFmpeg, FFprobe, whisper-cli, parakeet-mlx, Python, system Chrome, brew, git, cmake and the clipboard tool through one shared rule: a program comes from an absolute PATH entry outside the working directory, or from a fixed install dir. It never comes from the project folder or a relative PATH entry, and when no such copy exists HyperFrames says so instead of handing the bare name to the OS lookup. An explicit HYPERFRAMES_* override still points anywhere the person chooses.

What

  • New packages/parsers/src/trustedExecutable.ts, exported from @hyperframes/parsers/ff-binaries: isTrustedDirectory, isTrustedExecutablePath, trustedPathDirs, findTrustedExecutable.
  • findFfBinary uses it for the which result, the PATH scan, the common install dirs and the installer package. The .hyperframes/bin fallback is gone. An @ffmpeg-installer / @ffprobe-installer package is now looked up next to HyperFrames' own install (global or npx), not from the working directory's package.json.
  • New requireFfBinary(name): findFfBinary, or an ENOENT-coded "not found" error that names HYPERFRAMES_FFMPEG_PATH / HYPERFRAMES_FFPROBE_PATH. It never returns the bare name.
  • No bare-name fallback. The engine's getFfmpegBinary / getFfprobeBinary (also a public @hyperframes/engine export) and Studio's waveform, peak-map and audio-track helpers used to spawn plain ffmpeg / ffprobe when the lookup came back empty. Studio now reports the binary as missing. The engine getters return an absolute path inside HyperFrames' own install that is never created, in a folder named for the override (…/ffmpeg-not-found-set-HYPERFRAMES_FFMPEG_PATH/ffmpeg), so a spawn fails with ENOENT exactly as a missing FFmpeg always has.
  • AWS Lambda. The handler's bundled ffmpeg and ffprobe live in <task root>/bin, and the task root is Lambda's working directory, so the rule above would skip them. primeRuntimeEnv now sets HYPERFRAMES_FFMPEG_PATH and HYPERFRAMES_FFPROBE_PATH to the bundled files when they exist and no override is set. Cloud Run and the render --docker image install FFmpeg in /usr/local/bin and run from /app/... and /project, so they need no change.
  • findWhisper (and its brew/git/cmake checks), findSystemBrowser, findParakeet and findPython call findTrustedExecutable instead of parsing where / which output. The whisper.cpp install now runs brew, git and cmake by the path that lookup approved, not by name.
  • The clipboard copy in hyperframes add resolves pbcopy / clip / wl-copy / xclip / xsel through findTrustedExecutable and runs that path.
  • The media-use engine, which hyperframes media-use starts as a child process and which runs ffmpeg, codex and local models by name, gets a PATH that holds only the folders trustedPathDirs accepts.
  • The CLI skill's FFmpeg reference stops suggesting the project-local folder and says to keep binaries outside the project.

Why

A project folder is often something the person cloned or unpacked. Follows up #7511, which skipped relative PATH entries in the FFmpeg PATH scan and the working directory in findPython on Windows. Three gaps were left. The FFmpeg resolver still fell back to .hyperframes/bin and to an installer package resolved from the working directory. The other helper lookups still took the first line of where, which lists the working directory before PATH on Windows. And an absolute PATH entry inside the project, such as the working directory itself or its node_modules/.bin under npm run / npx, was still accepted. Review then showed that a lookup miss still ended in a bare-name spawn, so the OS search could reach those same folders; this PR closes that too.

Related work

Refs #7511. Open PRs #7253 (browser download failure message) and #6038 (Parakeet decode window) touch browser/manager.ts and whisper/parakeet.ts in different functions, so they should rebase cleanly either way.

How

Claude Opus 5.5
  • The rule (isTrustedDirectory): the directory must be absolute, and it must not be the working directory or inside it. Both sides go through realpathSync.native, and paths are compared case-insensitively on Windows. One carve-out: when the working directory is the home folder or one of its parents, only that folder itself is excluded. Without it, running the CLI from ~ would reject ~/.local/bin, ~/.volta/bin and similar per-user install dirs.
  • Windows walks PATH directly with .exe first and then PATHEXT, the same approach ffBinaries already used. That avoids the working-directory-first order of where and its console code-page decoding. Elsewhere which answers. If its first hit is untrusted (for example . on PATH), a PATH walk finds a later trusted copy. If which finds nothing, the result is still "not found", same as before.
  • Installer package: it resolves from import.meta.url, and the result must also pass the rule, so a CLI run from inside a project's own node_modules never picks the project's copy.
  • A missing FFmpeg still degrades where it did before. The first version of this change threw from the engine getters. That broke the streaming encoder's tested "failure result, not a throw" contract and every unit lane that runs without FFmpeg (module-level HAS_FFMPEG probes, mocked-spawn tests). The never-created absolute path keeps the failure asynchronous: runFfmpeg returns its usual spawn_error, ffprobe.ts maps the ENOENT to "ffprobe not found", the GPU and -version probes report none, and isFfmpegInfrastructureFailure and the audio-mixer and HEIC patterns still read ENOENT. No lookup by name happens on any of those paths. Studio's probeAudioTrack returns null (no audio track) and the waveform and peak decoders reject with the requireFfBinary message, which their routes already handle.
  • Lambda: useBundledBinaries(taskRoot, env) is the extracted, testable part of primeRuntimeEnv. It keeps prepending <task root>/bin to PATH as before and only adds the two overrides.
  • media-use PATH: the filter runs in mediaUseEngineEnv, keyed case-insensitively because Windows spells the variable Path. The engine's .mjs files ship as standalone skill scripts and cannot import @hyperframes/parsers, so scoping their PATH from the CLI side covers ffmpeg/ffprobe (ff-binaries.mjs), codex and the local-run shell commands at once.
  • Windows working-directory search: the CLI sets NoDefaultCurrentDirectoryInExePath=1 in its own environment on startup (utils/windowsExeSearch.ts, imported by cli.ts and the CLI's worker entry points). libuv's Windows search_path consults it via NeedCurrentDirectoryForExePathW since libuv 1.48.0 (libuv#4238), and Node 22.0.0, the CLI's minimum, ships 1.48.0. That covers the cwd-first part of any remaining by-name spawn from the CLI process and its children; it does not cover an absolute PATH entry inside the project, which is why the spawns above take an absolute path or a filtered PATH.
  • Not changed: doctor's unzip check only tests whether a command exists. core's analyzeMediaGrade keeps its "ffmpeg" default, but its only callers (media-treatment-analysis and the share host) pass resolved paths. Standalone skill scripts under skills/ that an agent runs directly, and repo dev scripts (regression harness, catalog preview generators), still call ffmpeg by name; they run in the agent's or developer's own shell, with that shell's lookup.

Test plan

  • Unit tests added/updated
    • parsers/src/trustedExecutable.test.ts: covers relative entries, the working directory and folders inside it, and the home-folder carve-out.
    • parsers/src/ffBinaries.test.ts: a project with .hyperframes/bin/{ffmpeg,ffprobe} and a seeded installer package (flat and pnpm layouts) resolves to nothing. A project ffmpeg that which reports, with a second copy in an install dir, resolves to the install-dir copy. A project-only copy with the project on PATH (plus . and bin) resolves to nothing. A Windows ffmpeg.exe in the working directory loses to the PATH copy. The Apple Silicon x86_64 skip is now exercised on PATH binaries. New: requireFfBinary throws an ENOENT naming the override instead of returning a bare name.
    • engine/src/utils/ffmpegBinaries.test.ts (new "with no trusted FFmpeg installed" block): with a project node_modules/.bin leading PATH and holding ffmpeg / ffprobe scripts that write a marker file, getFfmpegBinary returns the never-created path, runFfmpeg returns a spawn_error with ENOENT, detectGpuEncoder resolves null, runFfprobe rejects with "ffprobe not found", and no marker is written.
    • studio-server/src/helpers/noTrustedFfmpeg.test.ts (new): the same setup; decodeAudioPeaks and decodePeakMap reject with "ffmpeg not found" and probeAudioTrack returns null, with no marker written.
    • aws-lambda/src/handler.test.ts (useBundledBinaries): a task root with bin/ffmpeg and bin/ffprobe gets both overrides plus the PATH prefix; an override already set is kept; a bundle without one binary leaves that override unset; no bin folder changes nothing.
    • cli/src/whisper/manager.install.test.ts: brew runs as /opt/homebrew/bin/brew (the approved path) and never as brew (skipped on Windows, where the lookup walks the real PATH); the clone and build tests match git and cmake by name, with or without .exe.
    • engine/src/services/videoFrameExtractor.hdr.test.ts: the "macOS ffmpeg without zscale" case re-imports the engine with process.platform faked to darwin; on the Windows runner that fresh lookup cannot walk the Windows PATH, so the test now pins the FFmpeg it already found through the two overrides for that block.
    • studio-server/src/helpers/loudness.windowsHide.test.ts: now stubs the ff-binaries lookup like its sibling window tests, so a lane without FFmpeg still reaches the stubbed ffprobe.
    • cli/src/utils/clipboard.test.ts / clipboard.windowsHide.test.ts: the copy runs the trusted path (/usr/bin/pbcopy, C:\Windows\System32\clip.exe), and nothing runs when no trusted tool exists.
    • cli/src/commands/media-use.test.ts: with a project node_modules/.bin/codex that writes a marker, . and /usr/bin:/bin on PATH, the engine env's PATH is /usr/bin:/bin and running codex through /bin/sh with it writes no marker.
    • cli/src/whisper/trustedLookup.test.ts, cli/src/browser/manager.test.ts, cli/src/tts/python.test.ts: the Windows working-directory cases for whisper-cli, parakeet-mlx, Chrome and Python, as before.
    • Mutation checks, each reverted afterwards: restoring the engine's ?? "ffmpeg" / ?? "ffprobe" fallback fails all 3 engine tests (the stand-in runs, or the probe resolves with its empty output); restoring the Studio fallbacks fails both Studio tests (the stand-in ffmpeg runs); dropping the Lambda override loop fails the bundled-binaries test; running "brew" by name, the clipboard tool by name, or the media-use engine with the unfiltered PATH fails each matching test (the stand-in codex writes its marker); requireFfBinary returning the name fails its test. Earlier: making isTrustedDirectory accept every absolute folder fails 8 tests; restoring the .hyperframes/bin and working-directory installer fallbacks fails 3.
    • Suites: parsers ffBinaries + trustedExecutable; engine src/utils/, aacEncode, streamingEncoder, audioMixer (1048 passed, after the engine change too); studio-server src/helpers/ (694 passed); CLI src/whisper/, src/utils/clipboard*, src/commands/media-use, src/tts/python, src/browser/manager (340 passed); aws-lambda handler.test.ts (32 passed); typecheck of parsers, engine, studio-server, aws-lambda and producer; the comment ratchet and citation checks; check-cli-process-ownership; check-test-reachability.
  • Manual testing performed: I ran the built CLI's doctor --json from a project folder holding .hyperframes/bin/ffmpeg and a top-level ffmpeg. Each fake writes a marker file when run. With PATH set to only the Node folder (no which, no ffmpeg), doctor reports /usr/bin/ffmpeg. With the project folder first on PATH, it also reports /usr/bin/ffmpeg. Neither marker file was created. Not run locally: an engine/producer/Studio unit run on a machine with no FFmpeg at all (this box has /usr/bin/ffmpeg; CI's unit lanes cover it), a real Windows host (the Windows cases are simulated on Linux with process.platform), a real Lambda invocation (the handler change is covered by the unit test only), a real macOS whisper install through brew, and check-cli-lazy-deps against a fresh CLI build (the local build stopped on unlinked workspace packages unrelated to this change; the new imports are node: built-ins and @hyperframes/parsers only).
  • Documentation updated (if applicable)
  • Comments follow CONTRIBUTING.md "Comments"

— Rames Jusso

🤖 Generated with Claude Code

@github-actions

github-actions Bot commented Nov 11, 2026 •

Copy link
Copy Markdown
Contributor

Edit accuracy: accurate 267 (base branch 267), smooth 105 of those

The gate passes.
Smoothness is reported in the artifact, not gated. A case fails only if it fails 2 of 3 runs.

Quarantined, measured but not gated (8)

jrusso1020 and others added 2 commits November 11, 2026 07:18
…ever the project folder

FFmpeg, FFprobe, whisper-cli, parakeet-mlx, Python and system Chrome are now
found through one shared rule: an absolute PATH entry outside the working
directory, or a fixed install dir. A relative PATH entry and any folder
inside the working directory are skipped, on every platform. Windows walks
PATH itself instead of asking `where`, which lists the working directory
first.

FFmpeg discovery no longer falls back to `.hyperframes/bin` or to an
installer package in the project's node_modules; an installer package
installed next to HyperFrames still works. The env overrides are unchanged.
The CLI skill reference stops suggesting the project-local folder.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Windows now walks PATH instead of running `where`, so tests that faked
`where` output found the runner's real python and cmake. Both tests now set
PATH to a temp folder holding only the programs they expect.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@jrusso1020
jrusso1020 force-pushed the fix/trusted-helper-binaries branch from 45e60b2 to 708ab6f Compare November 11, 2026 07:19

@heygengenesis heygengenesis Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict: REQUEST CHANGES · 1 blocker · 1 important · 0 nits · head 708ab6f

The lookup rule is correct. On a miss, though, several callers still run the bare name, so PATH (or Windows' cwd-first search) can still pick the project's copy.

  • blocker: engine/src/utils/ffmpegBinaries.ts:10,14 (findFfBinary(...) ?? "ffmpeg") and the studio-server copies (peakMap.ts:69, waveform.ts:67, mediaMetadata.ts:298) spawn bare ffmpeg/ffprobe when the trusted lookup returns undefined. Repro on Linux, with system FFmpeg hidden, cwd = project, PATH=<proj>/node_modules/.bin:… (the npm run/npx shape), planted node_modules/.bin/ffmpeg: findFfBinary("ffmpeg") → undefined, then spawnSync(found ?? "ffmpeg") → the planted script runs. On Windows a project-root ffmpeg.exe runs the same way, because libuv's search_path tries the cwd first. Fix: fail with the install hint and never spawn the bare name. Lambda: the handler's cwd is /var/task and primeRuntimeEnv prepends /var/task/bin to PATH. That folder is inside the cwd, so this PR already rejects it, and Lambda renders today only through this fallback. Removing the fallback therefore needs primeRuntimeEnv to set HYPERFRAMES_FFMPEG_PATH/HYPERFRAMES_FFPROBE_PATH too.
  • important: some sibling call sites are outside the diff. On Windows, utils/clipboard.ts spawns bare clip.exe from hyperframes add, so a clip.exe in the project runs. media-use codex-provider.mjs:52 and local-run.mjs (shell: true) resolve by name the same way. Route them through findTrustedExecutable and spawn the result, or note them as out of scope in the PR body. ↓ inline for the one in this diff
Scope

Read all 14 files. Checked every findFfBinary consumer and every which/where and bare-name spawn under packages/. CI is green on the head; BLOCKED is the review gate only. No prior reviews.

— Genesis

Comment thread packages/cli/src/whisper/manager.ts Resolved
When no trusted FFmpeg or FFprobe is found, the engine, the producer's
probes and Studio's waveform, peak and audio-track helpers now report
"not found" with the HYPERFRAMES_FFMPEG_PATH / HYPERFRAMES_FFPROBE_PATH hint
instead of handing the bare name to the OS lookup. Callers that degraded
on a missing binary still do: runFfmpeg returns a spawn_error result, the
GPU and version probes report none, and Studio's audio helpers fail soft.

The Lambda handler names its bundled ffmpeg and ffprobe by path, since
they live under the task root, which is the working directory.

brew, git and cmake for the whisper.cpp install, and the clipboard tool
for `hyperframes add`, run by the absolute path the trusted lookup
approved. The media-use engine gets a PATH holding only trusted folders.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@jrusso1020 jrusso1020 changed the title fix(cli,parsers): take helper programs only from install locations, never the project folder fix: take helper programs only from install locations, never the project folder Nov 11, 2026
jrusso1020 and others added 2 commits November 11, 2026 08:45
Throwing from getFfmpegBinary turned every caller's missing-binary path
into a synchronous throw: the streaming encoder's "failure result, not a
throw" contract broke, and unit lanes that run without FFmpeg crashed at
module load. With no trusted copy the getters now return an absolute path
inside HyperFrames' own install that is never created, named for the
override to set. Spawning it fails with ENOENT exactly as before, and the
OS still never looks the bare name up. The call-site guards and fixture
changes that the throw needed are reverted.

The whisper install tests match git and cmake by name and skip the brew
case on Windows, where the lookup walks the real PATH.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… macOS on Windows

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@heygengenesis heygengenesis Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict: APPROVE · 0 blocker · 0 important · 0 nits — 3 resolved, 0 open, 0 new · 708ab6f → 73ecb9c

No engine or studio fallback runs bare-name FFmpeg/FFprobe any more. Lambda names its bundled binaries by path, and brew, git, cmake, the clipboard tools and the media-use child are all resolved through trusted paths. The new engine tests fail with the old fallback restored.

— Genesis

@jrusso1020
jrusso1020 added this pull request to the merge queue Nov 11, 2026
Merged via the queue into main with commit 84799bd Nov 11, 2026
140 checks passed
@jrusso1020
jrusso1020 deleted the fix/trusted-helper-binaries branch November 11, 2026 10:02
@miguel-heygen miguel-heygen mentioned this pull request Nov 11, 2026
3 of 4 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant