guardvibe 3.0.27 → 3.0.29
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
|
@@ -31,7 +31,7 @@ export const coreRules = [
|
|
|
31
31
|
severity: "high",
|
|
32
32
|
owasp: "A01:2025 Broken Access Control",
|
|
33
33
|
description: "API route handler without authentication middleware or auth check.",
|
|
34
|
-
pattern: /(?:app|router)\.(get|post|put|delete|patch)\s*\(\s*['"](?!(?:\/(?:api\/)?)?(?:health|status|ping|ready|live|metrics|public|favicon|robots|sitemap)\b)[^'"]*['"]\s*,\s*(?:async\s+)?\(
|
|
34
|
+
pattern: /(?:app|router)\.(get|post|put|delete|patch)\s*\(\s*['"](?!(?:\/(?:api\/)?)?(?:health|status|ping|ready|live|metrics|public|favicon|robots|sitemap)\b)[^'"]*['"]\s*,\s*(?:async\s+)?\(?\b(?:req|request)\b/gi,
|
|
35
35
|
languages: ["javascript", "typescript"],
|
|
36
36
|
fix: "Add authentication middleware before route handlers: app.get('/api/data', authMiddleware, handler). Use frameworks like Passport.js, Clerk, or Auth0.",
|
|
37
37
|
fixCode: "// Add auth middleware before handler\napp.get('/api/data', authMiddleware, async (req, res) => {\n // handler code\n});",
|
|
@@ -162,6 +162,16 @@ function hasAuthGuardPattern(code) {
|
|
|
162
162
|
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)) {
|
|
163
163
|
return true;
|
|
164
164
|
}
|
|
165
|
+
// Pattern 4: Express-style middleware function with auth-related name (sync, takes req/res/next).
|
|
166
|
+
// e.g. `function requireAuth(req, res, next)` or `const authMiddleware = (req, res, next) => {`
|
|
167
|
+
if (/(?:function\s+(?:require|auth|protect|verify|guard|ensure|check|assert|authorize)\w*\s*\(\s*req\b|(?:const|let)\s+(?:require|auth|protect|verify|guard|ensure|check|assert|authorize)\w*\s*=\s*(?:async\s+)?\(\s*req\b)/i.test(code)) {
|
|
168
|
+
return true;
|
|
169
|
+
}
|
|
170
|
+
// Pattern 5: middleware passed inline to express route registration.
|
|
171
|
+
// e.g. `app.get('/x', requireAuth, handler)` or `router.post('/y', authMiddleware, ...)`.
|
|
172
|
+
if (/(?:app|router)\.(?:get|post|put|delete|patch|all|use)\s*\([^,)]+,\s*(?:require|auth|protect|verify|guard|ensure|check|assert|authorize|isAuthenticated|isLoggedIn|hasPermission)\w*\s*[,\)]/i.test(code)) {
|
|
173
|
+
return true;
|
|
174
|
+
}
|
|
165
175
|
return false;
|
|
166
176
|
}
|
|
167
177
|
/**
|
|
@@ -304,6 +314,20 @@ export function analyzeCode(code, language, framework, filePath, configDir, rule
|
|
|
304
314
|
continue;
|
|
305
315
|
if (rule.id.startsWith("VG21") && !filePath && language !== "yaml")
|
|
306
316
|
continue;
|
|
317
|
+
// Skip credential rules in test files — fixtures and assertions intentionally use fake values.
|
|
318
|
+
const isTestFile = filePath && /(?:\.(?:spec|test|e2e|stories)\.(?:ts|tsx|js|jsx|mjs|cjs)$|\/__tests__\/|\/tests?\/|\/cypress\/|\/playwright\/)/i.test(filePath);
|
|
319
|
+
if (isTestFile && (rule.id === "VG001" || rule.id === "VG062"))
|
|
320
|
+
continue;
|
|
321
|
+
// Skip Expo-specific rule (VG708) when project is not an Expo app.
|
|
322
|
+
// The rule's regex incorrectly matches the literal strings "app.json"/"app.config.ts"
|
|
323
|
+
// appearing in unrelated configs (e.g. angular.json's tsConfig field).
|
|
324
|
+
if (rule.id === "VG708" && filePath) {
|
|
325
|
+
const fileName = filePath.split("/").pop() ?? "";
|
|
326
|
+
const isExpoConfigFile = /^app\.(json|config\.(js|ts|mjs|cjs))$/.test(fileName);
|
|
327
|
+
const importsExpo = /(?:from\s+['"]expo[\w-]*['"]|require\s*\(\s*['"]expo[\w-]*['"])/i.test(code);
|
|
328
|
+
if (!isExpoConfigFile && !importsExpo)
|
|
329
|
+
continue;
|
|
330
|
+
}
|
|
307
331
|
// ── Context-aware rule skipping (pattern-agnostic) ──────────────
|
|
308
332
|
const authRuleIds = new Set(["VG420", "VG952", "VG002", "VG402"]);
|
|
309
333
|
const adminRoleRuleIds = new Set(["VG426", "VG957"]);
|
|
@@ -538,12 +562,45 @@ export function analyzeCode(code, language, framework, filePath, configDir, rule
|
|
|
538
562
|
if (isHumanReadableString(lines, lineNumber))
|
|
539
563
|
continue;
|
|
540
564
|
}
|
|
565
|
+
// Skip credential rules when the variable name signals test/example/mock intent.
|
|
566
|
+
// e.g. `testingPassword`, `examplePassword`, `mockApiKey`, `placeholderSecret`.
|
|
567
|
+
if (rule.id === "VG001" || rule.id === "VG062") {
|
|
568
|
+
const matchedLine = lines[lineNumber - 1] ?? "";
|
|
569
|
+
if (/(?:^|\s|\b)(?:testing|example|mock|placeholder|sample|demo|fake|dummy|stub|fixture)[A-Z_]/.test(matchedLine))
|
|
570
|
+
continue;
|
|
571
|
+
}
|
|
572
|
+
// Skip VG010 (SQL injection) on Angular HTTP service calls — http.get/post/etc.
|
|
573
|
+
// are HTTP client methods, not SQL. The existing pattern's `get` keyword catches them.
|
|
574
|
+
if (rule.id === "VG010") {
|
|
575
|
+
const matchedLine = lines[lineNumber - 1] ?? "";
|
|
576
|
+
const isHttpClientCall = /(?:this\.)?(?:http|httpClient|httpService|api|client)\.(?:get|post|put|delete|patch|head|options)\s*\(/i.test(matchedLine);
|
|
577
|
+
const importsHttpClient = /from\s+['"]@angular\/common\/http['"]/i.test(code) || /import\s+.*HttpClient/i.test(code);
|
|
578
|
+
const fileIsAngularService = filePath ? /\.(?:service|component|directive|pipe|guard|resolver|interceptor)\.ts$/i.test(filePath) : false;
|
|
579
|
+
if (isHttpClientCall && (importsHttpClient || fileIsAngularService))
|
|
580
|
+
continue;
|
|
581
|
+
// Also skip when matched call has no SQL keyword anywhere on the line — covers fetch/axios template-literal URLs.
|
|
582
|
+
const hasSqlKeyword = /\b(?:SELECT|INSERT|UPDATE|DELETE|FROM|WHERE|JOIN|UNION|DROP|TRUNCATE|ALTER|CREATE\s+TABLE)\b/i.test(matchedLine);
|
|
583
|
+
const isFetchOrAxios = /(?:fetch|axios|got|ky|undici|request)\s*[.\(]|axios\.(?:get|post|put|delete|patch)/i.test(matchedLine);
|
|
584
|
+
if (isFetchOrAxios && !hasSqlKeyword)
|
|
585
|
+
continue;
|
|
586
|
+
}
|
|
541
587
|
// Skip supply chain rules for known legitimate packages
|
|
542
588
|
if (["VG872", "VG873"].includes(rule.id)) {
|
|
543
589
|
const pkgMatch = /"([\w@/-]+)"/.exec(match[0]);
|
|
544
590
|
if (pkgMatch && isLegitimatePackage(pkgMatch[1]))
|
|
545
591
|
continue;
|
|
546
592
|
}
|
|
593
|
+
// Skip VG863 for non-publishable apps. Signals that this is an application, not a library:
|
|
594
|
+
// no publishing fields (bin/exports/module/types), main does not point at a build dir,
|
|
595
|
+
// and start script runs a runtime/framework directly.
|
|
596
|
+
if (rule.id === "VG863") {
|
|
597
|
+
const hasPublishingFields = /"(?:bin|exports|module|types|typings)"\s*:/i.test(code);
|
|
598
|
+
const mainPointsToBuild = /"main"\s*:\s*"(?:dist|build|lib|out)\//i.test(code);
|
|
599
|
+
const runtimeNames = "node|nodemon|tsx|ts-node|next|nest|vite|remix|astro";
|
|
600
|
+
const startsAsApp = new RegExp('"start"\\s*:\\s*"(?:' + runtimeNames + ')\\b', "i").test(code);
|
|
601
|
+
if (!hasPublishingFields && !mainPointsToBuild && startsAsApp)
|
|
602
|
+
continue;
|
|
603
|
+
}
|
|
547
604
|
// Skip VG106 for non-secret variable names (TokenCount, tokenBalance, hashMap, etc.)
|
|
548
605
|
if (rule.id === "VG106") {
|
|
549
606
|
const varName = match[0].split(/\s*(?:===|!==|==|!=)/)[0].trim();
|
|
@@ -649,6 +706,11 @@ function isDuplicatePair(a, b) {
|
|
|
649
706
|
// Same rule name = same vulnerability
|
|
650
707
|
if (a.rule.name === b.rule.name)
|
|
651
708
|
return true;
|
|
709
|
+
// Both are SQL injection variants — VG010 (generic) and VG123 (template literal specific) overlap.
|
|
710
|
+
// VG123 is more specific so it should dominate. isMoreSpecific handles the prefix order.
|
|
711
|
+
const sqlInjectionRules = new Set(["VG010", "VG123"]);
|
|
712
|
+
if (sqlInjectionRules.has(a.rule.id) && sqlInjectionRules.has(b.rule.id))
|
|
713
|
+
return true;
|
|
652
714
|
// Both are XSS/innerHTML related — the core VG012+VG408 duplicate case
|
|
653
715
|
if (a.rule.name.includes("innerHTML") && b.rule.name.includes("innerHTML"))
|
|
654
716
|
return true;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "guardvibe",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.29",
|
|
4
4
|
"mcpName": "io.github.goklab/guardvibe",
|
|
5
5
|
"description": "Security MCP for vibe coding. 365 rules, 36 tools, CLI + doctor. Host security, auth coverage mapping, LLM-powered deep scan (IDOR/business logic), taint analysis. 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",
|