harden hook interpreter detection against injection and unquoted exec

Per adversarial review of the #1127 fix:

- Apply the filesystem-path allowlist to sys.executable before embedding it in
  the generated hook script. Paths with metacharacters outside [a-zA-Z0-9/_.@:\-]
  (spaces, dollar signs, backticks, semicolons) are replaced with an empty string
  so the pinned probe is safely skipped rather than injecting shell commands that
  execute on every git commit.
- Change _PINNED='...' to single-quote assignment so no shell expansion can occur
  even if a character slipped through the allowlist (belt and suspenders).
- Quote $GRAPHIFY_PYTHON in both nohup exec lines so paths with spaces work
  (common in Windows C:\Program Files\... installs).
- Update test to assert the sanitized value, not raw sys.executable, so the test
  stays correct after any future sanitization changes.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Safi
2026-06-03 15:40:06 +01:00
co-authored by Claude Sonnet 4.6
parent 2d5a10c0a2
commit 0fdfdedcaa
2 changed files with 22 additions and 8 deletions
+6 -4
View File
@@ -172,14 +172,16 @@ def test_install_embeds_pinned_interpreter(tmp_path):
fallbacks cannot import graphify (wrong venv), and the hook silently exits 0.
Pinning sys.executable at install time makes the hook work regardless of PATH.
"""
import sys
import re, sys
repo = _make_git_repo(tmp_path)
install(repo)
commit_hook = (repo / ".git" / "hooks" / "post-commit").read_text()
checkout_hook = (repo / ".git" / "hooks" / "post-checkout").read_text()
# The pinned interpreter path must appear in both generated scripts.
assert sys.executable in commit_hook, "pinned sys.executable missing from post-commit"
assert sys.executable in checkout_hook, "pinned sys.executable missing from post-checkout"
# Compute the sanitized value the same way install() does.
expected = sys.executable if not re.search(r"[^a-zA-Z0-9/_.@:\\-]", sys.executable) else ""
if expected:
assert expected in commit_hook, "sanitized sys.executable missing from post-commit"
assert expected in checkout_hook, "sanitized sys.executable missing from post-checkout"
# The placeholder must be fully substituted -- no __PINNED_PYTHON__ left.
assert "__PINNED_PYTHON__" not in commit_hook, "placeholder not substituted in post-commit"
assert "__PINNED_PYTHON__" not in checkout_hook, "placeholder not substituted in post-checkout"