guardvibe 3.1.12 → 3.1.14

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.
@@ -247,7 +247,7 @@ export const cveVersionRules = [
247
247
  severity: "critical",
248
248
  owasp: "A02:2025 Injection",
249
249
  description: "React 19.0.0 through 19.1.0 and Next.js 15.0.0 through 15.3.2 are vulnerable to React2Shell — a critical deserialization RCE in the React Flight protocol. Attackers send crafted POST payloads to any App Router endpoint to achieve remote code execution without authentication. CVSS 10.0. State-nexus threat groups exploited this within hours of disclosure (December 2025).",
250
- pattern: /["']react["']\s*:\s*["'](?:\^|~|>=?)?\s*19\.[01]\.\d+["']/g,
250
+ pattern: /["']react["']\s*:\s*["'](?:\^|~|>=?)?\s*(?:19\.0\.\d+|19\.1\.0)["']/g,
251
251
  languages: ["json"],
252
252
  fix: "Upgrade React to 19.1.1+ and Next.js to 15.3.3+: npm install react@latest react-dom@latest next@latest",
253
253
  fixCode: '// package.json — upgrade immediately\n"react": "^19.1.1",\n"react-dom": "^19.1.1",\n"next": "^15.3.3"',
@@ -23,7 +23,7 @@ export const modernStackRules = [
23
23
  severity: "medium",
24
24
  owasp: "API3:2023 Broken Object Property Level Authorization",
25
25
  description: "Using z.any() or z.unknown() for request body/input validation effectively disables validation, allowing any data through.",
26
- pattern: /(?:body|input|data|payload|params)\s*[:=]\s*z\.(?:any|unknown)\s*\(\s*\)/gi,
26
+ pattern: /\b(?:body|input|data|payload|params)\b\s*[:=]\s*z\.(?:any|unknown)\s*\(\s*\)(?!\s*\.(?:describe|nullish|nullable|optional|catch|default|transform|pipe|refine|superRefine|brand|readonly))/gi,
27
27
  languages: ["javascript", "typescript"],
28
28
  fix: "Define explicit Zod schemas for all inputs. Use z.object() with specific field types.",
29
29
  fixCode: '// BAD: no validation\nconst schema = z.object({ data: z.any() });\n\n// GOOD: explicit validation\nconst schema = z.object({\n name: z.string().min(1).max(200),\n email: z.string().email(),\n});',
@@ -370,7 +370,7 @@ export function analyzeCode(code, language, framework, filePath, configDir, rule
370
370
  // agent.get('/?q=' + sqlPayload) which match the regex but aren't database calls
371
371
  // - VG042/VG678: HTTP-response/security-header rules (tests don't serve to real users)
372
372
  const isTestFile = filePath && /(?:\.(?:[\w-]+-)?(?:spec|test|e2e|stories|cy)\.(?:ts|tsx|js|jsx|mjs|cjs)$|\/__tests__\/|\/tests?\/|\/cypress\/|\/playwright\/)/i.test(filePath);
373
- if (isTestFile && ["VG001", "VG062", "VG010", "VG011", "VG013", "VG014", "VG042", "VG130", "VG678", "VG955", "VG133", "VG1021", "VG409"].includes(rule.id))
373
+ if (isTestFile && ["VG001", "VG062", "VG010", "VG011", "VG012", "VG013", "VG014", "VG042", "VG130", "VG678", "VG955", "VG133", "VG1021", "VG409"].includes(rule.id))
374
374
  continue;
375
375
  // VG955 (Missing Pagination on List Endpoint): only fire on actual request-handling
376
376
  // surfaces — API routes, App Router `route.{ts,tsx}`, pages/api, or Server Actions.
@@ -615,6 +615,8 @@ export function analyzeCode(code, language, framework, filePath, configDir, rule
615
615
  continue;
616
616
  if (isReactNative)
617
617
  continue;
618
+ if (isBatchScriptFile)
619
+ continue;
618
620
  const isEmailFile = /(?:resend|nodemailer|sendEmail|sendMail|email\.send)/i.test(code);
619
621
  if (isEmailFile)
620
622
  continue;
@@ -883,6 +885,47 @@ export function analyzeCode(code, language, framework, filePath, configDir, rule
883
885
  const isServiceVerbCall = /(?:^|[\s=])(?:return\s+|await\s+)?this\.(?:get|post|put|delete|patch|head|options|fetch|request)\s*\(/i.test(matchedLine);
884
886
  if (isServiceVerbCall && !hasSqlKeyword)
885
887
  continue;
888
+ // Bare `.get(` / `.run(` / `.all(` triggers without a SQL keyword on the line.
889
+ // The pattern's keyword list is intentionally broad to cover SQLite verbs (`db.run`,
890
+ // `db.all`) and SQLite's `.prepare(...).get()` chain, but those verbs are also used
891
+ // by Redis (`redis.get(`key:${id}`)`), Next.js cookies/headers (`req.cookies.get(`name`)`),
892
+ // JS Map (`map.get(key)`), Tinybird pipes, etc. SQLite's `prepare` already triggers
893
+ // VG010 on the ascending side of the chain, so dropping the bare-verb form here
894
+ // preserves SQLite coverage while clearing the cache/cookie/Map false positives.
895
+ const triggerWordMatch = match[0].match(/^(\w+)/);
896
+ const triggerWord = triggerWordMatch ? triggerWordMatch[1].toLowerCase() : "";
897
+ const isWeakTrigger = triggerWord === "get" || triggerWord === "run" || triggerWord === "all";
898
+ if (isWeakTrigger && !hasSqlKeyword)
899
+ continue;
900
+ }
901
+ // Skip XSS-family rules (VG012/VG408/VG042/VG852) when a lint-suppression
902
+ // comment for the corresponding rule sits within a small window around the
903
+ // matched line. Universal: biome `lint/security/noDangerouslySetInnerHtml`
904
+ // ignore (most JSX cases) or eslint `react/no-danger` disable. Window is
905
+ // ±3 lines because (a) the rule may fire on the comment line itself when
906
+ // its pattern matches the `dangerouslySetInnerHtml` substring inside the
907
+ // biome ignore directive, and (b) JSX attribute rules often fire 2-3 lines
908
+ // below a `{/* biome-ignore ... */}` placed above the opening tag. The
909
+ // suppression comment is the developer's explicit acceptance of the
910
+ // exception (almost always accompanied by a sanitization rationale).
911
+ if (["VG012", "VG408", "VG042", "VG852"].includes(rule.id)) {
912
+ const window = lines
913
+ .slice(Math.max(0, lineNumber - 4), Math.min(lines.length, lineNumber + 3))
914
+ .join("\n");
915
+ if (/biome-ignore\s+lint\/security\/noDangerouslySetInnerHtml\b/i.test(window))
916
+ continue;
917
+ if (/eslint-disable(?:-next-line)?\b[^\n]{0,200}react\/no-danger\b/i.test(window))
918
+ continue;
919
+ }
920
+ // Skip VG012 when the right-hand side is a hardcoded string literal with
921
+ // no interpolation. No user input can flow in; the markup is fully
922
+ // developer-controlled.
923
+ if (rule.id === "VG012") {
924
+ const matchedLine = lines[lineNumber - 1] ?? "";
925
+ if (/\.\w+\s*=\s*"[^"\n]*"\s*;?\s*$/.test(matchedLine) && /\.innerHTML\s*=/.test(matchedLine))
926
+ continue;
927
+ if (/\.\w+\s*=\s*'[^'\n]*'\s*;?\s*$/.test(matchedLine) && /\.innerHTML\s*=/.test(matchedLine))
928
+ continue;
886
929
  }
887
930
  // Skip VG010/VG123 (SQL injection family) on jsforce SOQL calls. SOQL has
888
931
  // different injection semantics than SQL and jsforce does not support
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "guardvibe",
3
- "version": "3.1.12",
3
+ "version": "3.1.14",
4
4
  "mcpName": "io.github.goklab/guardvibe",
5
5
  "description": "Security MCP for vibe coding. 390 rules, 36 tools, CLI + doctor. Host security, auth coverage mapping, LLM-powered deep scan (IDOR/business logic), taint analysis, +25 AI-native rules (MCP supply-chain, RAG/vector poisoning, agent loop DoS, public-prefix LLM keys, sandbox bypass). 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",