@fourteensystems/shipguard 0.2.0 → 0.2.1
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/README.md +4 -2
- package/dist/rules/rate-limit-missing.d.ts.map +1 -1
- package/dist/rules/rate-limit-missing.js +81 -26
- package/dist/rules/rate-limit-missing.js.map +1 -1
- package/dist/rules/rate-limit-missing.test.d.ts +2 -0
- package/dist/rules/rate-limit-missing.test.d.ts.map +1 -0
- package/dist/rules/rate-limit-missing.test.js +325 -0
- package/dist/rules/rate-limit-missing.test.js.map +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -59,7 +59,7 @@ shipguard explain AUTH-BOUNDARY-MISSING
|
|
|
59
59
|
| Rule | Severity | What it catches |
|
|
60
60
|
|------|----------|----------------|
|
|
61
61
|
| AUTH-BOUNDARY-MISSING | critical | Mutation endpoints without auth checks |
|
|
62
|
-
| RATE-LIMIT-MISSING | critical |
|
|
62
|
+
| RATE-LIMIT-MISSING | critical | API routes without rate limiting (auth-aware severity) |
|
|
63
63
|
| TENANCY-SCOPE-MISSING | critical | Prisma queries without tenant scoping |
|
|
64
64
|
| WRAPPER-UNRECOGNIZED | high | HOF wrappers that couldn't be verified for auth/rate-limit enforcement |
|
|
65
65
|
|
|
@@ -108,11 +108,13 @@ Shipguard auto-detects your stack and adjusts detection accordingly:
|
|
|
108
108
|
|
|
109
109
|
### What It Skips
|
|
110
110
|
|
|
111
|
-
- Webhook routes (
|
|
111
|
+
- Webhook routes (any path containing `webhook`) — exempt from rate-limit
|
|
112
112
|
- Cron routes (`/api/cron/*`) — exempt from rate-limit
|
|
113
|
+
- Framework-managed routes (NextAuth catch-all, OAuth/SAML endpoints, callbacks, OG images) — exempt from rate-limit
|
|
113
114
|
- `GET`-only route handlers — not mutation surfaces
|
|
114
115
|
- Routes covered by `middleware.ts` auth — no double-flagging
|
|
115
116
|
- Routes wrapped by verified HOF wrappers (`withWorkspace(handler)` where auth+RL enforcement is proven)
|
|
117
|
+
- Authenticated routes get lower rate-limit severity (abuse requires stolen credentials)
|
|
116
118
|
|
|
117
119
|
See [PATTERNS.md](../../PATTERNS.md) for full detection logic.
|
|
118
120
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rate-limit-missing.d.ts","sourceRoot":"","sources":["../../src/rules/rate-limit-missing.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAa,MAAM,kBAAkB,CAAC;AAC7D,OAAO,KAAK,EAAE,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAInE,eAAO,MAAM,OAAO,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"rate-limit-missing.d.ts","sourceRoot":"","sources":["../../src/rules/rate-limit-missing.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAa,MAAM,kBAAkB,CAAC;AAC7D,OAAO,KAAK,EAAE,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAInE,eAAO,MAAM,OAAO,uBAAuB,CAAC;AAsC5C,wBAAgB,GAAG,CAAC,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,eAAe,GAAG,OAAO,EAAE,CAoFxE"}
|
|
@@ -17,11 +17,23 @@ const EXEMPT_PATH_PATTERNS = [
|
|
|
17
17
|
];
|
|
18
18
|
/**
|
|
19
19
|
* Webhook path patterns — rate limiting is inappropriate for inbound webhooks.
|
|
20
|
-
*
|
|
20
|
+
* Matches any path containing "webhook" (e.g., /stripe-webhook, /webhooks/stripe).
|
|
21
21
|
*/
|
|
22
22
|
const WEBHOOK_PATH_PATTERNS = [
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
/webhook/i,
|
|
24
|
+
];
|
|
25
|
+
/**
|
|
26
|
+
* Framework-managed routes where rate limiting is handled by the framework
|
|
27
|
+
* or is inappropriate (auth protocol flows, external callbacks, OG images).
|
|
28
|
+
*/
|
|
29
|
+
const FRAMEWORK_MANAGED_PATTERNS = [
|
|
30
|
+
/\/auth\/\[\.{3}[^\]]*\]/, // NextAuth catch-all: auth/[...nextauth], auth/[...params]
|
|
31
|
+
/\/callback\//, // Inbound callbacks from external services (OAuth, Stripe, Slack)
|
|
32
|
+
/\/callback$/, // Terminal callback path
|
|
33
|
+
/\/oauth\//, // OAuth protocol endpoints (token, userinfo, authorize)
|
|
34
|
+
/\/saml\//, // SAML SSO endpoints
|
|
35
|
+
/\/og\//, // OG image generation routes (stateless, CDN-cached)
|
|
36
|
+
/\/og$/, // Terminal OG path
|
|
25
37
|
];
|
|
26
38
|
export function run(index, config) {
|
|
27
39
|
const findings = [];
|
|
@@ -38,24 +50,36 @@ export function run(index, config) {
|
|
|
38
50
|
// Skip tRPC proxy routes — rate limiting is checked at the procedure level
|
|
39
51
|
if (index.trpc.detected && route.file === index.trpc.proxyFile)
|
|
40
52
|
continue;
|
|
53
|
+
// Skip framework-managed routes (NextAuth, OAuth, SAML, callbacks, OG images)
|
|
54
|
+
if (isFrameworkManaged(route.pathname))
|
|
55
|
+
continue;
|
|
41
56
|
const result = checkRoute(route, index, config);
|
|
42
57
|
if (result) {
|
|
58
|
+
const isAuthed = route.protection?.auth.satisfied ?? false;
|
|
43
59
|
findings.push({
|
|
44
60
|
ruleId: RULE_ID,
|
|
45
61
|
severity: capSeverity(result.severity, maxSeverity),
|
|
46
62
|
confidence: result.confidence,
|
|
47
|
-
message:
|
|
63
|
+
message: isAuthed
|
|
64
|
+
? `Authenticated API route has no recognized rate limiting`
|
|
65
|
+
: `Public API route has no recognized rate limiting`,
|
|
48
66
|
file: route.file,
|
|
49
67
|
line: result.line,
|
|
50
68
|
snippet: result.snippet,
|
|
51
69
|
evidence: result.evidence,
|
|
52
70
|
confidenceRationale: result.confidenceRationale,
|
|
53
|
-
remediation:
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
71
|
+
remediation: isAuthed
|
|
72
|
+
? [
|
|
73
|
+
"Consider adding rate limiting as defense-in-depth",
|
|
74
|
+
"Authenticated routes are lower risk but can still be abused with stolen credentials",
|
|
75
|
+
"If rate limiting is at the edge (Cloudflare, Vercel WAF), add a waiver",
|
|
76
|
+
]
|
|
77
|
+
: [
|
|
78
|
+
"Add rate limiting middleware or wrapper to this route",
|
|
79
|
+
"If using @upstash/ratelimit, wrap the handler with a rate limit check",
|
|
80
|
+
"If rate limiting is handled at the edge (Cloudflare, Vercel), add a waiver with reason",
|
|
81
|
+
"Add custom wrapper names to hints.rateLimit.wrappers in config",
|
|
82
|
+
],
|
|
59
83
|
tags: ["rate-limit", "server"],
|
|
60
84
|
});
|
|
61
85
|
}
|
|
@@ -119,32 +143,58 @@ function checkRoute(route, index, config) {
|
|
|
119
143
|
// Routes with cron key auth are server-to-server (no rate limiting needed)
|
|
120
144
|
if (hasCronKeyAuth(src))
|
|
121
145
|
return null;
|
|
122
|
-
//
|
|
146
|
+
// Determine auth status for severity modulation
|
|
147
|
+
const isAuthed = route.protection?.auth.satisfied ?? false;
|
|
123
148
|
const evidence = [];
|
|
124
|
-
evidence.push(`No rate limit wrapper calls matched: ${config.hints.rateLimit.wrappers.join(", ")}`);
|
|
125
|
-
evidence.push("No middleware-level rate limiting detected");
|
|
126
149
|
let severity;
|
|
127
150
|
let confidence;
|
|
128
151
|
let confidenceRationale;
|
|
129
152
|
const isMutation = route.signals.hasMutationEvidence || route.signals.hasDbWriteEvidence;
|
|
130
153
|
if (isMutation) {
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
154
|
+
if (isAuthed) {
|
|
155
|
+
severity = "med";
|
|
156
|
+
confidence = "med";
|
|
157
|
+
confidenceRationale = "Medium: authenticated mutation route — abuse requires stolen credentials";
|
|
158
|
+
evidence.push("route performs mutations");
|
|
159
|
+
evidence.push(...route.signals.mutationDetails);
|
|
160
|
+
evidence.push("route has auth boundary — rate limiting is secondary defense");
|
|
161
|
+
}
|
|
162
|
+
else {
|
|
163
|
+
severity = "critical";
|
|
164
|
+
confidence = "high";
|
|
165
|
+
confidenceRationale = "High: mutation route without rate limiting (higher abuse risk)";
|
|
166
|
+
evidence.push("route performs mutations (higher abuse risk)");
|
|
167
|
+
evidence.push(...route.signals.mutationDetails);
|
|
168
|
+
}
|
|
136
169
|
}
|
|
137
170
|
else if (hasBodyParsing(src)) {
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
171
|
+
if (isAuthed) {
|
|
172
|
+
severity = "low";
|
|
173
|
+
confidence = "low";
|
|
174
|
+
confidenceRationale = "Low: authenticated route with body parsing — abuse requires stolen credentials";
|
|
175
|
+
evidence.push("route reads request body");
|
|
176
|
+
evidence.push("route has auth boundary — rate limiting is secondary defense");
|
|
177
|
+
}
|
|
178
|
+
else {
|
|
179
|
+
severity = "high";
|
|
180
|
+
confidence = "high";
|
|
181
|
+
confidenceRationale = "High: route reads request body without rate limiting";
|
|
182
|
+
evidence.push("route reads request body");
|
|
183
|
+
}
|
|
142
184
|
}
|
|
143
185
|
else {
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
186
|
+
if (isAuthed) {
|
|
187
|
+
severity = "low";
|
|
188
|
+
confidence = "low";
|
|
189
|
+
confidenceRationale = "Low: authenticated GET-only route — rate limiting is good hygiene but low risk";
|
|
190
|
+
evidence.push("route has auth boundary — rate limiting is secondary defense");
|
|
191
|
+
}
|
|
192
|
+
else {
|
|
193
|
+
severity = "med";
|
|
194
|
+
confidence = "med";
|
|
195
|
+
confidenceRationale = "Medium: public API route without rate limiting (GET-only, lower risk)";
|
|
196
|
+
evidence.push("public API route without rate limiting");
|
|
197
|
+
}
|
|
148
198
|
}
|
|
149
199
|
return { severity, confidence, confidenceRationale, evidence };
|
|
150
200
|
}
|
|
@@ -192,6 +242,11 @@ function isWebhookPath(pathname) {
|
|
|
192
242
|
return false;
|
|
193
243
|
return WEBHOOK_PATH_PATTERNS.some((p) => p.test(pathname));
|
|
194
244
|
}
|
|
245
|
+
function isFrameworkManaged(pathname) {
|
|
246
|
+
if (!pathname)
|
|
247
|
+
return false;
|
|
248
|
+
return FRAMEWORK_MANAGED_PATTERNS.some((p) => p.test(pathname));
|
|
249
|
+
}
|
|
195
250
|
function hasBodyParsing(src) {
|
|
196
251
|
return /request\.json\s*\(|request\.formData\s*\(|req\.body/.test(src);
|
|
197
252
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rate-limit-missing.js","sourceRoot":"","sources":["../../src/rules/rate-limit-missing.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,IAAI,MAAM,WAAW,CAAC;AAI7B,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAEjD,MAAM,CAAC,MAAM,OAAO,GAAG,oBAAoB,CAAC;AAE5C;;;GAGG;AACH,MAAM,oBAAoB,GAAG;IAC3B,WAAW;IACX,SAAS;IACT,UAAU;IACV,SAAS;IACT,WAAW;IACX,UAAU,EAAK,mCAAmC;IAClD,WAAW,EAAI,uCAAuC;CACvD,CAAC;AAEF;;;GAGG;AACH,MAAM,qBAAqB,GAAG;IAC5B,
|
|
1
|
+
{"version":3,"file":"rate-limit-missing.js","sourceRoot":"","sources":["../../src/rules/rate-limit-missing.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rate-limit-missing.test.d.ts","sourceRoot":"","sources":["../../src/rules/rate-limit-missing.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
|
2
|
+
import { mkdirSync, writeFileSync, rmSync } from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { run } from "./rate-limit-missing.js";
|
|
5
|
+
/* ------------------------------------------------------------------ */
|
|
6
|
+
/* Helpers */
|
|
7
|
+
/* ------------------------------------------------------------------ */
|
|
8
|
+
const NO_SIGNALS = {
|
|
9
|
+
hasMutationEvidence: false,
|
|
10
|
+
hasDbWriteEvidence: false,
|
|
11
|
+
hasStripeWriteEvidence: false,
|
|
12
|
+
mutationDetails: [],
|
|
13
|
+
};
|
|
14
|
+
const MUTATION_SIGNALS = {
|
|
15
|
+
hasMutationEvidence: true,
|
|
16
|
+
hasDbWriteEvidence: true,
|
|
17
|
+
hasStripeWriteEvidence: false,
|
|
18
|
+
mutationDetails: ["prisma.create"],
|
|
19
|
+
};
|
|
20
|
+
function protectionSummary(opts) {
|
|
21
|
+
return {
|
|
22
|
+
auth: {
|
|
23
|
+
satisfied: opts.authSatisfied ?? false,
|
|
24
|
+
enforced: false,
|
|
25
|
+
sources: opts.authSatisfied ? ["direct"] : [],
|
|
26
|
+
details: [],
|
|
27
|
+
unverifiedWrappers: [],
|
|
28
|
+
},
|
|
29
|
+
rateLimit: {
|
|
30
|
+
satisfied: opts.rlSatisfied ?? false,
|
|
31
|
+
enforced: false,
|
|
32
|
+
sources: [],
|
|
33
|
+
details: [],
|
|
34
|
+
unverifiedWrappers: opts.unverifiedWrappers ?? [],
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
let tmpDir;
|
|
39
|
+
beforeEach(() => {
|
|
40
|
+
tmpDir = path.join("/tmp", `shipguard-rl-test-${Date.now()}-${Math.random().toString(36).slice(2)}`);
|
|
41
|
+
mkdirSync(tmpDir, { recursive: true });
|
|
42
|
+
});
|
|
43
|
+
afterEach(() => {
|
|
44
|
+
rmSync(tmpDir, { recursive: true, force: true });
|
|
45
|
+
});
|
|
46
|
+
/** Create a route file on disk and return a NextRoute pointing to it */
|
|
47
|
+
function createRoute(relPath, source, overrides = {}) {
|
|
48
|
+
const fullPath = path.join(tmpDir, relPath);
|
|
49
|
+
mkdirSync(path.dirname(fullPath), { recursive: true });
|
|
50
|
+
writeFileSync(fullPath, source);
|
|
51
|
+
const pathname = "/" + relPath
|
|
52
|
+
.replace(/\/route\.(ts|tsx|js|jsx)$/, "")
|
|
53
|
+
.replace(/^app\//, "");
|
|
54
|
+
return {
|
|
55
|
+
kind: "route-handler",
|
|
56
|
+
file: relPath,
|
|
57
|
+
isApi: pathname.startsWith("/api/") || pathname === "/api",
|
|
58
|
+
isPublic: true,
|
|
59
|
+
pathname,
|
|
60
|
+
signals: NO_SIGNALS,
|
|
61
|
+
protection: protectionSummary({}),
|
|
62
|
+
...overrides,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
function makeIndex(routes) {
|
|
66
|
+
return {
|
|
67
|
+
version: 1,
|
|
68
|
+
framework: "next-app-router",
|
|
69
|
+
rootDir: tmpDir,
|
|
70
|
+
deps: {
|
|
71
|
+
hasNextAuth: false, hasClerk: false, hasSupabase: false,
|
|
72
|
+
hasKinde: false, hasWorkOS: false, hasBetterAuth: false,
|
|
73
|
+
hasLucia: false, hasAuth0: false, hasIronSession: false,
|
|
74
|
+
hasFirebaseAuth: false, hasUpstashRatelimit: false, hasArcjet: false,
|
|
75
|
+
hasUnkey: false, hasPrisma: false, hasDrizzle: false, hasTrpc: false,
|
|
76
|
+
},
|
|
77
|
+
hints: {
|
|
78
|
+
auth: { functions: ["auth"], middlewareFiles: [], allowlistPaths: [] },
|
|
79
|
+
rateLimit: { wrappers: ["rateLimit"], allowlistPaths: [] },
|
|
80
|
+
tenancy: { orgFieldNames: [] },
|
|
81
|
+
},
|
|
82
|
+
middleware: { authLikely: false, rateLimitLikely: false, matcherPatterns: [] },
|
|
83
|
+
wrappers: { wrappers: new Map() },
|
|
84
|
+
routes: { all: routes, mutationRoutes: routes.filter(r => r.signals.hasMutationEvidence) },
|
|
85
|
+
serverActions: { all: [], mutationActions: [] },
|
|
86
|
+
trpc: { detected: false, procedures: [], mutationProcedures: [] },
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
function makeConfig(overrides = {}) {
|
|
90
|
+
return {
|
|
91
|
+
framework: "next-app-router",
|
|
92
|
+
include: ["app/**"],
|
|
93
|
+
exclude: [],
|
|
94
|
+
ci: { failOn: "critical", minConfidence: "high", minScore: 70, maxNewCritical: 0 },
|
|
95
|
+
scoring: { start: 100, penalties: { critical: 25, high: 10, med: 3, low: 1 } },
|
|
96
|
+
hints: {
|
|
97
|
+
auth: { functions: ["auth"], middlewareFiles: [], allowlistPaths: [] },
|
|
98
|
+
rateLimit: { wrappers: ["rateLimit"], allowlistPaths: [] },
|
|
99
|
+
tenancy: { orgFieldNames: [] },
|
|
100
|
+
},
|
|
101
|
+
rules: { "RATE-LIMIT-MISSING": { severity: "critical" } },
|
|
102
|
+
waiversFile: "shipguard.waivers.json",
|
|
103
|
+
...overrides,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
const BASIC_HANDLER = `export async function GET(request: Request) { return Response.json({ ok: true }); }`;
|
|
107
|
+
const MUTATION_HANDLER = `export async function POST(request: Request) {
|
|
108
|
+
const body = await request.json();
|
|
109
|
+
await prisma.user.create({ data: body });
|
|
110
|
+
return Response.json({ ok: true });
|
|
111
|
+
}`;
|
|
112
|
+
const BODY_HANDLER = `export async function POST(request: Request) {
|
|
113
|
+
const body = await request.json();
|
|
114
|
+
return Response.json({ received: true });
|
|
115
|
+
}`;
|
|
116
|
+
/* ------------------------------------------------------------------ */
|
|
117
|
+
/* Framework-managed exemptions */
|
|
118
|
+
/* ------------------------------------------------------------------ */
|
|
119
|
+
describe("framework-managed route exemptions", () => {
|
|
120
|
+
const config = makeConfig();
|
|
121
|
+
it("exempts NextAuth catch-all route", () => {
|
|
122
|
+
const route = createRoute("app/api/auth/[...nextauth]/route.ts", BASIC_HANDLER);
|
|
123
|
+
expect(run(makeIndex([route]), config)).toHaveLength(0);
|
|
124
|
+
});
|
|
125
|
+
it("exempts NextAuth with different param name", () => {
|
|
126
|
+
const route = createRoute("app/api/auth/[...params]/route.ts", BASIC_HANDLER);
|
|
127
|
+
expect(run(makeIndex([route]), config)).toHaveLength(0);
|
|
128
|
+
});
|
|
129
|
+
it("exempts OAuth token endpoint", () => {
|
|
130
|
+
const route = createRoute("app/api/oauth/token/route.ts", BASIC_HANDLER);
|
|
131
|
+
expect(run(makeIndex([route]), config)).toHaveLength(0);
|
|
132
|
+
});
|
|
133
|
+
it("exempts SAML callback route", () => {
|
|
134
|
+
const route = createRoute("app/api/auth/saml/callback/route.ts", BASIC_HANDLER);
|
|
135
|
+
expect(run(makeIndex([route]), config)).toHaveLength(0);
|
|
136
|
+
});
|
|
137
|
+
it("exempts callback routes from external services", () => {
|
|
138
|
+
const route = createRoute("app/api/callback/stripe/route.ts", BASIC_HANDLER);
|
|
139
|
+
expect(run(makeIndex([route]), config)).toHaveLength(0);
|
|
140
|
+
});
|
|
141
|
+
it("exempts nested callback routes", () => {
|
|
142
|
+
const route = createRoute("app/api/slack/callback/route.ts", BASIC_HANDLER, {
|
|
143
|
+
pathname: "/api/slack/callback",
|
|
144
|
+
});
|
|
145
|
+
expect(run(makeIndex([route]), config)).toHaveLength(0);
|
|
146
|
+
});
|
|
147
|
+
it("exempts OG image routes", () => {
|
|
148
|
+
const route = createRoute("app/api/og/analytics/route.tsx", BASIC_HANDLER);
|
|
149
|
+
expect(run(makeIndex([route]), config)).toHaveLength(0);
|
|
150
|
+
});
|
|
151
|
+
it("exempts terminal OG path", () => {
|
|
152
|
+
const route = createRoute("app/api/og/route.tsx", BASIC_HANDLER, {
|
|
153
|
+
pathname: "/api/og",
|
|
154
|
+
});
|
|
155
|
+
expect(run(makeIndex([route]), config)).toHaveLength(0);
|
|
156
|
+
});
|
|
157
|
+
it("does NOT exempt regular API routes", () => {
|
|
158
|
+
const route = createRoute("app/api/users/route.ts", BASIC_HANDLER);
|
|
159
|
+
const findings = run(makeIndex([route]), config);
|
|
160
|
+
expect(findings).toHaveLength(1);
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
/* ------------------------------------------------------------------ */
|
|
164
|
+
/* Improved webhook detection */
|
|
165
|
+
/* ------------------------------------------------------------------ */
|
|
166
|
+
describe("webhook path detection", () => {
|
|
167
|
+
const config = makeConfig();
|
|
168
|
+
it("exempts /webhook path", () => {
|
|
169
|
+
const route = createRoute("app/api/webhook/route.ts", BASIC_HANDLER, {
|
|
170
|
+
pathname: "/api/webhook",
|
|
171
|
+
});
|
|
172
|
+
expect(run(makeIndex([route]), config)).toHaveLength(0);
|
|
173
|
+
});
|
|
174
|
+
it("exempts compound webhook path like /stripe-webhook", () => {
|
|
175
|
+
const route = createRoute("app/api/billing/stripe-webhook/route.ts", BASIC_HANDLER, {
|
|
176
|
+
pathname: "/api/billing/stripe-webhook",
|
|
177
|
+
});
|
|
178
|
+
expect(run(makeIndex([route]), config)).toHaveLength(0);
|
|
179
|
+
});
|
|
180
|
+
it("exempts /webhooks/stripe nested path", () => {
|
|
181
|
+
const route = createRoute("app/api/webhooks/stripe/route.ts", BASIC_HANDLER, {
|
|
182
|
+
pathname: "/api/webhooks/stripe",
|
|
183
|
+
});
|
|
184
|
+
expect(run(makeIndex([route]), config)).toHaveLength(0);
|
|
185
|
+
});
|
|
186
|
+
});
|
|
187
|
+
/* ------------------------------------------------------------------ */
|
|
188
|
+
/* Existing exemptions still work */
|
|
189
|
+
/* ------------------------------------------------------------------ */
|
|
190
|
+
describe("existing exemptions", () => {
|
|
191
|
+
const config = makeConfig();
|
|
192
|
+
it("exempts health check routes", () => {
|
|
193
|
+
const route = createRoute("app/api/health/route.ts", BASIC_HANDLER, {
|
|
194
|
+
pathname: "/api/health",
|
|
195
|
+
});
|
|
196
|
+
expect(run(makeIndex([route]), config)).toHaveLength(0);
|
|
197
|
+
});
|
|
198
|
+
it("exempts cron routes", () => {
|
|
199
|
+
const route = createRoute("app/api/cron/daily/route.ts", BASIC_HANDLER, {
|
|
200
|
+
pathname: "/api/cron/daily",
|
|
201
|
+
});
|
|
202
|
+
expect(run(makeIndex([route]), config)).toHaveLength(0);
|
|
203
|
+
});
|
|
204
|
+
it("skips non-API routes", () => {
|
|
205
|
+
const route = createRoute("app/dashboard/route.ts", BASIC_HANDLER, {
|
|
206
|
+
pathname: "/dashboard",
|
|
207
|
+
isApi: false,
|
|
208
|
+
});
|
|
209
|
+
expect(run(makeIndex([route]), config)).toHaveLength(0);
|
|
210
|
+
});
|
|
211
|
+
it("skips routes with rate-limit protection satisfied", () => {
|
|
212
|
+
const route = createRoute("app/api/users/route.ts", BASIC_HANDLER, {
|
|
213
|
+
protection: protectionSummary({ rlSatisfied: true }),
|
|
214
|
+
});
|
|
215
|
+
expect(run(makeIndex([route]), config)).toHaveLength(0);
|
|
216
|
+
});
|
|
217
|
+
it("defers to WRAPPER-UNRECOGNIZED for unverified wrappers", () => {
|
|
218
|
+
const route = createRoute("app/api/users/route.ts", BASIC_HANDLER, {
|
|
219
|
+
protection: protectionSummary({ unverifiedWrappers: ["withCustom"] }),
|
|
220
|
+
});
|
|
221
|
+
expect(run(makeIndex([route]), config)).toHaveLength(0);
|
|
222
|
+
});
|
|
223
|
+
});
|
|
224
|
+
/* ------------------------------------------------------------------ */
|
|
225
|
+
/* Auth-aware severity: public routes (unchanged behavior) */
|
|
226
|
+
/* ------------------------------------------------------------------ */
|
|
227
|
+
describe("severity: public routes (no auth)", () => {
|
|
228
|
+
const config = makeConfig();
|
|
229
|
+
it("public mutation route → critical/high", () => {
|
|
230
|
+
const route = createRoute("app/api/users/route.ts", MUTATION_HANDLER, {
|
|
231
|
+
signals: MUTATION_SIGNALS,
|
|
232
|
+
protection: protectionSummary({ authSatisfied: false }),
|
|
233
|
+
});
|
|
234
|
+
const findings = run(makeIndex([route]), config);
|
|
235
|
+
expect(findings).toHaveLength(1);
|
|
236
|
+
expect(findings[0].severity).toBe("critical");
|
|
237
|
+
expect(findings[0].confidence).toBe("high");
|
|
238
|
+
});
|
|
239
|
+
it("public body-parsing route → high/high", () => {
|
|
240
|
+
const route = createRoute("app/api/upload/route.ts", BODY_HANDLER, {
|
|
241
|
+
protection: protectionSummary({ authSatisfied: false }),
|
|
242
|
+
});
|
|
243
|
+
const findings = run(makeIndex([route]), config);
|
|
244
|
+
expect(findings).toHaveLength(1);
|
|
245
|
+
expect(findings[0].severity).toBe("high");
|
|
246
|
+
expect(findings[0].confidence).toBe("high");
|
|
247
|
+
});
|
|
248
|
+
it("public GET-only route → med/med", () => {
|
|
249
|
+
const route = createRoute("app/api/data/route.ts", BASIC_HANDLER, {
|
|
250
|
+
protection: protectionSummary({ authSatisfied: false }),
|
|
251
|
+
});
|
|
252
|
+
const findings = run(makeIndex([route]), config);
|
|
253
|
+
expect(findings).toHaveLength(1);
|
|
254
|
+
expect(findings[0].severity).toBe("med");
|
|
255
|
+
expect(findings[0].confidence).toBe("med");
|
|
256
|
+
});
|
|
257
|
+
});
|
|
258
|
+
/* ------------------------------------------------------------------ */
|
|
259
|
+
/* Auth-aware severity: authed routes (new behavior) */
|
|
260
|
+
/* ------------------------------------------------------------------ */
|
|
261
|
+
describe("severity: authenticated routes", () => {
|
|
262
|
+
const config = makeConfig();
|
|
263
|
+
it("authed mutation route → med/med (downgraded from critical)", () => {
|
|
264
|
+
const route = createRoute("app/api/users/route.ts", MUTATION_HANDLER, {
|
|
265
|
+
signals: MUTATION_SIGNALS,
|
|
266
|
+
protection: protectionSummary({ authSatisfied: true }),
|
|
267
|
+
});
|
|
268
|
+
const findings = run(makeIndex([route]), config);
|
|
269
|
+
expect(findings).toHaveLength(1);
|
|
270
|
+
expect(findings[0].severity).toBe("med");
|
|
271
|
+
expect(findings[0].confidence).toBe("med");
|
|
272
|
+
expect(findings[0].evidence).toContain("route has auth boundary — rate limiting is secondary defense");
|
|
273
|
+
});
|
|
274
|
+
it("authed body-parsing route → low/low (downgraded from high)", () => {
|
|
275
|
+
const route = createRoute("app/api/upload/route.ts", BODY_HANDLER, {
|
|
276
|
+
protection: protectionSummary({ authSatisfied: true }),
|
|
277
|
+
});
|
|
278
|
+
const findings = run(makeIndex([route]), config);
|
|
279
|
+
expect(findings).toHaveLength(1);
|
|
280
|
+
expect(findings[0].severity).toBe("low");
|
|
281
|
+
expect(findings[0].confidence).toBe("low");
|
|
282
|
+
});
|
|
283
|
+
it("authed GET-only route → low/low (downgraded from med)", () => {
|
|
284
|
+
const route = createRoute("app/api/data/route.ts", BASIC_HANDLER, {
|
|
285
|
+
protection: protectionSummary({ authSatisfied: true }),
|
|
286
|
+
});
|
|
287
|
+
const findings = run(makeIndex([route]), config);
|
|
288
|
+
expect(findings).toHaveLength(1);
|
|
289
|
+
expect(findings[0].severity).toBe("low");
|
|
290
|
+
expect(findings[0].confidence).toBe("low");
|
|
291
|
+
expect(findings[0].evidence).toContain("route has auth boundary — rate limiting is secondary defense");
|
|
292
|
+
});
|
|
293
|
+
it("authed route gets different message and remediation than public", () => {
|
|
294
|
+
const authedRoute = createRoute("app/api/data/route.ts", BASIC_HANDLER, {
|
|
295
|
+
protection: protectionSummary({ authSatisfied: true }),
|
|
296
|
+
});
|
|
297
|
+
const publicRoute = createRoute("app/api/other/route.ts", BASIC_HANDLER, {
|
|
298
|
+
protection: protectionSummary({ authSatisfied: false }),
|
|
299
|
+
});
|
|
300
|
+
const authedFindings = run(makeIndex([authedRoute]), config);
|
|
301
|
+
const publicFindings = run(makeIndex([publicRoute]), config);
|
|
302
|
+
expect(authedFindings[0].message).toContain("Authenticated");
|
|
303
|
+
expect(publicFindings[0].message).toContain("Public");
|
|
304
|
+
expect(authedFindings[0].remediation).not.toEqual(publicFindings[0].remediation);
|
|
305
|
+
});
|
|
306
|
+
});
|
|
307
|
+
/* ------------------------------------------------------------------ */
|
|
308
|
+
/* Severity cap */
|
|
309
|
+
/* ------------------------------------------------------------------ */
|
|
310
|
+
describe("severity cap", () => {
|
|
311
|
+
it("caps severity at rule max from config", () => {
|
|
312
|
+
const config = makeConfig({
|
|
313
|
+
rules: { "RATE-LIMIT-MISSING": { severity: "high" } },
|
|
314
|
+
});
|
|
315
|
+
const route = createRoute("app/api/users/route.ts", MUTATION_HANDLER, {
|
|
316
|
+
signals: MUTATION_SIGNALS,
|
|
317
|
+
protection: protectionSummary({ authSatisfied: false }),
|
|
318
|
+
});
|
|
319
|
+
const findings = run(makeIndex([route]), config);
|
|
320
|
+
expect(findings).toHaveLength(1);
|
|
321
|
+
// Would be critical but capped to high
|
|
322
|
+
expect(findings[0].severity).toBe("high");
|
|
323
|
+
});
|
|
324
|
+
});
|
|
325
|
+
//# sourceMappingURL=rate-limit-missing.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rate-limit-missing.test.js","sourceRoot":"","sources":["../../src/rules/rate-limit-missing.test.ts"],"names":[],"mappings":"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"}
|