guardvibe 3.1.10 → 3.1.12

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.
@@ -392,7 +392,7 @@ export const coreRules = [
392
392
  severity: "high",
393
393
  owasp: "A10:2025 Server-Side Request Forgery",
394
394
  description: "User-controlled input is passed directly to fetch(), axios, or http.request() as the URL. Attackers can make the server request internal services (169.254.169.254 for cloud metadata, localhost admin panels, internal APIs) leading to data exfiltration or remote code execution.",
395
- pattern: /(?:fetch|axios\.(?:get|post|put|delete|patch|request)|got(?:\.(?:get|post|put|delete|patch))?|http\.(?:get|request)|https\.(?:get|request)|urllib\.request\.urlopen)\s*\(\s*(?!["'`]https?:\/\/|["'`]\/|`\/|`\$\{process\.env|new\s+URL)(?:[a-zA-Z_$]\w*)\s*[,)]/gi,
395
+ pattern: /(?:\bfetch|axios\.(?:get|post|put|delete|patch|request)|got(?:\.(?:get|post|put|delete|patch))?|http\.(?:get|request)|https\.(?:get|request)|urllib\.request\.urlopen)\s*\(\s*(?!["'`]https?:\/\/|["'`]\/|`\/|`\$\{process\.env|new\s+URL)(?:[a-zA-Z_$]\w*)\s*[,)]/gi,
396
396
  languages: ["javascript", "typescript", "python"],
397
397
  fix: "Validate URLs against an allowlist of trusted domains. Block private/internal IP ranges (10.x, 172.16-31.x, 192.168.x, 127.x, 169.254.x, ::1). Use a URL parser to check the hostname before making the request.",
398
398
  fixCode: '// Validate URL before fetching\nconst ALLOWED_HOSTS = ["api.example.com", "cdn.example.com"];\nconst parsed = new URL(userUrl);\nif (!ALLOWED_HOSTS.includes(parsed.hostname)) {\n throw new Error("Blocked: untrusted host");\n}\n// Also block private IPs\nconst ip = await dns.resolve(parsed.hostname);\nif (isPrivateIP(ip)) throw new Error("Blocked: internal host");\nawait fetch(parsed.href);',
@@ -321,6 +321,25 @@ export function analyzeCode(code, language, framework, filePath, configDir, rule
321
321
  /(?:Date\.now\(\)|timestamp|uuid|nanoid|crypto\.randomUUID)[\s\S]{0,80}?\.\s*(?:ext|split|pop)/i.test(code);
322
322
  const isPeerDeps = /["']peerDependencies["']/i.test(code);
323
323
  const codeHasAuthSession = /(?:supabase\.auth\.getUser|supabase\.auth\.getSession|getServerSession|auth\(\)|getSession\(\)|currentUser\(\))/i.test(code);
324
+ // Variables assigned a hardcoded URL literal in the same file
325
+ // (`let requestUrl = "https://graph.microsoft.com/..."`). Used by VG120 to
326
+ // skip server-side fetch calls whose URL is a compile-time constant. Built
327
+ // once per file — re-running the regex per match was the dominant cost when
328
+ // a file had many fetch sites.
329
+ const literalUrlVars = new Set();
330
+ if (/\bhttps?:\/\//.test(code)) {
331
+ const literalUrlAssignRe = /\b(?:const|let|var)\s+([a-zA-Z_$][\w$]*)\s*(?::\s*[\w<>[\]| ]+)?\s*=\s*["'`]https?:\/\//g;
332
+ let lit;
333
+ while ((lit = literalUrlAssignRe.exec(code)) !== null)
334
+ literalUrlVars.add(lit[1]);
335
+ }
336
+ // jsforce SOQL skip signal for VG123. jsforce's `conn.query()` is SOQL
337
+ // (Salesforce's query language), not SQL — different injection semantics, and
338
+ // jsforce does not support parameterized queries. The documented practice is
339
+ // manual escape via a `sanitize*Soql*` helper. File-level boolean: cheaper
340
+ // than re-testing both regexes per match.
341
+ const fileIsJsforceWithSoqlSanitizer = /from\s+["']@?jsforce[\w@/-]*["']/i.test(code) &&
342
+ /sanitiz\w*Soql\w*/i.test(code);
324
343
  // Config: check custom auth function names from .guardviberc
325
344
  if (!codeHasAuthGuard && config.authFunctions && config.authFunctions.length > 0) {
326
345
  const customPattern = new RegExp(`(?:${config.authFunctions.join("|")})\\s*\\(`, "i");
@@ -865,6 +884,16 @@ export function analyzeCode(code, language, framework, filePath, configDir, rule
865
884
  if (isServiceVerbCall && !hasSqlKeyword)
866
885
  continue;
867
886
  }
887
+ // Skip VG010/VG123 (SQL injection family) on jsforce SOQL calls. SOQL has
888
+ // different injection semantics than SQL and jsforce does not support
889
+ // parameterized queries — the documented practice is manual escape via a
890
+ // `sanitize*Soql*` helper. File must import jsforce AND use a SOQL
891
+ // sanitizer — both required, so a jsforce file that forgets to escape
892
+ // still fires. Both VG010 and VG123 are listed because the dedup logic
893
+ // (isDuplicatePair) collapses them on the same line; without skipping
894
+ // both, VG010 just takes over when VG123 is suppressed.
895
+ if ((rule.id === "VG123" || rule.id === "VG010") && fileIsJsforceWithSoqlSanitizer)
896
+ continue;
868
897
  // Skip supply chain rules for known legitimate packages
869
898
  if (["VG872", "VG873"].includes(rule.id)) {
870
899
  const pkgMatch = /"([\w@/-]+)"/.exec(match[0]);
@@ -953,6 +982,25 @@ export function analyzeCode(code, language, framework, filePath, configDir, rule
953
982
  if (isClientFile)
954
983
  continue;
955
984
  }
985
+ // Skip VG120 (SSRF) when the file is a React client component. SSRF requires the
986
+ // server to make the request; browser-side fetch in a client component runs from
987
+ // the user's own machine, so an attacker controlling the URL is just talking to
988
+ // their own network. Same client-marker shape used by VG106/VG407/VG678 narrowings.
989
+ if (rule.id === "VG120") {
990
+ const isClientFile = /^['"]use client['"]/.test(code.trimStart()) ||
991
+ /\b(?:useState|useEffect|useReducer|useRef|useMemo|useCallback|useContext|useTransition|useSyncExternalStore|useLayoutEffect)\s*\(/.test(code);
992
+ if (isClientFile)
993
+ continue;
994
+ // Skip when the URL variable is assigned a hardcoded literal in the same file
995
+ // (`let requestUrl = "https://..."` then `fetch(requestUrl)`). Mirrors the
996
+ // v3.1.7 VG409 literal-redirect skip — same shape, different sink. The set of
997
+ // literal-URL vars is built once per file (see top of analyzeCode).
998
+ if (literalUrlVars.size > 0) {
999
+ const urlVar = /(?:fetch|axios\.\w+|got(?:\.\w+)?|http\.\w+|https\.\w+|urllib\.request\.urlopen)\s*\(\s*([a-zA-Z_$][\w$]*)/.exec(match[0]);
1000
+ if (urlVar && literalUrlVars.has(urlVar[1]))
1001
+ continue;
1002
+ }
1003
+ }
956
1004
  // Skip VG1005 (.or() filter injection) when all interpolated variables are
957
1005
  // server-verified auth IDs (user.id, session.user.id, auth.uid, currentUser.id)
958
1006
  if (rule.id === "VG1005" && codeHasAuthSession) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "guardvibe",
3
- "version": "3.1.10",
3
+ "version": "3.1.12",
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",