refactor(hooks): convert git hooks from .sh to .mts (Node 25+)#1281
refactor(hooks): convert git hooks from .sh to .mts (Node 25+)#1281John-David Dalton (jdalton) wants to merge 3 commits intomainfrom
Conversation
Sync from socket-repo-template@f415207. All four hook files become .mts modules running on Node 25+ (stable type stripping, no flag). - .git-hooks/_helpers.mts (was _helpers.sh) — exports filterAllowedApiKeys + scanners for personal paths, AWS keys, GitHub tokens, private keys, AI attribution. - .git-hooks/commit-msg.mts - .git-hooks/pre-commit.mts - .git-hooks/pre-push.mts _helpers.mts hard-fails at module load if Node < 25. Husky shims invoke node directly.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 4 potential issues.
Autofix Details
Bugbot Autofix prepared a fix for the issue found in the latest run.
- ✅ Fixed: Linear issue reference check dropped during refactor
- Added
scanLinearReferencesto_helpers.mts(matching the old shell hook's LINEAR_TEAM_KEYS and linear.app URL patterns) and wired it intocommit-msg.mtsto block commits containing Linear issue references.
- Added
Or push these changes by commenting:
@cursor push dfdc208492
Preview (dfdc208492)
diff --git a/.git-hooks/_helpers.mts b/.git-hooks/_helpers.mts
--- a/.git-hooks/_helpers.mts
+++ b/.git-hooks/_helpers.mts
@@ -198,6 +198,34 @@
return hits
}
+// ── Linear issue reference scanner ─────────────────────────────────
+// CLAUDE.md "ABSOLUTE RULES": NEVER reference Linear issues in commits.
+// Team keys enumerated from the Socket workspace. PATCH listed before
+// PAT so the alternation matches the longer prefix first.
+
+const LINEAR_TEAM_KEYS =
+ 'ASK|AUTO|BOT|CE|CORE|DAT|DES|DEV|ENG|INFRA|LAB|MAR|MET|OPS|PAR|PATCH|PAT|PLAT|REA|SALES|SBOM|SEC|SMO|SUP|TES|TI|WEB'
+
+const LINEAR_ISSUE_RE = new RegExp(
+ `(?:^|[^A-Za-z0-9_])((?:${LINEAR_TEAM_KEYS})-[0-9]+)(?:$|[^A-Za-z0-9_])`,
+ 'gm',
+)
+
+const LINEAR_URL_RE = /linear\.app\/[A-Za-z0-9/_-]+/g
+
+export const scanLinearReferences = (commitMsg: string): string[] => {
+ const hits: string[] = []
+ const lines = commitMsg.split('\n').filter(l => !l.startsWith('#'))
+ const body = lines.join('\n')
+ for (const m of body.matchAll(LINEAR_ISSUE_RE)) {
+ hits.push(m[1]!)
+ }
+ for (const m of body.matchAll(LINEAR_URL_RE)) {
+ hits.push(m[0]!)
+ }
+ return hits.slice(0, 5)
+}
+
// ── AI attribution scanner ─────────────────────────────────────────
const AI_ATTRIBUTION_RE =
diff --git a/.git-hooks/commit-msg.mts b/.git-hooks/commit-msg.mts
--- a/.git-hooks/commit-msg.mts
+++ b/.git-hooks/commit-msg.mts
@@ -23,6 +23,7 @@
out,
red,
readFileForScan,
+ scanLinearReferences,
scanSocketApiKeys,
shouldSkipFile,
stripAiAttribution,
@@ -67,10 +68,26 @@
}
}
- // Auto-strip AI attribution lines from the commit message.
const commitMsgFile = process.argv[2]
if (commitMsgFile && existsSync(commitMsgFile)) {
const original = readFileSync(commitMsgFile, 'utf8')
+
+ // Block Linear issue references in the commit message.
+ const linearHits = scanLinearReferences(original)
+ if (linearHits.length) {
+ out(red('✗ Commit message references Linear issue(s):'))
+ for (const hit of linearHits) {
+ out(` ${hit}`)
+ }
+ out(
+ red(
+ 'Linear tracking lives in Linear. Remove the reference from the commit message.',
+ ),
+ )
+ errors++
+ }
+
+ // Auto-strip AI attribution lines from the commit message.
const { cleaned, removed } = stripAiAttribution(original)
if (removed > 0) {
writeFileSync(commitMsgFile, cleaned)You can send follow-ups to the cloud agent here.
Bugbot review on commit 1e30641 surfaced 4 regressions from the .sh→.mts conversion. Fixed all four: 1. **Linear issue reference check restored** (high) — the original commit-msg.sh blocked Socket Linear team-key references (ASK-123, ENG-456, linear.app URLs). Re-added scanLinearReferences to _helpers.mts and wired into commit-msg.mts. 2. **scanSocketApiKeys duplicate-line bug** (low) — the post-filter reconstructed LineHits via `hits.find(h => h.line === line)`, which collapses duplicates onto the first match's line number. Replaced with Set-membership filter that preserves all hits. 3. **SOCKET_CLI_NO_API_TOKEN restored in .husky/pre-commit** (medium) — original husky shim ran `SOCKET_CLI_NO_API_TOKEN=1 pnpm test --staged` so contributors without a real API token don't see test failures pre-commit. 4. **.env.precommit allowlist** (low) — pre-commit.mts allowlist only had example/test variants; commit-msg.mts had precommit too. Aligned both to allow .env.{example,test,precommit}. Smoke-tested commit-msg.mts: - "ENG-123 in body" → exit 1 (blocked) ✓ - "fix: legit" → exit 0 (clean) ✓
|
bugbot run |
1 similar comment
|
bugbot run |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Bugbot Autofix is ON. A cloud agent has been kicked off to fix the reported issue.
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit f504816. Configure here.
| f => | ||
| /^\.env(\.[^/]+)?$/.test(f) && | ||
| !/^\.env\.(example|test|precommit)$/.test(f), | ||
| ) |
There was a problem hiding this comment.
Pre-commit .env check misses subdirectory files
Medium Severity
The .env file check in pre-commit.mts applies the regex /^\.env(\.[^/]+)?$/ directly against the full relative path f, so only root-level .env files match. A staged file like packages/app/.env.local won't match because ^\.env fails against a path starting with packages/. In contrast, commit-msg.mts correctly uses basename(file) before the same regex test, catching .env files at any depth. The comment on line 66 explicitly says "Match commit-msg.mts allowlist", confirming the intent for parity.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit f504816. Configure here.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Bugbot Autofix prepared a fix for the issue found in the latest run.
- ✅ Fixed: Missing
set -elets security check failures pass silently- Added
set -eafter the shebang so any non-zero exit (including the security check) immediately aborts the hook.
- Added
Or push these changes by commenting:
@cursor push aeee0e4362
Preview (aeee0e4362)
diff --git a/.husky/pre-commit b/.husky/pre-commit
--- a/.husky/pre-commit
+++ b/.husky/pre-commit
@@ -1,4 +1,5 @@
#!/bin/sh
+set -e
# Optional checks - can be bypassed with --no-verify for fast local commits.
# Mandatory security checks ALSO run in pre-push hook.
#You can send follow-ups to the cloud agent here.
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit f504816. Configure here.
|
|
||
| # Check prerequisites. | ||
| # Run Socket security pre-commit checks (API keys, .DS_Store, etc.). | ||
| node .git-hooks/pre-commit.mts |
There was a problem hiding this comment.
Missing set -e lets security check failures pass silently
High Severity
The new node .git-hooks/pre-commit.mts security check is added to a shell script that lacks set -e. If the security check exits with code 1 (e.g. detecting an API key or .DS_Store), the shell script continues executing pnpm lint and pnpm test. If those subsequent commands succeed, the overall hook exit code is 0 and the commit goes through despite the security failure. This silently defeats the purpose of the pre-commit security gate.
Reviewed by Cursor Bugbot for commit f504816. Configure here.
…y/ scope) Consolidates PR #1280 (path-guard infra) and #1281 (.sh→.mts hook conversion) into this branch. Resolves the modify/delete conflict on .git-hooks/{commit-msg,pre-push} by accepting the .mts versions — the env allowlist tweak from #1279 (.env.precommit + skip-hook- scripts) is already covered in commit-msg.mts via shouldSkipFile and the precommit allowlist. Also renames internal hook packages to drop the @socketsecurity/ scope (hook-path-guard, hook-token-guard, hook-check-new-deps) — they're private:true and never published.
….mts conversion + bootstrap-from-registry Consolidates the work previously split across PRs #1279 (NODE_COMPILE_CACHE drop), #1280 (path-guard infra), and #1281 (.sh→.mts hook conversion) into a single commit. What's included: Env allowlist + .cache/ + CLAUDE.md - Drop NODE_COMPILE_CACHE convention from .env.precommit, .env.test - Allow .env.precommit at any depth in commit-msg hook - Skip hook scripts in scanners (they contain the literal regex) - Restore .cache/** exclude in tsconfigs - Propagate CLAUDE.md sorting + open-PR + paths + inclusive-language rules; Set constructor sort rule; don't-revert-untouched rule; replace whitelist/blacklist with allowlist/denylist Path-guard infra (.claude/hooks/path-guard/, scripts/check-paths.mts, .github/paths-allowlist.yml, .claude/skills/path-guard/) - Mantra: 1 path, 1 reference. PreToolUse hook on Edit|Write blocks multi-stage build paths constructed inline; companion gate runs in pnpm check - Template-literal path detection - Drift-resistant allowlist via exact-line OR snippet_hash match - --show-hashes CLI flag for authoring allowlist entries - Centralized vocabulary in segments.mts (hook + gate share one source for stage / build-root / mode / sibling-package sets) - Paren-balanced parser handles nested function-call args - Multi-line YAML reasons (| and > block scalars) - scripts/check.mts resolves the gate via path.join(scriptsDir,...) so it runs from any cwd (root or workspace package) Token-guard renamed from token-hygiene - Word-boundary match for sensitive env names - Step 1 (ALWAYS_DANGEROUS) now gates on hasRedaction so 'env | sed s/=.*/=<redacted>/' (the suggested fix) actually passes .sh → .mts hook conversion (Node 25+) - .git-hooks/_helpers.mts (was _helpers.sh) — exports filterAllowedApiKeys + scanners (personal paths, AWS keys, GitHub tokens, private keys, AI attribution, Linear issue refs) - .git-hooks/{commit-msg,pre-commit,pre-push}.mts (were .sh) - _helpers.mts hard-fails at module load if Node < 25 (relies on stable type stripping, no flag) - Husky shims invoke node directly - .husky/pre-commit runs tests with SOCKET_CLI_NO_API_TOKEN=1 so contributors without a real token don't see test failures Hook package rename - Drop @socketsecurity/ scope from internal hook packages (hook-path-guard, hook-token-guard, hook-check-new-deps); they are private:true and never published Bootstrap-from-registry (NEW) - scripts/bootstrap-from-registry.mts downloads zero-dep Socket packages (currently @socketsecurity/lib) from the npm registry directly into node_modules/ before pnpm install runs - Wired via package.json preinstall hook - Reads pinned version from pnpm-workspace.yaml catalog: OR root package.json devDependencies (whichever is set) - Solves the chicken-and-egg where setup.mts needs @socketsecurity/lib at module-load time but pnpm install hasn't run yet on a fresh clone
…rap + cascade Consolidated PR — combines the original work from #1279, #1280, #1281 plus follow-up commits (private-name rule, socket-registry pin cascades) into a single squashed commit. Includes: - env allowlist + .cache/ + CLAUDE.md hygiene (drop NODE_COMPILE_CACHE convention; restore .cache/** exclude in tsconfigs; propagate CLAUDE.md sorting/open-PR/paths/inclusive-language/Set-sort/ don't-revert-untouched/private-name rules; replace whitelist/blacklist with allowlist/denylist) - path-guard infra (PreToolUse hook + scripts/check-paths.mts gate + .github/paths-allowlist.yml + /path-guard skill — enforces "1 path, 1 reference" so multi-stage build paths are constructed exactly once) - token-guard hook (renamed from token-hygiene; word-boundary match for sensitive env names; ALWAYS_DANGEROUS gates on hasRedaction so redacted env dumps pass) - .sh -> .mts hook conversion on Node 25+ (stable type stripping; _helpers.mts hard-fails at module load if Node < 25; husky shims invoke node directly; SOCKET_CLI_NO_API_TOKEN=1 for pre-commit tests) - internal hook package rename (drop @socketsecurity/ scope from hook-path-guard, hook-token-guard, hook-check-new-deps; private, never published) - xport lock-step manifest (scripts/xport.mts + scripts/xport-schema.mts + scripts/xport-emit-schema.mts + xport.schema.json) - bootstrap-from-registry (scripts/bootstrap-from-registry.mts downloads zero-dep Socket packages from npm registry into node_modules/ via preinstall hook, solving fresh-clone chicken-and-egg) - socket-registry pins cascaded to ceab1e26 (picks up the @socketsecurity/lib bootstrap move from the install action into setup, so consumers calling only setup also benefit)



Converts the four shell-based git-hook files into TypeScript-first .mts modules running on Node 25+ (stable type stripping, no flag needed).
Files:
.git-hooks/_helpers.mts(was_helpers.sh) — exportsfilterAllowedApiKeys+ scanners for personal paths, AWS keys, GitHub tokens, private keys, AI attribution..git-hooks/commit-msg.mts.git-hooks/pre-commit.mts.git-hooks/pre-push.mts_helpers.mtshard-fails at module load if Node < 25.Husky shims invoke node directly.
Companion: socket-btm #120/#121 (merged), socket-repo-template@f415207.
Note
Medium Risk
Touches mandatory
pre-push/commit-msgenforcement and introduces a hard Node >=25 requirement, which can block commits/pushes if environments or regex/skip rules behave unexpectedly.Overview
Migrates the repo’s git hook security enforcement from shell scripts to Node
.mtsmodules, centralizing shared logic in.git-hooks/_helpers.mts(API-key allowlist, secret/path scanners, git wrappers) and hard-failing on Node < 25.Updates
commit-msg,pre-commit, andpre-pushto use the new scanners (including Linear reference blocking and AI-attribution stripping) and tightens/standardizes file skipping and.envallowlists, while Husky wrappers now invoke the hooks vianode.Reviewed by Cursor Bugbot for commit f504816. Configure here.