playbook/antigravity-awesome-skills/skills/vercel-optimize/lib/scanners/middleware-broad-matcher.mjs

56 lines
1.9 KiB
JavaScript

export const metadata = {
id: 'middleware-broad-matcher',
title: 'Middleware matcher missing or too broad',
severity: 'high',
billingDimension: 'edge-requests',
trafficIndependent: true,
description:
'middleware.ts without a config.matcher (or matcher: ["/(.*)"]) runs on every request including _next/static, _next/image, favicon.ico, and image asset fetches. Edge-request cost scales accordingly.',
fix:
'Scope the matcher to actual application paths. Example: matcher: ["/((?!_next/static|_next/image|favicon.ico|.*\\\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)"]',
citations: [
'https://nextjs.org/docs/app/building-your-application/routing/middleware',
],
excludeGlobs: ['node_modules/**', '.next/**', 'dist/**', '__tests__/**'],
includeGlobs: ['middleware.{ts,js,mjs}', 'src/middleware.{ts,js,mjs}'],
};
export function scan({ files }) {
const out = [];
for (const { path, content } of files) {
if (!isApplicable(path)) continue;
const exportsMiddleware = /export\s+(default\s+)?(async\s+)?function\s+middleware/.test(content)
|| /export\s+const\s+middleware\s*=/.test(content);
if (!exportsMiddleware) continue;
const configBlock = content.match(/export\s+const\s+config\s*=\s*\{([\s\S]*?)\}/);
const matcherStr = configBlock && configBlock[1].match(/matcher\s*:\s*([^,}]+)/);
let problem = null;
if (!configBlock || !matcherStr) {
problem = 'no config.matcher (runs on every request)';
} else {
const m = matcherStr[1];
if (/['"`]\s*\/\s*['"`]/.test(m) || /['"`]\/\(\.\*\)['"`]/.test(m)) {
problem = 'matcher = "/" or "/(.*)" (still covers everything)';
}
}
if (problem) {
out.push({
pattern: metadata.id,
file: path,
line: 1,
evidence: problem,
trafficIndependent: metadata.trafficIndependent,
});
}
}
return out;
}
function isApplicable(path) {
return /(^|\/)middleware\.(ts|js|mjs)$/.test(path);
}