유대선
프로젝트로
·기술 회고·3

Secret Guard: a local, opt-in pre-commit secret scanner with a BYO-honest install UI

Built a local-only pre-commit secret scanner (9 regex patterns, redacted findings) plus an explicit opt-in install card in the desktop guard zone. Two commits in one parallel pass; honest about its limits — opt-in, bypassable with --no-verify, deterministic regex not entropy.

DalkkakAI's product is the workspace, not the LLM, so the security features that matter are the ones that protect a solo founder from their own clumsy commits. Secret Guard is the first of those: a local, opt-in pre-commit hook that scans staged Git content for credentials and blocks the commit if it finds any. It landed in two commits on 2026-06-17 — 9367e39 (shared scanning foundation) and b0ad7c8 (desktop install UI) — built as parallel work items LE03 and LE04.

The scanner (packages/shared/src/secretGuard.ts)

The core is scanTextForSecrets(), driven by nine deterministic regex patterns: private-key blocks, OpenAI keys (sk-…), Anthropic keys (sk-ant-…), GitHub tokens (gh[opusr]_… / github_pat_…), Stripe live keys (sk_live_… / rk_live_…), Slack tokens (xox[baprs]-…), AWS access key IDs (AKIA/ASIA), bearer tokens, and a generic key/token/secret/password = … assignment catch-all. The first seven are severity high; the last two are medium.

The design decision I'm happiest with is that a finding never carries the secret. Each SecretGuardFinding exposes a fingerprint, a redactedValue of the shape [REDACTED:<patternId>:<length>], and an excerpt where the matched span is already replaced inline. The CLI formatter spells this out: "Only redacted values are shown below." So even the failure message that prints in your terminal — and would otherwise land in shell history or a screenshot — contains no live credential.

To keep the false-positive rate survivable, isObviousFalsePositive() drops anything under 16 chars, repeated-character runs (xxxx…), strings containing example/placeholder/changeme/your_/fake/dummy/test, template literals (${VAR}), and angle-bracket placeholders. There's also an allowlist: a line (or the line above it) carrying dalkkak-secret-guard: allow, …allow-test-fixture, or gitleaks:allow is skipped — which is exactly how the test fixtures embed realistic-looking keys without tripping the guard on themselves.

The hook and installer (scripts/install-secret-guard.mjs)

Installation is never automatic. The installer validates the target hard before touching anything: it must be a real Git working tree with a package.json whose name is ddalkkak (overridable only via an explicit env flag), and it refuses paths that resolve to / or a bare .git. It refuses to clobber an unrelated existing pre-commit hook unless --force is passed — and the UI deliberately does not expose --force. The generated hook execs --scan-staged; when that finds anything it prints the redacted report to stderr and exits 1, which aborts the commit.

The UI (apps/desktop/src/home/SecretGuardCard.tsx)

The desktop guard / 지키다 zone gets a Local Secret Guard card that reads real status through a Rust Tauri command (secret_guard_status) backed by --status --json. It shows the resolved hook path, the dry-run command, and an explicit Install hook button gated behind a confirmation dialog. The card's copy is the honest contract: "Scans staged Git content only," "Runs locally and does not upload secrets or findings," "Does not inspect terminal input, shell history, or unstaged files." No PTY interception, no uploads, no fake installed-state.

What it is NOT

Honesty over polish: this is a deterministic regex guard, not an entropy scanner — a high-entropy blob that matches no pattern slides through. It is opt-in (nothing installs on app launch), and like every Git hook it's bypassable with git commit --no-verify. Status and install depend on a local node because the hook is Node-backed; if node is missing the UI reports an error rather than faking a status. The desktop UI was verified by unit tests (shared: 8 tests; desktop: 241) and cargo check, but not yet by a live Tauri visual smoke test. It's a floor, not a vault.