guardvibe 3.0.46 → 3.0.48
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/advanced-security.js +2 -2
- package/build/index.js +2 -1
- package/build/tools/auth-coverage.js +2 -1
- package/build/tools/check-code.d.ts +6 -0
- package/build/tools/check-code.js +71 -17
- package/build/tools/cross-file-taint.js +2 -1
- package/build/tools/full-audit.js +5 -1
- package/build/tools/scan-secrets.js +5 -0
- package/build/tools/taint-analysis.d.ts +1 -1
- package/build/tools/taint-analysis.js +6 -1
- package/build/utils/ignore.js +2 -0
- package/package.json +1 -1
|
@@ -291,7 +291,7 @@ export const advancedSecurityRules = [
|
|
|
291
291
|
severity: "high",
|
|
292
292
|
owasp: "A03:2025 Injection",
|
|
293
293
|
description: "User-controlled input is used as an object property key via bracket notation (obj[userInput]). Attackers can access __proto__, constructor, or prototype to pollute object prototypes and bypass security checks.",
|
|
294
|
-
pattern: /(?:(?:req|request|body|query|params
|
|
294
|
+
pattern: /(?:(?:req|request|body|query|params)\.\w+|(?:const|let|var)\s+(?:\{[^}]*\}|\w+)\s*=\s*(?:await\s+)?(?:req|request)[\s\S]{0,50}?)[\s\S]{0,100}?\w+\s*\[\s*(?:key|field|prop|name|column|attr|param)\s*\]/gi,
|
|
295
295
|
languages: ["javascript", "typescript"],
|
|
296
296
|
fix: "Validate property names against an allowlist, or use Map instead of plain objects.",
|
|
297
297
|
fixCode: '// BAD: prototype pollution\nconst key = req.query.field;\nconst value = config[key];\n\n// GOOD: allowlist validation\nconst ALLOWED_FIELDS = new Set(["name", "email", "role"]);\nif (!ALLOWED_FIELDS.has(key)) return new Response("Invalid field", { status: 400 });\nconst value = config[key];\n\n// GOOD: use Map\nconst config = new Map([["name", "..."], ["email", "..."]]);\nconst value = config.get(key);',
|
|
@@ -304,7 +304,7 @@ export const advancedSecurityRules = [
|
|
|
304
304
|
severity: "medium",
|
|
305
305
|
owasp: "A04:2025 Insecure Design",
|
|
306
306
|
description: "Regular expression contains nested quantifiers ((a+)+), overlapping alternation with quantifiers (([a-z]+)*), or other patterns that cause catastrophic backtracking. Attackers can send crafted input to freeze the event loop.",
|
|
307
|
-
pattern: /\/(?:[
|
|
307
|
+
pattern: /\/(?:[^/\\\n]|\\.)*(?:\([^)\n]*[+*][^)\n]*\)\s*[+*]|\(\?:[^)\n]*[+*][^)\n]*\)\s*[+*])(?:[^/\\\n]|\\.)*\//g,
|
|
308
308
|
languages: ["javascript", "typescript"],
|
|
309
309
|
fix: "Rewrite the regex to avoid nested quantifiers. Use atomic groups or possessive quantifiers if available, or use the 'safe-regex' library to validate patterns.",
|
|
310
310
|
fixCode: '// BAD: catastrophic backtracking\nconst re = /(a+)+$/;\n\n// GOOD: no nested quantifiers\nconst re = /a+$/;\n\n// GOOD: validate with safe-regex\nimport safe from "safe-regex";\nif (!safe(pattern)) throw new Error("Unsafe regex");',
|
package/build/index.js
CHANGED
|
@@ -483,7 +483,8 @@ server.tool("explain_remediation", "Pass a GuardVibe rule ID (e.g. VG154) to get
|
|
|
483
483
|
const results = explainRemediation(rule_id, code, format, rules);
|
|
484
484
|
return { content: [{ type: "text", text: results }] };
|
|
485
485
|
});
|
|
486
|
-
// Tool 23: Quick file scan —
|
|
486
|
+
// Tool 23: Quick file scan — returns structured findings, not raw file contents.
|
|
487
|
+
// guardvibe-ignore VG880
|
|
487
488
|
server.tool("scan_file", "Scan a single file on disk by path for security vulnerabilities. Pass a file path — the tool reads the file itself. For inline code snippets, use check_code instead. Example: scan_file({file_path: 'src/api/route.ts'})", {
|
|
488
489
|
file_path: z.string().describe("Absolute or relative path to the file to scan"),
|
|
489
490
|
format: z.enum(["markdown", "json"]).default("json").describe("Output format"),
|
|
@@ -135,7 +135,7 @@ function hasAuthGuard(code) {
|
|
|
135
135
|
// 401/403 responses indicating auth enforcement
|
|
136
136
|
if (/(?:status:\s*(?:401|403)|new\s+Response\s*\([^)]*(?:401|403)|Unauthorized|Forbidden)/.test(code))
|
|
137
137
|
return true;
|
|
138
|
-
//
|
|
138
|
+
// guardvibe-ignore VG153
|
|
139
139
|
if (/await\s+(?:\w+\.)*\w*(?:auth|Auth|session|Session|permission|Permission|guard|Guard|verify|Verify|protect|Protect)\w*\s*\(/i.test(code))
|
|
140
140
|
return true;
|
|
141
141
|
return false;
|
|
@@ -210,6 +210,7 @@ export function analyzeAuthCoverage(routeFiles, middlewareContent, layoutFiles,
|
|
|
210
210
|
if (route.hasAuthGuard || route.middlewareCovered)
|
|
211
211
|
continue;
|
|
212
212
|
const isExcepted = authExceptions.some(exc => {
|
|
213
|
+
// guardvibe-ignore VG153
|
|
213
214
|
const excPath = exc.path.replace(/\[[\w]+\]/g, "[^/]+");
|
|
214
215
|
const regex = new RegExp("^" + excPath.replace(/\//g, "\\/") + "$");
|
|
215
216
|
return regex.test(route.urlPath) || route.urlPath === exc.path || route.urlPath.startsWith(exc.path + "/");
|
|
@@ -5,6 +5,12 @@ export interface Finding {
|
|
|
5
5
|
line: number;
|
|
6
6
|
confidence: "high" | "medium" | "low";
|
|
7
7
|
}
|
|
8
|
+
/**
|
|
9
|
+
* Detect if a file is a security rule definition file.
|
|
10
|
+
* These files intentionally contain vulnerable code patterns
|
|
11
|
+
* as regex matchers and fixCode examples — scanning them is meaningless.
|
|
12
|
+
*/
|
|
13
|
+
export declare function isRuleDefinitionFile(code: string, filePath?: string): boolean;
|
|
8
14
|
export declare function analyzeCode(code: string, language: string, framework?: string, filePath?: string, configDir?: string, rules?: SecurityRule[]): Finding[];
|
|
9
15
|
export declare function formatFindingsJson(findings: Finding[], extra?: Record<string, unknown>): string;
|
|
10
16
|
export declare function checkCode(code: string, language: string, framework?: string, filePath?: string, configDir?: string, format?: "markdown" | "json" | "buddy", rules?: SecurityRule[]): string;
|
|
@@ -3,6 +3,10 @@ import { owaspRules } from "../data/rules/index.js";
|
|
|
3
3
|
import { loadConfig } from "../utils/config.js";
|
|
4
4
|
import { loadIgnoreFile, isIgnored } from "../utils/ignore.js";
|
|
5
5
|
import { securityBanner } from "../utils/banner.js";
|
|
6
|
+
/** CVE version-pin rule IDs are VG900-VG931 (and only these). Other VG9xx IDs
|
|
7
|
+
* (VG983 Turso, VG990 SVG, VG998 OpenAI browser flag, etc.) are regular code-pattern
|
|
8
|
+
* rules and should NOT be exempted from comment / string-literal skip logic. */
|
|
9
|
+
const CVE_VERSION_RULE = /^VG9(?:0\d|1\d|2\d|3[01])$/;
|
|
6
10
|
function parseSuppressionsFromCode(lines) {
|
|
7
11
|
const suppressions = [];
|
|
8
12
|
const pattern = /(?:\/\/|#|<!--)\s*guardvibe-ignore(?:-next-line)?\s*(VG\d+)?(?:\s.*)?(?:-->)?/i;
|
|
@@ -17,8 +21,22 @@ function parseSuppressionsFromCode(lines) {
|
|
|
17
21
|
suppressions.push({ line: i + 2, ruleId });
|
|
18
22
|
}
|
|
19
23
|
else if (isCommentOnlyLine) {
|
|
24
|
+
// Comment-only line: suppress the comment's own line plus the next several
|
|
25
|
+
// lines, stopping early at a blank line or a new comment block. This makes
|
|
26
|
+
// suppress comments work for multi-line method chains (common Supabase / ORM
|
|
27
|
+
// builders span 3-5 lines from `.from(...)` through `.select(...).order(...)`).
|
|
20
28
|
suppressions.push({ line: i + 1, ruleId });
|
|
21
|
-
|
|
29
|
+
for (let j = 1; j <= 5; j++) {
|
|
30
|
+
const nextLine = lines[i + j];
|
|
31
|
+
if (nextLine === undefined)
|
|
32
|
+
break;
|
|
33
|
+
const trimmed = nextLine.trim();
|
|
34
|
+
if (trimmed === "")
|
|
35
|
+
break;
|
|
36
|
+
if (/^\s*(?:\/\/|#|<!--)/.test(nextLine))
|
|
37
|
+
break;
|
|
38
|
+
suppressions.push({ line: i + 1 + j, ruleId });
|
|
39
|
+
}
|
|
22
40
|
}
|
|
23
41
|
else {
|
|
24
42
|
suppressions.push({ line: i + 1, ruleId });
|
|
@@ -66,10 +84,7 @@ function isInsideStringLiteral(lines, lineNumber, code, matchIndex) {
|
|
|
66
84
|
if (/^\s*\+\s*["']/.test(line))
|
|
67
85
|
return true; // + "string continuation"
|
|
68
86
|
// 3. Line contains escaped newlines (\n) suggesting it's inside a string value
|
|
69
|
-
const _quotesBefore = line.substring(0, line.indexOf(trimmed.charAt(0)));
|
|
70
87
|
if (/\\n/.test(line) && /["'`].*\\n/.test(line)) {
|
|
71
|
-
// Extra check: is the match portion inside quotes on this line?
|
|
72
|
-
const _matchEnd = matchIndex + 20; // approximate
|
|
73
88
|
const lineStart = code.lastIndexOf("\n", matchIndex) + 1;
|
|
74
89
|
const col = matchIndex - lineStart;
|
|
75
90
|
const beforeCol = line.substring(0, col);
|
|
@@ -79,11 +94,12 @@ function isInsideStringLiteral(lines, lineNumber, code, matchIndex) {
|
|
|
79
94
|
return true;
|
|
80
95
|
}
|
|
81
96
|
// 4. Look backwards for property assignment context (fixCode, description, etc.)
|
|
97
|
+
// Includes display-string props (title, message, label) used by audit / report
|
|
98
|
+
// tools to surface findings — these contain mention of vulnerable patterns by name.
|
|
99
|
+
const PROP_RE = /^(?:fixCode|fix|description|exploit|audit|title|message|label|reason|details|summary|hint)\s*[:=]/;
|
|
82
100
|
for (let i = lineNumber - 1; i >= Math.max(0, lineNumber - 20); i--) {
|
|
83
101
|
const prev = lines[i]?.trimStart() || "";
|
|
84
|
-
if (
|
|
85
|
-
return true;
|
|
86
|
-
if (/^(?:fixCode|fix|description|exploit|audit)\s*:\s*$/.test(prev))
|
|
102
|
+
if (PROP_RE.test(prev))
|
|
87
103
|
return true;
|
|
88
104
|
// Hit a rule boundary — stop looking
|
|
89
105
|
if (/^\s*id\s*:\s*["']VG/.test(prev))
|
|
@@ -117,7 +133,7 @@ function isHumanReadableString(lines, lineNumber) {
|
|
|
117
133
|
* These files intentionally contain vulnerable code patterns
|
|
118
134
|
* as regex matchers and fixCode examples — scanning them is meaningless.
|
|
119
135
|
*/
|
|
120
|
-
function isRuleDefinitionFile(code, filePath) {
|
|
136
|
+
export function isRuleDefinitionFile(code, filePath) {
|
|
121
137
|
// Path-based: known rule definition directories
|
|
122
138
|
if (filePath && /(?:\/rules\/|\/data\/rules\/)/.test(filePath)) {
|
|
123
139
|
// Confirm it actually exports SecurityRule objects
|
|
@@ -125,6 +141,10 @@ function isRuleDefinitionFile(code, filePath) {
|
|
|
125
141
|
return true;
|
|
126
142
|
}
|
|
127
143
|
}
|
|
144
|
+
// Path-based: framework guides and similar pure-documentation files that hold
|
|
145
|
+
// example code inside markdown template literals
|
|
146
|
+
if (filePath && /(?:^|\/)framework-guides\.ts$/.test(filePath))
|
|
147
|
+
return true;
|
|
128
148
|
// Content-based: file defines multiple VG rules with pattern: regex
|
|
129
149
|
if (/id:\s*["']VG\d+["']/g.test(code) && /pattern:\s*\//.test(code)) {
|
|
130
150
|
const ruleCount = (code.match(/id:\s*["']VG\d+["']/g) || []).length;
|
|
@@ -378,6 +398,12 @@ export function analyzeCode(code, language, framework, filePath, configDir, rule
|
|
|
378
398
|
// for batch processing, not for serving to clients.
|
|
379
399
|
if (rule.id === "VG955" && (isBatchScriptFile || isCronRoute))
|
|
380
400
|
continue;
|
|
401
|
+
// Skip VG1006 (select('*') exposes columns) in batch/script paths — debug scripts,
|
|
402
|
+
// migrations, seeds, and fixtures run under service-role and write/log data
|
|
403
|
+
// server-side; '*' doesn't expose anything to a client. Application routes that
|
|
404
|
+
// do expose data still get flagged.
|
|
405
|
+
if (rule.id === "VG1006" && isBatchScriptFile)
|
|
406
|
+
continue;
|
|
381
407
|
// Skip VG132 (Missing Request Body Size Limit) on Next.js route handlers and
|
|
382
408
|
// pages/api endpoints — Next.js/Vercel apply a default 4.5MB body limit at the
|
|
383
409
|
// platform layer, which is what the rule is checking for.
|
|
@@ -526,7 +552,7 @@ export function analyzeCode(code, language, framework, filePath, configDir, rule
|
|
|
526
552
|
// to match top-level dependency declarations in package.json. Lock files contain
|
|
527
553
|
// sub-package peer dependency ranges (e.g. "next": ">=13.2.0" from a transitive dep)
|
|
528
554
|
// which look like vulnerable pins but represent peer requirements, not installed versions.
|
|
529
|
-
if (filePath &&
|
|
555
|
+
if (filePath && CVE_VERSION_RULE.test(rule.id) && /(?:package-lock\.json|yarn\.lock|pnpm-lock\.yaml|npm-shrinkwrap\.json)$/.test(filePath))
|
|
530
556
|
continue;
|
|
531
557
|
// Skip VG430 (Supabase anon key on server) when file properly separates client/server
|
|
532
558
|
// or is a React Native/mobile client (anon key with AsyncStorage is correct pattern)
|
|
@@ -589,18 +615,35 @@ export function analyzeCode(code, language, framework, filePath, configDir, rule
|
|
|
589
615
|
const lineNumber = beforeMatch.split("\n").length;
|
|
590
616
|
if (isLineSuppressed(suppressions, lineNumber, rule.id))
|
|
591
617
|
continue;
|
|
592
|
-
// Skip matches on comment lines
|
|
593
|
-
// CVE version rules (
|
|
594
|
-
|
|
595
|
-
|
|
618
|
+
// Skip matches on comment lines and inside string literals.
|
|
619
|
+
// CVE version-pin rules (VG900-VG931) are exempt — they scan package.json
|
|
620
|
+
// dependency declarations where these contexts don't apply.
|
|
621
|
+
// For multi-line matches, only string-literal skip is applied: the match's
|
|
622
|
+
// starting line may legitimately be a comment while the vulnerable code is
|
|
623
|
+
// on a later line (e.g. VG966 OAuth callback comment + handler).
|
|
624
|
+
if (!CVE_VERSION_RULE.test(rule.id)) {
|
|
625
|
+
const isMultiLineMatch = match[0].includes("\n");
|
|
626
|
+
if (!isMultiLineMatch && isInComment(lines, lineNumber))
|
|
596
627
|
continue;
|
|
597
|
-
}
|
|
598
|
-
// Skip matches inside string literals (fixCode, description, template strings)
|
|
599
|
-
// This prevents rule definition files and docs from triggering false positives
|
|
600
|
-
if (!rule.id.startsWith("VG9")) {
|
|
601
628
|
if (isInsideStringLiteral(lines, lineNumber, code, match.index))
|
|
602
629
|
continue;
|
|
603
630
|
}
|
|
631
|
+
// VG020 (wildcard dep version) on package.json: skip the `engines` block —
|
|
632
|
+
// `"node": ">=18.0.0"` is a runtime constraint, not a dependency range.
|
|
633
|
+
if (rule.id === "VG020" && filePath && /package\.json$/.test(filePath)) {
|
|
634
|
+
let inEngines = false;
|
|
635
|
+
for (let j = lineNumber - 1; j >= Math.max(0, lineNumber - 6); j--) {
|
|
636
|
+
const prev = lines[j] ?? "";
|
|
637
|
+
if (/"engines"\s*:\s*\{/.test(prev)) {
|
|
638
|
+
inEngines = true;
|
|
639
|
+
break;
|
|
640
|
+
}
|
|
641
|
+
if (/^\s*\}/.test(prev))
|
|
642
|
+
break; // closed a previous block — not in engines
|
|
643
|
+
}
|
|
644
|
+
if (inEngines)
|
|
645
|
+
continue;
|
|
646
|
+
}
|
|
604
647
|
// Skip hardcoded-credential rules when the value is a human-readable sentence
|
|
605
648
|
if (rule.id === "VG001" || rule.id === "VG062") {
|
|
606
649
|
if (isHumanReadableString(lines, lineNumber))
|
|
@@ -645,6 +688,7 @@ export function analyzeCode(code, language, framework, filePath, configDir, rule
|
|
|
645
688
|
const mainPointsToBuild = /"main"\s*:\s*"(?:dist|build|lib|out)\//i.test(code);
|
|
646
689
|
const runtimeNames = "node|nodemon|tsx|ts-node|next|nest|vite|remix|astro";
|
|
647
690
|
// Allow leading env-var assignments: NODE_OPTIONS=..., NODE_ENV=production, PORT=3000, etc.
|
|
691
|
+
// guardvibe-ignore VG153
|
|
648
692
|
const startsAsApp = new RegExp('"start"\\s*:\\s*"(?:[A-Z_][A-Z0-9_]*=\\S+\\s+)*(?:' + runtimeNames + ')\\b', "i").test(code);
|
|
649
693
|
if (!hasPublishingFields && !mainPointsToBuild && startsAsApp)
|
|
650
694
|
continue;
|
|
@@ -674,6 +718,16 @@ export function analyzeCode(code, language, framework, filePath, configDir, rule
|
|
|
674
718
|
if (/\.(?:order|range|limit)\s*\(/i.test(matched))
|
|
675
719
|
continue;
|
|
676
720
|
}
|
|
721
|
+
// Skip VG1006 (select('*') exposes columns) for count-only queries —
|
|
722
|
+
// .select('*', { count: 'exact', head: true }) returns only a row count, never
|
|
723
|
+
// materializes rows, so '*' doesn't expose any columns. The rule's pattern
|
|
724
|
+
// truncates at the `*` quote, so we look at the next ~120 chars for the head
|
|
725
|
+
// option (typical select(...) options object fits in that window).
|
|
726
|
+
if (rule.id === "VG1006") {
|
|
727
|
+
const tail = code.substring(match.index, match.index + (match[0].length + 120));
|
|
728
|
+
if (/\bhead\s*:\s*true\b/i.test(tail))
|
|
729
|
+
continue;
|
|
730
|
+
}
|
|
677
731
|
// Skip VG155 (CSRF) in Next.js App Router route handlers (app/.../route.{ts,tsx,js,jsx}).
|
|
678
732
|
// App Router protects state-changing requests by default: SameSite=Lax cookies block
|
|
679
733
|
// cross-site cookie attachment, and JSON Content-Type triggers CORS preflight. Bearer-token
|
|
@@ -602,6 +602,7 @@ function deriveSeverity(sinkType) {
|
|
|
602
602
|
return "medium";
|
|
603
603
|
}
|
|
604
604
|
function getSinkFix(sinkType) {
|
|
605
|
+
// guardvibe-ignore VG014
|
|
605
606
|
const fixes = {
|
|
606
607
|
"sql-injection": "Use parameterized queries instead of string interpolation.",
|
|
607
608
|
"code-injection": "Never pass user input to eval() or Function constructor.",
|
|
@@ -618,7 +619,7 @@ export function analyzeCrossFileTaint(files) {
|
|
|
618
619
|
const lang = detectLang(file.path);
|
|
619
620
|
if (lang === "unknown")
|
|
620
621
|
continue;
|
|
621
|
-
const findings = analyzeTaint(file.content, lang);
|
|
622
|
+
const findings = analyzeTaint(file.content, lang, file.path);
|
|
622
623
|
if (findings.length > 0)
|
|
623
624
|
perFileFindings.set(file.path, findings);
|
|
624
625
|
}
|
|
@@ -67,7 +67,11 @@ function parseSectionCounts(parsed) {
|
|
|
67
67
|
}
|
|
68
68
|
function collectJsFiles(dir, maxFiles = 200) {
|
|
69
69
|
const files = [];
|
|
70
|
-
const
|
|
70
|
+
const config = loadConfig(resolve(dir));
|
|
71
|
+
const skip = new Set([
|
|
72
|
+
"node_modules", ".git", ".next", "build", "dist", ".turbo", "coverage",
|
|
73
|
+
...config.scan.exclude,
|
|
74
|
+
]);
|
|
71
75
|
function walk(d) {
|
|
72
76
|
if (files.length >= maxFiles)
|
|
73
77
|
return;
|
|
@@ -4,6 +4,7 @@ import { execFileSync } from "child_process";
|
|
|
4
4
|
import { secretPatterns, calculateEntropy } from "../data/secret-patterns.js";
|
|
5
5
|
import { loadConfig } from "../utils/config.js";
|
|
6
6
|
import { isExcludedFilename } from "../utils/constants.js";
|
|
7
|
+
import { isRuleDefinitionFile } from "./check-code.js";
|
|
7
8
|
const DEFAULT_SECRET_EXCLUDES = new Set(["node_modules", ".git", "build", "dist"]);
|
|
8
9
|
const SOURCE_FILE_EXTENSIONS = new Set([
|
|
9
10
|
".js", ".jsx", ".mjs", ".cjs",
|
|
@@ -14,6 +15,10 @@ const SOURCE_FILE_EXTENSIONS = new Set([
|
|
|
14
15
|
const CONFIG_FILE_EXTENSIONS = new Set([".yml", ".yaml", ".toml", ".json", ".cfg", ".ini", ".conf"]);
|
|
15
16
|
export function scanContent(content, filename) {
|
|
16
17
|
const findings = [];
|
|
18
|
+
// Skip security rule definition files — pattern strings inside them frequently
|
|
19
|
+
// resemble real secret formats by design.
|
|
20
|
+
if (isRuleDefinitionFile(content, filename))
|
|
21
|
+
return findings;
|
|
17
22
|
for (const sp of secretPatterns) {
|
|
18
23
|
sp.pattern.lastIndex = 0;
|
|
19
24
|
let match;
|
|
@@ -18,5 +18,5 @@ export interface TaintFinding {
|
|
|
18
18
|
description: string;
|
|
19
19
|
fix: string;
|
|
20
20
|
}
|
|
21
|
-
export declare function analyzeTaint(code: string, language: string): TaintFinding[];
|
|
21
|
+
export declare function analyzeTaint(code: string, language: string, filePath?: string): TaintFinding[];
|
|
22
22
|
export declare function formatTaintFindings(findings: TaintFinding[], format: "markdown" | "json"): string;
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* Basic taint analysis — tracks user input flowing into dangerous sinks.
|
|
4
4
|
* Not a full AST/CFG analysis, but follows variable assignments through lines.
|
|
5
5
|
*/
|
|
6
|
+
import { isRuleDefinitionFile } from "./check-code.js";
|
|
6
7
|
// User input sources (tainted data entry points)
|
|
7
8
|
const TAINT_SOURCES = [
|
|
8
9
|
{ pattern: /(?:req|request)\.(?:body|query|params|headers|cookies)\b/g, type: "http-input" },
|
|
@@ -111,9 +112,13 @@ function propagateTaint(assignments, lines) {
|
|
|
111
112
|
}
|
|
112
113
|
}
|
|
113
114
|
}
|
|
114
|
-
export function analyzeTaint(code, language) {
|
|
115
|
+
export function analyzeTaint(code, language, filePath) {
|
|
115
116
|
if (!["javascript", "typescript"].includes(language))
|
|
116
117
|
return [];
|
|
118
|
+
// Skip security rule definition files — they intentionally contain vulnerable
|
|
119
|
+
// code snippets in pattern regexes, fixCode strings, and exploit examples.
|
|
120
|
+
if (isRuleDefinitionFile(code, filePath))
|
|
121
|
+
return [];
|
|
117
122
|
const lines = code.split("\n");
|
|
118
123
|
const findings = [];
|
|
119
124
|
const assignments = extractAssignments(lines);
|
package/build/utils/ignore.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "guardvibe",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.48",
|
|
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",
|