Reproduction
Recorded in commit 1fd0d53301edc0399a74cf244229269aee507b58:
The hook's own tamper detection could not see the channel that redirected it
commitlore hooks install
git config --local commitlore.bin /tmp/evil.sh
git commit -F <message with an invalid trailer key>
→ ATTACKER-RAN
→ the commit succeeded; validation never ran
and commitlore hooks status compared the stub byte for byte and doctor reported
the hook installed and healthy the whole time.
This is not a privilege escalation, and the fix does not pretend it is.
Measured: a malicious commitlore.bin does not survive git clone — neither
.git/config nor .git/hooks is transferred — and both sit behind the same
filesystem permission, so anyone who can set the config can overwrite the hook
directly. It is closed because our own integrity check looked at a channel the
hook does not use, and because writing a git config key is far quieter than
writing an executable into .git/hooks.
Two changes. The stub's catch-all branch — *) exec "$recorded" … — is gone: a
recorded path must end in .js or .mjs and run under the recorded interpreter,
which reduces "any executable" to "a JavaScript file run by a known node", the
authority the CLI already has. Anything else falls through to the remaining
resolution steps rather than being executed.
And hooks status and doctor now read the recorded paths and say so. A hook
whose stub is byte-perfect but whose recorded target is a stranger no longer
reports ok; it names the path, that it is not a .js, and that it sits outside
the package root. COMMITLORE_BIN is surfaced too — it legitimately overrides
everything, and an operator debugging a silent validation failure should see it.
Reproduction
Recorded in commit 1fd0d53301edc0399a74cf244229269aee507b58:
The hook's own tamper detection could not see the channel that redirected it
and
commitlore hooks statuscompared the stub byte for byte anddoctorreportedthe hook installed and healthy the whole time.
This is not a privilege escalation, and the fix does not pretend it is.
Measured: a malicious
commitlore.bindoes not survivegit clone— neither.git/confignor.git/hooksis transferred — and both sit behind the samefilesystem permission, so anyone who can set the config can overwrite the hook
directly. It is closed because our own integrity check looked at a channel the
hook does not use, and because writing a git config key is far quieter than
writing an executable into
.git/hooks.Two changes. The stub's catch-all branch —
*) exec "$recorded" …— is gone: arecorded path must end in
.jsor.mjsand run under the recorded interpreter,which reduces "any executable" to "a JavaScript file run by a known node", the
authority the CLI already has. Anything else falls through to the remaining
resolution steps rather than being executed.
And
hooks statusanddoctornow read the recorded paths and say so. A hookwhose stub is byte-perfect but whose recorded target is a stranger no longer
reports
ok; it names the path, that it is not a.js, and that it sits outsidethe package root.
COMMITLORE_BINis surfaced too — it legitimately overrideseverything, and an operator debugging a silent validation failure should see it.