Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
fix: upgrade cross-spawn (ReDoS), harden hook install and spawn handling
- Bump cross-spawn to ^7.0.5 and which to ^4; drop unused spawn-sync.
- Fix spawnSync result checks (use status/signal/error, not .code).
- Treat non-zero / null close codes from npm run spawns reliably.
- Install hook via absolute path to package hook script for Yarn PnP; chmod 0755.
- Hook: cd to git root before require.resolve; use exec for node.
- Install: guard gitdir parse; avoid fs.existsSync(null) on missing .git.
- Dev: mocha 10, assume 2, nyc; engines node>=16; stub tty in tests.
- Version 1.2.3; add package-lock.json; ignore .nyc_output.

Addresses GH-167, GH-160, GH-157; mitigates GH-166 (cwd / exit handling).

Made-with: Cursor
  • Loading branch information
3rd-Eden committed Apr 28, 2026
commit c610f1423706686b2ce2b6e046acf0a9e060d5ad
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
node_modules
npm-debug.log
coverage
.nyc_output
.tern-port
11 changes: 10 additions & 1 deletion hook
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,15 @@ elif [[ -x "$LOCAL" ]]; then
BINARY="$LOCAL"
fi

#
# Run from the repository root so `require.resolve('pre-commit')` works for Yarn PnP,
# and GUI git clients that invoke hooks with an unexpected cwd still resolve deps.
#
REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null)" || REPO_ROOT=""
if [[ -n "$REPO_ROOT" ]]; then
cd "$REPO_ROOT" || exit 1
fi

#
# Add --dry-run cli flag support so we can execute this hook without side effects
# and see if it works in the current environment
Expand All @@ -46,5 +55,5 @@ if [[ $* == *--dry-run* ]]; then
exit 1
fi
else
"$BINARY" "$("$BINARY" -e "console.log(require.resolve('pre-commit'))")"
exec "$BINARY" "$("$BINARY" -e "console.log(require.resolve('pre-commit'))")"
fi
22 changes: 18 additions & 4 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
'use strict';

//
// cross-spawn.spawnSync returns the same shape as child_process.spawnSync
// (`status`, not `code`).
//
function failedSpawn(result) {
if (!result) return true;
if (result.error) return true;
if (result.signal) return true;
return result.status !== 0;
}

var spawn = require('cross-spawn')
, which = require('which')
, path = require('path')
Expand Down Expand Up @@ -173,8 +184,8 @@ Hook.prototype.initialize = function initialize() {
this.root = this.exec(this.git, ['rev-parse', '--show-toplevel']);
this.status = this.exec(this.git, ['status', '--porcelain']);

if (this.status.code) return this.log(Hook.log.status, 0);
if (this.root.code) return this.log(Hook.log.root, 0);
if (failedSpawn(this.status)) return this.log(Hook.log.status, 0);
if (failedSpawn(this.root)) return this.log(Hook.log.root, 0);

this.status = this.status.stdout.toString().trim();
this.root = this.root.stdout.toString().trim();
Expand Down Expand Up @@ -229,8 +240,11 @@ Hook.prototype.run = function runner() {
env: process.env,
cwd: hooked.root,
stdio: [0, 1, 2]
}).once('close', function closed(code) {
if (code) return hooked.log(hooked.format(Hook.log.failure, script, code));
}).once('close', function closed(code, signal) {
var exitCode = typeof code === 'number' ? code : (signal ? 1 : 0);
if (exitCode !== 0) {
return hooked.log(hooked.format(Hook.log.failure, script, exitCode));
}

again(scripts);
});
Expand Down
35 changes: 20 additions & 15 deletions install.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,17 @@ var fs = require('fs')
, path = require('path')
, os = require('os')
, hook = path.join(__dirname, 'hook')
, hookAbs = path.resolve(hook)
, root = path.resolve(__dirname, '..', '..')
, exists = fs.existsSync || path.existsSync;

//
// POSIX single-quoted string for embedding paths in generated shell scripts.
//
function shellSingleQuote(str) {
return '\'' + str.replace(/'/g, '\'\\\'\'') + '\'';
}

//
// Gather the location of the possible hidden .git directory, the hooks
// directory which contains all git hooks and the absolute location of the
Expand Down Expand Up @@ -45,10 +53,10 @@ function getGitFolderPath(currentPath) {
//
// Resolve git directory for submodules
//
if (exists(git) && fs.lstatSync(git).isFile()) {
if (git && exists(git) && fs.lstatSync(git).isFile()) {
var gitinfo = fs.readFileSync(git).toString()
, gitdirmatch = /gitdir: (.+)/.exec(gitinfo)
, gitdir = gitdirmatch.length == 2 ? gitdirmatch[1] : null;
, gitdir = gitdirmatch && gitdirmatch.length === 2 ? gitdirmatch[1].trim() : null;

Comment thread
3rd-Eden marked this conversation as resolved.
Outdated
if (gitdir !== null) {
git = path.resolve(root, gitdir);
Expand Down Expand Up @@ -92,20 +100,17 @@ if (exists(precommit) && !fs.lstatSync(precommit).isSymbolicLink()) {
try { fs.unlinkSync(precommit); }
catch (e) {}

// Create generic precommit hook that launches this modules hook (as well
// as stashing - unstashing the unstaged changes)
// TODO: we could keep launching the old pre-commit scripts
var hookRelativeUnixPath = hook.replace(root, '.');

if(os.platform() === 'win32') {
hookRelativeUnixPath = hookRelativeUnixPath.replace(/[\\\/]+/g, '/');
// Delegate to this package's `hook` script using an absolute path so Yarn Plug'n'Play
// and other layouts without `node_modules/pre-commit` still work. The hook script
// changes to the git root before resolving `pre-commit` via Node.
//
var hookLauncher = hookAbs;
if (os.platform() === 'win32') {
hookLauncher = hookLauncher.replace(/\\/g, '/');
}

var precommitContent = '#!/usr/bin/env bash' + os.EOL
+ hookRelativeUnixPath + os.EOL
+ 'RESULT=$?' + os.EOL
+ '[ $RESULT -ne 0 ] && exit 1' + os.EOL
+ 'exit 0' + os.EOL;
+ 'exec bash ' + shellSingleQuote(hookLauncher) + ' "$@"' + os.EOL;

//
// It could be that we do not have rights to this folder which could cause the
Expand All @@ -121,10 +126,10 @@ catch (e) {
console.error('pre-commit:');
}

try { fs.chmodSync(precommit, '777'); }
try { fs.chmodSync(precommit, 0o755); }
catch (e) {
console.error('pre-commit:');
console.error('pre-commit: chmod 0777 the pre-commit file in your .git/hooks folder because:');
console.error('pre-commit: chmod 0755 the pre-commit file in your .git/hooks folder because:');
console.error('pre-commit: '+ e.message);
console.error('pre-commit:');
}
Loading