guardvibe 3.0.54 → 3.0.55
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.
|
@@ -18,7 +18,7 @@ export const paymentRules = [
|
|
|
18
18
|
severity: "critical",
|
|
19
19
|
owasp: "A01:2025 Broken Access Control",
|
|
20
20
|
description: "Stripe webhook endpoint processes events without verifying the webhook signature. Anyone can send fake payment events.",
|
|
21
|
-
pattern: /(?:\/api\/webhook|\/api\/stripe|webhook.*stripe)[\s\S]*?(?:req\.body|request\.json|JSON\.parse)[\s\S]{0,300}?(?![\s\S]{0,300}?(?:constructEvent|verifyHeader|stripe\.webhooks))/g,
|
|
21
|
+
pattern: /(?:\/api\/webhook|\/api\/stripe|webhook.*stripe)[\s\S]*?(?:req\.body|request\.json|JSON\.parse)[\s\S]{0,300}?(?![\s\S]{0,300}?(?:constructEvent|verifyHeader|stripe\.webhooks|svix\.verify|webhook\.verify|verify\w*Webhook|verifyWebhookSignature|wh\.verify|crypto\.timingSafeEqual))/g,
|
|
22
22
|
languages: ["javascript", "typescript"],
|
|
23
23
|
fix: "Always verify Stripe webhook signatures using stripe.webhooks.constructEvent().",
|
|
24
24
|
fixCode: "// Verify webhook signature\nconst sig = request.headers.get('stripe-signature')!;\nconst event = stripe.webhooks.constructEvent(\n body, sig, process.env.STRIPE_WEBHOOK_SECRET!\n);",
|
|
@@ -25,17 +25,25 @@ function parseSuppressionsFromCode(lines) {
|
|
|
25
25
|
// lines, stopping early at a blank line or a new comment block. This makes
|
|
26
26
|
// suppress comments work for multi-line method chains (common Supabase / ORM
|
|
27
27
|
// builders span 3-5 lines from `.from(...)` through `.select(...).order(...)`).
|
|
28
|
+
// Additional adjacent `guardvibe-ignore` comments are treated as part of the
|
|
29
|
+
// same header block (they don't break the suppression chain) so users can
|
|
30
|
+
// stack multiple rule suppressions above the same code.
|
|
28
31
|
suppressions.push({ line: i + 1, ruleId });
|
|
29
|
-
|
|
32
|
+
let codeLinesCovered = 0;
|
|
33
|
+
for (let j = 1; j <= 10 && codeLinesCovered < 5; j++) {
|
|
30
34
|
const nextLine = lines[i + j];
|
|
31
35
|
if (nextLine === undefined)
|
|
32
36
|
break;
|
|
33
37
|
const trimmed = nextLine.trim();
|
|
34
38
|
if (trimmed === "")
|
|
35
39
|
break;
|
|
36
|
-
|
|
37
|
-
|
|
40
|
+
// Comment continuation lines (additional `guardvibe-ignore` directives or plain
|
|
41
|
+
// explanation comments below the directive) are part of the same header block —
|
|
42
|
+
// don't break the chain, but don't count them against the 5-line code budget.
|
|
43
|
+
if (/^\s*(?:\/\/|#|<!--|\*)/.test(nextLine))
|
|
44
|
+
continue;
|
|
38
45
|
suppressions.push({ line: i + 1 + j, ruleId });
|
|
46
|
+
codeLinesCovered++;
|
|
39
47
|
}
|
|
40
48
|
}
|
|
41
49
|
else {
|
|
@@ -358,7 +366,7 @@ export function analyzeCode(code, language, framework, filePath, configDir, rule
|
|
|
358
366
|
// ── Context-aware rule skipping (pattern-agnostic) ──────────────
|
|
359
367
|
const authRuleIds = new Set(["VG420", "VG952", "VG002", "VG402"]);
|
|
360
368
|
const adminRoleRuleIds = new Set(["VG426", "VG957"]);
|
|
361
|
-
const rateLimitRuleIds = new Set(["VG956", "VG030"]);
|
|
369
|
+
const rateLimitRuleIds = new Set(["VG956", "VG030", "VG1004"]);
|
|
362
370
|
const isWebhookRoute = filePath && /webhook/i.test(filePath);
|
|
363
371
|
const isCronRoute = filePath && /(?:cron|scheduled|jobs?)\//i.test(filePath);
|
|
364
372
|
const isAdminRoute = filePath && /\/admin\//i.test(filePath);
|
|
@@ -373,6 +381,34 @@ export function analyzeCode(code, language, framework, filePath, configDir, rule
|
|
|
373
381
|
/(?:app|router)\.use\s*\(\s*(?:[^,)]*,\s*)?\w*(?:[Ll]imiter|[Tt]hrottle|[Rr]ate[Ll]imit|[Ss]low[Dd]own|[Bb]rute)\w*\s*\)/.test(code);
|
|
374
382
|
if (hasGlobalRateLimit)
|
|
375
383
|
continue;
|
|
384
|
+
// Per-route Next.js / Server Action pattern: file imports a rate-limiter factory and
|
|
385
|
+
// uses `.check(`/`.limit(` at call sites. Common in App Router route handlers and
|
|
386
|
+
// React Server Actions where there's no shared `app.use(...)` middleware.
|
|
387
|
+
const hasPerRouteRateLimit = /\b(?:createRateLimiter|createRedisRateLimiter|createSlidingWindow|Ratelimit\.slidingWindow|express-rate-limit|hono-rate-limiter|@upstash\/ratelimit)\b/.test(code) &&
|
|
388
|
+
/\b\w+\s*\.\s*(?:check|limit)\s*\(/.test(code);
|
|
389
|
+
if (hasPerRouteRateLimit)
|
|
390
|
+
continue;
|
|
391
|
+
}
|
|
392
|
+
// Skip VG1010 (Server Action without input validation) when the file uses a schema
|
|
393
|
+
// validator (zod / joi / yup / valibot) on its arguments. Rule fires at the file's
|
|
394
|
+
// 'use server' directive (line 1) but validation lives inside the function body.
|
|
395
|
+
if (rule.id === "VG1010") {
|
|
396
|
+
const hasSchemaValidation = /\b(?:z\.\w+|zod\.\w+|joi\.\w+|Joi\.\w+|yup\.\w+|valibot)/.test(code) &&
|
|
397
|
+
/\.\s*(?:parse|safeParse|validate|validateSync|parseAsync)\s*\(/.test(code);
|
|
398
|
+
if (hasSchemaValidation)
|
|
399
|
+
continue;
|
|
400
|
+
}
|
|
401
|
+
// Skip VG601 (Stripe Webhook Missing Signature Verification) when the file calls a
|
|
402
|
+
// webhook-verification function anywhere — Stripe's `constructEvent`, Svix-style
|
|
403
|
+
// `verifyPolarWebhook`/`verifyClerkWebhook`/`svix.verify`, or generic HMAC compare via
|
|
404
|
+
// `crypto.timingSafeEqual`. The base regex's negative lookahead only checks 300 chars
|
|
405
|
+
// *after* the body parse and misses the safer verify-then-parse ordering used by
|
|
406
|
+
// svix-style webhooks (verify raw bytes, parse only after).
|
|
407
|
+
if (rule.id === "VG601") {
|
|
408
|
+
const hasWebhookVerification = /\b(?:stripe\.webhooks\.constructEvent|svix\.verify|\w*\.\s*verify\s*\([^)]*signature|verify\w*Webhook|verifyWebhookSignature|wh\.verify)\b/.test(code) ||
|
|
409
|
+
/crypto\.timingSafeEqual\s*\(/.test(code);
|
|
410
|
+
if (hasWebhookVerification)
|
|
411
|
+
continue;
|
|
376
412
|
}
|
|
377
413
|
// Skip auth rules when code has any auth guard pattern (naming-agnostic)
|
|
378
414
|
if (codeHasAuthGuard && authRuleIds.has(rule.id))
|
|
@@ -69,7 +69,8 @@ function collectJsFiles(dir, maxFiles = 200) {
|
|
|
69
69
|
const files = [];
|
|
70
70
|
const config = loadConfig(resolve(dir));
|
|
71
71
|
const skip = new Set([
|
|
72
|
-
"node_modules", ".git", ".next", "
|
|
72
|
+
"node_modules", ".git", ".next", ".vercel", ".output", ".astro", ".svelte-kit",
|
|
73
|
+
"build", "dist", ".turbo", "coverage", ".nuxt", ".cache",
|
|
73
74
|
...config.scan.exclude,
|
|
74
75
|
]);
|
|
75
76
|
// `maxFiles = Infinity` is the contract for full mode (CLI --full flag): scan everything.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "guardvibe",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.55",
|
|
4
4
|
"mcpName": "io.github.goklab/guardvibe",
|
|
5
5
|
"description": "Security MCP for vibe coding. 365 rules, 36 tools, CLI + doctor. Host security, auth coverage mapping, LLM-powered deep scan (IDOR/business logic), taint analysis. Plus Next.js, Supabase, Clerk, Stripe, Prisma, tRPC, Hono, GraphQL, Convex, Turso, Uploadthing, AI SDK, and the full AI-generated stack.",
|
|
6
6
|
"type": "module",
|