Skip to content

fix: quote paths in the generated hook shim - #1509

Open
addielaruee wants to merge 1 commit into
evilmartians:masterfrom
addielaruee:fix/quote-hook-template-paths
Open

fix: quote paths in the generated hook shim#1509
addielaruee wants to merge 1 commit into
evilmartians:masterfrom
addielaruee:fix/quote-hook-template-paths

Conversation

@addielaruee

@addielaruee addielaruee commented Aug 28, 2026

Copy link
Copy Markdown

Closes #1488

Context

When lefthook install generates a hook, it bakes the path to the running binary (os.Executable(), the .LefthookPathCurrent template value) into the shim. That path was interpolated unquoted, so a binary living under a directory with a space (common on macOS: iCloud Drive, Google Drive, any folder with a space in its name) is word-split by the generated sh, every branch of call_lefthook() fails, and the shim prints Can't find lefthook in PATH. Because that terminal branch exits 0 (unless AssertLefthookInstalled is set), the commit still succeeds, so the hook is silently and permanently skipped with no signal. Unlike the config values, this path is detected at install time, so the user cannot quote it themselves.

Reproduced with the issue's steps (a lefthook binary living under /tmp/my project/bin):

  • Before: the shim contains elif /tmp/my project/bin/lefthook -h ...; committing prints Can't find lefthook in PATH and succeeds with the hook never running.
  • After: the shim contains elif '/tmp/my project/bin/lefthook' -h ...; committing runs the hook.

Changes

  • Added a shellescape template function (POSIX single quotes, '\'' escaping) and applied it to the two .LefthookPathCurrent interpolations in hook.tmpl.
  • Left the lefthook and rc config values unquoted on purpose: lefthook is documented as an executable path or command (e.g. bundle exec lefthook, LEFTHOOK_VERBOSE=1 lefthook), and rc is documented with tilde and environment-variable expansion (e.g. ~/.lefthookrc, "${XDG_CONFIG_HOME:-$HOME/.config}/lefthookrc"). Those rely on the shell interpreting them, and the docs make quoting a spaced path there the user's responsibility. Escaping them would turn commands into nonexistent filenames and stop documented expansions from resolving.
  • Added internal/templates/templates_test.go: table-driven tests that the auto-detected path is single-quoted, and that a configured command, an env-prefixed command, and tilde/env-expansion rc paths are passed through unquoted.

Verified locally: go test ./internal/... and golangci-lint run (pinned v2.11.4) pass. No config structs changed, so schema.json is untouched.

@addielaruee
addielaruee requested a review from mrexox as a code owner August 28, 2026 12:45
@greptile-apps

greptile-apps Bot commented Aug 28, 2026

Copy link
Copy Markdown

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains; both previously reported regressions are fixed by limiting escaping to the auto-detected executable path.

Reviews (2): Last reviewed commit: "fix: quote the auto-detected executable ..." | Re-trigger Greptile

Comment thread internal/templates/hook.tmpl Outdated
elif test -n {{ shellescape .LefthookPath }}
then
{{ .LefthookPath }} "$@"
{{ shellescape .LefthookPath }} "$@"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Command values become literal paths

When lefthook contains a documented shell command such as bundle exec lefthook, shellescape turns the entire value into one command word, causing the shim to look for an executable literally named bundle exec lefthook instead of running the configured command.

Comment thread internal/templates/hook.tmpl Outdated
{{- if .Rc}}
{{/* Load rc file, which may export ENV variables */}}
[ -f {{.Rc}} ] && . {{.Rc}}
[ -f {{ shellescape .Rc }} ] && . {{ shellescape .Rc }}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 RC path expansion is disabled

When rc uses a documented value such as ~/.lefthookrc or ${XDG_CONFIG_HOME:-$HOME/.config}/lefthookrc, single-quoting suppresses the required shell expansion, causing the file check to fail and the hook to silently skip the configured environment setup.

When lefthook installs a hook it bakes the path to the running binary
(os.Executable) into the generated shim. That path was interpolated
unquoted, so a binary living under a directory with a space (common on
macOS: iCloud Drive, Google Drive, any folder with a space) was
word-split by the generated sh, every branch of call_lefthook failed,
and the shim printed "Can't find lefthook in PATH". Because that branch
exits 0, the commit still succeeded, so the hook was silently and
permanently skipped with no signal.

Escape that path with POSIX single quotes via a shellescape template
function. The user cannot quote it themselves since it is detected at
install time.

The `lefthook` and `rc` config values are intentionally left unquoted:
`lefthook` is documented as an executable path or command (for example
`bundle exec lefthook`), and `rc` is documented with tilde and
environment-variable expansion (for example `~/.lefthookrc`). Both rely
on the shell interpreting them, and the docs make quoting a spaced path
there the user's responsibility.

Fixes evilmartians#1488
@addielaruee
addielaruee force-pushed the fix/quote-hook-template-paths branch from 33bbc87 to 0b511ef Compare August 29, 2026 02:57
@addielaruee

Copy link
Copy Markdown
Author

Good catch. The first revision single-quoted all three interpolated values, which was wrong: lefthook is documented as an executable path or command (e.g. bundle exec lefthook) and rc supports tilde and $VAR expansion (e.g. ~/.lefthookrc), so quoting those breaks documented behavior.

I narrowed the fix to only .LefthookPathCurrent, the path detected from os.Executable() at install time. That value is always a real filesystem path and the user has no way to quote it themselves, so escaping it is safe and is the actual case in #1488. The lefthook and rc values are now left untouched, and the tests assert that a configured command and tilde/env-expansion rc paths pass through unquoted.

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.

Generated hook shim interpolates paths unquoted: a space in the path silently disables all hooks

1 participant