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.
package/build/data/rules/core.js
CHANGED
|
@@ -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: /(
|
|
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.
|
|
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",
|