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
- for (let j = 1; j <= 5; j++) {
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
- if (/^\s*(?:\/\/|#|<!--)/.test(nextLine))
37
- break;
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", "build", "dist", ".turbo", "coverage",
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.54",
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",