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.