Your AI Security Scanner Is a Smoke Detector With No Fire Department
I've been running an automated security pipeline on my AI agent infrastructure for a few weeks. It runs daily scans, quarterly red team exercises, and a sentinel that checks database invariants every hour. It found real vulnerabilities. Critical ones. Every single day.
It also never fixed any of them.
The same exec_sql SECURITY DEFINER function — callable by anonymous users, bypassing every RLS policy in the database — was flagged as CRITICAL six times across two days. Each time, the system logged a task, classified it as "proposed — awaiting approval," and sent me a Telegram message. Then it did nothing. The next scan found the same thing. Sent the same message. Waited again.
I had built a smoke detector that screams every morning and has no fire department.
The Detection Trap
Here's the pattern I see everywhere in security automation:
- Build a scanner that finds vulnerabilities
- Log findings to a database
- Send alerts
- Call it "automated security"
That's not automation. That's monitoring with extra steps. The human is still the remediation layer. And the human is busy building features, not triaging security queues.
My pipeline had three classification lanes:
- GREEN — auto-fix and verify (blocklist sync). This worked.
- AMBER — propose a fix, notify the operator, wait. This was a polite way of doing nothing.
- RED — investigate and report. This was an assertive way of doing nothing.
GREEN was the only lane that actually healed anything, and it covered exactly one finding type: blocklist drift between two files. Everything else — SECDEF grants, public role policies, RLS gaps — went to AMBER or RED and rotted.
The Insight: Separate Detection From Remediation
The fix seems obvious in retrospect: the same system that detects the problem should fix the problem. But there's a reason most security automation stops at detection.
Automated remediation is terrifying.
If your scanner finds a rogue database function and automatically revokes its permissions, what if that function was actually critical? What if the fix breaks production? What if the verification is wrong and it marks a live vulnerability as "healed"?
This fear is valid. But fear of automated remediation is not an argument for no remediation. It's an argument for careful automated remediation.
The Architecture: A Remediation Registry
Here's what I built:
1. A registry of approved fix patterns.
A database table (conn_security_remediations) that stores:
- A finding pattern — what this remediation matches against
- Remediation SQL — the fix to execute
- Verification SQL — a query that returns rows if the problem exists, zero rows if it's clean
- Task-close patterns — which open tasks to mark completed when healed
- An auto_heal flag — has a human approved this pattern?
The key design decision: humans approve fix patterns, machines execute them. You don't approve individual fixes. You approve a class of fix. "Any SECDEF function with PUBLIC EXECUTE that isn't on the allowlist should have its public access revoked." That's the pattern. The machine applies it to every instance, forever.
2. An auto-remediate function.
A database RPC (conn_auto_remediate()) that:
- Iterates through enabled remediation patterns
- Runs the verification query to check if the problem exists
- If yes: executes the remediation SQL
- Re-runs verification to confirm the fix worked
- Closes matching tasks in the queue
- Returns a structured report
It's idempotent. You can call it a thousand times. If there's nothing to fix, it returns empty. If there's something to fix, it fixes it and proves it worked.
3. A unified pipeline.
Instead of scanner → triage → alert → wait → next scanner → same alert, the flow is now:
scan → find → log task → auto-remediate → verify → report
One function call. One Telegram message. The message tells you: here's what was found, here's the problem, here's how it was fixed, here's the verification. If something couldn't be auto-fixed (because it requires code changes, not database changes), it tells you that too, with enough context to act.
Four Patterns That Self-Heal
Today the registry has four approved patterns:
SECDEF PUBLIC grants. Any SECURITY DEFINER function with a PUBLIC EXECUTE grant that isn't on an explicit allowlist gets revoked. The allowlist is maintained in the remediation SQL itself. New functions that shouldn't be public get caught and fixed automatically.
Public role policies. Any RLS policy granted to the public role (OID 0) gets dropped. In Supabase, the service role bypasses RLS entirely, so public-role policies are always redundant — they either do nothing (if they have a restrictive qual) or they're dangerous (if they don't).
Anon write policies. Any INSERT, UPDATE, or DELETE policy granted to the anon role gets dropped. The anonymous role should never have write access to anything.
RLS coverage gaps. Any public table without RLS enabled gets it enabled automatically. The database already has an event trigger (ensure_rls) that does this for new tables, but this catches any that slip through.
The First Run
The first time conn_auto_remediate() ran, it:
- Revoked PUBLIC EXECUTE from four SECDEF functions (
exec_sql,auto_approve_safe_actions,check_action_item_duplicate,paf_creator_can_access) - Dropped a wide-open policy on
agent_journal_entries(qual was literallytrue— full CRUD for anyone) - Dropped two redundant
service_role_onlypolicies - Closed seven stale tasks in the security queue
The sentinel went from 2 critical failures to 0. Eight checks, eight passes.
The exec_sql function — the one that let anyone with the anon key read every table in the database — had been flagged as critical for two days. Fixed in under a second.
What Stays Manual
Not everything can be auto-healed. The remaining 39 open security tasks require code changes: Discord bot auth checks, PostgREST filter injection sanitization, rate limiting on auth endpoints, ops dashboard JWT validation. These involve editing application code across multiple files and frameworks. They surface in the report with enough context to act, but they need a human (or an AI with code access) to write the fix.
The line is clear: database-level fixes auto-heal, application-level fixes surface for action. If the sentinel can express the problem and solution in SQL, it fixes itself. If it requires TypeScript, it asks for help.
The Lesson
If your security automation ends at detection, you haven't automated security. You've automated alert fatigue.
The remediation registry pattern works because it separates the hard problem (deciding what's safe to fix) from the easy problem (executing the fix). Humans make the hard decision once. Machines execute the easy part forever.
The smoke detector now has a fire department. It's a small department — four firefighters for four types of fire. But it grows every time a new vulnerability class gets an approved fix pattern. And the fires it handles never come back.
Build the scanner. Then build the thing that makes the scanner unnecessary.