ctrlsec
v1.4.0
Published
CTRL_ — security audit MCP server for AI-generated Next.js, Vite and Express apps. Finds exposed keys, unsafe queries and open row-level security, then walks your coding agent through fixing them. Extra depth on Supabase.
Maintainers
Readme
CTRL_
Finds the security mistakes AI coding agents leave behind — before you ship.
An MCP server for JavaScript and TypeScript projects — Next.js, Vite and Express. Your coding agent runs the audit, then CTRL_ walks it through fixing each finding, one at a time, re-checking after every edit. 31 of the 35 checks need no backend at all. Four are Supabase-specific — row-level security, policies that let every row through, public storage buckets, and a service-role key reaching the browser — and a Supabase project can opt into a live check against the database itself.
Quick start
Write your editor's MCP config automatically — detects Cursor, VS Code, Claude Desktop and Windsurf:
npx ctrlsec initOr add it by hand:
{
"mcpServers": {
"ctrlsec": {
"command": "npx",
"args": ["-y", "[email protected]"]
}
}
}Restart your editor, then say "run a CTRL_ audit." The version is pinned deliberately: a bad release should not break every user at once.
The same binary is a CLI, for demos and CI:
npx ctrlsec audit # current directory
npx ctrlsec audit ./apps/web # or a subdirectoryIt exits non-zero when anything is severe enough to block a launch, so it can gate a pipeline.
What it catches
35 checks, grouped by how they are fixed:
| Category | Checks | Examples |
| --- | --- | --- |
| Secrets & Config | 10 | live API key in the repo, committed .env, private key file, secret in a CI config |
| Injection | 6 | SQL built by interpolation or concatenation, eval, shell commands, unescaped HTML |
| Auth & Access Control | 5 | unverified JWTs, admin gates enforced only in the browser, tokens in localStorage |
| Framework & Client | 5 | whole process.env sent to the client, server-only env vars in browser code |
| Database & BaaS | 3 | row-level security off, a policy that lets every row through, a public storage bucket |
| Transport & Headers | 3 | wildcard CORS with credentials, unverified webhooks, plain http:// |
| AI & LLM | 2 | provider key exposed to the browser, a completion call with no token cap |
| Dependencies | 1 | install scripts that run unreviewed code |
Every response says how many checks ran and how many were skipped, so "nothing found" can never be confused with "nothing was looked at".
The live Supabase check
Most scanners read your migration files and infer what should be true. CTRL_ can go further: with your permission it connects to your own Supabase project with the public anon key — the same key any visitor's browser holds — and asks the database directly whether a table answers. A table that returns a row to an anonymous request is not a guess about a missing policy. It is proof.
It is opt-in and stays inside hard limits, all confined to one auditable module:
- Granted only by running
npx ctrlsec audit --probeyourself, and remembered per Supabase project inctrl.config.json. The MCP tools can read that consent, never grant it. - The public anon key only — never
service_role, even when it is sitting in.env. - Read-only.
select ... limit 1and bucket listing. No writes, no schema changes. - Row contents are discarded the moment they arrive. Only "did anything come back" is kept.
- Bounded: 25 tables per scan, a 4-second timeout, and any failure falls back to reading migrations rather than reporting a pass.
Without consent, CTRL_ reads supabase/migrations/*.sql instead. Anything it can resolve
neither way is reported as could not be verified and named explicitly — never quietly
passed.
The fix loop
ctrl_audit finds and groups the issues. ctrl_fix works through them:
| Action | What it does |
| --- | --- |
| next | the most severe unresolved issue, with a pre-authored fix |
| verify | re-scans, confirms the issue is gone, and hands over the next one |
| explain | the same finding in plain language, for a human |
| ignore | suppresses it in .ctrlignore — a scope and a written reason are required |
Two things keep the loop honest. Regression detection: if the edit introduced a new finding, verification aborts and says so instead of marking the original resolved. And a two-attempt cap: after two failed attempts an issue is marked Requires Human Review and the loop moves on, rather than letting an agent grind at it.
Verification confirms the security rule is resolved. It cannot confirm your application still works — run your own tests for that.
Your code never leaves your machine
Analysis is entirely local. No source, no file paths, no findings and no secrets are uploaded anywhere — the rules ship inside this package and run in the same process your editor spawns. Nothing is downloaded at scan time either: rules delivered over a network would make a security scanner an attractive supply-chain target.
The one network request CTRL_ can make is the Supabase probe described above — to your own project, only after you have granted consent on the command line.
CTRL_ reports which checks passed and which did not. It will never tell you an application is secure; no tool can know that.
Supported stacks
| Stack | Detected as | Notes |
| --- | --- | --- |
| Next.js App Router | next-app | server routes; client boundary from "use client" |
| Next.js Pages Router | next-pages | detected from a pages/ directory |
| Vite + React | vite | the shape Lovable and Bolt export — no server, VITE_ env prefix |
| Express | express | server routes, no client bundle |
Supabase, Firebase and Clerk are detected from your dependencies. Four checks are gated on Supabase — the three Database & BaaS rules above, plus the service-role key check, which is about a Supabase key specifically. On a Firebase or Clerk project those four are reported as skipped rather than counted as passed. The same applies to framework-specific checks — on a Vite project the four Next.js config checks are reported as skipped, by name.
Example output
What your agent receives, and relays to you:
**CTRL_ audit complete**
**3 issues found** · 3 fix now
Checked 31 of 35 rules. 4 skipped — no Next.js detected.
**Fix now**
- **S03** — Anyone who can clone this repo has every secret in your `.env` file · `.env`
- **S01** — Anyone who can read this repo can use your live API key · `src/lib/billing.ts`
- **S02** — Your admin database key is shipped to every visitor's browser · `src/lib/supabase.ts`
**1 table could not be verified** — profiles. Run `npx ctrlsec audit --probe` to check it live.Findings lead with what an attacker can do, not with the name of the mechanism.
Security
Found a vulnerability in CTRL_ itself? Email [email protected]. Please do not report it publicly.
More at heyctrl.com.
