guardvibe 1.9.6 → 2.0.1
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/api-security.js +2 -2
- package/build/data/rules/auth.js +2 -2
- package/build/data/rules/core.js +49 -1
- package/build/data/rules/nextjs.js +2 -2
- package/build/tools/check-code.js +73 -12
- package/build/utils/config.d.ts +4 -0
- package/build/utils/config.js +1 -0
- package/package.json +1 -1
|
@@ -33,7 +33,7 @@ export const apiSecurityRules = [
|
|
|
33
33
|
severity: "high",
|
|
34
34
|
owasp: "API2:2023 Broken Authentication",
|
|
35
35
|
description: "Next.js Route Handler that performs data operations without any authentication check. API routes are publicly accessible by default.",
|
|
36
|
-
pattern: /export\s+(?:async\s+)?function\s+(?:GET|POST|PUT|DELETE|PATCH)\s*\([^)]*\)\s*\{(?:(?!auth\s*\(|getServerSession|currentUser|getUser|requireAuth|requireAdmin|requireRole|isAuthenticated|verifyToken|checkAuth|clerkClient|getToken|session|protect|withAuth)[\s\S]){10,}?(?:prisma|db|supabase|query|fetch|sql)\.\w+/g,
|
|
36
|
+
pattern: /export\s+(?:async\s+)?function\s+(?:GET|POST|PUT|DELETE|PATCH)\s*\([^)]*\)\s*\{(?:(?!auth\s*\(|getServerSession|currentUser|getUser|requireAuth|requireAdmin|requireRole|isAuthenticated|verifyToken|checkAuth|clerkClient|getToken|session|protect|withAuth|verifyAuth|checkPermission|assertAuth|ensureAuth|guardAuth|validateAuth|authorize|checkSession|verifySession|validateSession|ensureAuthenticated)[\s\S]){10,}?(?:prisma|db|supabase|query|fetch|sql)\.\w+/g,
|
|
37
37
|
languages: ["javascript", "typescript"],
|
|
38
38
|
fix: "Add authentication at the start of every Route Handler that reads or writes data.",
|
|
39
39
|
fixCode: 'import { auth } from "@clerk/nextjs/server";\n\nexport async function GET() {\n const { userId } = await auth();\n if (!userId) return new Response("Unauthorized", { status: 401 });\n // ... data access\n}',
|
|
@@ -96,7 +96,7 @@ export const apiSecurityRules = [
|
|
|
96
96
|
severity: "high",
|
|
97
97
|
owasp: "API5:2023 Broken Function Level Authorization",
|
|
98
98
|
description: "Endpoint in /admin or /api/admin path performs operations without verifying admin role or permissions. Any authenticated user could access admin functionality.",
|
|
99
|
-
pattern: /(?:\/api\/admin|\/admin)[\s\S]*?export\s+(?:async\s+)?function\s+(?:GET|POST|PUT|DELETE|PATCH)\s*\([^)]*\)\s*\{(?:(?!role|isAdmin|orgRole|permission|requireAdmin|checkRole|adminOnly|org:admin)[\s\S]){10,}?(?:prisma|db|supabase|sql)\.\w+/g,
|
|
99
|
+
pattern: /(?:\/api\/admin|\/admin)[\s\S]*?export\s+(?:async\s+)?function\s+(?:GET|POST|PUT|DELETE|PATCH)\s*\([^)]*\)\s*\{(?:(?!role|isAdmin|orgRole|permission|requireAdmin|checkRole|adminOnly|org:admin|verifyAuth|checkPermission|assertAuth|ensureAuth|authorize)[\s\S]){10,}?(?:prisma|db|supabase|sql)\.\w+/g,
|
|
100
100
|
languages: ["javascript", "typescript"],
|
|
101
101
|
fix: "Always verify admin role/permissions in admin endpoints.",
|
|
102
102
|
fixCode: 'const { userId, orgRole } = await auth();\nif (orgRole !== "org:admin") {\n return new Response("Forbidden", { status: 403 });\n}',
|
package/build/data/rules/auth.js
CHANGED
|
@@ -6,7 +6,7 @@ export const authRules = [
|
|
|
6
6
|
severity: "high",
|
|
7
7
|
owasp: "A01:2025 Broken Access Control",
|
|
8
8
|
description: "Route handler accesses database without authentication check. Anyone can call this endpoint.",
|
|
9
|
-
pattern: /export\s+(?:async\s+)?function\s+(?:GET|POST|PUT|DELETE|PATCH)\s*\([^)]*\)\s*\{(?:(?!auth\s*\(|getServerSession|currentUser|getUser|requireAuth|requireAdmin|isAuthenticated|verifyToken|checkAuth|protect)[\s\S])*?(?:prisma|db|supabase)\.\w+/g,
|
|
9
|
+
pattern: /export\s+(?:async\s+)?function\s+(?:GET|POST|PUT|DELETE|PATCH)\s*\([^)]*\)\s*\{(?:(?!auth\s*\(|getServerSession|currentUser|getUser|requireAuth|requireAdmin|isAuthenticated|verifyToken|checkAuth|protect|verifyAuth|checkPermission|assertAuth|ensureAuth|guardAuth|validateAuth|authorize|checkSession|verifySession|validateSession|ensureAuthenticated|withAuth)[\s\S])*?(?:prisma|db|supabase)\.\w+/g,
|
|
10
10
|
languages: ["javascript", "typescript"],
|
|
11
11
|
fix: "Add authentication check at the start of every route handler that accesses data.",
|
|
12
12
|
fixCode: 'import { auth } from "@clerk/nextjs/server";\n\nexport async function GET() {\n const { userId } = await auth();\n if (!userId) return new Response("Unauthorized", { status: 401 });\n const data = await db.query(...);\n}',
|
|
@@ -69,7 +69,7 @@ export const authRules = [
|
|
|
69
69
|
severity: "high",
|
|
70
70
|
owasp: "A01:2025 Broken Access Control",
|
|
71
71
|
description: "Admin or dashboard route handler does not verify user role or permissions.",
|
|
72
|
-
pattern: /(?:\/admin|\/dashboard)[\s\S]*?export\s+(?:async\s+)?function\s+(?:GET|POST|PUT|DELETE|PATCH|default)\s*\([^)]*\)\s*\{(?:(?!role|permission|isAdmin|orgRole|checkRole|requireAdmin|requireRole|adminOnly)[\s\S])*?\}/g,
|
|
72
|
+
pattern: /(?:\/admin|\/dashboard)[\s\S]*?export\s+(?:async\s+)?function\s+(?:GET|POST|PUT|DELETE|PATCH|default)\s*\([^)]*\)\s*\{(?:(?!role|permission|isAdmin|orgRole|checkRole|requireAdmin|requireRole|adminOnly|verifyAuth|checkPermission|assertAuth|ensureAuth|authorize)[\s\S])*?\}/g,
|
|
73
73
|
languages: ["javascript", "typescript"],
|
|
74
74
|
fix: "Always verify user roles and permissions in admin routes.",
|
|
75
75
|
fixCode: 'import { auth } from "@clerk/nextjs/server";\n\nexport async function GET() {\n const { userId, orgRole } = await auth();\n if (orgRole !== "org:admin") {\n return new Response("Forbidden", { status: 403 });\n }\n}',
|
package/build/data/rules/core.js
CHANGED
|
@@ -19,7 +19,7 @@ export const coreRules = [
|
|
|
19
19
|
severity: "critical",
|
|
20
20
|
owasp: "A01:2025 Broken Access Control",
|
|
21
21
|
description: "Cloud provider API key or token pattern detected in source code (AWS, GitHub, OpenAI, Stripe).",
|
|
22
|
-
pattern: /(?:AKIA[0-9A-Z]{16}|(?:ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9_]{36,}|sk-[A-Za-z0-9]{20,}|sk_live_[A-Za-z0-9]{5,}|rk_live_[A-Za-z0-9]{5,})/g,
|
|
22
|
+
pattern: /(?:AKIA[0-9A-Z]{16}|(?:ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9_]{36,}|sk-(?:proj-)?[A-Za-z0-9\-_]{20,}|sk_live_[A-Za-z0-9]{5,}|rk_live_[A-Za-z0-9]{5,})/g,
|
|
23
23
|
languages: ["javascript", "typescript", "python", "go", "html", "shell"],
|
|
24
24
|
fix: "Remove hardcoded keys immediately. Use environment variables or a secrets manager (AWS Secrets Manager, Vault). Rotate any compromised keys.",
|
|
25
25
|
fixCode: "// Store keys in environment variables\nconst awsKey = process.env.AWS_ACCESS_KEY_ID;\nconst githubToken = process.env.GITHUB_TOKEN;",
|
|
@@ -386,4 +386,52 @@ export const coreRules = [
|
|
|
386
386
|
fixCode: '// BAD: user input in event handler\n// `<img onerror="${userInput}">`\n\n// GOOD: use addEventListener\nconst img = document.createElement("img");\nimg.addEventListener("error", () => handleError(sanitizedInput));',
|
|
387
387
|
compliance: ["SOC2:CC7.1"],
|
|
388
388
|
},
|
|
389
|
+
{
|
|
390
|
+
id: "VG111",
|
|
391
|
+
name: "SSRF via User-Controlled URL",
|
|
392
|
+
severity: "high",
|
|
393
|
+
owasp: "A10:2025 Server-Side Request Forgery",
|
|
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)(?:[a-zA-Z_$]\w*)\s*[,)]/gi,
|
|
396
|
+
languages: ["javascript", "typescript", "python"],
|
|
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
|
+
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);',
|
|
399
|
+
compliance: ["SOC2:CC7.1", "PCI-DSS:Req6.5.1"],
|
|
400
|
+
},
|
|
401
|
+
{
|
|
402
|
+
id: "VG112",
|
|
403
|
+
name: "XSS via insertAdjacentHTML",
|
|
404
|
+
severity: "high",
|
|
405
|
+
owasp: "A02:2025 Injection",
|
|
406
|
+
description: "insertAdjacentHTML() renders raw HTML strings into the DOM without sanitization, enabling Cross-Site Scripting (XSS). Unlike textContent, this method parses and executes HTML including script tags and event handlers.",
|
|
407
|
+
pattern: /\.insertAdjacentHTML\s*\(\s*["'](?:beforebegin|afterbegin|beforeend|afterend)["']\s*,/gi,
|
|
408
|
+
languages: ["javascript", "typescript"],
|
|
409
|
+
fix: "Use textContent or innerText for plain text. If HTML is needed, sanitize with DOMPurify first.",
|
|
410
|
+
fixCode: '// BAD: XSS risk\nel.insertAdjacentHTML("beforeend", userInput);\n\n// GOOD: plain text\nel.insertAdjacentText("beforeend", userInput);\n\n// GOOD: sanitized HTML\nimport DOMPurify from "dompurify";\nel.insertAdjacentHTML("beforeend", DOMPurify.sanitize(userInput));',
|
|
411
|
+
compliance: ["SOC2:CC7.1", "PCI-DSS:Req6.5.7"],
|
|
412
|
+
},
|
|
413
|
+
{
|
|
414
|
+
id: "VG113",
|
|
415
|
+
name: "XSS/SSRF via XMLHttpRequest",
|
|
416
|
+
severity: "high",
|
|
417
|
+
owasp: "A02:2025 Injection",
|
|
418
|
+
description: "XMLHttpRequest.open() is called with a user-controlled URL. This can lead to SSRF (server-side) or data exfiltration (client-side) if the URL is not validated.",
|
|
419
|
+
pattern: /\.open\s*\(\s*["'](?:GET|POST|PUT|DELETE|PATCH)["']\s*,\s*(?:url|uri|href|endpoint|target|userUrl|inputUrl|src)\b/gi,
|
|
420
|
+
languages: ["javascript", "typescript"],
|
|
421
|
+
fix: "Validate the URL against an allowlist before passing to XMLHttpRequest.open(). Prefer fetch() with URL validation over raw XHR.",
|
|
422
|
+
fixCode: '// Validate URL before XHR\nconst allowed = ["https://api.example.com"];\nconst parsed = new URL(userUrl);\nif (!allowed.some(a => userUrl.startsWith(a))) throw new Error("Blocked");\nxhr.open("GET", parsed.href);',
|
|
423
|
+
compliance: ["SOC2:CC7.1"],
|
|
424
|
+
},
|
|
425
|
+
{
|
|
426
|
+
id: "VG114",
|
|
427
|
+
name: "SQL Injection via Template Literal",
|
|
428
|
+
severity: "critical",
|
|
429
|
+
owasp: "A02:2025 Injection",
|
|
430
|
+
description: "SQL query constructed using JavaScript template literals with interpolated variables. Template literal SQL is just as dangerous as string concatenation — any user-controlled value can break out of the SQL context and inject arbitrary queries.",
|
|
431
|
+
pattern: /(?:query|execute|raw|sql|prepare|QueryRow|QueryContext|db\.run|db\.all|db\.get|connection\.query)\s*\(\s*`[^`]*\$\{/gi,
|
|
432
|
+
languages: ["javascript", "typescript"],
|
|
433
|
+
fix: "Use parameterized queries. For tagged template literals (e.g. Prisma sql``, Drizzle sql``), the tagged version IS safe — only raw template literals passed to query() are dangerous.",
|
|
434
|
+
fixCode: '// BAD: template literal SQL\ndb.query(`SELECT * FROM users WHERE id = \'${userId}\'`);\n\n// GOOD: parameterized query\ndb.query("SELECT * FROM users WHERE id = $1", [userId]);\n\n// ALSO SAFE: tagged template literal (Prisma/Drizzle)\nimport { sql } from "drizzle-orm";\ndb.execute(sql`SELECT * FROM users WHERE id = ${userId}`);',
|
|
435
|
+
compliance: ["SOC2:CC7.1", "PCI-DSS:Req6.5.1"],
|
|
436
|
+
},
|
|
389
437
|
];
|
|
@@ -30,7 +30,7 @@ export const nextjsRules = [
|
|
|
30
30
|
severity: "critical",
|
|
31
31
|
owasp: "A01:2025 Broken Access Control",
|
|
32
32
|
description: "Server Action performs data mutations without verifying user authentication. Anyone can invoke Server Actions directly via POST request.",
|
|
33
|
-
pattern: /["']use server["'][\s\S]{0,500}?export\s+async\s+function\s+\w+\s*\([^)]*\)\s*\{(?![\s\S]{0,800}?(?:auth\s*\(|getServerSession|currentUser|getUser|requireAuth|clerkClient))/g,
|
|
33
|
+
pattern: /["']use server["'][\s\S]{0,500}?export\s+async\s+function\s+\w+\s*\([^)]*\)\s*\{(?![\s\S]{0,800}?(?:auth\s*\(|getServerSession|currentUser|getUser|requireAuth|requireAdmin|clerkClient|verifyAuth|checkPermission|assertAuth|ensureAuth|authorize|withAuth))/g,
|
|
34
34
|
languages: ["javascript", "typescript"],
|
|
35
35
|
fix: "Always verify authentication at the start of every Server Action.",
|
|
36
36
|
fixCode: '"use server";\nimport { auth } from "@clerk/nextjs/server";\n\nexport async function deleteItem(id: string) {\n const { userId } = await auth();\n if (!userId) throw new Error("Unauthorized");\n}',
|
|
@@ -114,7 +114,7 @@ export const nextjsRules = [
|
|
|
114
114
|
severity: "medium",
|
|
115
115
|
owasp: "A01:2025 Broken Access Control",
|
|
116
116
|
description: "redirect() or NextResponse.redirect() uses user-controlled input (searchParams, query) which can redirect users to malicious sites.",
|
|
117
|
-
pattern: /redirect\s*\(\s*(?:searchParams|params|req\.query|request\.url|url|query)\
|
|
117
|
+
pattern: /(?:redirect|NextResponse\.redirect|res\.redirect|Response\.redirect)\s*\(\s*(?:searchParams|params|req\.query|request\.url|url|query|returnTo|callbackUrl|next|goto|returnUrl|redirectUrl|destination)\b/gi,
|
|
118
118
|
languages: ["javascript", "typescript"],
|
|
119
119
|
fix: "Validate redirect URLs against an allowlist of trusted domains.",
|
|
120
120
|
fixCode: '// Validate redirect URL\nconst ALLOWED_HOSTS = ["example.com"];\nconst target = searchParams.get("next") ?? "/";\ntry {\n const url = new URL(target, request.url);\n if (!ALLOWED_HOSTS.includes(url.hostname)) redirect("/");\n redirect(url.pathname);\n} catch {\n redirect("/");\n}',
|
|
@@ -126,6 +126,53 @@ function isRuleDefinitionFile(code, filePath) {
|
|
|
126
126
|
}
|
|
127
127
|
return false;
|
|
128
128
|
}
|
|
129
|
+
/**
|
|
130
|
+
* Detect if code contains an auth guard pattern — regardless of function name.
|
|
131
|
+
* Matches patterns like:
|
|
132
|
+
* const { userId } = await someFunction(); if (!userId) return/throw;
|
|
133
|
+
* const { error } = await someFunction(); if (error) return error;
|
|
134
|
+
* const session = await someFunction(); if (!session) throw/return;
|
|
135
|
+
* await someFunction(); // + early return pattern
|
|
136
|
+
*
|
|
137
|
+
* This is naming-agnostic: works for requireAdmin, verifyAuth, checkPermission,
|
|
138
|
+
* ensureLoggedIn, or any custom auth wrapper.
|
|
139
|
+
*/
|
|
140
|
+
function hasAuthGuardPattern(code) {
|
|
141
|
+
// Pattern 1: destructured result checked with early return/throw
|
|
142
|
+
// e.g., const { userId } = await xxx(); if (!userId) return;
|
|
143
|
+
// e.g., const { error } = await xxx(); if (error) return error;
|
|
144
|
+
if (/(?:const|let)\s+\{[^}]*\}\s*=\s*await\s+\w+\s*\([^)]*\)\s*;?\s*\n\s*if\s*\(\s*!?\w+/.test(code)) {
|
|
145
|
+
if (/if\s*\([^)]*\)\s*(?:return|throw)\b/.test(code))
|
|
146
|
+
return true;
|
|
147
|
+
}
|
|
148
|
+
// Pattern 2: result assigned then checked
|
|
149
|
+
// e.g., const session = await xxx(); if (!session) return;
|
|
150
|
+
if (/(?:const|let)\s+\w+\s*=\s*await\s+\w+\s*\([^)]*\)\s*;?\s*\n\s*if\s*\(\s*!\w+/.test(code)) {
|
|
151
|
+
return true;
|
|
152
|
+
}
|
|
153
|
+
// Pattern 3: function called with await that contains auth-like keywords in name
|
|
154
|
+
// Broad catch: any function name containing auth/session/permission/guard/verify/protect
|
|
155
|
+
if (/await\s+(?:\w+\.)*\w*(?:auth|Auth|session|Session|permission|Permission|guard|Guard|verify|Verify|protect|Protect|check|Check|ensure|Ensure|require|Require|assert|Assert|authorize|Authorize)\w*\s*\(/i.test(code)) {
|
|
156
|
+
return true;
|
|
157
|
+
}
|
|
158
|
+
return false;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Detect if code has a role/permission check — regardless of function name.
|
|
162
|
+
* Matches: role === "admin", permission check, role-based condition.
|
|
163
|
+
*/
|
|
164
|
+
function hasRoleCheckPattern(code) {
|
|
165
|
+
// Direct role/permission comparison
|
|
166
|
+
if (/(?:role|permission|isAdmin|access|level)\s*(?:===|!==|==|!=)\s*["']/i.test(code))
|
|
167
|
+
return true;
|
|
168
|
+
// Function call with role/permission-like args
|
|
169
|
+
if (/(?:check|require|verify|ensure|assert|has|can)\w*\s*\(\s*["'](?:admin|manager|editor|owner|moderator|superadmin)/i.test(code))
|
|
170
|
+
return true;
|
|
171
|
+
// Destructured role check: const { role } = ...; if (role !== "admin")
|
|
172
|
+
if (/\brole\b[\s\S]{0,100}?(?:!==|===)\s*["']/i.test(code))
|
|
173
|
+
return true;
|
|
174
|
+
return false;
|
|
175
|
+
}
|
|
129
176
|
export function analyzeCode(code, language, framework, filePath, configDir, rules) {
|
|
130
177
|
// Skip files that are security rule definitions (they intentionally contain
|
|
131
178
|
// vulnerable code patterns as regex matchers and fixCode examples)
|
|
@@ -136,6 +183,15 @@ export function analyzeCode(code, language, framework, filePath, configDir, rule
|
|
|
136
183
|
const findings = [];
|
|
137
184
|
const lines = code.split("\n");
|
|
138
185
|
const suppressions = parseSuppressionsFromCode(lines);
|
|
186
|
+
// Pre-analyze: detect auth guards and role checks pattern-agnostically
|
|
187
|
+
let codeHasAuthGuard = hasAuthGuardPattern(code);
|
|
188
|
+
const codeHasRoleCheck = hasRoleCheckPattern(code);
|
|
189
|
+
// Config: check custom auth function names from .guardviberc
|
|
190
|
+
if (!codeHasAuthGuard && config.authFunctions && config.authFunctions.length > 0) {
|
|
191
|
+
const customPattern = new RegExp(`(?:${config.authFunctions.join("|")})\\s*\\(`, "i");
|
|
192
|
+
if (customPattern.test(code))
|
|
193
|
+
codeHasAuthGuard = true;
|
|
194
|
+
}
|
|
139
195
|
const effectiveRules = rules ?? owaspRules;
|
|
140
196
|
for (const rule of effectiveRules) {
|
|
141
197
|
if (!rule.languages.includes(language))
|
|
@@ -152,25 +208,30 @@ export function analyzeCode(code, language, framework, filePath, configDir, rule
|
|
|
152
208
|
continue;
|
|
153
209
|
if (rule.id.startsWith("VG21") && !filePath && language !== "yaml")
|
|
154
210
|
continue;
|
|
155
|
-
// Context-aware
|
|
211
|
+
// ── Context-aware rule skipping (pattern-agnostic) ──────────────
|
|
212
|
+
const authRuleIds = new Set(["VG420", "VG952", "VG002", "VG402"]);
|
|
213
|
+
const adminRoleRuleIds = new Set(["VG426", "VG957"]);
|
|
214
|
+
const rateLimitRuleIds = new Set(["VG956", "VG030"]);
|
|
156
215
|
const isWebhookRoute = filePath && /webhook/i.test(filePath);
|
|
216
|
+
const isCronRoute = filePath && /(?:cron|scheduled|jobs?)\//i.test(filePath);
|
|
217
|
+
const isAdminRoute = filePath && /\/admin\//i.test(filePath);
|
|
218
|
+
// Skip auth rules when code has any auth guard pattern (naming-agnostic)
|
|
219
|
+
if (codeHasAuthGuard && authRuleIds.has(rule.id))
|
|
220
|
+
continue;
|
|
221
|
+
// Skip admin role rules when code has any role/permission check
|
|
222
|
+
if (codeHasRoleCheck && adminRoleRuleIds.has(rule.id))
|
|
223
|
+
continue;
|
|
224
|
+
// Skip auth rules for webhook routes with signature verification
|
|
157
225
|
const hasSignatureVerification = isWebhookRoute && /(?:verify|signature|hmac|constructEvent|svix|webhookSecret|createHmac|X-Signature|stripe-signature)/i.test(code);
|
|
158
|
-
const authRuleIds = new Set(["VG420", "VG952", "VG002"]);
|
|
159
226
|
if (hasSignatureVerification && authRuleIds.has(rule.id))
|
|
160
227
|
continue;
|
|
161
|
-
//
|
|
162
|
-
const isCronRoute = filePath && /(?:cron|scheduled|jobs?)\//i.test(filePath);
|
|
163
|
-
const rateLimitRuleIds = new Set(["VG956", "VG030"]);
|
|
228
|
+
// Skip rate limiting for cron and webhook routes
|
|
164
229
|
if (isCronRoute && rateLimitRuleIds.has(rule.id))
|
|
165
230
|
continue;
|
|
166
|
-
// Context-aware: skip rate limiting rules for webhook routes
|
|
167
|
-
// Webhooks are called by external services, not users — rate limiting is irrelevant
|
|
168
231
|
if (isWebhookRoute && rateLimitRuleIds.has(rule.id))
|
|
169
232
|
continue;
|
|
170
|
-
//
|
|
171
|
-
|
|
172
|
-
const hasAdminAuth = isAdminRoute && /(?:requireAdmin|adminOnly|orgRole|org:admin|isAdmin|checkRole|requireRole)/i.test(code);
|
|
173
|
-
if (hasAdminAuth && rateLimitRuleIds.has(rule.id))
|
|
233
|
+
// Skip rate limiting for admin routes with auth guard
|
|
234
|
+
if (isAdminRoute && codeHasAuthGuard && rateLimitRuleIds.has(rule.id))
|
|
174
235
|
continue;
|
|
175
236
|
// Skip npm package rules (VG863/VG864/VG865): only apply to package.json files
|
|
176
237
|
if ((rule.id === "VG863" || rule.id === "VG864" || rule.id === "VG865") && filePath && !filePath.endsWith("package.json"))
|
|
@@ -199,7 +260,7 @@ export function analyzeCode(code, language, framework, filePath, configDir, rule
|
|
|
199
260
|
: rule;
|
|
200
261
|
// Context-aware severity: downgrade rate limiting/pagination issues in admin routes
|
|
201
262
|
// Admin routes behind requireAdmin have lower brute-force risk
|
|
202
|
-
if (isAdminRoute &&
|
|
263
|
+
if (isAdminRoute && codeHasAuthGuard) {
|
|
203
264
|
const downgradeInAdmin = new Set(["VG955"]); // pagination in admin is less critical
|
|
204
265
|
if (downgradeInAdmin.has(rule.id) && effectiveRule.severity === "medium") {
|
|
205
266
|
effectiveRule = { ...effectiveRule, severity: "low" };
|
package/build/utils/config.d.ts
CHANGED
|
@@ -22,6 +22,10 @@ export interface GuardVibeConfig {
|
|
|
22
22
|
};
|
|
23
23
|
plugins: string[];
|
|
24
24
|
compliance?: CompliancePolicy;
|
|
25
|
+
/** Custom auth function names that GuardVibe should recognize as auth guards.
|
|
26
|
+
* e.g. ["requireAdmin", "verifyUser", "ensureLoggedIn"]
|
|
27
|
+
* These are added ON TOP of the built-in pattern-agnostic detection. */
|
|
28
|
+
authFunctions?: string[];
|
|
25
29
|
}
|
|
26
30
|
export declare function loadConfig(dir?: string): GuardVibeConfig;
|
|
27
31
|
export declare function resetConfigCache(): void;
|
package/build/utils/config.js
CHANGED
|
@@ -67,6 +67,7 @@ export function loadConfig(dir) {
|
|
|
67
67
|
exceptions: Array.isArray(parsed.compliance.exceptions) ? parsed.compliance.exceptions : [],
|
|
68
68
|
requiredControls: Array.isArray(parsed.compliance.requiredControls) ? parsed.compliance.requiredControls : undefined,
|
|
69
69
|
} : undefined,
|
|
70
|
+
authFunctions: Array.isArray(parsed.authFunctions) ? parsed.authFunctions : undefined,
|
|
70
71
|
};
|
|
71
72
|
}
|
|
72
73
|
catch { }
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "guardvibe",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.1",
|
|
4
4
|
"description": "Security MCP for vibe coding. 277 rules, 24 tools for Next.js, Supabase, Clerk, Stripe, Prisma, tRPC, Hono, GraphQL, Convex, Turso, Uploadthing, AI SDK, and the full AI-generated stack.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|