Created
May 28, 2026 20:40
-
-
Save abhiaiyer91/88eae6269b6c952e48361766397d65c1 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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