| name | gatefrisk |
| description | Frisk your HTTP routes for UNGATED MUTATING endpoints before you ship. Triggers automatically when you are about to report a coding task done and your diff added or changed a route handler (or on /gatefrisk). An endpoint that writes, deletes, pays, trades, or triggers a job with no auth check is an open switch on the public internet — a crawler's GET can flip it. gatefrisk enumerates the changed routes, classifies each one mutating vs read-only, checks the mutating ones for a gate, and refuses to say "done" while a mutating route ships open. |
gatefrisk — no ungated writes ship
Agents (and humans) add "just for now" endpoints: /run, /retry, /close,
/admin/rebuild. They work in the demo, they ship, and then anything on the
internet can call them — a search crawler following a GET is enough. This
happened for real: an anonymous GET to an open /run route executed a trading
step on three live portfolios at 7am, hours before the scheduled run. A linter
can't catch this — it doesn't know which routes mutate and which auth pattern
this codebase uses. The agent writing the diff knows both.
When to run
- Automatically, right before you report a coding task complete, if the diff
added or changed anything route-shaped: an HTTP handler, a
fetch() worker
export, a routes//api/ file, a webhook, an RPC method.
- On demand when the user types
/gatefrisk (audits the current diff, or a
named file/dir).
What it looks at
Only the routes touched by the diff — new handlers and handlers whose body
changed. Don't re-audit the whole app; stay scoped and fast. For each touched
route, read enough of the surrounding file to see the framework's auth idiom
(middleware, guard call, header check) — the check is contextual, not regex.
The process
- Enumerate touched routes. From the diff, list every reachable handler:
Express/Hono/Fastify (
app.get/post/...), Next.js (app/api/**/route.ts,
pages/api), Cloudflare Workers (fetch() + url.pathname branches),
Django/Flask/FastAPI decorators, webhooks, cron-trigger HTTP mirrors.
- Classify each route: MUTATING or read-only. Mutating = it writes state or
causes an effect: DB insert/update/delete, order/trade/payment calls, file
writes, cache purges, job/queue triggers, emails/posts, config changes,
closePosition(...), env.KV.put(...). The HTTP verb is a hint, not the
truth — a GET that runs a job is mutating (that's the classic hole).
- Check every MUTATING route for a gate. A gate = any caller check before
the effect: auth middleware on the router, a session/JWT/API-key/bearer
verification, HMAC signature check (webhooks), an allow-list, mTLS. Find the
codebase's existing idiom first and judge against it. "The URL is secret" and
"it's just a demo/staging" are not gates.
- Check the gate is real. A check that only proves the SERVER has
credentials (e.g. a "creds exist" guard), a comparison against a hardcoded
token in the repo, or a gate placed AFTER the effect does not count.
- Downgrade honestly. Read-only routes that leak nothing sensitive are
fine open — say so and move on. Idempotent-but-effectful (e.g. re-runs a
sync) is still mutating.
What to do with findings
- Do the safe, mechanical fix yourself: apply the codebase's existing auth
idiom to the open route (same middleware, same bearer-secret pattern the
neighbouring routes use), or return 405 for verbs that shouldn't exist. State
the fix in the report.
- Escalate when the gate is a design decision: which auth scheme a fresh
codebase should adopt, whether an endpoint should exist at all, webhook
signature secrets that need provisioning. Describe the risk, give 1–2
options, ask approve / fix / skip.
- Never invent a finding. A read-only public endpoint is not a
vulnerability. False alarms train people to ignore you.
The hard rule
Do not report the task as done while a mutating route in the diff has no
gate (neither fixed nor explicitly accepted by the user). If everything is
gated or read-only, say so in one line and finish.
Output format
gatefrisk — N touched route(s)
✗ HIGH POST /okx/close closes live positions, no auth → gated with Bearer CRON_SECRET (same idiom as /admin) [fixed]
✗ HIGH GET /run triggers a trading step, anonymous GET can fire it — needs your auth decision [escalated]
✓ GET /state, /health read-only, fine open
1 route needs your call before this is done.
Be terse. Real signal only. The severity is HIGH whenever money, user data, or
jobs can be moved by an anonymous caller; MEDIUM when authenticated-but-wrong
users could (missing ownership check); LOW for hardening.