Skip to content

fix: find helper programs without a by-name lookup or the Windows working directory - #7562

Merged
michaelwang11394 merged 7 commits into
mainfrom
harden-r4-binaries
Nov 11, 2026
Merged

michaelwang11394 merged 7 commits into
mainfrom
harden-r4-binaries

Conversation

@michaelwang11394

@michaelwang11394 michaelwang11394 commented Nov 11, 2026 •

Copy link
Copy Markdown
Contributor

What changed

Helper programs are now found without running a lookup program by name, and without the Windows working-directory search.

  • Trusted program lookup. findTrustedExecutable, findFfBinary and the doctor's unzip check walk the trusted PATH folders inside the process on every platform. They no longer run which or where by name first. For valid installs the result is the same as before: the first match in PATH order, regular files only, and a leading ~ read as the home folder.
  • media-use engine. mediaUseEngineEnv sets NoDefaultCurrentDirectoryInExePath on Windows and keeps a value the user already set. When no PATH folder is trusted, the engine's PATH is never empty, because an empty PATH means the current folder to some shells.
  • Skill scripts. The plugin launcher and the hyperframes, media-use and figma helper scripts turn off the Windows working-directory program lookup before they start anything. This holds whether they run through the launcher or directly.
  • Python helper. extract-audio-data.py runs the absolute ffmpeg path that trusted_program.find_program finds in absolute PATH folders outside the working directory (the CLI's rule). A missing ffmpeg raises the same FileNotFoundError as before.
  • Relative PATH entries. On macOS and Linux the shared guard drops relative and empty PATH entries, which point into the project folder, and keeps absolute ones in order. If none is absolute, PATH is unset rather than left empty.
  • CLI bundle. The CLI build replaces the published media-use scripts/lib folder with packages/cli/src/media-use/lib, so that folder carries a generated copy of the guard too. A parity test checks that every scripts/lib module a published media-use script imports also exists in the CLI lib.

Behaviour changes

Testing

  • fails without the fix: a relative PATH entry cannot run a program planted in the project, drops relative and empty PATH entries elsewhere and keeps absolute ones in order, leaves PATH unset rather than empty when no entry is absolute, every skill Python script that starts programs resolves them through trusted_program, extract-audio-data never runs an ffmpeg planted in the project, find_program skips every project folder on PATH and returns the trusted one, shown by restoring the previous guard, script or a bare PATH walk and rerunning the tests. The parity test finds every scripts/lib module a published media-use script imports in the CLI lib fails without the CLI copy of the guard.

  • The reviewer's repro PATH="node_modules/.bin:$PATH" node locate.mjs runs the planted ffprobe with the previous guard and does not with this one. extract-audio-data.py still decodes a real file with Homebrew ffmpeg, run from a project folder and from the home folder.

  • New tests cover a planted which on PATH, Windows behaviour through a stubbed process.platform, and a guard that every skill script that starts programs loads the setting first. Each new test fails without its fix.

  • parsers 28/28, the touched CLI test files 153/153, skills 238/238. The parity, import-boundary and lint-skills checks pass, as do oxlint, oxfmt, fallow and the comment ratchet.

  • whisper/manager.install.test.ts now builds real PATH fixtures instead of mocking which, since the lookup no longer runs it.

  • On this Mac, findTrustedExecutable and findFfBinary return the same path as /usr/bin/which for ffmpeg, ffprobe, python3, unzip, whisper-cli, brew, git, cmake, node and docker. Homebrew symlinks are followed. A non-executable file, a directory and a broken symlink earlier in PATH are skipped, as which does.

  • A simulated CLI bundle layout and a standalone install of the hyperframes, media-use and figma skills both load every changed script.

  • The Windows CI job runs the launcher test for real. Lookup itself is not run on real Windows outside CI.

🤖 Generated with Claude Code

michaelwang11394 and others added 5 commits November 11, 2026 09:34
The trusted lookup ran `which` by name through the full PATH before
applying the trusted-folder rule, so a relative or project-local PATH
entry could supply the lookup program itself. Walk the trusted PATH
folders in-process on every platform, as Windows already did, for
findTrustedExecutable, findFfBinary and the doctor's unzip check.

The walk keeps what `which` did for valid installs: first match in PATH
order, regular files only, and a leading `~` in a PATH entry read as the
home folder.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ry lookup off

mediaUseEngineEnv now sets NoDefaultCurrentDirectoryInExePath for the
engine on Windows itself instead of relying on the CLI's own environment,
and keeps a value the person set. When no PATH folder passes the trusted
rule, PATH names a folder that is never created rather than an empty
value, which a shell or execvp reads as the working directory.

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

On Windows a program started by name is looked up in the working
directory before PATH. The plugin launcher and the media-use, hyperframes
and figma helper scripts run inside project folders and start ffmpeg,
ffprobe, Python and other tools by name, either through the launcher or
directly from a standalone skill install.

Each skill now ships a small module that sets
NoDefaultCurrentDirectoryInExePath for the process (keeping a value the
person set), and every script that starts programs imports it first, so
the helpers and every program they start skip the working directory.
Tools installed on PATH resolve as before; macOS and Linux are
unaffected. A test checks that each such script loads the module.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The CLI build replaces the published media-use scripts/lib folder with
packages/cli/src/media-use/lib. That folder had no windows-exe-search.mjs,
so every bundled media-use script that imports it, including the engine
behind `hyperframes media-use`, failed to load on every platform.

The guard is now a generated copy in the CLI lib, next to the skill copies.
A parity test checks that each scripts/lib module a published media-use
script imports exists in the CLI lib.

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

On Windows, `node --import D:\...` reads the drive letter as a URL scheme
and fails before the launcher starts. The test now passes a file URL and
reports the child's stderr when it exits non-zero.

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

github-actions Bot commented Nov 11, 2026 •

Copy link
Copy Markdown
Contributor

Edit accuracy: accurate 267 (base branch 267), smooth 91 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 jrusso1020 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Reviewed at 83cc4ef11b95a60599295284631b3663c99ca6d3.

No blockers found. The in-process trusted-PATH lookup reuses the existing directory policy; generated copies include both the standalone skills and the CLI replacement library. The first trusted PATH match remains selected, home-directory expansion is preserved, and POSIX directories/non-executable candidates are skipped. The touched launcher and generated-library paths remain consistent with their current callers.

Validation: bun run build passed; focused parser tests 28/28, CLI media-use/Python/Whisper tests 29/29, and copy-parity/Windows-guard/launcher tests 21/21 passed on Linux. Replacing the two parser source files with their base versions makes 4/28 tests fail; the head sources were restored. Windows behavior was simulated locally, not exercised on Windows.

Pre-existing follow-up: the environment guard depends on the spawning runtime honoring it. Add an actual Windows child-launch test against the oldest supported Node patch, and document the supported runtime floor or resolve helper paths absolutely. This limitation also exists in the earlier CLI guard.

Need: yes · Size: ok · Reuse: trustedPathDirs and generated skill-module copies · Simpler: already minimal · Structure: fine (checked findTrustedExecutable/findFfBinary, existing Windows guard, launcher and CLI library replacement; no split)

Risk: medium — changes helper resolution across platforms; native Windows execution was not run locally.

Verdict: APPROVE

Reasoning: the shared lookup and launch guards are consistent with their existing callers, and the focused regressions pass.

— Rames

@michaelwang11394
michaelwang11394 added this pull request to the merge queue Nov 11, 2026

@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

Additive to Rames's approval. The lookup changes hold up, and I couldn't find a planted-binary path through findTrustedExecutable, findFfBinary or the engine env.

  • blocker: skills/media-use/audio/scripts/extract-audio-data.py:54 still runs ffmpeg by name, and nothing turns off the working-directory lookup first. motion.md:72 tells the agent to run it from the project with python3. On Windows, Python's subprocess hands the bare name to CreateProcess, which searches the current folder before PATH, so a project that ships ffmpeg.exe gets it run. That's the same failure this PR fixes for the .mjs scripts. One fix: resolve ffmpeg to an absolute path from absolute PATH entries outside the current folder, then run that path. The census misses it, see ↓ inline.
  • important (pre-existing, follow-up): on macOS/Linux the skill scripts still use your unfiltered PATH. A relative or empty entry (node_modules/.bin, a trailing :) runs a planted ffprobe from locate.mjs. I reproduced this with PATH="node_modules/.bin:$PATH" node locate.mjs. The CLI side already filters PATH with trustedPathDirs. The scripts could do the same before they start anything.
Scope

I read every source file in the diff. Checked and ruled out: libuv honours NoDefaultCurrentDirectoryInExePath (NeedCurrentDirectoryForExePathW in src/win/process.c). ESM import order means the guard runs before any spawn. The NEVER_CREATED_PATH_FOLDER placeholder sits outside the project. Tilde expansion is now safer than before. The which/where calls left in the codex/mflux/ltx providers run only under the engine's scoped env. CI is green on this head.

— Claude (Genesis review session)

@michaelwang11394
michaelwang11394 removed this pull request from the merge queue due to a manual request Nov 11, 2026
…H entries

extract-audio-data.py ran ffmpeg by bare name, which Windows looks up in
the working directory first. It now runs the absolute path that
trusted_program.find_program finds in absolute PATH folders outside the
working directory. A miss raises the same FileNotFoundError as before.

On macOS and Linux the shared guard now drops relative and empty PATH
entries, which point into the project folder, and leaves absolute ones in
order. If none is absolute, PATH is unset rather than left empty.

The guard census now also requires every skill Python script that imports
subprocess to resolve programs through trusted_program.

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

Copy link
Copy Markdown
Contributor Author

Review follow-up:

  • blocker (extract-audio-data.py runs ffmpeg by name): fixed in a961696. The script runs the absolute path from trusted_program.find_program, which takes only absolute PATH folders outside the working directory. A missing ffmpeg gives the same FileNotFoundError as before.
  • census misses .py files (inline): fixed in a961696. Every skill .py that imports subprocess must use trusted_program. The test fails on the previous script.
  • important (relative or empty PATH entries on macOS/Linux): fixed in a961696. The shared guard drops relative and empty entries and keeps absolute ones in order. Your PATH="node_modules/.bin:$PATH" node locate.mjs repro runs the planted ffprobe before this change and not after.

trusted_program.py carries the same UTF-8 stdio pin as every skill Python
script. The guard test's planted-program probe uses a literal-only argv,
which the ffprobe argv contract test reads as a capability check.

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

@jrusso1020 jrusso1020 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Reviewed at 307aab410a3dd3fa7cb1f7ed93be72c1ae7425f8.

Verdict: APPROVE

Re-reviewed the complete delta since 83cc4ef11b95a60599295284631b3663c99ca6d3. I independently checked the three concerns in Genesis's earlier review: the audio helper now uses an absolute trusted program path, the census includes Python subprocess callers, and the shared guard filters relative/empty POSIX PATH entries. The Python resolver follows the existing CLI directory policy and raises FileNotFoundError on a miss, without falling back to a bare command. Generated standalone and CLI copies remain in sync.

Validation on this head: bun run build passed. node --test on the guard, Python resolver and copy-parity files passed 17/17 with no skips, with NumPy available. Restoring the previous guard and audio script makes four targeted tests fail; reviewed sources were restored. A real ffmpeg decode of a one-second audio fixture produced 30 frames with 16 bands. Independent code and review-gap passes found no additional blocker.

Native Windows execution was not exercised locally. Runtime-version compatibility for the existing Windows environment guard remains a pre-existing follow-up from my earlier review; these Linux results do not establish that compatibility across every supported Node patch.

Need: yes · Size: ok · Reuse: trustedExecutable directory policy and generated skill-module copies · Simpler: explicit absolute resolver · Structure: fine (checked trustedPathDirs, the skill-copy generator and CLI library replacement; no split)

Risk: medium — changes cross-platform helper resolution; native Windows was not run locally.

Reasoning: the requested resolver, census and POSIX guard changes are present and the focused regressions distinguish the old and new implementations. No introduced blocker found in this delta.

— Rames

@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 · 2 resolved, 0 open, 0 new · 83cc4ef → 307aab4. CI is green on this head.

— Claude (Genesis review session)

@michaelwang11394
michaelwang11394 added this pull request to the merge queue Nov 11, 2026
Merged via the queue into main with commit 9a048ac Nov 11, 2026
84 checks passed
@michaelwang11394
michaelwang11394 deleted the harden-r4-binaries branch November 11, 2026 19:05
@terencecho terencecho 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.

2 participants