guardvibe 3.1.10 → 3.1.11

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,18 @@ 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
+ }
324
336
  // Config: check custom auth function names from .guardviberc
325
337
  if (!codeHasAuthGuard && config.authFunctions && config.authFunctions.length > 0) {
326
338
  const customPattern = new RegExp(`(?:${config.authFunctions.join("|")})\\s*\\(`, "i");
@@ -953,6 +965,25 @@ export function analyzeCode(code, language, framework, filePath, configDir, rule
953
965
  if (isClientFile)
954
966
  continue;
955
967
  }
968
+ // Skip VG120 (SSRF) when the file is a React client component. SSRF requires the
969
+ // server to make the request; browser-side fetch in a client component runs from
970
+ // the user's own machine, so an attacker controlling the URL is just talking to
971
+ // their own network. Same client-marker shape used by VG106/VG407/VG678 narrowings.
972
+ if (rule.id === "VG120") {
973
+ const isClientFile = /^['"]use client['"]/.test(code.trimStart()) ||
974
+ /\b(?:useState|useEffect|useReducer|useRef|useMemo|useCallback|useContext|useTransition|useSyncExternalStore|useLayoutEffect)\s*\(/.test(code);
975
+ if (isClientFile)
976
+ continue;
977
+ // Skip when the URL variable is assigned a hardcoded literal in the same file
978
+ // (`let requestUrl = "https://..."` then `fetch(requestUrl)`). Mirrors the
979
+ // v3.1.7 VG409 literal-redirect skip — same shape, different sink. The set of
980
+ // literal-URL vars is built once per file (see top of analyzeCode).
981
+ if (literalUrlVars.size > 0) {
982
+ const urlVar = /(?:fetch|axios\.\w+|got(?:\.\w+)?|http\.\w+|https\.\w+|urllib\.request\.urlopen)\s*\(\s*([a-zA-Z_$][\w$]*)/.exec(match[0]);
983
+ if (urlVar && literalUrlVars.has(urlVar[1]))
984
+ continue;
985
+ }
986
+ }
956
987
  // Skip VG1005 (.or() filter injection) when all interpolated variables are
957
988
  // server-verified auth IDs (user.id, session.user.id, auth.uid, currentUser.id)
958
989
  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.11",
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",