-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcommit-msg.mts
More file actions
130 lines (116 loc) · 4.09 KB
/
commit-msg.mts
File metadata and controls
130 lines (116 loc) · 4.09 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
#!/usr/bin/env node
// Socket Security Commit-msg Hook
//
// Two responsibilities:
// 1. Block commits that introduce API keys / .env files (security
// layer that runs even when pre-commit is bypassed via
// `--no-verify`).
// 2. Auto-strip AI attribution lines from the commit message before
// git records the commit.
//
// Wired via .git-hooks/commit-msg (the sibling shell shim), which git
// invokes when `core.hooksPath` points at .git-hooks/ — set by
// `node scripts/install-git-hooks.mts` at `pnpm install` time. The
// shim execs this .mts file with the path to the commit message file
// as argv[2] (after the script path itself).
import { existsSync, readFileSync, writeFileSync } from 'node:fs'
import path from 'node:path'
import process from 'node:process'
import { getDefaultLogger } from '@socketsecurity/lib-stable/logger/default'
import {
gitLines,
readFileForScan,
scanGitHubTokens,
scanLinearRefs,
scanSocketApiKeys,
shouldSkipFile,
stripAiAttribution,
} from './_helpers.mts'
const logger = getDefaultLogger()
const main = (): number => {
let errors = 0
const committedFiles = gitLines(
'diff',
'--cached',
'--name-only',
'--diff-filter=ACM',
)
for (const file of committedFiles) {
if (!file || shouldSkipFile(file)) {
continue
}
const text = readFileForScan(file)
if (!text) {
continue
}
// Socket API keys (allowlist-aware).
const apiHits = scanSocketApiKeys(text)
if (apiHits.length > 0) {
logger.fail('Potential API key detected in commit!')
logger.info(`File: ${file}`)
errors++
}
// .env files at any depth — allow only .env.example, .env.test,
// .env.precommit (templates / tracked placeholders).
const base = path.basename(file)
if (
/^\.env(\.[^/]+)?$/.test(base) &&
!/^\.env\.(example|precommit|test)$/.test(base)
) {
logger.fail('.env file in commit!')
logger.info(`File: ${file}`)
errors++
}
}
// Block Linear issue references in the commit message. Linear
// tracking lives in Linear; commit history stays tool-agnostic. The
// canonical CLAUDE.md "public-surface hygiene" block documents the
// policy; this hook makes it mechanical so a typo in a hot rebase
// can't slip through.
const commitMsgFile = process.argv[2]
if (commitMsgFile && existsSync(commitMsgFile)) {
const original = readFileSync(commitMsgFile, 'utf8')
const linearHits = scanLinearRefs(original)
if (linearHits.length > 0) {
logger.fail('Commit message references Linear issue(s):')
for (const ref of linearHits) {
logger.info(` ${ref}`)
}
logger.info(
'Linear tracking lives in Linear. Remove the reference from the commit message.',
)
errors++
}
// GitHub tokens in the commit message body. Pasting a `ghs_*` /
// `ghp_*` / `ghu_*` token into a commit message is exactly the
// leak vector commit-msg should block (the body lands in the
// remote repo's commit-log permanently — can't be unpushed). The
// scanGitHubTokens regex covers both the classic opaque format
// and the new JWT format from the 2026-05-15 GitHub rollout.
const ghHits = scanGitHubTokens(original)
if (ghHits.length > 0) {
logger.fail('Commit message contains a potential GitHub token:')
for (const hit of ghHits.slice(0, 3)) {
logger.info(` line ${hit.lineNumber}: ${hit.line.trim()}`)
}
logger.info(
'Remove the token from the commit message. If this is intentional documentation of a token-shape pattern, paste the value into a test fixture instead, not the commit message.',
)
errors++
}
// Auto-strip AI attribution lines from the commit message.
const { cleaned, removed } = stripAiAttribution(original)
if (removed > 0) {
writeFileSync(commitMsgFile, cleaned)
logger.success(
`Auto-stripped ${removed} AI attribution line(s) from commit message`,
)
}
}
if (errors > 0) {
logger.fail('Commit blocked by security validation')
return 1
}
return 0
}
process.exit(main())