Skip to content

Instantly share code, notes, and snippets.

@abhiaiyer91
Created May 28, 2026 20:40
Show Gist options
  • Select an option

  • Save abhiaiyer91/88eae6269b6c952e48361766397d65c1 to your computer and use it in GitHub Desktop.

Select an option

Save abhiaiyer91/88eae6269b6c952e48361766397d65c1 to your computer and use it in GitHub Desktop.
export const meta = {
name: 'audit-route-auth',
description: 'Audit every API route handler for missing/incorrect auth checks',
phases: [
{ title: 'Audit', detail: 'one agent per handler file flags auth gaps' },
{ title: 'Verify', detail: 'adversarially confirm each flagged finding' },
{ title: 'Synthesize', detail: 'rank confirmed findings into a report' },
],
}
const HANDLER_DIR =
'/Users/abhiramaiyer/.superset/worktrees/703d6cea-773f-45c3-9874-25bf37f95407/well-indigo/packages/server/src/serve
r/handlers'
const FILES =
'a2a.ts,agent-builder.ts,agent-versions.ts,agents.ts,auth.ts,background-tasks.ts,builder-registry.ts,channels.ts,co
nversations.ts,datasets.ts,editor-builder.ts,logs.ts,mcp-client-versions.ts,mcp.ts,memory.ts,observability-new-endpoi
nts.ts,observability.ts,processor-providers.ts,processors.ts,prompt-block-versions.ts,responses.ts,schedules.ts,score
r-versions.ts,scores.ts,stored-agent-favorites.ts,stored-agents.ts,stored-mcp-clients.ts,stored-prompt-blocks.ts,stor
ed-scorers.ts,stored-skill-favorites.ts,stored-skills.ts,stored-workspaces.ts,system.ts,tool-providers.ts,tools.ts,ve
ctor.ts,workflows.ts,workspace.ts'.split(
',',
)
const AUTH_MODEL = `
AUTH MODEL for @mastra/server routes (from packages/server/CLAUDE.md):
- Routes are defined with two builders from route-builder.ts:
* createRoute({...}) — PROTECTED BY DEFAULT. The framework runs coreAuthMiddleware on it.
When RBAC is configured, a permission is auto-derived from path+method:
GET -> resource:read, PUT/PATCH/POST -> resource:write (POST -> resource:execute when the
path contains an operation segment like /generate /stream /execute /start), DELETE -> resource:delete.
Resource = first path segment. This is generally SAFE.
* createPublicRoute({...}) — sets requiresAuth:false and BYPASSES authentication & authorization ENTIRELY.
- An explicit createRoute({ requiresAuth: false, ... }) is the same as createPublicRoute: it bypasses auth.
- A route may override the derived permission with requiresPermission: 'resource:action' (or an array = ANY-of),
or scope to a resource with fga: { resourceType, resourceIdParam, permission }.
WHAT COUNTS AS A FINDING (a missing/incorrect auth check):
HIGH:
- createPublicRoute (or requiresAuth:false) on a route that reads/writes data, executes an agent/workflow/tool,
touches memory/threads/storage/vectors, performs a mutation (POST/PUT/PATCH/DELETE), or exposes config/secrets.
Public is ONLY legitimate for: login/logout/token issuance, OAuth/SSO callbacks, health/ping, OpenAPI spec,
and genuinely public discovery endpoints. Anything else public = HIGH.
- A route defined WITHOUT createRoute/createPublicRoute (a raw object literal pushed into the routes array,
or a handler registered outside the builder) — it skips the auth pipeline.
MEDIUM:
- requiresPermission that is clearly weaker than the operation (e.g. a DELETE/admin/destructive op whose
explicit requiresPermission is ':read', or downgrades a write to read), or a derived permission that is
wrong because the resource segment is generic/misleading and would grant cross-resource access.
- A mutating/sensitive route whose requiresPermission array is so broad it defeats the intent.
LOW / NOTE:
- Public route that looks intentional but is worth a second look; or a sensitive route relying solely on
derived permission with no resource (fga) scoping where cross-tenant leakage is plausible.
DO NOT FLAG: ordinary createRoute() routes that rely on the default protection + derived permission — that is the
intended design. Only flag deviations from it. Auth.ts public routes for login/oauth/callbacks are EXPECTED — only
flag them if a clearly sensitive non-auth operation is among them.`
const FINDING_SCHEMA = {
type: 'object',
additionalProperties: false,
required: ['file', 'totalRoutesScanned', 'findings'],
properties: {
file: { type: 'string' },
totalRoutesScanned: { type: 'number' },
publicRouteCount: { type: 'number' },
findings: {
type: 'array',
items: {
type: 'object',
additionalProperties: false,
required: ['method', 'path', 'builder', 'severity', 'issue', 'evidence', 'reasoning'],
properties: {
method: { type: 'string' },
path: { type: 'string' },
builder: { type: 'string', description: 'createRoute | createPublicRoute | requiresAuth:false | raw |
other' },
severity: { type: 'string', enum: ['HIGH', 'MEDIUM', 'LOW'] },
issue: { type: 'string', description: 'one-line description of the auth gap' },
evidence: { type: 'string', description: 'file:line reference and the relevant code snippet' },
reasoning: { type: 'string', description: 'why this is or might be exploitable / sensitive' },
},
},
},
},
}
const VERDICT_SCHEMA = {
type: 'object',
additionalProperties: false,
required: ['confirmed', 'severity', 'rationale'],
properties: {
confirmed: {
type: 'boolean',
description: 'true if this is a genuine missing/incorrect auth check, false if justified/benign',
},
severity: { type: 'string', enum: ['HIGH', 'MEDIUM', 'LOW'] },
rationale: { type: 'string' },
correctedEvidence: { type: 'string', description: 'corrected file:line if the original was wrong, else empty' },
},
}
phase('Audit')
// Stage 1: audit each handler file. Stage 2: verify each finding from that file (pipeline, no barrier).
const perFile = await pipeline(
FILES,
f =>
agent(
`${AUTH_MODEL}
You are auditing ONE route handler file for missing or incorrect authentication/authorization checks.
File to audit: ${HANDLER_DIR}/${f}
Steps:
1. Read the ENTIRE file (it may be large — read it fully, including helper definitions and the exported routes
array).
2. Enumerate every route it defines. For each, note: HTTP method, path, which builder (createRoute /
createPublicRoute / raw object), any requiresAuth override, any requiresPermission, any fga config.
3. If a route's auth setup deviates from the safe default (see WHAT COUNTS AS A FINDING), record a finding with a
precise file:line evidence reference and a short code snippet.
4. If the file is a thin re-export, follow imports only within this same handlers directory if needed, but stay
focused on ${f}.
Report totalRoutesScanned (count of all routes in the file), publicRouteCount (how many are createPublicRoute or
requiresAuth:false), and the findings array (ONLY problematic routes — empty array if all routes follow the safe
protected-by-default pattern). Be precise; do not invent findings for ordinary createRoute routes.`,
{ label: `audit:${f}`, phase: 'Audit', schema: FINDING_SCHEMA },
),
audit => {
if (!audit || !audit.findings || audit.findings.length === 0) return audit
return parallel(
audit.findings.map(fd => () =>
agent(
`${AUTH_MODEL}
You are an adversarial verifier. Another agent flagged a possible missing/incorrect auth check. Your job is to REFUTE
it unless the evidence is solid. Default to confirmed=false if the route actually follows the safe
protected-by-default createRoute pattern or the public exposure is legitimately for auth/health/spec.
Open the file and inspect the exact route at the cited location.
File: ${HANDLER_DIR}/${audit.file}
Flagged route: ${fd.method} ${fd.path}
Claimed builder/issue: ${fd.builder}${fd.issue}
Cited evidence: ${fd.evidence}
Claimed reasoning: ${fd.reasoning}
Verify against the real code: Is it truly defined with createPublicRoute or requiresAuth:false (i.e. auth-bypassed)?
Does the endpoint actually do something sensitive (read/write data, execute, mutate, expose secrets)? Or is it a
normal protected createRoute, or a legitimately-public auth/health/spec endpoint? Set confirmed accordingly, assign
the correct severity, and give a tight rationale. Fix the evidence line reference if it was wrong.`,
{ label: `verify:${audit.file}:${fd.method} ${fd.path}`.slice(0, 60), phase: 'Verify', schema:
VERDICT_SCHEMA },
).then(v => ({ ...fd, file: audit.file, verdict: v })),
),
)
},
)
const audits = perFile.filter(Boolean)
const totalRoutes = audits.reduce((n, a) => n + (a.totalRoutesScanned || 0), 0)
const totalPublic = audits.reduce((n, a) => n + (a.publicRouteCount || 0), 0)
const filesScanned = audits.length
// findings come back as either the audit object (no findings) or an array of verified findings
const verifiedFindings = perFile
.filter(x => Array.isArray(x))
.flat()
.filter(Boolean)
const confirmed = verifiedFindings.filter(f => f.verdict && f.verdict.confirmed)
const rejected = verifiedFindings.filter(f => f.verdict && !f.verdict.confirmed)
log(
`Scanned ${filesScanned} handler files, ${totalRoutes} routes (${totalPublic} public). Confirmed
${confirmed.length} findings, rejected ${rejected.length}.`,
)
phase('Synthesize')
const report = await agent(
`You are writing the final security audit report on missing/incorrect auth checks across the Mastra server route
handlers.
Coverage: ${filesScanned} handler files audited, ${totalRoutes} total routes enumerated, ${totalPublic} routes are
public (createPublicRoute / requiresAuth:false).
CONFIRMED findings (verified by an adversarial second pass), as JSON:
${JSON.stringify(
confirmed.map(f => ({
file: f.file,
method: f.method,
path: f.path,
builder: f.builder,
severity: f.verdict.severity,
issue: f.issue,
evidence: f.verdict.correctedEvidence || f.evidence,
rationale: f.verdict.rationale,
})),
null,
2,
)}
REJECTED candidate findings (flagged then refuted), as JSON (include a brief "why dismissed" understanding, do not
re-litigate):
${JSON.stringify(
rejected.map(f => ({ file: f.file, path: f.path, issue: f.issue, whyDismissed: f.verdict.rationale })),
null,
2,
)}
Write a concise markdown report with:
1. A one-paragraph summary (coverage + headline result: are there genuine auth gaps or is the surface clean?).
2. A "Confirmed findings" section grouped by severity (HIGH, then MEDIUM, then LOW). For each: file:line,
method+path, the issue, why it matters, and a concrete remediation (e.g. switch createPublicRoute->createRoute, add
requiresPermission, add fga scoping). If there are zero confirmed findings, say so plainly and explain what that
means (the protected-by-default pattern is held consistently).
3. A short "Reviewed and cleared" note summarizing what was checked and why the dismissed candidates are fine.
Keep it scannable. Return ONLY the markdown.`,
{ label: 'synthesize-report', phase: 'Synthesize' },
)
return { filesScanned, totalRoutes, totalPublic, confirmedCount: confirmed.length, rejectedCount: rejected.length,
report }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment