server-action-audit 0.0.0-stage → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.fr.md +55 -0
- package/README.md +182 -2
- package/dist/chunk-RCEEE6TI.js +677 -0
- package/dist/chunk-RCEEE6TI.js.map +1 -0
- package/dist/cli.js +1106 -0
- package/dist/cli.js.map +1 -0
- package/dist/eslint.cjs +796 -0
- package/dist/eslint.cjs.map +1 -0
- package/dist/eslint.d.cts +15 -0
- package/dist/eslint.d.ts +15 -0
- package/dist/eslint.js +112 -0
- package/dist/eslint.js.map +1 -0
- package/dist/index.cjs +1045 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +153 -0
- package/dist/index.d.ts +153 -0
- package/dist/index.js +340 -0
- package/dist/index.js.map +1 -0
- package/package.json +89 -4
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/ast.ts","../src/options.ts","../src/analyze.ts","../src/scan.ts","../src/resolve.ts","../src/format.ts","../src/github.ts","../src/version.ts"],"sourcesContent":["export { analyzeModule, type ServerAction, type ActionStatus, type AnalyzeInput, type ImportResolver, type ModuleSource } from './analyze.js'\nexport { resolveOptions, type AuditOptions, type ResolvedOptions } from './options.js'\nexport { scanProject, loadConfig, type ProjectReport, type FileReport } from './scan.js'\nexport { createNodeResolver, parseModule, resolveImportPath } from './resolve.js'\nexport { formatPretty, formatJson, formatGithub, formatMarkdown, formatSarif } from './format.js'\nexport { pullRequestFromEnv, upsertComment, COMMENT_MARKER, type PullRequest } from './github.js'\nexport { VERSION } from './version.js'\n","/** Minimal ESTree helpers, shared by the CLI (typescript-estree) and ESLint (espree or typescript-eslint). */\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport type Node = { type: string; range?: [number, number]; loc?: { start: { line: number; column: number } }; [key: string]: any }\n\nexport interface Comment {\n type: string\n value: string\n range?: [number, number]\n}\n\nconst SKIP = new Set(['parent', 'loc', 'range', 'tokens', 'comments', 'typeAnnotation', 'returnType', 'typeParameters'])\n\n/** Calls `visit` on every node under `root`. Return `false` from `visit` to skip a node's children. */\nexport function walk(root: Node | null | undefined, visit: (node: Node) => boolean | void): void {\n if (!root || typeof root.type !== 'string') return\n if (visit(root) === false) return\n for (const key of Object.keys(root)) {\n if (SKIP.has(key)) continue\n const value = root[key]\n if (Array.isArray(value)) {\n for (const child of value) if (child && typeof child.type === 'string') walk(child, visit)\n } else if (value && typeof value.type === 'string') {\n walk(value, visit)\n }\n }\n}\n\nexport function isFunction(node: Node | null | undefined): boolean {\n return !!node && (node.type === 'FunctionDeclaration' || node.type === 'FunctionExpression' || node.type === 'ArrowFunctionExpression')\n}\n\n/** Leading string-literal statements of a program or function body. */\nexport function directives(body: Node[] | undefined): string[] {\n const found: string[] = []\n for (const statement of body ?? []) {\n if (statement.type !== 'ExpressionStatement') break\n if (typeof statement.directive === 'string') found.push(statement.directive)\n else if (statement.expression?.type === 'Literal' && typeof statement.expression.value === 'string') found.push(statement.expression.value)\n else break\n }\n return found\n}\n\nexport function functionDirectives(fn: Node): string[] {\n return fn.body?.type === 'BlockStatement' ? directives(fn.body.body) : []\n}\n\nfunction unwrap(node: Node): Node {\n let current = node\n while (current.type === 'ChainExpression' || current.type === 'TSNonNullExpression' || current.type === 'TSAsExpression' || current.type === 'ParenthesizedExpression') {\n current = current.expression\n }\n return current\n}\n\n/**\n * Dotted path of a call's callee: `auth` → \"auth\", `supabase.auth.getUser` → \"supabase.auth.getUser\",\n * `(await createClient()).auth.getUser` → \"*.auth.getUser\". Computed members become \"*\".\n */\nexport function calleePath(callee: Node): string {\n const node = unwrap(callee)\n if (node.type === 'Identifier') return node.name\n if (node.type === 'MemberExpression') {\n const property = !node.computed && node.property?.type === 'Identifier' ? node.property.name : '*'\n return `${calleePath(node.object)}.${property}`\n }\n if (node.type === 'Super') return 'super'\n if (node.type === 'ThisExpression') return 'this'\n return '*'\n}\n\nexport function lastSegment(path: string): string {\n const index = path.lastIndexOf('.')\n return index === -1 ? path : path.slice(index + 1)\n}\n\n/** Every call expression inside `root`, nested functions included. */\nexport function callsIn(root: Node | null | undefined): Node[] {\n const calls: Node[] = []\n walk(root, (node) => {\n if (node.type === 'CallExpression') calls.push(node)\n })\n return calls\n}\n\n/**\n * Calls that run on the main path of `fn`: top-level statements, the condition of an `if`, the\n * body of a `try`. Calls in an `if` branch, a loop body or a nested callback may never run, so\n * they do not count as a guard. Inside an expression everything counts, so helper idioms like\n * `return token ? verify(token) : null` still do.\n *\n * With `followReturned`, a function returned at the top level is included too: that is the\n * function a wrapper like `withTeam(action)` actually runs on each request.\n */\nexport function unconditionalCalls(fn: Node, followReturned = false): Node[] {\n const calls: Node[] = []\n const expression = (node: Node | null | undefined): void => {\n if (!node || typeof node.type !== 'string' || isFunction(node)) return\n if (node.type === 'CallExpression') calls.push(node)\n for (const key of Object.keys(node)) {\n if (SKIP.has(key)) continue\n const value = node[key]\n if (Array.isArray(value)) value.forEach((child) => child && expression(child))\n else if (value && typeof value.type === 'string') expression(value)\n }\n }\n const returned = (node: Node | null | undefined): void => {\n if (followReturned && node && isFunction(node)) calls.push(...unconditionalCalls(node))\n else expression(node)\n }\n const statement = (node: Node | null | undefined): void => {\n if (!node) return\n switch (node.type) {\n case 'BlockStatement':\n for (const child of node.body ?? []) statement(child)\n break\n case 'ExpressionStatement':\n expression(node.expression)\n break\n case 'VariableDeclaration':\n for (const declarator of node.declarations ?? []) expression(declarator.init)\n break\n case 'ReturnStatement':\n returned(node.argument)\n break\n case 'ThrowStatement':\n expression(node.argument)\n break\n case 'IfStatement':\n expression(node.test)\n break\n case 'TryStatement':\n statement(node.block)\n break\n case 'SwitchStatement':\n expression(node.discriminant)\n break\n case 'DoWhileStatement':\n statement(node.body)\n break\n case 'LabeledStatement':\n statement(node.body)\n break\n }\n }\n if (fn.body?.type === 'BlockStatement') statement(fn.body)\n else returned(fn.body)\n return calls\n}\n\n/**\n * For `a.b(x).c(y).d(fn)`: the root identifier (\"a\"), the method names in call order ([\"b\", \"c\", \"d\"])\n * and the function arguments passed anywhere in the chain.\n */\nexport function callChain(call: Node): { root: string | null; methods: string[]; functions: Node[]; identifiers: string[] } {\n const methods: string[] = []\n const functions: Node[] = []\n /** Identifiers passed as arguments: `.use(authMiddleware)`. */\n const identifiers: string[] = []\n let current: Node = unwrap(call)\n for (;;) {\n if (current.type === 'CallExpression') {\n for (const argument of current.arguments ?? []) {\n if (isFunction(argument)) functions.unshift(argument)\n else if (argument.type === 'Identifier') identifiers.unshift(argument.name)\n }\n const callee = unwrap(current.callee)\n if (callee.type === 'MemberExpression') {\n methods.unshift(!callee.computed && callee.property?.type === 'Identifier' ? callee.property.name : '*')\n current = unwrap(callee.object)\n continue\n }\n if (callee.type === 'Identifier') return { root: callee.name, methods, functions, identifiers }\n return { root: null, methods, functions, identifiers }\n }\n if (current.type === 'MemberExpression') {\n methods.unshift(!current.computed && current.property?.type === 'Identifier' ? current.property.name : '*')\n current = unwrap(current.object)\n continue\n }\n if (current.type === 'AwaitExpression') {\n current = unwrap(current.argument)\n continue\n }\n return { root: current.type === 'Identifier' ? current.name : null, methods, functions, identifiers }\n }\n}\n\nexport function position(node: Node): { line: number; column: number } {\n return node.loc ? { line: node.loc.start.line, column: node.loc.start.column + 1 } : { line: 0, column: 0 }\n}\n","import { lastSegment } from './ast.js'\n\nexport interface AuditOptions {\n /**\n * Extra functions that check who is calling. A plain name matches the last part of the call\n * (`getMe` matches `getMe()` and `lib.getMe()`); a dotted name matches the end of the path\n * (`session.require` matches `ctx.session.require()`).\n */\n authFunctions?: string[]\n /** Extra higher-order functions that wrap an action with an auth check: `export const a = withTeam(async () => …)`. */\n authWrappers?: string[]\n /** Extra action clients with auth middleware (next-safe-action, zsa): `teamClient.action(…)`. */\n authClients?: string[]\n /** Actions that are public on purpose (login, newsletter…). Same effect as a `// @public-action` comment. */\n publicActions?: string[]\n /** Files to skip (CLI only), as globs relative to the project root: `[\"app/legacy/**\"]`. */\n ignore?: string[]\n}\n\n/** Auth.js / NextAuth v5 and Clerk: a bare `auth()` call. Other libraries' `x.auth()` are not checks. */\nconst EXACT_PATHS = ['auth']\n\n/** Matched against the last part of the call. */\nconst NAMES = [\n 'currentUser', // Clerk\n 'getServerSession', // NextAuth v4\n 'getKindeServerSession', // Kinde\n 'validateRequest', // Lucia\n 'getIronSession', // iron-session\n 'withAuth', // WorkOS AuthKit\n 'verifyIdToken', // Firebase Admin\n 'verifySessionCookie', // Firebase Admin\n 'jwtVerify', // jose\n 'verifyToken',\n 'verifyJwt',\n 'verifyAccessToken',\n 'getSession',\n 'verifySession',\n 'getCurrentUser',\n 'getAuthUser',\n 'getAuthenticatedUser',\n 'getSessionUser',\n 'getUserOrThrow',\n 'requireAuth',\n 'requireUser',\n 'requireSession',\n 'requireAdmin',\n 'requireRole',\n 'requirePermission',\n 'checkAuth',\n 'ensureAuth',\n 'ensureUser',\n 'assertAuth',\n 'assertUser',\n 'isAuthenticated',\n 'authenticate',\n 'authorize',\n]\n\n/** Matched against the end of the call path. */\nconst SUFFIXES = [\n 'auth.getUser', // Supabase\n 'auth.getClaims', // Supabase\n 'auth.getSession', // Supabase\n 'auth.protect', // Clerk\n 'api.getSession', // Better Auth\n]\n\n/** Wrapper or client names that suggest an auth check: withAuth, validatedActionWithUser, authActionClient… */\nexport const AUTH_NAME = /auth|protect|guard|session|user|admin|private|secure|logged/i\n\n/**\n * Helpers worth following when looking for an auth check: getCurrentUser, requireOwner, getViewer,\n * verifySession… A helper with an unrelated name (sendEmail, getLocale) may read the session for\n * other reasons, such as the user's language, so it is not followed.\n */\nexport const HELPER_NAME = /auth|session|user|viewer|account|member|owner|admin|role|permission|access|identity|token|login|guard|protect|secure|private|require|ensure|assert|verify|check|current|^me$|^whoami$/i\n\n/**\n * Guards that read the session to keep logged-in users OUT (sign-up, login, password reset):\n * `throwIfAuthenticated`, `redirectIfLoggedIn`, `requireGuest`. They call `getSession()` but\n * protect nothing, so they are never followed or trusted.\n */\nexport const GUEST_GUARD = /(?:throw|redirect|fail|reject|error)If(?:Authenticated|Authed|LoggedIn|SignedIn|Session)|requireGuest|guestOnly|onlyGuests?|(?:require|ensure|assert)(?:Not|No)(?:Authenticated|LoggedIn|SignedIn|Session|User)|unauthenticated|anonymous/i\n\nexport interface ResolvedOptions {\n exactPaths: Set<string>\n names: Set<string>\n suffixes: string[]\n authWrappers: Set<string>\n authClients: Set<string>\n publicActions: Set<string>\n ignore: string[]\n}\n\nexport function resolveOptions(options: AuditOptions = {}): ResolvedOptions {\n const extra = options.authFunctions ?? []\n return {\n exactPaths: new Set(EXACT_PATHS),\n names: new Set([...NAMES, ...extra.filter((name) => !name.includes('.'))]),\n suffixes: [...SUFFIXES, ...extra.filter((name) => name.includes('.'))],\n authWrappers: new Set(options.authWrappers ?? []),\n authClients: new Set(options.authClients ?? []),\n publicActions: new Set(options.publicActions ?? []),\n ignore: options.ignore ?? [],\n }\n}\n\nexport function isAuthCall(path: string, options: ResolvedOptions): boolean {\n if (options.exactPaths.has(path)) return true\n if (options.names.has(lastSegment(path))) return true\n return options.suffixes.some((suffix) => path === suffix || path.endsWith(`.${suffix}`))\n}\n\n/** `schema.parse(x)`, `v.safeParse(schema, x)`… but not `JSON.parse` or `Date.parse`. */\nconst VALIDATION_METHODS = new Set(['parse', 'safeParse', 'parseAsync', 'safeParseAsync', 'validate', 'validateSync', 'validateAsync', 'assert'])\nconst NOT_VALIDATION = new Set(['JSON', 'Date', 'Number', 'URL', 'path', 'Math', 'BigInt', 'console', 'assert'])\n\nexport function isValidationCall(path: string): boolean {\n const method = lastSegment(path)\n if (!VALIDATION_METHODS.has(method)) return false\n const owner = path.includes('.') ? path.slice(0, path.lastIndexOf('.')) : ''\n return !NOT_VALIDATION.has(lastSegment(owner) || owner)\n}\n","import { callChain, calleePath, callsIn, directives, functionDirectives, isFunction, position, unconditionalCalls, walk, type Comment, type Node } from './ast.js'\nimport { AUTH_NAME, GUEST_GUARD, HELPER_NAME, isAuthCall, isValidationCall, type ResolvedOptions } from './options.js'\n\nexport type ActionStatus = 'protected' | 'unprotected' | 'public'\n\nexport interface ServerAction {\n /** Exported name, function name, or `(inline)` for an anonymous inline action. */\n name: string\n /** `module`: exported from a \"use server\" file. `inline`: a function with its own \"use server\" directive. */\n kind: 'module' | 'inline'\n line: number\n column: number\n status: ActionStatus\n /** Why it counts as protected or public: `auth()`, `getUser() → supabase.auth.getUser()`, `authActionClient client`… */\n reason: string | null\n /** `false` when the action takes arguments and nothing validates them; `null` when it takes none. */\n validated: boolean | null\n /** Advice when the tool could not see an auth check. */\n hint: string | null\n node: Node\n}\n\nexport interface ModuleSource {\n ast: Node\n filename: string\n}\n\n/** Turns an import specifier into the parsed module, or `null` (package, missing file…). */\nexport type ImportResolver = (specifier: string, fromFile: string) => ModuleSource | null\n\nexport interface AnalyzeInput {\n ast: Node\n text: string\n filename: string\n comments?: Comment[]\n}\n\nconst PUBLIC_MARKER = /@public-action\\b/\nconst CLIENT_METHODS = new Set(['action', 'stateAction', 'handler', 'createServerAction', 'createAction'])\nconst SCHEMA_METHODS = new Set(['schema', 'inputSchema', 'input', 'bindArgsSchemas'])\nconst MAX_DEPTH = 4\n\ntype Reexport = { source: string; imported: string }\n\n/** What a module declares at the top level: functions, imports and exports. */\nclass ModuleScope {\n readonly functions = new Map<string, Node>()\n readonly imports = new Map<string, Reexport>()\n readonly exports = new Map<string, string | Reexport>()\n /** Top-level declarations by name, with the statement that holds them (for comments). */\n readonly declarations = new Map<string, { init: Node; statement: Node }>()\n\n constructor(readonly ast: Node, readonly filename: string) {\n for (const statement of ast.body ?? []) this.index(statement)\n }\n\n private declare(name: string, init: Node, statement: Node): void {\n this.declarations.set(name, { init, statement })\n if (isFunction(init)) this.functions.set(name, init)\n // `export const getUser = cache(async () => …)`: the wrapped function is what runs.\n else if (init.type === 'CallExpression') {\n const wrapped = (init.arguments ?? []).find((argument: Node) => isFunction(argument))\n if (wrapped) this.functions.set(name, wrapped)\n }\n }\n\n private index(statement: Node): void {\n const declaration = statement.type === 'ExportNamedDeclaration' ? statement.declaration : statement\n if (statement.type === 'ImportDeclaration' && statement.importKind !== 'type') {\n for (const specifier of statement.specifiers ?? []) {\n const imported =\n specifier.type === 'ImportDefaultSpecifier' ? 'default' : specifier.type === 'ImportNamespaceSpecifier' ? '*' : (specifier.imported?.name ?? specifier.imported?.value)\n this.imports.set(specifier.local.name, { source: statement.source.value, imported })\n }\n return\n }\n if (declaration?.type === 'FunctionDeclaration' && declaration.id) {\n this.declare(declaration.id.name, declaration, statement)\n if (statement.type === 'ExportNamedDeclaration') this.exports.set(declaration.id.name, declaration.id.name)\n }\n if (declaration?.type === 'VariableDeclaration') {\n for (const declarator of declaration.declarations ?? []) {\n if (declarator.id?.type !== 'Identifier' || !declarator.init) continue\n this.declare(declarator.id.name, declarator.init, statement)\n if (statement.type === 'ExportNamedDeclaration') this.exports.set(declarator.id.name, declarator.id.name)\n }\n }\n if (statement.type === 'ExportNamedDeclaration' && !statement.declaration) {\n for (const specifier of statement.specifiers ?? []) {\n const exported = specifier.exported?.name ?? specifier.exported?.value\n const local = specifier.local?.name ?? specifier.local?.value\n this.exports.set(exported, statement.source ? { source: statement.source.value, imported: local } : local)\n }\n }\n if (statement.type === 'ExportDefaultDeclaration') {\n const value = statement.declaration\n if (value?.type === 'Identifier') this.exports.set('default', value.name)\n else if (value) {\n this.declare('default', value, statement)\n this.exports.set('default', 'default')\n }\n }\n }\n}\n\ninterface Context {\n options: ResolvedOptions\n resolver: ImportResolver | undefined\n scopes: Map<string, ModuleScope>\n}\n\nfunction scopeOf(module: ModuleSource, context: Context): ModuleScope {\n let scope = context.scopes.get(module.filename)\n if (!scope) {\n scope = new ModuleScope(module.ast, module.filename)\n context.scopes.set(module.filename, scope)\n }\n return scope\n}\n\n/** The function a name refers to in `scope`: a local function, or an imported one (followed across files). */\nfunction resolveFunction(name: string, scope: ModuleScope, context: Context, depth: number): { fn: Node; scope: ModuleScope } | null {\n const local = scope.functions.get(name)\n if (local) return { fn: local, scope }\n const binding = scope.imports.get(name)\n if (!binding || !context.resolver || depth > MAX_DEPTH) return null\n return exportedFunction(binding, scope, context, depth + 1)\n}\n\nfunction exportedFunction(binding: Reexport, from: ModuleScope, context: Context, depth: number): { fn: Node; scope: ModuleScope } | null {\n if (depth > MAX_DEPTH || binding.imported === '*') return null\n const module = context.resolver?.(binding.source, from.filename)\n if (!module) return null\n const scope = scopeOf(module, context)\n const entry = scope.exports.get(binding.imported)\n if (entry === undefined) return null\n if (typeof entry === 'string') {\n const fn = scope.functions.get(entry)\n return fn ? { fn, scope } : resolveFunction(entry, scope, context, depth)\n }\n return exportedFunction(entry, scope, context, depth + 1)\n}\n\n/**\n * First auth check that always runs when `fn` runs, directly or through helpers it calls:\n * \"auth()\", \"getUser() → auth()\". A check inside a branch or a callback does not count.\n */\nfunction authEvidence(fn: Node, scope: ModuleScope, context: Context, depth = 0, seen = new Set<Node>(), followReturned = false): string | null {\n if (depth > MAX_DEPTH || seen.has(fn)) return null\n seen.add(fn)\n const calls = unconditionalCalls(fn, followReturned)\n for (const call of calls) {\n const path = calleePath(call.callee)\n if (isAuthCall(path, context.options)) return `${path}()`\n }\n for (const call of calls) {\n const path = calleePath(call.callee)\n if (path.includes('.') || path === '*' || GUEST_GUARD.test(path) || !HELPER_NAME.test(path)) continue\n const helper = resolveFunction(path, scope, context, depth)\n if (!helper) continue\n const evidence = authEvidence(helper.fn, helper.scope, context, depth + 1, seen)\n if (evidence) return `${path}() → ${evidence}`\n }\n return null\n}\n\nfunction boundNames(pattern: Node | null | undefined, names: string[] = []): string[] {\n if (!pattern) return names\n if (pattern.type === 'Identifier') names.push(pattern.name)\n else if (pattern.type === 'ObjectPattern') for (const property of pattern.properties ?? []) boundNames(property.type === 'RestElement' ? property.argument : property.value, names)\n else if (pattern.type === 'ArrayPattern') for (const element of pattern.elements ?? []) boundNames(element, names)\n else if (pattern.type === 'AssignmentPattern') boundNames(pattern.left, names)\n else if (pattern.type === 'RestElement') boundNames(pattern.argument, names)\n return names\n}\n\n/**\n * An inline action that uses a value the enclosing component got from an auth check:\n * `const session = await auth()` in the page, then `if (!session) return` in the action.\n * Next.js encrypts the variables an inline action closes over, so the action sees the\n * session of the render that created it.\n */\nfunction closureEvidence(action: Node, enclosing: Node, scope: ModuleScope, context: Context): string | null {\n if (enclosing.body?.type !== 'BlockStatement') return null\n const fromAuth = new Map<string, string>()\n for (const statement of enclosing.body.body ?? []) {\n if (statement.type !== 'VariableDeclaration') continue\n for (const declarator of statement.declarations ?? []) {\n const evidence = authEvidence({ type: 'ArrowFunctionExpression', params: [], body: declarator.init } as Node, scope, context)\n if (evidence) for (const name of boundNames(declarator.id)) fromAuth.set(name, evidence)\n }\n }\n if (!fromAuth.size) return null\n let found: string | null = null\n walk(action.body, (node) => {\n if (found) return false\n if (node.type === 'Identifier' && fromAuth.has(node.name)) found = `${node.name} from ${fromAuth.get(node.name)} in the enclosing component`\n })\n return found\n}\n\nfunction declarationFrom(binding: Reexport, from: ModuleScope, context: Context, depth: number): { init: Node; scope: ModuleScope } | null {\n if (depth > MAX_DEPTH || binding.imported === '*' || !context.resolver) return null\n const module = context.resolver(binding.source, from.filename)\n if (!module) return null\n const scope = scopeOf(module, context)\n const entry = scope.exports.get(binding.imported)\n if (typeof entry === 'string') return resolveDeclaration(entry, scope, context, depth + 1)\n return entry ? declarationFrom(entry, scope, context, depth + 1) : null\n}\n\n/** What a top-level name is initialized with, following imports: `actionClient.use(…)`, `cache(…)`. */\nfunction resolveDeclaration(name: string, scope: ModuleScope, context: Context, depth: number): { init: Node; scope: ModuleScope } | null {\n const local = scope.declarations.get(name)\n if (local) return { init: local.init, scope }\n const binding = scope.imports.get(name)\n return binding ? declarationFrom(binding, scope, context, depth) : null\n}\n\n/**\n * Auth check in the definition of a wrapper, a client or a middleware, following clients derived\n * from other clients: `apiKeyClient = actionClient.use(…)`, where `actionClient` checks the session.\n */\nfunction definitionEvidence(name: string, scope: ModuleScope, context: Context, depth = 0): string | null {\n if (depth > MAX_DEPTH || GUEST_GUARD.test(name)) return null\n if (depth > 0 && (context.options.authClients.has(name) || context.options.authWrappers.has(name) || AUTH_NAME.test(name))) return name\n const definition = resolveFunction(name, scope, context, 0)\n const evidence = definition && authEvidence(definition.fn, definition.scope, context, 1, new Set(), true)\n if (evidence) return `${name} → ${evidence}`\n const declaration = resolveDeclaration(name, scope, context, 0)\n if (declaration?.init.type === 'CallExpression') {\n const chain = callChain(declaration.init)\n for (const base of [chain.root, ...chain.identifiers]) {\n if (!base || base === name) continue\n const inherited = definitionEvidence(base, declaration.scope, context, depth + 1)\n if (inherited) return `${name} → ${inherited}`\n }\n }\n return null\n}\n\nfunction validatesDirectly(fn: Node): boolean {\n return callsIn(fn.body).some((call) => isValidationCall(calleePath(call.callee)))\n}\n\nfunction parameterCount(fn: Node): number {\n return (fn.params ?? []).length\n}\n\n/** Comments right before `node` (only whitespace or other comments in between). */\nfunction leadingComments(node: Node, comments: Comment[], text: string): Comment[] {\n const start = node.range?.[0]\n if (start === undefined) return []\n const found: Comment[] = []\n let cursor = start\n for (const comment of [...comments].reverse()) {\n const [from, to] = comment.range ?? [Infinity, Infinity]\n if (to > cursor) continue\n if (text.slice(to, cursor).trim() !== '') break\n found.push(comment)\n cursor = from\n }\n return found\n}\n\n/** Comments at the top of a function body, before its first real statement. */\nfunction bodyHeadComments(fn: Node, comments: Comment[]): Comment[] {\n if (fn.body?.type !== 'BlockStatement' || !fn.body.range) return []\n const [bodyStart, bodyEnd] = fn.body.range as [number, number]\n const statements: Node[] = fn.body.body ?? []\n const firstReal = statements.find((statement) => !(statement.type === 'ExpressionStatement' && typeof statement.directive === 'string'))\n const limit = firstReal?.range?.[0] ?? bodyEnd\n return comments.filter((comment) => comment.range && comment.range[0] > bodyStart && comment.range[1] <= limit)\n}\n\ninterface Candidate {\n name: string\n kind: 'module' | 'inline'\n /** The function itself, or the wrapper call (`withAuth(fn)`, `client.action(fn)`). */\n target: Node\n /** Nodes whose leading comments may mark the action public. */\n anchors: Node[]\n /** For an inline action: the component or function it is declared in. */\n enclosing?: Node | null\n}\n\nfunction moduleCandidates(scope: ModuleScope): Candidate[] {\n const candidates: Candidate[] = []\n for (const [exported, entry] of scope.exports) {\n if (typeof entry !== 'string') continue\n const declaration = scope.declarations.get(entry)\n if (!declaration) continue\n const { init, statement } = declaration\n const target = init.type === 'AwaitExpression' ? init.argument : init\n if (!isFunction(target) && target.type !== 'CallExpression') continue\n // `export default async function createPost()` reads better as \"createPost\" than \"default\".\n const name = exported === 'default' && target.id?.type === 'Identifier' ? target.id.name : exported\n candidates.push({ name, kind: 'module', target, anchors: [statement, init] })\n }\n return candidates\n}\n\n/** Functions with their own \"use server\" directive, named from where they appear. */\nfunction inlineCandidates(ast: Node, skip: Set<Node>): Candidate[] {\n const candidates: Candidate[] = []\n const visit = (node: Node, parent: Node | null, grandparent: Node | null, enclosing: Node | null): void => {\n if (isFunction(node) && !skip.has(node) && functionDirectives(node).includes('use server')) {\n let name = node.id?.name as string | undefined\n if (!name && parent?.type === 'VariableDeclarator' && parent.id?.type === 'Identifier') name = parent.id.name\n if (!name && (parent?.type === 'Property' || parent?.type === 'MethodDefinition') && parent.key?.type === 'Identifier') name = parent.key.name\n if (!name && parent?.type === 'JSXExpressionContainer' && grandparent?.type === 'JSXAttribute') name = `(inline ${grandparent.name?.name ?? 'action'})`\n candidates.push({ name: name ?? '(inline)', kind: 'inline', target: node, anchors: [parent?.type === 'VariableDeclarator' ? grandparent ?? node : node], enclosing })\n }\n const inner = isFunction(node) ? node : enclosing\n for (const key of Object.keys(node)) {\n if (key === 'parent' || key === 'loc' || key === 'range') continue\n const value = node[key]\n if (Array.isArray(value)) {\n for (const child of value) if (child && typeof child.type === 'string') visit(child, node, parent, inner)\n } else if (value && typeof value.type === 'string') {\n visit(value, node, parent, inner)\n }\n }\n }\n visit(ast, null, null, null)\n return candidates\n}\n\nfunction analyzeCandidate(candidate: Candidate, scope: ModuleScope, context: Context, comments: Comment[], text: string): ServerAction {\n const { target } = candidate\n let reason: string | null = null\n let validated: boolean | null = null\n let hint: string | null = null\n const handlers: Node[] = []\n\n if (isFunction(target)) {\n handlers.push(target)\n reason = authEvidence(target, scope, context)\n if (!reason && candidate.enclosing) reason = closureEvidence(target, candidate.enclosing, scope, context)\n validated = parameterCount(target) === 0 ? null : validatesDirectly(target)\n } else {\n const chain = callChain(target)\n handlers.push(...chain.functions)\n const isClient = chain.methods.some((method) => CLIENT_METHODS.has(method))\n const root = chain.root\n const trusted = root !== null && (context.options.authClients.has(root) || context.options.authWrappers.has(root) || (AUTH_NAME.test(root) && !GUEST_GUARD.test(root)))\n if (trusted) reason = isClient ? `${root} client` : `${root}() wrapper`\n for (const handler of chain.functions) reason ??= authEvidence(handler, scope, context)\n // Read the definition of the wrapper, the client and its `.use(middleware)`:\n // `withTeam` → getUser() → verifyToken(), `myClient = base.use(async () => getSession())`.\n for (const name of [root, ...chain.identifiers]) {\n if (!reason && name) reason = definitionEvidence(name, scope, context)\n }\n if (isClient) {\n validated = chain.methods.some((method) => SCHEMA_METHODS.has(method)) ? true : null\n } else if (root && /valid|schema|zod|parse/i.test(root)) {\n validated = true\n } else {\n const handler = chain.functions[0]\n validated = !handler || parameterCount(handler) === 0 ? null : validatesDirectly(handler)\n }\n if (!reason && root) {\n hint = isClient\n ? `If \"${root}\" has auth middleware, add it to \"authClients\".`\n : `If ${root}() checks the session, add it to \"authWrappers\".`\n }\n }\n\n const markerComments = [\n ...candidate.anchors.flatMap((anchor) => leadingComments(anchor, comments, text)),\n ...handlers.flatMap((handler) => bodyHeadComments(handler, comments)),\n ]\n const markedPublic = markerComments.some((comment) => PUBLIC_MARKER.test(comment.value))\n const listedPublic = context.options.publicActions.has(candidate.name)\n const status: ActionStatus = reason ? 'protected' : markedPublic || listedPublic ? 'public' : 'unprotected'\n if (status === 'public') {\n reason = markedPublic ? '@public-action' : 'publicActions'\n hint = null\n }\n const { line, column } = position(candidate.anchors[0] ?? target)\n return { name: candidate.name, kind: candidate.kind, line, column, status, reason, validated, hint, node: candidate.anchors[0] ?? target }\n}\n\n/** Every Server Action declared in one module, with its protection status. */\nexport function analyzeModule(input: AnalyzeInput, options: ResolvedOptions, resolver?: ImportResolver): ServerAction[] {\n if (!input.text.includes('use server')) return []\n const context: Context = { options, resolver, scopes: new Map() }\n const scope = scopeOf({ ast: input.ast, filename: input.filename }, context)\n const comments = input.comments ?? (input.ast.comments as Comment[] | undefined) ?? []\n const candidates: Candidate[] = []\n if (directives(input.ast.body).includes('use server')) candidates.push(...moduleCandidates(scope))\n const covered = new Set(candidates.map((candidate) => candidate.target))\n candidates.push(...inlineCandidates(input.ast, covered))\n return candidates.map((candidate) => analyzeCandidate(candidate, scope, context, comments, input.text)).sort((a, b) => a.line - b.line || a.column - b.column)\n}\n\n","import { existsSync, readFileSync, readdirSync } from 'node:fs'\nimport { join, relative, sep } from 'node:path'\nimport { analyzeModule, type ServerAction } from './analyze.js'\nimport { resolveOptions, type AuditOptions } from './options.js'\nimport { createNodeResolver, parseModule } from './resolve.js'\n\nexport interface FileReport {\n /** Relative to the project root, with forward slashes. */\n file: string\n actions: ServerAction[]\n}\n\nexport interface ProjectReport {\n root: string\n files: FileReport[]\n /** Files that mention \"use server\" but could not be parsed. */\n errors: { file: string; message: string }[]\n summary: { actions: number; protected: number; unprotected: number; public: number; unvalidated: number }\n}\n\nconst SKIP_DIRS = new Set(['node_modules', '.next', '.git', 'dist', 'build', 'out', 'coverage', '.turbo', '.vercel', '.output', '.svelte-kit', '.cache'])\nconst SOURCE = /\\.(?:[cm]?[jt]s|[jt]sx)$/\n\nfunction globToRegExp(glob: string): RegExp {\n let pattern = ''\n for (let i = 0; i < glob.length; i++) {\n const char = glob[i]!\n if (char === '*' && glob[i + 1] === '*') {\n pattern += glob[i + 2] === '/' ? '(?:.*/)?' : '.*'\n i += glob[i + 2] === '/' ? 2 : 1\n } else if (char === '*') pattern += '[^/]*'\n else if (char === '?') pattern += '[^/]'\n else pattern += char.replace(/[.+^${}()|[\\]\\\\]/g, '\\\\$&')\n }\n return new RegExp(`^${pattern}$`)\n}\n\nfunction* sourceFiles(dir: string): Generator<string> {\n for (const entry of readdirSync(dir, { withFileTypes: true })) {\n if (entry.isDirectory()) {\n if (!SKIP_DIRS.has(entry.name) && !entry.name.startsWith('.')) yield* sourceFiles(join(dir, entry.name))\n } else if (entry.isFile() && SOURCE.test(entry.name) && !entry.name.endsWith('.d.ts')) {\n yield join(dir, entry.name)\n }\n }\n}\n\n/** Options from `server-action-audit.config.json`, or the `serverActionAudit` key of package.json. */\nexport function loadConfig(root: string, configPath?: string): AuditOptions {\n if (configPath) return JSON.parse(readFileSync(configPath, 'utf8')) as AuditOptions\n const file = join(root, 'server-action-audit.config.json')\n if (existsSync(file)) return JSON.parse(readFileSync(file, 'utf8')) as AuditOptions\n const pkg = join(root, 'package.json')\n if (existsSync(pkg)) {\n const config = (JSON.parse(readFileSync(pkg, 'utf8')) as { serverActionAudit?: AuditOptions }).serverActionAudit\n if (config) return config\n }\n return {}\n}\n\nexport function scanProject(root: string, options: AuditOptions = {}): ProjectReport {\n const resolved = resolveOptions(options)\n const ignore = resolved.ignore.map(globToRegExp)\n const resolver = createNodeResolver()\n const report: ProjectReport = { root, files: [], errors: [], summary: { actions: 0, protected: 0, unprotected: 0, public: 0, unvalidated: 0 } }\n\n for (const path of sourceFiles(root)) {\n const file = relative(root, path).split(sep).join('/')\n if (ignore.some((pattern) => pattern.test(file))) continue\n const text = readFileSync(path, 'utf8')\n if (!text.includes('use server')) continue\n let actions: ServerAction[]\n try {\n actions = analyzeModule({ ast: parseModule(text, path), text, filename: path }, resolved, resolver)\n } catch (error) {\n report.errors.push({ file, message: (error as Error).message })\n continue\n }\n if (!actions.length) continue\n report.files.push({ file, actions })\n for (const action of actions) {\n report.summary.actions++\n report.summary[action.status]++\n if (action.validated === false) report.summary.unvalidated++\n }\n }\n report.files.sort((a, b) => a.file.localeCompare(b.file))\n return report\n}\n","import { existsSync, readFileSync, statSync } from 'node:fs'\nimport { dirname, join, resolve } from 'node:path'\nimport { parse } from '@typescript-eslint/typescript-estree'\nimport type { Node } from './ast.js'\nimport type { ImportResolver, ModuleSource } from './analyze.js'\n\nconst EXTENSIONS = ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs', '.mts', '.cts']\n\nexport function parseModule(text: string, filename: string): Node {\n return parse(text, {\n jsx: !/\\.[cm]?ts$/.test(filename),\n loc: true,\n range: true,\n comment: true,\n }) as unknown as Node\n}\n\nfunction isFile(path: string): boolean {\n try {\n return statSync(path).isFile()\n } catch {\n return false\n }\n}\n\n/** In TypeScript ESM, `import './session.js'` points at session.ts. */\nconst TS_SOURCES: Record<string, string[]> = { '.js': ['.ts', '.tsx'], '.jsx': ['.tsx'], '.mjs': ['.mts'], '.cjs': ['.cts'] }\n\nfunction tryFile(base: string): string | null {\n if (isFile(base)) return base\n const jsExtension = base.match(/\\.(js|jsx|mjs|cjs)$/)?.[0]\n if (jsExtension) {\n for (const extension of TS_SOURCES[jsExtension] ?? []) {\n const candidate = base.slice(0, -jsExtension.length) + extension\n if (isFile(candidate)) return candidate\n }\n }\n for (const extension of EXTENSIONS) if (isFile(base + extension)) return base + extension\n for (const extension of EXTENSIONS) if (isFile(join(base, `index${extension}`))) return join(base, `index${extension}`)\n return null\n}\n\n/** JSON with comments and trailing commas, as tsconfig.json allows. */\nfunction parseJsonc(text: string): unknown {\n let out = ''\n let inString = false\n for (let i = 0; i < text.length; i++) {\n const char = text[i]!\n if (inString) {\n out += char\n if (char === '\\\\') out += text[++i] ?? ''\n else if (char === '\"') inString = false\n } else if (char === '\"') {\n inString = true\n out += char\n } else if (char === '/' && text[i + 1] === '/') {\n while (i < text.length && text[i] !== '\\n') i++\n out += '\\n'\n } else if (char === '/' && text[i + 1] === '*') {\n i = text.indexOf('*/', i + 2)\n if (i === -1) break\n i++\n } else {\n out += char\n }\n }\n return JSON.parse(out.replace(/,(\\s*[}\\]])/g, '$1'))\n}\n\ninterface PathMapping {\n base: string\n paths: Record<string, string[]>\n}\n\nconst mappings = new Map<string, PathMapping | null>()\n\n/** `compilerOptions.paths` of the nearest tsconfig.json or jsconfig.json. */\nfunction pathMapping(fromDir: string): PathMapping | null {\n if (mappings.has(fromDir)) return mappings.get(fromDir)!\n let mapping: PathMapping | null = null\n for (const name of ['tsconfig.json', 'jsconfig.json']) {\n const file = join(fromDir, name)\n if (!existsSync(file)) continue\n try {\n const config = parseJsonc(readFileSync(file, 'utf8')) as { compilerOptions?: { baseUrl?: string; paths?: Record<string, string[]> } }\n const options = config.compilerOptions ?? {}\n if (options.paths) mapping = { base: resolve(fromDir, options.baseUrl ?? '.'), paths: options.paths }\n } catch {\n mapping = null\n }\n break\n }\n if (!mapping) {\n const parent = dirname(fromDir)\n mapping = parent !== fromDir && !existsSync(join(fromDir, 'package.json')) ? pathMapping(parent) : null\n }\n mappings.set(fromDir, mapping)\n return mapping\n}\n\nfunction resolveAlias(specifier: string, fromFile: string): string | null {\n const mapping = pathMapping(dirname(fromFile))\n if (!mapping) return null\n for (const [pattern, targets] of Object.entries(mapping.paths)) {\n const star = pattern.indexOf('*')\n let captured: string | null = null\n if (star === -1) captured = specifier === pattern ? '' : null\n else {\n const prefix = pattern.slice(0, star)\n const suffix = pattern.slice(star + 1)\n if (specifier.startsWith(prefix) && specifier.endsWith(suffix) && specifier.length >= prefix.length + suffix.length) {\n captured = specifier.slice(prefix.length, specifier.length - suffix.length)\n }\n }\n if (captured === null) continue\n for (const target of targets) {\n const file = tryFile(resolve(mapping.base, target.replace('*', captured)))\n if (file) return file\n }\n }\n return null\n}\n\nexport function resolveImportPath(specifier: string, fromFile: string): string | null {\n if (specifier.startsWith('.')) return tryFile(resolve(dirname(fromFile), specifier))\n if (specifier.startsWith('/')) return tryFile(specifier)\n return resolveAlias(specifier, fromFile)\n}\n\n/**\n * Resolves relative imports and tsconfig `paths` aliases (`@/lib/session`), then parses the file.\n * Packages from node_modules are not followed. Parsed files are cached until they change on disk.\n */\nexport function createNodeResolver(): ImportResolver {\n const cache = new Map<string, { mtime: number; module: ModuleSource | null }>()\n return (specifier, fromFile) => {\n const file = resolveImportPath(specifier, fromFile)\n if (!file) return null\n let mtime = 0\n try {\n mtime = statSync(file).mtimeMs\n } catch {\n return null\n }\n const cached = cache.get(file)\n if (cached && cached.mtime === mtime) return cached.module\n let module: ModuleSource | null = null\n try {\n module = { ast: parseModule(readFileSync(file, 'utf8'), file), filename: file }\n } catch {\n module = null\n }\n cache.set(file, { mtime, module })\n return module\n }\n}\n","import type { ServerAction } from './analyze.js'\nimport type { ProjectReport } from './scan.js'\n\nconst useColor = process.env.FORCE_COLOR ? true : !process.env.NO_COLOR && !!process.stdout.isTTY\nconst paint = (code: string) => (text: string) => (useColor ? `\\x1b[${code}m${text}\\x1b[0m` : text)\nconst red = paint('31')\nconst green = paint('32')\nconst yellow = paint('33')\nconst cyan = paint('36')\nconst dim = paint('2')\nconst bold = paint('1')\n\nfunction describe(action: ServerAction): string {\n const parts: string[] = []\n if (action.status === 'protected') parts.push(dim(action.reason ?? ''))\n if (action.status === 'public') parts.push(cyan(`public (${action.reason})`))\n if (action.status === 'unprotected') parts.push(red('no auth check'))\n if (action.validated === false) parts.push(yellow('input not validated'))\n return parts.join(dim(' · '))\n}\n\nfunction symbol(action: ServerAction): string {\n if (action.status === 'unprotected') return red('✗')\n if (action.validated === false) return yellow('!')\n if (action.status === 'public') return cyan('○')\n return green('✓')\n}\n\nexport function formatPretty(report: ProjectReport, options: { quiet?: boolean } = {}): string {\n const { summary } = report\n const lines: string[] = []\n const fileCount = report.files.length\n lines.push(bold(`server-action-audit · ${summary.actions} Server Action${summary.actions === 1 ? '' : 's'} in ${fileCount} file${fileCount === 1 ? '' : 's'}`), '')\n\n for (const { file, actions } of report.files) {\n const shown = options.quiet ? actions.filter((a) => a.status === 'unprotected' || a.validated === false) : actions\n if (!shown.length) continue\n lines.push(bold(file))\n const width = Math.min(32, Math.max(...shown.map((a) => a.name.length)))\n for (const action of shown) {\n const where = dim(`${action.line}:${action.column}`.padEnd(7))\n lines.push(` ${symbol(action)} ${action.name.padEnd(width)} ${where} ${describe(action)}`)\n if (action.status === 'unprotected' && action.hint) lines.push(` ${dim(action.hint)}`)\n }\n lines.push('')\n }\n\n for (const error of report.errors) lines.push(yellow(`skipped ${error.file}: ${error.message}`))\n if (report.errors.length) lines.push('')\n\n if (!summary.actions) {\n lines.push('No Server Actions found.')\n return lines.join('\\n')\n }\n lines.push(\n [\n summary.unprotected ? red(`✗ ${summary.unprotected} unprotected`) : green('✗ 0 unprotected'),\n green(`✓ ${summary.protected} protected`),\n cyan(`○ ${summary.public} public`),\n summary.unvalidated ? yellow(`! ${summary.unvalidated} with unvalidated input`) : dim('! 0 with unvalidated input'),\n ].join(dim(' · ')),\n )\n if (summary.unprotected) {\n lines.push(\n '',\n dim('Every Server Action is a public endpoint: anyone can call it with a POST request.'),\n dim('Check the session inside each action, or mark intentional ones with // @public-action.'),\n dim('https://nextjs.org/docs/app/guides/data-security'),\n )\n }\n return lines.join('\\n')\n}\n\nexport function formatJson(report: ProjectReport): string {\n return JSON.stringify(\n {\n summary: report.summary,\n files: report.files.map(({ file, actions }) => ({\n file,\n actions: actions.map(({ node: _node, ...action }) => action),\n })),\n errors: report.errors,\n },\n null,\n 2,\n )\n}\n\nconst AUTH_MESSAGE = (name: string) =>\n `Server Action \"${name}\" has no authorization check: anyone can call it with a POST request. Check the session inside the action, or mark it with // @public-action if it is public on purpose.`\nconst VALIDATION_MESSAGE = (name: string) =>\n `Server Action \"${name}\" uses its arguments without validating them. They come straight from the client: parse them with a schema (Zod, Valibot…).`\n\ninterface Finding {\n rule: 'require-auth' | 'require-input-validation'\n level: 'error' | 'warning'\n file: string\n action: ServerAction\n message: string\n}\n\nfunction findings(report: ProjectReport): Finding[] {\n const found: Finding[] = []\n for (const { file, actions } of report.files) {\n for (const action of actions) {\n if (action.status === 'unprotected') {\n found.push({ rule: 'require-auth', level: 'error', file, action, message: AUTH_MESSAGE(action.name) + (action.hint ? ` ${action.hint}` : '') })\n }\n if (action.validated === false) {\n found.push({ rule: 'require-input-validation', level: 'warning', file, action, message: VALIDATION_MESSAGE(action.name) })\n }\n }\n }\n return found\n}\n\n/** GitHub Actions workflow commands: the findings show up as annotations on the pull request. */\nexport function formatGithub(report: ProjectReport): string {\n const data = (text: string) => text.replace(/%/g, '%25').replace(/\\r/g, '%0D').replace(/\\n/g, '%0A')\n const property = (text: string) => data(text).replace(/:/g, '%3A').replace(/,/g, '%2C')\n const lines = findings(report).map(\n ({ rule, level, file, action, message }) =>\n `::${level} file=${property(file)},line=${action.line},col=${action.column},title=${property(`server-action-audit/${rule}`)}::${data(message)}`,\n )\n const { summary } = report\n lines.push(`server-action-audit: ${summary.actions} Server Actions, ${summary.unprotected} unprotected, ${summary.protected} protected, ${summary.public} public, ${summary.unvalidated} with unvalidated input.`)\n return lines.join('\\n')\n}\n\n/**\n * Markdown, for a pull request comment or a job summary. With `sourceUrl` (a URL ending in `/` that the\n * report's file paths can be appended to), each location links to the code.\n */\nexport function formatMarkdown(report: ProjectReport, options: { sourceUrl?: string; limit?: number } = {}): string {\n const { summary } = report\n const fileCount = report.files.length\n const total = `${summary.actions} Server Action${summary.actions === 1 ? '' : 's'} in ${fileCount} file${fileCount === 1 ? '' : 's'}`\n const lines = ['### server-action-audit', '']\n const found = report.files.flatMap(({ file, actions }) =>\n actions.filter((action) => action.status === 'unprotected' || action.validated === false).map((action) => ({ file, action })),\n )\n\n if (!summary.actions) lines.push('No Server Actions found.')\n else if (!found.length) lines.push(`✅ ${total}, no problems found.`)\n else {\n lines.push(\n [\n summary.unprotected ? `**❌ ${summary.unprotected} unprotected**` : '0 unprotected',\n `${summary.protected} protected`,\n `${summary.public} public`,\n summary.unvalidated ? `**⚠️ ${summary.unvalidated} with unvalidated input**` : '0 with unvalidated input',\n ].join(' · ') + ` (${total})`,\n '',\n '| | Action | Where | Problem |',\n '| --- | --- | --- | --- |',\n )\n // Table cells end at a pipe, even inside code spans\n const cell = (text: string) => text.replace(/\\|/g, '\\\\|')\n const segment = (part: string) => encodeURIComponent(part).replace(/[()!'*]/g, (char) => `%${char.charCodeAt(0).toString(16).toUpperCase()}`)\n const limit = options.limit ?? 50\n for (const { file, action } of found.slice(0, limit)) {\n const where = `${file}:${action.line}`\n const link = options.sourceUrl ? `[${cell(where)}](${options.sourceUrl}${file.split('/').map(segment).join('/')}#L${action.line})` : `\\`${cell(where)}\\``\n const unprotected = action.status === 'unprotected'\n const problem = [unprotected && 'No authorization check', action.validated === false && (unprotected ? 'input not validated' : 'Input not validated')].filter(Boolean).join(', ')\n lines.push(`| ${unprotected ? '❌' : '⚠️'} | \\`${cell(action.name)}\\` | ${link} | ${problem} |`)\n }\n if (found.length > limit) lines.push('', `…and ${found.length - limit} more. Run \\`npx server-action-audit\\` to see them all.`)\n if (summary.unprotected) {\n lines.push(\n '',\n 'Every Server Action is a public endpoint: anyone can call it with a POST request. Check the session inside each action, or mark intentional ones with `// @public-action`. See the [Next.js data security guide](https://nextjs.org/docs/app/guides/data-security).',\n )\n }\n }\n\n if (report.errors.length) {\n lines.push('', `Skipped ${report.errors.length} file${report.errors.length === 1 ? '' : 's'} that could not be parsed: ${report.errors.map((error) => `\\`${error.file}\\``).join(', ')}.`)\n }\n return lines.join('\\n')\n}\n\n/** SARIF 2.1.0, for GitHub code scanning and other security dashboards. */\nexport function formatSarif(report: ProjectReport, version: string): string {\n const rules = [\n {\n id: 'require-auth',\n name: 'RequireAuth',\n shortDescription: { text: 'Server Action without an authorization check' },\n fullDescription: { text: 'Every Next.js Server Action is a public endpoint. This one does not check who is calling it.' },\n helpUri: 'https://github.com/i6moons/server-action-audit#readme',\n defaultConfiguration: { level: 'error' },\n properties: { tags: ['security', 'nextjs'], 'security-severity': '7.5', precision: 'medium' },\n },\n {\n id: 'require-input-validation',\n name: 'RequireInputValidation',\n shortDescription: { text: 'Server Action without input validation' },\n fullDescription: { text: 'The action uses arguments sent by the client without parsing them with a schema.' },\n helpUri: 'https://github.com/i6moons/server-action-audit#readme',\n defaultConfiguration: { level: 'warning' },\n properties: { tags: ['security', 'nextjs'], 'security-severity': '4.0', precision: 'medium' },\n },\n ]\n const results = findings(report).map(({ rule, level, file, action, message }) => ({\n ruleId: rule,\n ruleIndex: rule === 'require-auth' ? 0 : 1,\n level,\n message: { text: message },\n locations: [{ physicalLocation: { artifactLocation: { uri: file }, region: { startLine: action.line, startColumn: action.column } } }],\n partialFingerprints: { actionName: `${file}#${action.name}` },\n }))\n return JSON.stringify(\n {\n $schema: 'https://json.schemastore.org/sarif-2.1.0.json',\n version: '2.1.0',\n runs: [{ tool: { driver: { name: 'server-action-audit', version, informationUri: 'https://github.com/i6moons/server-action-audit', rules } }, results }],\n },\n null,\n 2,\n )\n}\n","import { readFileSync } from 'node:fs'\nimport { VERSION } from './version.js'\n\n/** Hidden at the top of the comment, so the next run edits it instead of adding another one. */\nexport const COMMENT_MARKER = '<!-- server-action-audit -->'\n\nexport interface PullRequest {\n apiUrl: string\n serverUrl: string\n /** owner/name */\n repository: string\n number: number\n headSha?: string\n token: string\n}\n\ninterface IssueComment {\n id: number\n body?: string\n}\n\n/** The pull request a GitHub Actions run is for, from its environment, or why there is none. */\nexport function pullRequestFromEnv(env: NodeJS.ProcessEnv = process.env): PullRequest | string {\n if (!env.GITHUB_REPOSITORY || !env.GITHUB_EVENT_PATH) return 'not running in GitHub Actions'\n if (!env.GITHUB_TOKEN) return 'GITHUB_TOKEN is not set'\n const event = JSON.parse(readFileSync(env.GITHUB_EVENT_PATH, 'utf8')) as { pull_request?: { number: number; head?: { sha?: string } } }\n if (!event.pull_request) return `the \"${env.GITHUB_EVENT_NAME ?? 'unknown'}\" event is not about a pull request`\n return {\n apiUrl: env.GITHUB_API_URL || 'https://api.github.com',\n serverUrl: env.GITHUB_SERVER_URL || 'https://github.com',\n repository: env.GITHUB_REPOSITORY,\n number: event.pull_request.number,\n headSha: event.pull_request.head?.sha,\n token: env.GITHUB_TOKEN,\n }\n}\n\n/**\n * Posts the report as a comment on the pull request, or updates the one an earlier run posted.\n * A clean report is only written over an earlier comment: a pull request with nothing to fix gets no comment.\n */\nexport async function upsertComment(\n pr: PullRequest,\n body: string,\n options: { clean: boolean; fetch?: typeof fetch },\n): Promise<'created' | 'updated' | 'skipped'> {\n const request = async <T>(method: string, path: string, payload?: object): Promise<T> => {\n const response = await (options.fetch ?? fetch)(`${pr.apiUrl}${path}`, {\n method,\n headers: {\n accept: 'application/vnd.github+json',\n authorization: `Bearer ${pr.token}`,\n 'user-agent': `server-action-audit/${VERSION}`,\n 'x-github-api-version': '2022-11-28',\n ...(payload ? { 'content-type': 'application/json' } : {}),\n },\n body: payload ? JSON.stringify(payload) : undefined,\n })\n if (!response.ok) {\n const message = await response.json().then(\n (error) => (error as { message?: string }).message,\n () => undefined,\n )\n throw Object.assign(new Error(`${method} ${path} answered ${response.status}${message ? ` ${message}` : ''}`), { status: response.status })\n }\n return (await response.json()) as T\n }\n\n let previous: IssueComment | undefined\n for (let page = 1; !previous; page++) {\n const comments = await request<IssueComment[]>('GET', `/repos/${pr.repository}/issues/${pr.number}/comments?per_page=100&page=${page}`)\n previous = comments.find((comment) => comment.body?.startsWith(COMMENT_MARKER))\n if (comments.length < 100) break\n }\n\n const text = `${COMMENT_MARKER}\\n${body}`\n if (previous) {\n if (previous.body === text) return 'skipped'\n await request('PATCH', `/repos/${pr.repository}/issues/comments/${previous.id}`, { body: text })\n return 'updated'\n }\n if (options.clean) return 'skipped'\n await request('POST', `/repos/${pr.repository}/issues/${pr.number}/comments`, { body: text })\n return 'created'\n}\n","/** Kept in sync with package.json by test/version.test.ts. */\nexport const VERSION = '0.2.0'\n"],"mappings":"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","names":["module","import_node_fs","import_node_path","module","import_node_fs"]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/** Minimal ESTree helpers, shared by the CLI (typescript-estree) and ESLint (espree or typescript-eslint). */
|
|
2
|
+
type Node = {
|
|
3
|
+
type: string;
|
|
4
|
+
range?: [number, number];
|
|
5
|
+
loc?: {
|
|
6
|
+
start: {
|
|
7
|
+
line: number;
|
|
8
|
+
column: number;
|
|
9
|
+
};
|
|
10
|
+
};
|
|
11
|
+
[key: string]: any;
|
|
12
|
+
};
|
|
13
|
+
interface Comment {
|
|
14
|
+
type: string;
|
|
15
|
+
value: string;
|
|
16
|
+
range?: [number, number];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
interface AuditOptions {
|
|
20
|
+
/**
|
|
21
|
+
* Extra functions that check who is calling. A plain name matches the last part of the call
|
|
22
|
+
* (`getMe` matches `getMe()` and `lib.getMe()`); a dotted name matches the end of the path
|
|
23
|
+
* (`session.require` matches `ctx.session.require()`).
|
|
24
|
+
*/
|
|
25
|
+
authFunctions?: string[];
|
|
26
|
+
/** Extra higher-order functions that wrap an action with an auth check: `export const a = withTeam(async () => …)`. */
|
|
27
|
+
authWrappers?: string[];
|
|
28
|
+
/** Extra action clients with auth middleware (next-safe-action, zsa): `teamClient.action(…)`. */
|
|
29
|
+
authClients?: string[];
|
|
30
|
+
/** Actions that are public on purpose (login, newsletter…). Same effect as a `// @public-action` comment. */
|
|
31
|
+
publicActions?: string[];
|
|
32
|
+
/** Files to skip (CLI only), as globs relative to the project root: `["app/legacy/**"]`. */
|
|
33
|
+
ignore?: string[];
|
|
34
|
+
}
|
|
35
|
+
interface ResolvedOptions {
|
|
36
|
+
exactPaths: Set<string>;
|
|
37
|
+
names: Set<string>;
|
|
38
|
+
suffixes: string[];
|
|
39
|
+
authWrappers: Set<string>;
|
|
40
|
+
authClients: Set<string>;
|
|
41
|
+
publicActions: Set<string>;
|
|
42
|
+
ignore: string[];
|
|
43
|
+
}
|
|
44
|
+
declare function resolveOptions(options?: AuditOptions): ResolvedOptions;
|
|
45
|
+
|
|
46
|
+
type ActionStatus = 'protected' | 'unprotected' | 'public';
|
|
47
|
+
interface ServerAction {
|
|
48
|
+
/** Exported name, function name, or `(inline)` for an anonymous inline action. */
|
|
49
|
+
name: string;
|
|
50
|
+
/** `module`: exported from a "use server" file. `inline`: a function with its own "use server" directive. */
|
|
51
|
+
kind: 'module' | 'inline';
|
|
52
|
+
line: number;
|
|
53
|
+
column: number;
|
|
54
|
+
status: ActionStatus;
|
|
55
|
+
/** Why it counts as protected or public: `auth()`, `getUser() → supabase.auth.getUser()`, `authActionClient client`… */
|
|
56
|
+
reason: string | null;
|
|
57
|
+
/** `false` when the action takes arguments and nothing validates them; `null` when it takes none. */
|
|
58
|
+
validated: boolean | null;
|
|
59
|
+
/** Advice when the tool could not see an auth check. */
|
|
60
|
+
hint: string | null;
|
|
61
|
+
node: Node;
|
|
62
|
+
}
|
|
63
|
+
interface ModuleSource {
|
|
64
|
+
ast: Node;
|
|
65
|
+
filename: string;
|
|
66
|
+
}
|
|
67
|
+
/** Turns an import specifier into the parsed module, or `null` (package, missing file…). */
|
|
68
|
+
type ImportResolver = (specifier: string, fromFile: string) => ModuleSource | null;
|
|
69
|
+
interface AnalyzeInput {
|
|
70
|
+
ast: Node;
|
|
71
|
+
text: string;
|
|
72
|
+
filename: string;
|
|
73
|
+
comments?: Comment[];
|
|
74
|
+
}
|
|
75
|
+
/** Every Server Action declared in one module, with its protection status. */
|
|
76
|
+
declare function analyzeModule(input: AnalyzeInput, options: ResolvedOptions, resolver?: ImportResolver): ServerAction[];
|
|
77
|
+
|
|
78
|
+
interface FileReport {
|
|
79
|
+
/** Relative to the project root, with forward slashes. */
|
|
80
|
+
file: string;
|
|
81
|
+
actions: ServerAction[];
|
|
82
|
+
}
|
|
83
|
+
interface ProjectReport {
|
|
84
|
+
root: string;
|
|
85
|
+
files: FileReport[];
|
|
86
|
+
/** Files that mention "use server" but could not be parsed. */
|
|
87
|
+
errors: {
|
|
88
|
+
file: string;
|
|
89
|
+
message: string;
|
|
90
|
+
}[];
|
|
91
|
+
summary: {
|
|
92
|
+
actions: number;
|
|
93
|
+
protected: number;
|
|
94
|
+
unprotected: number;
|
|
95
|
+
public: number;
|
|
96
|
+
unvalidated: number;
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
/** Options from `server-action-audit.config.json`, or the `serverActionAudit` key of package.json. */
|
|
100
|
+
declare function loadConfig(root: string, configPath?: string): AuditOptions;
|
|
101
|
+
declare function scanProject(root: string, options?: AuditOptions): ProjectReport;
|
|
102
|
+
|
|
103
|
+
declare function parseModule(text: string, filename: string): Node;
|
|
104
|
+
declare function resolveImportPath(specifier: string, fromFile: string): string | null;
|
|
105
|
+
/**
|
|
106
|
+
* Resolves relative imports and tsconfig `paths` aliases (`@/lib/session`), then parses the file.
|
|
107
|
+
* Packages from node_modules are not followed. Parsed files are cached until they change on disk.
|
|
108
|
+
*/
|
|
109
|
+
declare function createNodeResolver(): ImportResolver;
|
|
110
|
+
|
|
111
|
+
declare function formatPretty(report: ProjectReport, options?: {
|
|
112
|
+
quiet?: boolean;
|
|
113
|
+
}): string;
|
|
114
|
+
declare function formatJson(report: ProjectReport): string;
|
|
115
|
+
/** GitHub Actions workflow commands: the findings show up as annotations on the pull request. */
|
|
116
|
+
declare function formatGithub(report: ProjectReport): string;
|
|
117
|
+
/**
|
|
118
|
+
* Markdown, for a pull request comment or a job summary. With `sourceUrl` (a URL ending in `/` that the
|
|
119
|
+
* report's file paths can be appended to), each location links to the code.
|
|
120
|
+
*/
|
|
121
|
+
declare function formatMarkdown(report: ProjectReport, options?: {
|
|
122
|
+
sourceUrl?: string;
|
|
123
|
+
limit?: number;
|
|
124
|
+
}): string;
|
|
125
|
+
/** SARIF 2.1.0, for GitHub code scanning and other security dashboards. */
|
|
126
|
+
declare function formatSarif(report: ProjectReport, version: string): string;
|
|
127
|
+
|
|
128
|
+
/** Hidden at the top of the comment, so the next run edits it instead of adding another one. */
|
|
129
|
+
declare const COMMENT_MARKER = "<!-- server-action-audit -->";
|
|
130
|
+
interface PullRequest {
|
|
131
|
+
apiUrl: string;
|
|
132
|
+
serverUrl: string;
|
|
133
|
+
/** owner/name */
|
|
134
|
+
repository: string;
|
|
135
|
+
number: number;
|
|
136
|
+
headSha?: string;
|
|
137
|
+
token: string;
|
|
138
|
+
}
|
|
139
|
+
/** The pull request a GitHub Actions run is for, from its environment, or why there is none. */
|
|
140
|
+
declare function pullRequestFromEnv(env?: NodeJS.ProcessEnv): PullRequest | string;
|
|
141
|
+
/**
|
|
142
|
+
* Posts the report as a comment on the pull request, or updates the one an earlier run posted.
|
|
143
|
+
* A clean report is only written over an earlier comment: a pull request with nothing to fix gets no comment.
|
|
144
|
+
*/
|
|
145
|
+
declare function upsertComment(pr: PullRequest, body: string, options: {
|
|
146
|
+
clean: boolean;
|
|
147
|
+
fetch?: typeof fetch;
|
|
148
|
+
}): Promise<'created' | 'updated' | 'skipped'>;
|
|
149
|
+
|
|
150
|
+
/** Kept in sync with package.json by test/version.test.ts. */
|
|
151
|
+
declare const VERSION = "0.2.0";
|
|
152
|
+
|
|
153
|
+
export { type ActionStatus, type AnalyzeInput, type AuditOptions, COMMENT_MARKER, type FileReport, type ImportResolver, type ModuleSource, type ProjectReport, type PullRequest, type ResolvedOptions, type ServerAction, VERSION, analyzeModule, createNodeResolver, formatGithub, formatJson, formatMarkdown, formatPretty, formatSarif, loadConfig, parseModule, pullRequestFromEnv, resolveImportPath, resolveOptions, scanProject, upsertComment };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/** Minimal ESTree helpers, shared by the CLI (typescript-estree) and ESLint (espree or typescript-eslint). */
|
|
2
|
+
type Node = {
|
|
3
|
+
type: string;
|
|
4
|
+
range?: [number, number];
|
|
5
|
+
loc?: {
|
|
6
|
+
start: {
|
|
7
|
+
line: number;
|
|
8
|
+
column: number;
|
|
9
|
+
};
|
|
10
|
+
};
|
|
11
|
+
[key: string]: any;
|
|
12
|
+
};
|
|
13
|
+
interface Comment {
|
|
14
|
+
type: string;
|
|
15
|
+
value: string;
|
|
16
|
+
range?: [number, number];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
interface AuditOptions {
|
|
20
|
+
/**
|
|
21
|
+
* Extra functions that check who is calling. A plain name matches the last part of the call
|
|
22
|
+
* (`getMe` matches `getMe()` and `lib.getMe()`); a dotted name matches the end of the path
|
|
23
|
+
* (`session.require` matches `ctx.session.require()`).
|
|
24
|
+
*/
|
|
25
|
+
authFunctions?: string[];
|
|
26
|
+
/** Extra higher-order functions that wrap an action with an auth check: `export const a = withTeam(async () => …)`. */
|
|
27
|
+
authWrappers?: string[];
|
|
28
|
+
/** Extra action clients with auth middleware (next-safe-action, zsa): `teamClient.action(…)`. */
|
|
29
|
+
authClients?: string[];
|
|
30
|
+
/** Actions that are public on purpose (login, newsletter…). Same effect as a `// @public-action` comment. */
|
|
31
|
+
publicActions?: string[];
|
|
32
|
+
/** Files to skip (CLI only), as globs relative to the project root: `["app/legacy/**"]`. */
|
|
33
|
+
ignore?: string[];
|
|
34
|
+
}
|
|
35
|
+
interface ResolvedOptions {
|
|
36
|
+
exactPaths: Set<string>;
|
|
37
|
+
names: Set<string>;
|
|
38
|
+
suffixes: string[];
|
|
39
|
+
authWrappers: Set<string>;
|
|
40
|
+
authClients: Set<string>;
|
|
41
|
+
publicActions: Set<string>;
|
|
42
|
+
ignore: string[];
|
|
43
|
+
}
|
|
44
|
+
declare function resolveOptions(options?: AuditOptions): ResolvedOptions;
|
|
45
|
+
|
|
46
|
+
type ActionStatus = 'protected' | 'unprotected' | 'public';
|
|
47
|
+
interface ServerAction {
|
|
48
|
+
/** Exported name, function name, or `(inline)` for an anonymous inline action. */
|
|
49
|
+
name: string;
|
|
50
|
+
/** `module`: exported from a "use server" file. `inline`: a function with its own "use server" directive. */
|
|
51
|
+
kind: 'module' | 'inline';
|
|
52
|
+
line: number;
|
|
53
|
+
column: number;
|
|
54
|
+
status: ActionStatus;
|
|
55
|
+
/** Why it counts as protected or public: `auth()`, `getUser() → supabase.auth.getUser()`, `authActionClient client`… */
|
|
56
|
+
reason: string | null;
|
|
57
|
+
/** `false` when the action takes arguments and nothing validates them; `null` when it takes none. */
|
|
58
|
+
validated: boolean | null;
|
|
59
|
+
/** Advice when the tool could not see an auth check. */
|
|
60
|
+
hint: string | null;
|
|
61
|
+
node: Node;
|
|
62
|
+
}
|
|
63
|
+
interface ModuleSource {
|
|
64
|
+
ast: Node;
|
|
65
|
+
filename: string;
|
|
66
|
+
}
|
|
67
|
+
/** Turns an import specifier into the parsed module, or `null` (package, missing file…). */
|
|
68
|
+
type ImportResolver = (specifier: string, fromFile: string) => ModuleSource | null;
|
|
69
|
+
interface AnalyzeInput {
|
|
70
|
+
ast: Node;
|
|
71
|
+
text: string;
|
|
72
|
+
filename: string;
|
|
73
|
+
comments?: Comment[];
|
|
74
|
+
}
|
|
75
|
+
/** Every Server Action declared in one module, with its protection status. */
|
|
76
|
+
declare function analyzeModule(input: AnalyzeInput, options: ResolvedOptions, resolver?: ImportResolver): ServerAction[];
|
|
77
|
+
|
|
78
|
+
interface FileReport {
|
|
79
|
+
/** Relative to the project root, with forward slashes. */
|
|
80
|
+
file: string;
|
|
81
|
+
actions: ServerAction[];
|
|
82
|
+
}
|
|
83
|
+
interface ProjectReport {
|
|
84
|
+
root: string;
|
|
85
|
+
files: FileReport[];
|
|
86
|
+
/** Files that mention "use server" but could not be parsed. */
|
|
87
|
+
errors: {
|
|
88
|
+
file: string;
|
|
89
|
+
message: string;
|
|
90
|
+
}[];
|
|
91
|
+
summary: {
|
|
92
|
+
actions: number;
|
|
93
|
+
protected: number;
|
|
94
|
+
unprotected: number;
|
|
95
|
+
public: number;
|
|
96
|
+
unvalidated: number;
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
/** Options from `server-action-audit.config.json`, or the `serverActionAudit` key of package.json. */
|
|
100
|
+
declare function loadConfig(root: string, configPath?: string): AuditOptions;
|
|
101
|
+
declare function scanProject(root: string, options?: AuditOptions): ProjectReport;
|
|
102
|
+
|
|
103
|
+
declare function parseModule(text: string, filename: string): Node;
|
|
104
|
+
declare function resolveImportPath(specifier: string, fromFile: string): string | null;
|
|
105
|
+
/**
|
|
106
|
+
* Resolves relative imports and tsconfig `paths` aliases (`@/lib/session`), then parses the file.
|
|
107
|
+
* Packages from node_modules are not followed. Parsed files are cached until they change on disk.
|
|
108
|
+
*/
|
|
109
|
+
declare function createNodeResolver(): ImportResolver;
|
|
110
|
+
|
|
111
|
+
declare function formatPretty(report: ProjectReport, options?: {
|
|
112
|
+
quiet?: boolean;
|
|
113
|
+
}): string;
|
|
114
|
+
declare function formatJson(report: ProjectReport): string;
|
|
115
|
+
/** GitHub Actions workflow commands: the findings show up as annotations on the pull request. */
|
|
116
|
+
declare function formatGithub(report: ProjectReport): string;
|
|
117
|
+
/**
|
|
118
|
+
* Markdown, for a pull request comment or a job summary. With `sourceUrl` (a URL ending in `/` that the
|
|
119
|
+
* report's file paths can be appended to), each location links to the code.
|
|
120
|
+
*/
|
|
121
|
+
declare function formatMarkdown(report: ProjectReport, options?: {
|
|
122
|
+
sourceUrl?: string;
|
|
123
|
+
limit?: number;
|
|
124
|
+
}): string;
|
|
125
|
+
/** SARIF 2.1.0, for GitHub code scanning and other security dashboards. */
|
|
126
|
+
declare function formatSarif(report: ProjectReport, version: string): string;
|
|
127
|
+
|
|
128
|
+
/** Hidden at the top of the comment, so the next run edits it instead of adding another one. */
|
|
129
|
+
declare const COMMENT_MARKER = "<!-- server-action-audit -->";
|
|
130
|
+
interface PullRequest {
|
|
131
|
+
apiUrl: string;
|
|
132
|
+
serverUrl: string;
|
|
133
|
+
/** owner/name */
|
|
134
|
+
repository: string;
|
|
135
|
+
number: number;
|
|
136
|
+
headSha?: string;
|
|
137
|
+
token: string;
|
|
138
|
+
}
|
|
139
|
+
/** The pull request a GitHub Actions run is for, from its environment, or why there is none. */
|
|
140
|
+
declare function pullRequestFromEnv(env?: NodeJS.ProcessEnv): PullRequest | string;
|
|
141
|
+
/**
|
|
142
|
+
* Posts the report as a comment on the pull request, or updates the one an earlier run posted.
|
|
143
|
+
* A clean report is only written over an earlier comment: a pull request with nothing to fix gets no comment.
|
|
144
|
+
*/
|
|
145
|
+
declare function upsertComment(pr: PullRequest, body: string, options: {
|
|
146
|
+
clean: boolean;
|
|
147
|
+
fetch?: typeof fetch;
|
|
148
|
+
}): Promise<'created' | 'updated' | 'skipped'>;
|
|
149
|
+
|
|
150
|
+
/** Kept in sync with package.json by test/version.test.ts. */
|
|
151
|
+
declare const VERSION = "0.2.0";
|
|
152
|
+
|
|
153
|
+
export { type ActionStatus, type AnalyzeInput, type AuditOptions, COMMENT_MARKER, type FileReport, type ImportResolver, type ModuleSource, type ProjectReport, type PullRequest, type ResolvedOptions, type ServerAction, VERSION, analyzeModule, createNodeResolver, formatGithub, formatJson, formatMarkdown, formatPretty, formatSarif, loadConfig, parseModule, pullRequestFromEnv, resolveImportPath, resolveOptions, scanProject, upsertComment };
|