Files
my-vault/.scripts/verify-vault.mjs
T
windyboy f77a4afd50 security: widen verifier (openssh/hyphenated-sk/ctx7sk/credential-assign/telegram/conn-string), fix GIT_WORKFLOW guidance
- BEGIN [A-Z0-9 ]*PRIVATE KEY now covers OPENSSH/RSA/EC variants
- sk- patterns tolerate hyphens and prefixed families (ctx7sk-)
- credential-assign catches PASSWORD=/token:/|API_KEY| tables (placeholder-aware)
- telegram bot tokens + connection strings with embedded passwords
- .obsidian/** third-party plugin code excluded from value scan (noise)
- regression: all 12 files named in REVIEW-FOLLOWUP now flagged
- GIT_WORKFLOW: drop git add . / Windows path, add verifier step,
  note force-push exception for the one-time history rewrite
2026-09-26 12:09:11 +08:00

97 lines
3.6 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env node
import { execSync } from 'node:child_process'
import { readFileSync } from 'node:fs'
// 值形态的占位符 / 环境引用:不算泄漏
const PLACEHOLDER =
/(\{\{|<[^>]*>|\$\{|process\.env|\benv\[|your[-_]|example|sample|placeholder|changeme|redacted|\bxxx\b|\.\.\.|\[hidden\])/i
// 只报路径与行号,绝不打印匹配到的内容
const KEY_PATTERNS = [
// 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,
],
]
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) {
if (file === '.scripts/verify-vault.mjs') continue
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')
// .obsidian/** 是第三方插件代码与状态,值形态规则误报极高(minified JS/CSS);
// 密钥载体 data.json 已解除跟踪,这里只保留 64 位 hex 检查
const isObsidian = file.startsWith('.obsidian/')
for (let i = 0; i < lines.length; i++) {
const line = lines[i]
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}`)
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)
}