2026-09-26 11:31:48 +08:00
|
|
|
|
#!/usr/bin/env node
|
|
|
|
|
|
|
|
|
|
|
|
import { execSync } from 'node:child_process'
|
|
|
|
|
|
import { readFileSync } from 'node:fs'
|
|
|
|
|
|
|
2026-09-26 12:09:11 +08:00
|
|
|
|
// 值形态的占位符 / 环境引用:不算泄漏
|
|
|
|
|
|
const PLACEHOLDER =
|
|
|
|
|
|
/(\{\{|<[^>]*>|\$\{|process\.env|\benv\[|your[-_]|example|sample|placeholder|changeme|redacted|\bxxx\b|\.\.\.|\[hidden\])/i
|
|
|
|
|
|
|
|
|
|
|
|
// 只报路径与行号,绝不打印匹配到的内容
|
2026-09-26 11:31:48 +08:00
|
|
|
|
const KEY_PATTERNS = [
|
2026-09-26 12:09:11 +08:00
|
|
|
|
// OpenAI 系(sk-or-v1 / sk-proj / sk-svcacct / 裸 sk-),键内允许连字符
|
|
|
|
|
|
['openai-key', /\bsk-[A-Za-z0-9_-]{16,}/, false],
|
|
|
|
|
|
// 第三方 "xxxsk-" 前缀族(如 ctx7sk-…)
|
|
|
|
|
|
['prefixed-key', /\b[A-Za-z0-9]{2,8}sk-[A-Za-z0-9_-]{16,}/, false],
|
|
|
|
|
|
['aws', /\bAKIA[0-9A-Z]{16}\b/, false],
|
|
|
|
|
|
['github-pat', /\bgh[pousr]_[A-Za-z0-9]{20,}/, false],
|
|
|
|
|
|
['gitlab-pat', /\bglpat-[A-Za-z0-9_-]{20,}/, false],
|
|
|
|
|
|
// 覆盖 OPENSSH / RSA / EC / ENCRYPTED 等全部变体
|
|
|
|
|
|
['private-key', /BEGIN [A-Z0-9 ]*PRIVATE KEY/, false],
|
|
|
|
|
|
['slack', /\bxox[baprs]-[A-Za-z0-9-]{10,}/, false],
|
|
|
|
|
|
// 关键字赋值 / 表格单元格:PASSWORD=… / token: … / | API_KEY | …(值须 ≥16 位密钥形态字符)
|
|
|
|
|
|
[
|
|
|
|
|
|
'credential-assign',
|
|
|
|
|
|
/[A-Za-z0-9_-]{0,32}(?:password|passwd|pwd|token|secret|api[_-]?key|apikey|access[_-]?key|secret[_-]?key|bot[_-]?token|private[_-]?key|credential)["']?\s*[:=|]\s*["']?([A-Za-z0-9_+/=~.!@#$%^&*?:-]{16,})/i,
|
|
|
|
|
|
true,
|
|
|
|
|
|
],
|
|
|
|
|
|
// Telegram bot token:<数字id>:AA<hash>
|
|
|
|
|
|
['telegram-bot', /\b\d{8,10}:AA[A-Za-z0-9_-]{30,}/, false],
|
|
|
|
|
|
// 带内嵌口令的连接串 postgres://user:pass@host
|
|
|
|
|
|
[
|
|
|
|
|
|
'conn-string',
|
|
|
|
|
|
/\b(?:postgres(?:ql)?|mysql|redis|mongodb(?:\+srv)?|amqp|smtp|mssql):\/\/[^\s'"@:/]+:[^\s'"@]{4,}@/,
|
|
|
|
|
|
false,
|
|
|
|
|
|
],
|
2026-09-26 11:31:48 +08:00
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
const OBSIDIAN_HEX_PATTERN = /^[0-9a-f]{64}$/i
|
|
|
|
|
|
|
|
|
|
|
|
let found = false
|
|
|
|
|
|
|
|
|
|
|
|
const trackedFiles = execSync('git ls-files', { encoding: 'utf8' })
|
|
|
|
|
|
.split('\n')
|
|
|
|
|
|
.filter(Boolean)
|
|
|
|
|
|
|
|
|
|
|
|
for (const file of trackedFiles) {
|
2026-09-26 11:34:50 +08:00
|
|
|
|
if (file === '.scripts/verify-vault.mjs') continue
|
|
|
|
|
|
|
2026-09-26 11:31:48 +08:00
|
|
|
|
if (file.endsWith('.png') || file.endsWith('.jpg') || file.endsWith('.jpeg') || file.endsWith('.gif') || file.endsWith('.webp') || file.endsWith('.svg') || file.endsWith('.ico') || file.endsWith('.woff') || file.endsWith('.woff2') || file.endsWith('.ttf') || file.endsWith('.eot') || file.endsWith('.pdf') || file.endsWith('.zip') || file.endsWith('.gz') || file.endsWith('.tar') || file.endsWith('.mp4') || file.endsWith('.mp3') || file.endsWith('.wav')) {
|
|
|
|
|
|
continue
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
let content
|
|
|
|
|
|
try {
|
|
|
|
|
|
content = readFileSync(file, 'utf8')
|
|
|
|
|
|
} catch {
|
|
|
|
|
|
continue
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const lines = content.split('\n')
|
2026-09-26 12:09:11 +08:00
|
|
|
|
// .obsidian/** 是第三方插件代码与状态,值形态规则误报极高(minified JS/CSS);
|
|
|
|
|
|
// 密钥载体 data.json 已解除跟踪,这里只保留 64 位 hex 检查
|
|
|
|
|
|
const isObsidian = file.startsWith('.obsidian/')
|
2026-09-26 11:31:48 +08:00
|
|
|
|
for (let i = 0; i < lines.length; i++) {
|
|
|
|
|
|
const line = lines[i]
|
|
|
|
|
|
|
2026-09-26 12:09:11 +08:00
|
|
|
|
if (!isObsidian) {
|
|
|
|
|
|
for (const [name, pattern, checkValue] of KEY_PATTERNS) {
|
|
|
|
|
|
const m = pattern.exec(line)
|
|
|
|
|
|
if (!m) continue
|
|
|
|
|
|
// 赋值类:值是占位符 / 环境引用时放行
|
|
|
|
|
|
if (checkValue) {
|
|
|
|
|
|
const value = m[1]
|
|
|
|
|
|
if (!value || PLACEHOLDER.test(value)) continue
|
|
|
|
|
|
}
|
|
|
|
|
|
console.error(`KEY LEAK (${name}): ${file}:${i + 1}`)
|
2026-09-26 11:31:48 +08:00
|
|
|
|
found = true
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (file.startsWith('.obsidian/plugins/') && file.endsWith('.json')) {
|
|
|
|
|
|
const tokens = line.split(/[\s":,]+/).filter(Boolean)
|
|
|
|
|
|
for (const token of tokens) {
|
|
|
|
|
|
if (OBSIDIAN_HEX_PATTERN.test(token) && token.length === 64) {
|
|
|
|
|
|
console.error(`OBSIDIAN HEX KEY: ${file}:${i + 1}`)
|
|
|
|
|
|
found = true
|
|
|
|
|
|
break
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (found) {
|
|
|
|
|
|
process.exit(1)
|
|
|
|
|
|
}
|