@alikhanmammadli/secret-sentinel
v0.1.0
Published
A Claude Code PreToolUse hook that blocks hardcoded secrets, missing auth checks, open CORS, and disabled DB rules before they are written to disk.
Maintainers
Readme
Secret Sentinel
A Claude Code hook that blocks security flaws the moment they'd be written to disk — not after they're committed, deployed, or exploited.
Hardcoded secrets, missing auth checks, open CORS, disabled database rules. Secret Sentinel runs inside your Claude Code session as a PreToolUse hook, inspects every file Claude is about to write or edit, and stops the write before it happens.
🛑 BLOCKED — Hardcoded Stripe secret key detected
File: src/payments.ts, line 3
Found: Stripe live secret key — sk_l************zXcV
Why this matters: this is a live Stripe key, not a test one. Anyone who can see
this file can charge your Stripe account, issue refunds, or read your customer data.Why this exists
AI-generated code ships with a higher vulnerability rate than hand-written code, and vibe-coded apps disproportionately leak secrets, skip auth, and leave databases wide open.
Traditional workflows caught this with friction — code review, a senior engineer spotting a hardcoded key, a deploy checklist. Vibe coding removes that friction on purpose. That's the point of it. But it also removes the people who used to catch the mistakes.
Existing tools — gitleaks, GitHub secret scanning, post-deploy scanners — all catch the problem after the fact: at commit time, at push time, or once the app is already live. By then you've already looked at the change, said "looks good," and moved on.
Secret Sentinel sits one step earlier, inside the loop itself.
Install
npx @alikhanmammadli/secret-sentinel initThat's it. It adds a PreToolUse entry to your project's .claude/settings.json and prints exactly what it wrote:
Secret Sentinel: added the PreToolUse hook in
/your/project/.claude/settings.json (created)
matcher: Write|Edit
command: node /path/to/secret-sentinel/dist/src/hook.js
It will check every file Claude Code writes or edits in this project.No account, no dashboard, no server. It merges into an existing settings.json without touching your other settings or hooks, and re-running it updates in place rather than adding a duplicate.
Requires Node 20.10+.
What it catches
| Scanner | Catches | Severity |
|---|---|---|
| secrets | AWS / Stripe / GitHub / Slack / Google keys, private key blocks, DB connection strings with credentials, generic API_KEY = "..." assignments, plus a Shannon-entropy fallback for secrets that match no known format | blocks |
| db-rules | Firestore allow read, write: if true, Supabase RLS policies with USING (true) and no auth check, DISABLE ROW LEVEL SECURITY | blocks |
| cors | Wildcard origins, origin: true, bare cors() | warns — blocks when paired with credentials |
| auth | Express routes and Next.js API routes defined with no auth guard | warns |
Blocks stop the write. Warns prompt you in-session, and you approve or reject right there.
Before / after
Blocked: a hardcoded secret
Claude tries to write src/payments.ts:
import Stripe from "stripe";
export const stripe = new Stripe("sk_live_51QwErTyUiOpAsDfGhJkLzXcV");The write never lands. Claude sees this instead:
🛑 BLOCKED — Hardcoded Stripe secret key detected
File: src/payments.ts, line 3
Found: Stripe live secret key — sk_l************zXcV
Why this matters: this is a live Stripe key, not a test one. Anyone who can see
this file can charge your Stripe account, issue refunds, or read your customer data.
Fix: move it to an environment variable instead:
1. Add STRIPE_SECRET_KEY=... to your .env file, using the value currently on line 3
2. Add .env to .gitignore if it isn't already
3. Reference it in code as process.env.STRIPE_SECRET_KEY
If this credential was ever committed or shared, rotate it — removing it from
the file does not un-leak it.
Override: if this is deliberate, tell Claude why and it can go ahead.
To stop it being flagged here again, add this to "allow" in .secret-sentinel.json:
{ "id": "secrets/stripe-live-key", "paths": ["src/payments.ts"] }Note the key is masked. The finding goes into your transcript, so it doesn't echo the credential back.
Blocked: an open database
Claude tries to write firestore.rules:
match /{document=**} {
allow read, write: if true;
}🛑 BLOCKED — Firestore rules allow public read/write
File: firestore.rules, line 4
Found: a Firestore security rule granting access `if true` — that is, to everyone
Why this matters: this rule lets anyone on the internet read and write this
collection directly, without going through your app at all. They don't need an
account, and they don't need to find a bug — the database is simply open. This is
the single most common way vibe-coded apps leak their entire user table.
Fix: gate the rule on authentication and ownership instead of `true`:
1. Require a signed-in user: allow read: if request.auth != null;
2. Scope writes to the owner: allow write: if request.auth.uid == userId;
3. Re-deploy the rules and re-test with the Firestore rules simulatorWarned: open CORS
Heuristic findings prompt rather than block, so you stay in control:
⚠️ WARNING — Open CORS policy detected
File: src/server.ts, line 2
Found: a CORS `origin` set to the `*` wildcard
Why this matters: any website on the internet can make browser requests to this
API and read the responses. If any endpoint returns data that isn't fully public,
another site can pull it out of your users' browsers.
Fix: replace the wildcard with an explicit allowlist:
1. List the origins that actually need access, e.g.
const allowed = ['https://app.yourdomain.com'];
2. Pass that list as the origin: app.use(cors({ origin: allowed }))
3. Keep the list in an environment variable if it differs per environmentYou approve, and the write goes through. Reject, and Claude fixes it.
How it works
Claude Code fires a PreToolUse hook before a tool call runs, and lets the hook allow, block, or prompt. Secret Sentinel is that hook.
- Claude proposes a write. Claude Code sends the hook a JSON payload on stdin with the tool name and its arguments.
- The file is normalized.
Writecarries the whole file.Editcarries onlyold_string/new_string, so Secret Sentinel reads the current file and applies the edit itself — scanners always see complete file content, never a fragment. - Scanners run. Each is a pure
(filePath, content) => Finding[]function. Every finding carries a plain-English explanation of what was found, why it's risky, and what to do instead. - Your config applies. Ignored paths, allowlisted findings, and severity overrides from
.secret-sentinel.json. - A decision goes back.
denyblocks the write outright.askprompts you. Nothing found means the write proceeds under your normal permission settings.
It fails closed. If a scanner crashes, the rules file is corrupt, or anything else goes wrong, Secret Sentinel blocks and tells you why, rather than waving the write through. A crash that blocks is a bug you report; a crash that allows is a leaked credential nobody notices.
Tuning it
False positives are the reason tools like this get uninstalled, so there's a .secret-sentinel.json in your project root:
{
"ignorePaths": ["dist", "vendor", "**/*.generated.ts"],
"allow": [
{ "id": "secrets/entropy", "paths": ["src/legacy/fixtures.ts"] }
],
"severity": {
"cors": "block",
"auth": "warn"
}
}ignorePaths— globs that are never scanned at all.allow— findings confirmed as false positives. Scope them to a path (as above) so you don't silence a rule everywhere; a bare"secrets/entropy"string suppresses it project-wide if that's genuinely what you want.severity— override per scanner ("cors") or per finding ("cors/wildcard-origin-option").
The everyday override isn't this file, though — it's the in-session prompt. Reach for the config only when something keeps re-triggering.
Limitations
Worth knowing before you rely on it:
- Write coverage is
WriteandEdit. Files written via Bash redirects or other tools bypass the hook. - The auth scanner sees one file at a time. A Next.js route protected only by
middleware.tswill warn. Mark it// @publicto silence it. - It is not a SAST tool. The goal is the specific, high-frequency mistakes that actually ship — not comprehensive coverage. Keep using gitleaks and friends.
- Frameworks covered are Express and Next.js for auth, Supabase and Firestore for database rules.
Development
npm install
npm testnpm test builds first, then runs the suite: scanner fixtures (known-bad and known-good), config behavior, the installer's merge logic, and the fail-closed guarantees.
License
MIT #� �s�e�c�r�e�t�-�s�e�n�t�i�n�a�l� � �
