keysnag
Detects insecure Stripe payment integrations, such as unverified webhook signatures, client-supplied charge amounts, and missing idempotency keys.
Provides security checks for Supabase projects, including Row Level Security policies, storage bucket exposure, and cross-account data access through the Supabase REST API.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@keysnagScan the current repo for critical security issues before I push."
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
The problem
You built something real with Claude Code, Cursor, Lovable or Bolt. It works. Now you are about to put it in front of real users, and you have no security team.
This is how apps like yours actually get breached, in order of how often it happens:
What goes wrong | What it costs |
A | Full read/write of your database, forged charges, an API bill you did not agree to |
Row Level Security is off, or a policy says | Any signed-up user reads or edits everyone's data by changing an id |
A route trusts a | Same thing, one |
A Stripe webhook never verifies its signature, or the price comes from the client | Free product |
A dependency pinned to whatever version the model remembered | A CVE you never wrote |
No rate limit on the LLM route you shipped | One user loops it and drains your credits |
In 2025 one scan of 1,645 apps built with a single AI builder found 170 with wide-open databases. Independent testing puts the share of AI-generated code with an OWASP Top 10 flaw at around 45%. The r/vibecoding thread with the most upvotes this year is a list of the same seven mistakes found in "every single" AI-built app a consultancy cleaned up.
Every one of those is a known, catchable defect class. keysnag catches them before the push.
Related MCP server: Security Scanner MCP
How it works
You (or your agent) run
git push.keysnag's pre-push hook scans the tracked tree and, if you gave it a URL or database, the live site and the schema.
A critical finding (a leaked secret, RLS off on user data, a proven cross-account read) blocks the push with the file, the line, and a fix written so a coding agent can apply it. Inside Claude Code, the plugin hook wakes Claude with the findings so it fixes them before continuing.
Everything else is saved to
keysnag.report.mdas advisories for you to triage when you choose. Clean code goes through untouched.
It is a plain git hook, so it behaves identically whether you push from Cursor, VS Code, a terminal, or CI.
Install in 60 seconds
No install step. Run it straight from npm inside your project:
# one-off scan of the current repo
npx keysnag scan
# add the pre-push gate (chains any hook you already have, gitignores its reports)
npx keysnag installPrefer a local checkout? git clone https://github.com/Sheldon-desouza/keysnag.git && cd keysnag && npm install && npm run build, then node dist/cli.js scan --repo /path/to/your/app.
Optional: point it at your live site and database to unlock the live checks. Copy .env.example to .env in the keysnag folder and fill in what you have:
KEYSNAG_SITE_URL=https://your-app.vercel.app # live-URL probes
KEYSNAG_PG_URL=postgres://readonly:... # RLS + storage audit (read-only role)
KEYSNAG_SUPABASE_URL=https://xxxx.supabase.co # cross-account test
KEYSNAG_SUPABASE_ANON_KEY=eyJ...
KEYSNAG_TOKEN_A=eyJ... # two test users' JWTs
KEYSNAG_TOKEN_B=eyJ...Any check whose inputs are missing is skipped and the report tells you exactly which variable would turn it on. Nothing is ever a false failure.
What it checks
13 checks, grouped by what they need. "Blocks a push?" reflects the default gate (--fail-on critical): only critical findings stop a push; everything else is reported and left for you.
Repo / static (no inputs beyond the code itself)
Check | What it catches | Blocks a push? |
| API keys, | yes |
| Secrets in | yes (TLS-off, public secret) |
| API routes and server actions that hit the database with no auth check, routes that trust a client-supplied user id, | yes (writes with no auth) |
| SQL string concatenation, | yes (request-derived SQL/eval/exec) |
| Stripe webhooks that never verify the signature, charge amounts taken from the client, missing idempotency keys | yes |
| Obfuscated base64-eval chains, hardcoded password bypasses, auth-skip flags, | yes (obfuscated eval, literal bypass) |
| LLM calls made from the browser, AI routes with no auth or no spend bound, prompts built from raw input, unsanitised LLM output rendered as HTML | warn |
| Known CVEs for your exact lockfile versions with the upgrade that fixes them, unpinned deps, install-time scripts, lockfile drift | yes (critical CVE with a fix) |
Before a repo goes public
Check | What it catches | Inputs | Blocks a push? |
| Things that are not secrets but should not be public: your local home-directory paths, names you mark as private (a client, an employer, an internal project), committed scaffolding such as | optional private-terms list | no (run with |
List private names in a gitignored .keysnag-private file, one per line, or in KEYSNAG_PRIVATE_TERMS as a comma-separated list. Never put them in keysnag.config.json, because that file is usually committed and the list would leak itself. Then run:
npx keysnag scan --checks leaks --fail-on highLive site
Check | What it catches | Inputs | Blocks a push? |
| Exposed |
| partial |
Database
Check | What it catches | Inputs | Blocks a push? |
| RLS off, |
| yes |
| Public Supabase storage buckets, wide-open storage policies, edge functions with JWT verification off, cron routes with no shared secret |
| yes (public bucket with user files) |
Accounts
Check | What it catches | Inputs | Blocks a push? |
| User B reading, or no-op-writing, user A's rows through the Supabase REST API. Proves the gap the way an attacker would, without changing real data | Supabase URL, anon key, two test users' JWTs | yes |
The full threat model, the evidence behind each check, severity rules and what is planned next: docs/THREAT-MODEL-AND-CHECKS.md.
Why you can trust it
Most scanners get switched off within a week because they cry wolf. keysnag was built around one rule: a check may block a push only after it has proven zero false positives on a real production codebase. Until then it warns.
That rule was tested, not assumed. Every check was run against a real production Next.js + Supabase app with 1,400 tracked files:
The first full run produced 144 findings. Hand-verification showed all but 28 were false positives, each with a systematic cause: a storage-key name mistaken for a secret, a Next.js JSON-LD tag mistaken for XSS, a framework's blanket 401 mistaken for an unprotected endpoint,
m.role === 'user'mistaken for a backdoor.Each cause became a fix with a regression fixture reproducing the real line. The final run is 16 findings, all genuine, zero critical: ten real dependency advisories with their upgrade versions and six medium code advisories.
Then three rounds of independent review by a fresh reviewer that built none of it, actively trying to silence the rules. It found and forced fixes for a CVE check that was throwing away severity data, a hook that could never wake Claude, a raw password printed into a report, and a service client being counted as an admin check.
The other trust rules, all enforced in code and tests:
Never prints a secret. Every value in a finding is masked to its first and last four characters.
Deterministic. Rules, catalogues and live probes. No model guessing at your code, so results are the same every run.
Local only. No telemetry. Network only to the site, Supabase project and database you point it at, plus one opt-out call to OSV.dev for CVE data.
Exceptions must justify themselves. You can allow a finding you have reviewed, but every allow entry must state a reason and the control that bounds abuse, or keysnag refuses to run. Allowed findings still appear, as info. See Suppressing a finding.
Controlling what blocks
keysnag scan --fail-on critical # default: only critical findings exit non-zero
keysnag scan --fail-on off # never block, just report (warn mode)
keysnag scan --diff main # scope static checks to files changed since main
keysnag scan --no-osv # skip the one third-party call (OSV.dev CVE lookup)
keysnag install --pre-commit # also run the secret sweep on every commit
keysnag uninstall # remove the hooks, restore anything they chained--diff <ref> speeds up large repos; secrets always sweeps the full tree, because a secret anywhere is a secret. Install adds keysnag.report.md and .json to your .gitignore so the report never gets swept into a commit.
Install in Claude Code
claude plugin marketplace add Sheldon-desouza/keysnag
claude plugin install keysnag@keysnagOnce installed, keysnag auto-runs on every git commit and git push you make from inside Claude Code. If it finds a blocking finding it feeds it straight back to Claude, which fixes it before continuing, so you never have to notice a failed push. It also adds an on-demand command:
/security-checkThat runs the scan and walks you through fixing what it finds, one finding at a time, critical first. It pairs well with Anthropic's own security-guidance plugin: that one reviews the diff as Claude writes; keysnag gates what actually leaves the machine, and it also works in Cursor, Codex and CI where an editor plugin cannot.
Install as an MCP server
keysnag runs as an MCP stdio server exposing run_security_check (the full report) plus one check_<name> tool per check, so Cursor, Claude Desktop, Codex or any MCP client can call it directly:
{
"mcpServers": {
"keysnag": {
"command": "npx",
"args": ["-y", "keysnag-mcp"],
"env": {
"KEYSNAG_REPO_DIR": "/absolute/path/to/your/project",
"KEYSNAG_SITE_URL": "https://your-app.vercel.app"
}
}
}
}Suppressing a finding
Sometimes a finding is a real match on code that is, on inspection, acceptable: an admin tool behind its own gate, a webhook that verifies signatures a different way, a public helper with no user data. keysnag lets you record that decision instead of ignoring the finding. Every suppression must say why it is acceptable and, separately, what stops it being abused.
Add an allow array to keysnag.config.json, or use the helper:
keysnag allow authz.route_without_auth src/app/api/public/contact/route.ts \
--reason "Public contact form, unauthenticated by design." \
--bound "Per-IP rate limit of 5 per hour, returns 429."idis a concrete finding id (secret.stripe_key) or a dotted prefix (secret.).locationis a file, afile:line, or a glob. A location that matches everything is only allowed with a full concrete id, never a prefix.reasonandboundare mandatory. keysnag exits 1 before scanning, naming the entry, if either is missing.
A matched finding is never dropped: it becomes info, gets an [allowed] prefix, and carries the reason and bound in every report. It just never blocks.
Privacy
No telemetry, no analytics, no phone-home.
Network access only to the site, Supabase project, or database you provide. Never to a keysnag server.
One exception:
depslooks up known CVEs for your exact lockfile versions via OSV.dev's public API. Opt out with--no-osvorKEYSNAG_ALLOW_OSV=false; the offline hygiene rules still run.Secrets are always masked in findings, reports, logs and the CLI.
The RLS and storage audits are designed for a read-only Postgres role.
What it does not do
It targets Next.js + Supabase apps on Vercel today. Other stacks get a clean skip, never a false result.
The static rules are heuristics tuned to stay quiet rather than catch everything. A rule that cannot be made low-false-positive ships as a warning, not a blocker. If a finding does not apply to your code, that belongs in the allow-list with a reason, not a bug report.
It is automated scanning, not a substitute for a professional penetration test. keysnag will never tell you your app is unhackable. It removes the defect classes behind most real breaches of AI-built apps, and nothing more.
The cross-account write test is a bounded no-op: it writes back the exact value already there, on at most one row, only to prove the gap exists.
Roadmap
Tier 2: AST-based analysis to replace the regex heuristics in
authz,injectionandconfig; secrets in git history; GitHub Actions workflow injection; Vercel and Supabase auth-config assertions (rate limits, leaked-password protection); an authenticated crawl that replays every request as the other test user.Tier 3: an optional LLM review layer at push time as a plug-in check, and continuous post-deploy monitoring.
Contributing
Every check ships with a fixture that trips it and a real-repo run that does not. If you add a rule, add both, and run npm test. Bug reports for a false positive should include the line that fired; that is the most valuable issue you can file.
Licence
MIT. Built by Sheldon de Souza with Keegan de Souza, a recent cybersecurity graduate who contributes to the threat model and check catalogue.
Looking for: Claude security skill · Claude cybersecurity · Codex cybersecurity · Cursor security check · Claude Code security plugin · vibe coding security · security for AI-built apps · Lovable security scanner · Bolt security · Supabase RLS checker · Supabase security audit · Next.js security gate · pre-push security hook · git pre-push security scan · secret scanner for AI-generated code · IDOR checker · Stripe webhook verification check · OSV dependency scanner · AI founder security tool · security for non-technical founders · MCP security server. keysnag is a free, open-source pre-push security gate for apps built with AI coding agents.
This server cannot be deployed
Maintenance
Related MCP Connectors
Zero-config MCP security scanner for AI-generated apps. 25K+ vulnerability patterns.
Compliance & security scan for your app: secrets, exposed files, headers, privacy, AI-disclosure.
Threat modeling, code/cloud/pipeline scanning, shadow-AI discovery, compliance checks and fixes.
- VulX WatchOAuthai.vulx
Independent security review for AI-built apps. Watch a GitHub repo. Never a patch.
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceEnables comprehensive security scanning of code repositories to detect secrets, vulnerabilities, dependency issues, and configuration problems. Provides real-time security checks and best practice recommendations to help developers identify and prevent security issues.7 npm2MIT
- AlicenseNot gradedqualityDmaintenanceAutomatically detects security vulnerabilities in AI-generated code, scanning for hardcoded secrets, injection flaws, XSS, weak cryptography, authentication issues, path traversal, and vulnerable dependencies across JavaScript, Python, Java, and Go.30 npm2MIT
- AlicenseAqualityFmaintenanceEnables scanning projects for leaked secrets and security issues directly from Claude Code, detecting secret categories, dangerous code patterns, and git hygiene issues.228 PyPI1MIT
- AlicenseNot gradedqualityAmaintenanceProvides pre-deploy AI-assisted security and readiness checks for AI-built apps, including scanning for secrets, auth gaps, and payment safety.5MIT