guardvibe 3.1.18 → 3.1.20
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/tools/check-code.js +54 -0
- package/package.json +1 -1
|
@@ -417,6 +417,13 @@ export function analyzeCode(code, language, framework, filePath, configDir, rule
|
|
|
417
417
|
// Server-side batch context: scripts, migrations, seeds. These run offline or
|
|
418
418
|
// on-deploy, not against user requests, so DoS-from-unbounded-results doesn't apply.
|
|
419
419
|
const isBatchScriptFile = filePath && /\/(?:scripts?|migrations?|seeds?|fixtures?)\//i.test(filePath);
|
|
420
|
+
// Code-generator/scaffold templates. CLI tools (create-t3-app, create-next-app,
|
|
421
|
+
// create-react-app, etc.) bundle "Hello World" example files under cli/template/
|
|
422
|
+
// or templates/ that are intentionally minimal — no auth, no input validation,
|
|
423
|
+
// no rate limiting. These get copied into user projects where the user is
|
|
424
|
+
// expected to customize them. Flagging them in the CLI tool's own audit produces
|
|
425
|
+
// noise without surfacing real production risk.
|
|
426
|
+
const isTemplateFile = filePath && /\/(?:templates?|scaffolds?|stubs?|boilerplate)\//i.test(filePath);
|
|
420
427
|
// Skip rate-limit rules when the file installs a global rate limiter via app.use().
|
|
421
428
|
// Covers `app.use(rateLimit({...}))`, `app.use(limiter)`, `app.use('/api', rateLimit({...}))`,
|
|
422
429
|
// and named middleware vars matching limiter naming conventions.
|
|
@@ -490,6 +497,11 @@ export function analyzeCode(code, language, framework, filePath, configDir, rule
|
|
|
490
497
|
// request handler to authorize. Rule still fires inside route handlers and Server Actions.
|
|
491
498
|
if (rule.id === "VG1008" && isBatchScriptFile)
|
|
492
499
|
continue;
|
|
500
|
+
// Skip tRPC educational/scaffold rules (VG970 publicProcedure-DB, VG971 missing-input)
|
|
501
|
+
// in template/scaffold files. CLI tools like create-t3-app ship intentionally simple
|
|
502
|
+
// examples under cli/template/ that the user is expected to replace before deploying.
|
|
503
|
+
if ((rule.id === "VG970" || rule.id === "VG971") && isTemplateFile)
|
|
504
|
+
continue;
|
|
493
505
|
// Skip VG961 (z.any/z.unknown) in batch scripts and cron routes — `data: z.any()` and
|
|
494
506
|
// similar opaque fields in migration/seed scripts and cron job payloads are intentional
|
|
495
507
|
// passthroughs (e.g. Tinybird `tb.buildPipe({ parameters: ..., data: z.any() })`),
|
|
@@ -842,6 +854,48 @@ export function analyzeCode(code, language, framework, filePath, configDir, rule
|
|
|
842
854
|
if (/\b\w*Ref\.current\b/.test(matchedLine))
|
|
843
855
|
continue;
|
|
844
856
|
}
|
|
857
|
+
// VG850 (AI Prompt Injection via User Input): only fire when at least one of the
|
|
858
|
+
// interpolations in the system-prompt template literal looks like user input. The
|
|
859
|
+
// pattern matches any `${...}` interpolation, but apps commonly compose system
|
|
860
|
+
// prompts from constants — `system: ` + "`${codePrompt}\\n...`" + ` — and these
|
|
861
|
+
// constants are not attacker-controlled. User-input shapes: `req.X`/`body.X`/
|
|
862
|
+
// `params.X`/`query.X`/`searchParams.X`, or bare identifiers named `userInput`,
|
|
863
|
+
// `userMessage`, `userPrompt`, `prompt`, `input`, `message`.
|
|
864
|
+
if (rule.id === "VG850") {
|
|
865
|
+
// Match starts at `system:` and ends at `${`. Look at the surrounding window
|
|
866
|
+
// for the rest of the template literal (it can span multiple lines).
|
|
867
|
+
const window = lines.slice(Math.max(0, lineNumber - 1), Math.min(lines.length, lineNumber + 6)).join("\n");
|
|
868
|
+
const interpolations = Array.from(window.matchAll(/\$\{([^}]+)\}/g)).map(m => m[1].trim());
|
|
869
|
+
if (interpolations.length > 0) {
|
|
870
|
+
const isUserInput = (expr) => /\b(?:req|request|body|query|params|searchParams)\.\w+/.test(expr) ||
|
|
871
|
+
/^(?:userInput|userMessage|userPrompt|prompt|input|message)\b/.test(expr) ||
|
|
872
|
+
/\buser(?:Input|Message|Prompt|Query|Text|Content|Data)\b/.test(expr);
|
|
873
|
+
if (!interpolations.some(isUserInput))
|
|
874
|
+
continue;
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
// VG999 (AI Request Without maxTokens): skip when the call uses structured output
|
|
878
|
+
// (`output: Output.array(...)` / `output: Output.object(...)`) — token usage is
|
|
879
|
+
// bounded by the schema, not free-form text. The match already excludes calls
|
|
880
|
+
// that explicitly set maxTokens; structured-output calls are similarly bounded.
|
|
881
|
+
// Need to look at a wider window than match[0] because the match terminates at
|
|
882
|
+
// `}` and `output:` may appear elsewhere in the call options.
|
|
883
|
+
if (rule.id === "VG999") {
|
|
884
|
+
if (/\boutput\s*:\s*Output\.(?:array|object|enum)\s*\(/i.test(match[0]))
|
|
885
|
+
continue;
|
|
886
|
+
}
|
|
887
|
+
// VG1027 (Conversation Messages Serialized to Client With System Role): skip when
|
|
888
|
+
// the response is built via a filter/conversion helper that strips the system role.
|
|
889
|
+
// Vercel AI SDK convention names: `convertToUIMessages` (recommended), `filterMessages`,
|
|
890
|
+
// `pickRole`, `sanitizeMessages`, `publicMessages`. Pattern matches up to the
|
|
891
|
+
// `messages` key but doesn't include the value, so check the surrounding lines.
|
|
892
|
+
if (rule.id === "VG1027") {
|
|
893
|
+
const surrounding = lines.slice(Math.max(0, lineNumber - 1), Math.min(lines.length, lineNumber + 3)).join("\n");
|
|
894
|
+
// No trailing \b — the helper names are CamelCase and continue with more word chars
|
|
895
|
+
// (convertToUIMessages, sanitizeMessagesForClient, etc.). Prefix match is intentional.
|
|
896
|
+
if (/\b(?:convertToUI|filterMessages|pickRole|sanitizeMessages|publicMessages|visibleMessages|userMessages|convertMessages)/i.test(surrounding))
|
|
897
|
+
continue;
|
|
898
|
+
}
|
|
845
899
|
// VG152 (Object Injection via Dynamic Property Access): only fire on bracket-key
|
|
846
900
|
// ASSIGNMENT (`obj[key] = ...`) — that's the prototype-pollution shape. Read-only
|
|
847
901
|
// bracket access (`obj[key]` on RHS, in conditional, in function arg) does not
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "guardvibe",
|
|
3
|
-
"version": "3.1.
|
|
3
|
+
"version": "3.1.20",
|
|
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",
|