Skip to content

fix(hooks): stop probing a /bin/sh launcher wrapper as if it were python (#3027) - #3109

Open
abhay-codes07 wants to merge 2 commits into
Graphify-Labs:v8from
abhay-codes07:fix/hook-sh-wrapper-launcher
Open

fix(hooks): stop probing a /bin/sh launcher wrapper as if it were python (#3027)#3109
abhay-codes07 wants to merge 2 commits into
Graphify-Labs:v8from
abhay-codes07:fix/hook-sh-wrapper-launcher

Conversation

@abhay-codes07

Copy link
Copy Markdown
Contributor

Closes #3027.

The problem

pipx generates the graphify launcher via distlib's "exec trick" when the install path contains a space — macOS's ~/Library/Application Support/pipx is the common case. That launcher's shebang is #!/bin/sh, and its second line execs the real interpreter:

#!/bin/sh
'''exec' "/Users/me/Library/Application Support/pipx/venvs/graphifyy/bin/python" "$0" "$@"
' '''

The hook parsed the shebang into GRAPHIFY_PYTHON=/bin/sh and ran /bin/sh -c "import importlib.util, sys; ...". import is not a shell builtin, so it resolved on PATH to ImageMagick's import screenshot tool, which dumped its full usage text on every commit — only stderr was silenced. The probe did fail and the chain fell back to python3, so the graph still rebuilt; the bug was the noise, and the wasted opportunity to find the right interpreter.

The change

In the shebang branch of _PYTHON_DETECT:

  • if the shebang names something that is not a python, take the interpreter from the wrapper's exec line instead (sed on the second line of the 256-byte head the script already reads);
  • never probe an interpreter whose basename is not python* / pypy* — so a shell is never handed -c "import ..." again, whatever the wrapper looks like.

The path recovered from a wrapper contains a space by construction, and the injection allowlist rejected spaces. A space is not a shell metacharacter here — $GRAPHIFY_PYTHON is always expanded quoted — so the allowlist now matches against the path with spaces folded to _, leaving the proven pattern itself byte-for-byte unchanged (the #2126/#2641 allowlist tests, which extract that exact pattern from the emitted text, still pass).

Tests

tests/test_hook_sh_wrapper_launcher.py — 5 tests that run the real emitted _PYTHON_DETECT under sh on a machine whose PATH holds a /bin/sh wrapper, an ImageMagick-style import that prints usage, and ambient pythons that cannot import graphify: the wrapper's interpreter (in a directory with a space) is adopted and import is never run; a wrapper with no exec line falls through quietly; a wrapper whose interpreter fails the probe is not adopted; a plain python shebang still resolves; and the emitted script gates the probe on the interpreter's name. With the fix reverted, 3 of 5 fail. tests/test_hooks.py is unchanged; the full suite matches the v8 baseline.

…hon (Graphify-Labs#3027)

pipx (via distlib's exec trick, used when the install path has spaces —
macOS's "Application Support/pipx") generates a `graphify` launcher whose
shebang is `#!/bin/sh` and whose second line execs the real interpreter.
The hook parsed that shebang into GRAPHIFY_PYTHON and ran
`/bin/sh -c "import ..."`: `import` is not a shell builtin, so it resolved
on PATH to ImageMagick's screenshot tool and dumped its usage text on
every commit (only stderr was silenced). The probe did fail and fall back
to python3, so the rebuild ran — the bug was the noise.

The shebang branch now takes the interpreter from the wrapper's exec line
(exec' "/path/to/python" "$0" "$@") when the shebang names something that
is not a python, and never probes an interpreter whose name is not
python*/pypy*. Because such a path contains a space by construction, the
injection allowlist folds spaces before matching — a space is not a
metacharacter, $GRAPHIFY_PYTHON is always expanded quoted, and the
allowlist pattern itself is unchanged.
Copilot AI lite review requested due to automatic review settings August 26, 2026 11:24

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@graphify-labs graphify-labs Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Graphify reviewed this change.

Worth a look — the grounded gate found no coupling regressions or blocking issues, but 1 advisory finding(s) below merit a look before merge.


Graphify review — findings

Fixes the commit hook probing a /bin/sh launcher wrapper as if it were the Python interpreter (#3027): when pipx emits a shebang-wrapper (install paths with spaces, e.g. macOS's "Application Support/pipx"), the hook now reads the interpreter from the wrapper's exec line instead of running sh -c "import graphify", which resolved import on PATH to ImageMagick's screenshot tool and dumped usage text on every commit. Restricts probing to interpreters whose basename matches python*/pypy*, blanking anything else, and folds spaces before the path-safety allowlist check so quoted paths with spaces survive. Adds test_hook_sh_wrapper_launcher.py exercising the wrapper-exec, no-exec-line, non-importable, and plain-shebang paths against a stubbed PATH containing a fake import.

Worth a look

  • Shebang detection now rejects valid Python interpreters not named python/pypygraphify/hooks.py:96 · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
Analysis details — impact, health, verification

Impact & health

Graphify review

Impact — 248 functions depend on the 50 functions this change touches.

Health — this change adds coupling hotspots:

  • new: dispatch_command() — 2 callers, 122 callees
  • new: install() — 33 callers, 7 callees
  • new: dispatch_install_cli() — 2 callers, 31 callees
  • new: status() — 8 callers, 6 callees
  • new: uninstall() — 9 callers, 5 callees
  • new: uninstall_all() — 2 callers, 13 callees
  • new: test_poisoned_manifest_is_healed() — 0 callers, 6 callees

Verification — 248 functions in the blast radius were not formally verified this run (proofs are advisory here).

Gate & verification

graphify gate

PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.

Advisory (not blocking):

  • verification_scope: 98 function(s) in the blast radius were not formally verified this run

· 7 more finding(s) on lines outside this diff (see the check run).

python3.12, python@3.12, cpython and pypy3 all pass the probe gate; sh,
bash and node still do not.
@abhay-codes07

Copy link
Copy Markdown
Contributor Author

Follow-up commit on the review's point about interpreter names: the probe gate is now *python*|*pypy*, so python3.12, python@3.12, cpython and pypy3 all pass while sh/bash/node still do not.

@graphify-labs graphify-labs Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Graphify reviewed this change.

Worth a look — the grounded gate found no coupling regressions or blocking issues, but 1 advisory finding(s) below merit a look before merge.


Graphify review — findings

Fixes the git hook launcher probing a /bin/sh pipx wrapper as if it were Python, which resolved import graphify to ImageMagick's import tool and dumped usage text on every commit (#3027). The shebang detection in _PYTHON_DETECT now parses the wrapper's exec line for the real interpreter, refuses to probe anything whose basename doesn't look Python-like (*python*/*pypy*), and folds spaces before the path-allowlist check so space-containing venv paths survive. Adds test_hook_sh_wrapper_launcher.py covering the exec-line adoption, the missing-exec-line and non-importing-interpreter fall-throughs, and the plain-shebang path.

Worth a look

  • Space-folding allowlist permits spaces in interpreter path, weakening injection guardgraphify/hooks.py · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
Analysis details — impact, health, verification

Impact & health

Graphify review

Impact — 248 functions depend on the 50 functions this change touches.

Health — this change adds coupling hotspots:

  • new: dispatch_command() — 2 callers, 122 callees
  • new: install() — 33 callers, 7 callees
  • new: dispatch_install_cli() — 2 callers, 31 callees
  • new: status() — 8 callers, 6 callees
  • new: uninstall() — 9 callers, 5 callees
  • new: uninstall_all() — 2 callers, 13 callees
  • new: test_poisoned_manifest_is_healed() — 0 callers, 6 callees

Verification — 248 functions in the blast radius were not formally verified this run (proofs are advisory here).

Gate & verification

graphify gate

PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.

Advisory (not blocking):

  • verification_scope: 98 function(s) in the blast radius were not formally verified this run

· 7 more finding(s) on lines outside this diff (see the check run).

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.

post-commit hook runs ImageMagick's import when graphify launcher is a /bin/sh wrapper

2 participants