prodguard
v0.10.0
Published
Fails your CI when an AI coding agent disables Supabase Row Level Security, widens a Postgres grant, exposes a service_role key, or skips Stripe webhook verification. Zero-dependency Node CLI and GitHub Action, 22 rules.
Maintainers
Readme
ProdGuard
Fails your CI when an AI coding agent disables Supabase Row Level Security, widens a Postgres grant, exposes a service_role key, or skips Stripe webhook verification. Zero-dependency Node CLI and GitHub Action, 22 rules.
npx prodguard checkZero dependencies. Runs in about a second. Works with whatever agent you use — Claude Code, Cursor, Copilot, Codex.
Why this exists
An AI agent asked to make something work will often take the fastest path, and the fastest path is frequently to remove whatever is blocking it. RLS switched off to fix a query. A paywall gate pinned to false for a demo. Email confirmations disabled to speed up testing.
None of it looks wrong afterwards. The tests still pass, because there are rarely tests on billing. The app still looks fine, because from the outside a broken paywall looks exactly like a working one.
This checks for that specific class of change — the things that quietly cost money or leak data — and fails your build when it finds them.
Never used a terminal command? Start here
Two minutes, and it installs nothing permanently.
1. Check you have Node. Open Terminal (Mac: press ⌘+Space, type terminal, enter) or PowerShell (Windows: Start menu). Paste:
node --versionA number like v20.11.0 means you're set. command not found means install Node from nodejs.org, then close and reopen Terminal.
2. See what it does, without touching your code.
npx prodguard check --demoIt asks Ok to proceed? (y) the first time — press enter. This runs against a broken example app inside the tool. Your files are not read.
3. Point it at your project. Type cd and a space, then drag your project folder onto the Terminal window (that pastes the path), press enter, then:
npx prodguard check4. Read the results. Each finding names a file and line, like src/pages/Dashboard.jsx:20. Open it, go to that line, decide for yourself. A finding is a prompt to look, not a verdict.
5. Optional — run it automatically.
npx prodguard initWrites two small files. Commit them and GitHub runs the check on every pull request, including the ones your agent opens.
Stuck? Open an issue and say where you got lost.
What it catches
| Check | Severity | What it means in plain English |
|---|---|---|
| paywall-disabled | 🔴 critical | Your paid features are unlocked for everyone |
| stripe-webhook-unverified | 🔴 critical | Anyone can POST a fake payment and unlock an account |
| service-role-key-exposed | 🔴 critical | Your admin database key can reach the browser |
| rls-disabled | 🔴 critical | Row Level Security is off — users can read each other's rows |
| live-secret-committed | 🔴 critical | A live Stripe/Anthropic/OpenAI key is in your source tree |
| rls-never-enabled | 🟠 high | A table was created and RLS was never turned on for it |
| email-verification-disabled | 🟠 high | Accounts go live without proving they own the email |
| credential-file-committed | 🔴 critical | A recovery-codes or credentials file is sitting in the repo |
| jwt-not-verified | 🔴 critical | Login tokens are decoded but never signature-checked |
| firebase-rules-open | 🔴 critical | Firebase rules allow anyone to read or write |
| cors-wildcard-credentials | 🟠 high | Any website can call your API with your users' cookies |
| destructive-migration | 🟠 high | A migration drops or truncates a table |
| security-definer-anon-executable | 🔴 critical | A SECURITY DEFINER function is granted to anon, so anyone can run it as the owner |
| security-definer-view | 🟠 high | A view in public has no security_invoker, so it reads past RLS |
| security-definer-search-path | 🟡 medium | A SECURITY DEFINER function has no fixed SET search_path |
| anon-write-grant | 🔴 critical | Insert/update/delete granted to anon or PUBLIC on a table |
| duplicate-permissive-policy | 🔴 critical | A USING (true) policy sits beside a real one — permissive policies are OR'd |
| grant-all-on-table | 🟠 high | GRANT ALL supersedes column-level grants and includes TRUNCATE |
| policy-missing-to-clause | 🟠 high | No TO clause plus an identity-independent predicate, so anon gets rows |
| policy-references-missing-column | 🟡 medium | A policy predicate names a column no migration creates |
| grant-schema-wide | 🔴 critical | GRANT ... ON ALL TABLES IN SCHEMA to a client-reachable role |
| policy-write-check-open | 🔴 critical | Scoped USING paired with WITH CHECK (true), so writes are unrestricted |
Reports are written for humans, not scanners:
🔴 CRITICAL Paid features are unlocked for everyone paywall-disabled
Your paywall is hardcoded open. Every visitor gets the paid product for
free, and your Stripe subscriptions stop meaning anything.
src/pages/Dashboard.jsx:20
const locked = false
→ `locked` is pinned to `false` instead of being derived from the
→ user's subscription.No CWE numbers. If you can build the app, you can read the report.
Usage
npx prodguard check --demo # see all nine fire on a broken example app
npx prodguard check # scan the current directory
npx prodguard check ./my-app # scan somewhere else
npx prodguard check --strict # also fail on HIGH, not just CRITICAL
npx prodguard check --json # machine-readable
npx prodguard rules # list every checkExit code is 1 when something blocking is found, so it works in CI as-is.
Put it in front of your agent
npx prodguard initWrites a config file and a GitHub Action that runs on every pull request — including the ones your agent opens. Commit both and dangerous changes stop merging.
Want it even earlier, before a commit is even made:
echo 'npx prodguard check' >> .husky/pre-commitConfiguration
.prodguardrc.json:
{
"strict": false,
"allow": [
"destructive-migration:supabase/migrations/001_teardown.sql",
"rls-never-enabled"
]
}An allow entry is either a rule id (mute it everywhere) or ruleId:path-fragment (mute it in matching files only). Prefer the narrow form — muting a whole rule is how these things rot.
What it is not
- Not a linter. It only looks for changes that cost money or leak data.
- Not an observability dashboard. Nothing to sign up for, no data leaves your machine.
- Not a guarantee. A clean run means these specific checks didn't fire. It does not mean your app is secure.
It uses pattern matching, not full program analysis. It will miss creative ways of breaking the same things, and it can be wrong — if it flags something that's genuinely fine, add an allow entry and move on.
Contributing a rule
Every rule lives in src/rules.js and is about twenty lines: an id, a severity, a plain-English explanation, and a run(files) that returns findings. If an agent broke something in your app in a way ProdGuard didn't catch, that's a rule worth adding — open an issue with the diff.
node test/run.jsThe suite scans two fixture repos: test/fixtures/bad must trip every rule, test/fixtures/good must trip none.
Use it from inside Claude Code
The agent that turns these controls off can also be the one that checks. skill/SKILL.md in this
repo is a Claude Code skill: drop it in and Claude runs the scan itself, reads the findings, and
proposes the fix before you ship.
mkdir -p .claude/skills/prodguard
curl -o .claude/skills/prodguard/SKILL.md \
https://raw.githubusercontent.com/Felix0731/prodguard/main/skill/SKILL.mdIt tells Claude what the tool does not cover as clearly as what it does, so a clean run never gets reported to you as "your app is secure".
License
MIT
