guardvibe 3.1.3 → 3.1.5
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.
|
@@ -47,7 +47,11 @@ export const advancedSecurityRules = [
|
|
|
47
47
|
severity: "high",
|
|
48
48
|
owasp: "A04:2025 Insecure Design",
|
|
49
49
|
description: "Code reads a value, checks a condition, then updates based on the check — without a database transaction. Two concurrent requests can both pass the check before either writes, leading to double-spending, overselling, or duplicate operations.",
|
|
50
|
-
|
|
50
|
+
// Negative lookahead at the start of the if-body skips the 404-mapping shape
|
|
51
|
+
// (`if (!x) { return …; }`). Without it, the engine's backtracking matched updates
|
|
52
|
+
// that lived OUTSIDE the if-block (within 500-char window after the brace), making
|
|
53
|
+
// every `findUnique → if(!x) 404 → update` admin route a false hit.
|
|
54
|
+
pattern: /(?:findUnique|findFirst|findOne|findById)\s*\([\s\S]{0,200}?\)\s*;?\s*\n[\s\S]{0,300}?if\s*\([\s\S]{0,200}?\)\s*\{(?!\s*(?:return\b|throw\b|res\.\w+\(|response\.\w+\(|next\.\w+\(|NextResponse\.))[\s\S]{0,500}?(?:\.update\s*\(|\.delete\s*\(|\.decrement|\.increment)(?:(?!\$transaction|\.transaction|BEGIN|SERIALIZABLE|FOR UPDATE|NOWAIT)[\s\S]){0,300}?\}/g,
|
|
51
55
|
languages: ["javascript", "typescript"],
|
|
52
56
|
fix: "Wrap check-then-act sequences in a database transaction, or use atomic operations (e.g., UPDATE WHERE balance >= amount).",
|
|
53
57
|
fixCode: '// BAD: race condition\nconst account = await db.account.findUnique({ where: { id } });\nif (account.balance >= 100) {\n await db.account.update({ where: { id }, data: { balance: { decrement: 100 } } });\n}\n\n// GOOD: atomic transaction\nawait db.$transaction(async (tx) => {\n const account = await tx.account.findUnique({ where: { id } });\n if (account.balance < 100) throw new Error("Insufficient");\n await tx.account.update({ where: { id }, data: { balance: { decrement: 100 } } });\n});',
|
|
@@ -96,7 +96,12 @@ export const aiToolRuntimeRules = [
|
|
|
96
96
|
severity: "high",
|
|
97
97
|
owasp: "A05:2025 Security Misconfiguration",
|
|
98
98
|
description: "AI tool / MCP tool parameter schema (`z.enum(...)`, JSON Schema `enum`) is constructed at runtime from user input, fetched data, or a mutable variable. Runtime-mutable schemas defeat the safety guarantees the LLM relies on — an attacker can widen the accepted enum set or inject schema fields by poisoning the input.",
|
|
99
|
-
|
|
99
|
+
// Lowercase-start identifier required: PascalCase (`FraudAlertStatus`) and SCREAMING_SNAKE
|
|
100
|
+
// (`STATUSES`) are TypeScript enum imports / module-level const arrays — compile-time
|
|
101
|
+
// static, not user-mutable. Real attack shape uses lowercase variable names
|
|
102
|
+
// (`allowedActions`, `userActions`, `...userInput`). Template-literal interpolation in
|
|
103
|
+
// the JSON-schema branch (`enum: \`...${x}...\``) stays matched — that IS a real risk.
|
|
104
|
+
pattern: /(?:z\.enum\s*\(\s*(?!\[\s*["'])(?:[a-z_$][\w$]*|\.\.\.[a-z_$]\w*)|["']enum["']\s*:\s*(?!\[\s*(?:["']|true|false|null|\d))(?:[a-z_$][\w$]*\b|`[^`]*\$\{))/g,
|
|
100
105
|
languages: ["javascript", "typescript"],
|
|
101
106
|
fix: "Define enum values as static literal arrays in source. Never compute schema enums from runtime data.",
|
|
102
107
|
fixCode: '// SAFE:\nparameters: z.object({\n action: z.enum(["read", "list"]),\n})\n\n// UNSAFE — user controls allowed actions:\n// parameters: z.object({ action: z.enum(allowedActions) })',
|
|
@@ -53,7 +53,10 @@ export const dockerfileRules = [
|
|
|
53
53
|
severity: "medium",
|
|
54
54
|
owasp: "A05:2025 Security Misconfiguration",
|
|
55
55
|
description: "ADD has extra features (URL fetch, tar extraction) that can introduce unexpected behavior. Use COPY for local files.",
|
|
56
|
-
|
|
56
|
+
// Anchor to start of line + case-sensitive: Docker `ADD` instruction is uppercase and
|
|
57
|
+
// begins a line. Matching `add` case-insensitive caught `RUN pnpm add`, `apk add`,
|
|
58
|
+
// `yarn add`, etc. — package-manager subcommands inside RUN, not Docker instructions.
|
|
59
|
+
pattern: /^ADD\s+(?!https?:\/\/)\S+\s+\S+/gm,
|
|
57
60
|
languages: ["dockerfile"],
|
|
58
61
|
fix: "Use COPY instead of ADD for local files. Only use ADD for URLs or tar extraction.",
|
|
59
62
|
fixCode: "# Use COPY for local files\nCOPY ./src /app/src\n# Only use ADD for remote files or tar extraction\n# ADD https://example.com/file.tar.gz /app/",
|
|
@@ -90,7 +90,10 @@ export const nextjsRules = [
|
|
|
90
90
|
severity: "high",
|
|
91
91
|
owasp: "A01:2025 Broken Access Control",
|
|
92
92
|
description: "Sensitive data (tokens, secrets, internal IDs) appears to be passed from server to client component as props.",
|
|
93
|
-
|
|
93
|
+
// Require no-space `=` to match JSX prop syntax (`token={value}`) and exclude JS object
|
|
94
|
+
// literal assignments (`apiKey = { ... }`, `token = { ... }`) common in test helpers,
|
|
95
|
+
// default-param destructuring, and config objects.
|
|
96
|
+
pattern: /(?:(?:^|[^a-zA-Z])(?:secret|token|password|apiKey|api_key|privateKey|private_key|internalId|ssn|creditCard|credit_card))=\{[\s\S]*?\}/g,
|
|
94
97
|
languages: ["javascript", "typescript"],
|
|
95
98
|
fix: "Never pass sensitive data as props to client components. Keep secrets server-side.",
|
|
96
99
|
fixCode: "// Keep sensitive data server-side\nexport default async function Page() {\n const secret = process.env.API_SECRET;\n const publicData = await fetchData(secret);\n return <ClientComponent data={publicData} />;\n}",
|
|
@@ -471,6 +471,33 @@ export function analyzeCode(code, language, framework, filePath, configDir, rule
|
|
|
471
471
|
// Skip destructive DDL rules (VG540-VG542) and view rules (VG439) in migration directories
|
|
472
472
|
if ((rule.id.startsWith("VG54") || rule.id === "VG439") && isMigrationFile)
|
|
473
473
|
continue;
|
|
474
|
+
// VG146 (Unquoted .env Value): only fire on `.env` / `.env.local` / `.env.production` etc.
|
|
475
|
+
// Bash scripts use `${VAR:-default}` and similar expansions that legitimately contain
|
|
476
|
+
// `{`, `}`, `:` characters; matching them as "unquoted env values" is a FP class.
|
|
477
|
+
if (rule.id === "VG146" && filePath && !/(?:^|\/)\.env(?:\.[\w.-]+)?$/.test(filePath))
|
|
478
|
+
continue;
|
|
479
|
+
// VG200 (Container running as root): skip when a USER directive exists anywhere in the file.
|
|
480
|
+
// The rule's regex with `(?:(?!^USER)[\s\S])*` is unreliable across multi-stage builds; a
|
|
481
|
+
// file-level check is more robust.
|
|
482
|
+
if (rule.id === "VG200" && /^USER\s+\S+/m.test(code))
|
|
483
|
+
continue;
|
|
484
|
+
// VG206 (Missing HEALTHCHECK): skip when a HEALTHCHECK directive exists anywhere. The
|
|
485
|
+
// rule's regex requires HEALTHCHECK *after* CMD/ENTRYPOINT, but Dockerfiles commonly place
|
|
486
|
+
// HEALTHCHECK before CMD (nginx production stage), producing FPs.
|
|
487
|
+
if (rule.id === "VG206" && /^HEALTHCHECK\s+/m.test(code))
|
|
488
|
+
continue;
|
|
489
|
+
// VG407 (Server Data Leaked to Client Component): skip files that ARE client components.
|
|
490
|
+
// Signals: the `"use client"` directive (Next.js App Router) OR usage of React state/effect
|
|
491
|
+
// hooks (universal client-render signal — Remix, Vite-React, Pages Router, etc.). The rule
|
|
492
|
+
// targets server→client prop-boundary leaks; intra-client passing of local form state to
|
|
493
|
+
// a child component (e.g. PasswordStrengthIndicator) is not the same boundary.
|
|
494
|
+
if (rule.id === "VG407") {
|
|
495
|
+
const head = code.slice(0, 1000);
|
|
496
|
+
const hasUseClient = /^\s*['"]use client['"];?\s*$/m.test(head);
|
|
497
|
+
const hasReactStateHooks = /\b(?:useState|useReducer|useEffect|useLayoutEffect|useRef|useMemo|useCallback|useContext|useTransition|useSyncExternalStore)\s*\(/.test(code);
|
|
498
|
+
if (hasUseClient || hasReactStateHooks)
|
|
499
|
+
continue;
|
|
500
|
+
}
|
|
474
501
|
// Skip SQL injection rules in schema/migration .sql files (DDL, not user input)
|
|
475
502
|
if (rule.id === "VG543" && (isMigrationFile || isSqlSchemaFile))
|
|
476
503
|
continue;
|
|
@@ -651,12 +678,24 @@ export function analyzeCode(code, language, framework, filePath, configDir, rule
|
|
|
651
678
|
effectiveRule = { ...effectiveRule, severity: "low" };
|
|
652
679
|
}
|
|
653
680
|
}
|
|
681
|
+
// VG202 (latest/untagged image): pre-compute `AS <alias>` names from the file so
|
|
682
|
+
// matches against intermediate-stage references (`FROM base AS builder`) can be
|
|
683
|
+
// filtered out at match time. The set is null for other rules to avoid wasted work.
|
|
684
|
+
const dockerStageAliases = rule.id === "VG202"
|
|
685
|
+
? new Set(Array.from(code.matchAll(/^FROM\s+\S+\s+AS\s+(\w[\w.-]*)/gim)).map(m => m[1].toLowerCase()))
|
|
686
|
+
: null;
|
|
654
687
|
let match;
|
|
655
688
|
while ((match = rule.pattern.exec(code)) !== null) {
|
|
656
689
|
const beforeMatch = code.substring(0, match.index);
|
|
657
690
|
const lineNumber = beforeMatch.split("\n").length;
|
|
658
691
|
if (isLineSuppressed(suppressions, lineNumber, rule.id))
|
|
659
692
|
continue;
|
|
693
|
+
// VG202: skip when the FROM target matches a previous AS-alias in the same file.
|
|
694
|
+
if (dockerStageAliases) {
|
|
695
|
+
const target = match[0].replace(/^FROM\s+/i, "").split(/[:@\s]/)[0].toLowerCase();
|
|
696
|
+
if (dockerStageAliases.has(target))
|
|
697
|
+
continue;
|
|
698
|
+
}
|
|
660
699
|
// Skip matches on comment lines and inside string literals.
|
|
661
700
|
// CVE version-pin rules (VG900-VG931) are exempt — they scan package.json
|
|
662
701
|
// dependency declarations where these contexts don't apply.
|
|
@@ -802,6 +841,8 @@ export function analyzeCode(code, language, framework, filePath, configDir, rule
|
|
|
802
841
|
const matched = match[0];
|
|
803
842
|
if (/\bin\s*:\s*\[/i.test(matched))
|
|
804
843
|
continue; // where: { x: { in: [...] } }
|
|
844
|
+
if (/\bin\s*:\s*[a-zA-Z_$]/i.test(matched))
|
|
845
|
+
continue; // where: { x: { in: someArray } } — variable-spread is also caller-bounded
|
|
805
846
|
if (/\b(?:id|[a-zA-Z]+Id)\s*:\s*\{?\s*in\s*:/i.test(matched))
|
|
806
847
|
continue; // where: { partnerId: { in: ids } }
|
|
807
848
|
if (/\b(?:id|[a-zA-Z]+Id)\s*:\s*[a-zA-Z_$]/i.test(matched))
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "guardvibe",
|
|
3
|
-
"version": "3.1.
|
|
3
|
+
"version": "3.1.5",
|
|
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",
|