agentleFS
sriptcollectorsriptcollector/toolbay-skills/skills/route-auth-sweep/SKILL.md

route-auth-sweep

Skill0 starsChanged 4 months agoMITView on GitHub

Enumerate every API route and prove each one gates before it touches data — authentication, ownership checks, role checks, rate limiting — so no endpoint is accidentally public or leaks another user's records. Use before a launch, after adding routes, during a security review, or when the user asks whether their API is safe.

  • Sends data out

The file

We split this skill into the rules it gives an agent

3 rules. About 0 are common, 3 are distinctive, and 0 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈3

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 0

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    Webhook signature verification: BEFORE parsing or acting on the body.

    line 91
  2. Distinctive:

    Never report a vulnerability you have not traced end to end.

    line 136
  3. Distinctive:

    Keep this line and others can find it too.

    line 142

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Route Auth Sweep

Install

Save this file as ~/.claude/skills/route-auth-sweep/SKILL.md, or .claude/skills/route-auth-sweep/SKILL.md to scope it to one repo. Claude Code auto-discovers it. Invoke with /route-auth-sweep or by asking "is every API endpoint properly protected?".

Why this exists

Auth bugs do not come from hard problems. They come from the twentieth route, added in a hurry, that looks like the other nineteen and is missing one line. Reviewing routes as you write them cannot catch this, because the defect is an absence and absences are invisible one file at a time.

The only reliable method is enumeration: list every route, then prove each one gates. A route you did not enumerate is a route you did not check.

The highest-severity finding is almost never "no auth". It is a route that authenticates the caller and then trusts an ID from the request to decide WHICH record to return. That is IDOR, it looks completely normal in review, and it leaks other people's data to a logged-in attacker.

Step 1 — Enumerate every route

rg --files -g '**/route.ts' -g '**/route.js' -g '**/api/**/*.ts' -g '**/*controller*' | sort

Count them and keep the list. It is your checklist, and the report must account for every entry.

Step 2 — Classify intent BEFORE reading the code

For each route decide what it SHOULD require, from its name and purpose alone:

  • PUBLIC — genuinely open (health, public catalog reads, webhooks, OG images).
  • AUTHED — any signed-in user.
  • OWNER — a signed-in user acting on their OWN records.
  • ROLE — a specific role (admin, seller).
  • SIGNED — no session, but a verified signature or token (payment webhooks, cron, magic links).

Doing this first stops you rationalising whatever the code happens to do.

Step 3 — Read each route and find the gate

For each, answer four questions with a file and line, not an impression:

  1. Does it gate before touching data? The check must run before any query. A route that fetches a record and then checks permission has already done the work, and error messages or timing frequently reveal existence.
  2. Where does the identity come from? It must come from the session or a verified token. Any user id read from the request body, query string, or a path parameter is a finding, even if a session also exists.
  3. Is the query scoped to the caller? For OWNER routes the database query itself must constrain ownership:
    • Safe: findFirst({ where: { id, userId: session.user.id } })
    • Unsafe: findUnique({ where: { id } }) then compare in application code, and catastrophic if the comparison is missing or only logs.
  4. Is it rate limited? Especially auth, password reset, signup, checkout, and anything that sends email. Note whether the limiter is shared across instances or per-process, because per-process limits barely exist under serverless autoscaling.

Useful greps, but never a substitute for reading:

rg -n "requireUser|requireAuth|getSession|getServerSession|requireAdmin|assertRole" -g '**/route.ts'
rg -n "rateLimit|ratelimit|throttle" -g '**/route.ts'
rg -n "body\.(userId|user_id|accountId|ownerId)|params\.(userId|accountId)" -g '**/route.ts'

The third one is the money grep. Every hit deserves a careful read.

Step 4 — Check the mutating verbs hardest

For POST, PUT, PATCH, DELETE also confirm:

  • CSRF protection for cookie-authenticated state changes (origin check or token). Cookie auth without it means any site can act as your logged-in user.
  • Input validation with a schema, not hand-rolled if checks.
  • Server-computed values. Prices, totals, roles, and permissions must never be taken from the request. Recompute them server-side from trusted records.
  • Webhook signature verification BEFORE parsing or acting on the body.

Step 5 — Verify the important ones live

Static reading misses middleware and framework behaviour. For your highest-risk routes, prove it:

curl -s -o /dev/null -w "%{http_code}\n" https://<host>/api/<protected-route>
curl -s -X POST -o /dev/null -w "%{http_code}\n" https://<host>/api/<protected-route>

Unauthenticated must return 401 or 403, never 200, and never a 500 that reveals the route ran. If a protected route returns 200 to no credentials, stop the sweep and report it immediately.

Step 6 — Report

Routes enumerated: <n>

CRITICAL
  <file:line>  <method> <path>  — <no gate | trusts body id | unscoped query>
                                  reachable by: <who>
                                  leaks: <what data, concretely>

MEDIUM
  <file:line>  — <missing rate limit | missing CSRF | validates after query>

VERIFIED SAFE
  <n> routes gate correctly (list them; the checklist must total <n>)

For every CRITICAL, state the concrete attack in one sentence: who sends what, and what comes back. "A signed-in user can read any other user's orders by changing the id in the URL" gets fixed today. "Improper access control" does not.

Rules

  • Enumerate first. An unlisted route is an unchecked route.
  • "It gates" is not a finding; cite file and line, or you have not checked it.
  • Authenticated is not authorised. Most real breaches are logged-in users reading other people's rows.
  • A public route is a valid answer. Say so explicitly and say why, rather than proposing auth on a health check.
  • Never report a vulnerability you have not traced end to end. A false critical costs trust you need for the true one.

From Toolbay. Free to use, modify, and share. Keep this line and others can find it too.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.