Files
my-vault/.scripts/verify-vault.mjs
T
windyboy a3ee8c09cf security: scrub live credentials from tracked notes; point values at Vaultwarden
- replace real keys/passwords/tokens/connection strings with {{SECRET_*}}
  placeholders across 24 files (review-named 12 + noise-audit finds:
  WAQI/Dovecot/GPS/VPN subscriptions/Work runbooks/Plane API key)
- widen verifier: OPENSSH/RSA key headers, hyphenated sk-/prefixed sk-,
  credential assignments & table cells, telegram bot tokens, conn strings
- placeholder/default-value/config-name exemptions to kill doc FPs
- skip .obsidian plugin code and frozen Inbox-Clippings from value scan
- GIT_WORKFLOW: no git add ., verifier before commit, force-push exception
- final regression: 0 flagged in tracked scope (was 43)
2026-09-26 12:26:57 +08:00

105 lines
4.1 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\[|\byour|example|sample|placeholder|changeme|change_me|redacted|\bxxx\b|\.\.\.|\[hidden\]|super[_-]?secret|environ|\.\w)/i
// 公共默认口令:不算泄漏(本地开发默认值)
const DEFAULT_VALUES =
/^(postgres|hass|litellm|root|changeme|admin|123456)$/i
const DEFAULT_SUFFIX = /(?:^|[_-])(?:password|passwd|secret|token|key|config)$/i
// 全大写下划线 = 未填的配置名占位(如 MAILBOX_PASSWORD)
const CONFIG_NAME = /^[A-Z0-9_-]{8,}$/
// 只报路径与行号,绝不打印匹配到的内容
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'"@:/]+:([A-Za-z0-9_+/=~.!@#$%^&*?:-]{4,})@/,
true,
],
]
const OBSIDIAN_HEX_PATTERN = /^[0-9a-f]{64}$/i
let found = false
const trackedFiles = execSync('git ls-files', { encoding: 'utf8' })
.split('\n')
.filter(Boolean)
// 剪藏目录是第三方教程的冻结导入格式(阶段 3 边界),其中的示例凭据不是本库的
const SKIP_PREFIXES = ['.obsidian/', '04_Archive/Inbox-Clippings/']
for (const file of trackedFiles) {
if (file === '.scripts/verify-vault.mjs') continue
if (SKIP_PREFIXES.some((p) => file.startsWith(p))) 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')
for (let i = 0; i < lines.length; i++) {
const line = lines[i]
if (!file.startsWith('.obsidian/')) {
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) || DEFAULT_VALUES.test(value) || DEFAULT_SUFFIX.test(value) || CONFIG_NAME.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)
}