guardvibe 1.9.4 → 1.9.6
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/cli.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { createRequire } from "module";
|
|
3
|
-
import { readFileSync, writeFileSync, mkdirSync, existsSync, chmodSync, unlinkSync } from "fs";
|
|
3
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync, chmodSync, unlinkSync, statSync } from "fs";
|
|
4
4
|
import { join, dirname } from "path";
|
|
5
5
|
import { homedir } from "os";
|
|
6
6
|
const require = createRequire(import.meta.url);
|
|
@@ -50,6 +50,9 @@ function setupPlatform(name) {
|
|
|
50
50
|
}
|
|
51
51
|
if (existing.mcpServers["guardvibe"]) {
|
|
52
52
|
console.log(` [OK] GuardVibe already configured in ${platform.description}`);
|
|
53
|
+
// Still ensure CLAUDE.md and .gitignore are set up
|
|
54
|
+
if (name === "claude")
|
|
55
|
+
setupClaudeHooksAndGuide();
|
|
53
56
|
return true;
|
|
54
57
|
}
|
|
55
58
|
existing.mcpServers["guardvibe"] = GUARDVIBE_MCP_CONFIG;
|
|
@@ -225,9 +228,6 @@ jobs:
|
|
|
225
228
|
with:
|
|
226
229
|
node-version: "22"
|
|
227
230
|
|
|
228
|
-
- name: Install dependencies
|
|
229
|
-
run: npm ci
|
|
230
|
-
|
|
231
231
|
- name: Run GuardVibe security scan
|
|
232
232
|
run: npx -y guardvibe-scan --format sarif --output guardvibe-results.sarif
|
|
233
233
|
|
|
@@ -523,8 +523,8 @@ function printUsage() {
|
|
|
523
523
|
Options:
|
|
524
524
|
--format <type> Output format: markdown (default), json, sarif
|
|
525
525
|
--output <file> Write results to file instead of stdout
|
|
526
|
-
--fail-on <level> Exit 1 when findings at this level exist
|
|
527
|
-
|
|
526
|
+
--fail-on <level> Exit 1 when findings at this level or above exist
|
|
527
|
+
critical (default) | high | medium | low | none
|
|
528
528
|
--baseline <file> Compare against a previous scan JSON for fix tracking
|
|
529
529
|
--save-baseline Save current scan as baseline (.guardvibe-baseline.json)
|
|
530
530
|
--version, -V Print version and exit
|
|
@@ -618,7 +618,14 @@ async function main() {
|
|
|
618
618
|
const cliArgs = args.slice(1);
|
|
619
619
|
const { flags, positional } = parseArgs(cliArgs);
|
|
620
620
|
const targetPath = positional[0] ?? ".";
|
|
621
|
-
|
|
621
|
+
// If target is a file (not directory), auto-redirect to check mode
|
|
622
|
+
if (targetPath !== "." && existsSync(targetPath) && !statSync(targetPath).isDirectory()) {
|
|
623
|
+
console.log(` [INFO] "${targetPath}" is a file. Running: guardvibe check ${targetPath}\n`);
|
|
624
|
+
await runFileCheck(targetPath, flags);
|
|
625
|
+
}
|
|
626
|
+
else {
|
|
627
|
+
await runDirectoryScan(targetPath, flags);
|
|
628
|
+
}
|
|
622
629
|
}
|
|
623
630
|
else if (command === "diff") {
|
|
624
631
|
const cliArgs = args.slice(1);
|
|
@@ -163,6 +163,10 @@ export function analyzeCode(code, language, framework, filePath, configDir, rule
|
|
|
163
163
|
const rateLimitRuleIds = new Set(["VG956", "VG030"]);
|
|
164
164
|
if (isCronRoute && rateLimitRuleIds.has(rule.id))
|
|
165
165
|
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
|
+
if (isWebhookRoute && rateLimitRuleIds.has(rule.id))
|
|
169
|
+
continue;
|
|
166
170
|
// Context-aware: skip rate limiting rules for admin routes that have admin auth
|
|
167
171
|
const isAdminRoute = filePath && /\/admin\//i.test(filePath);
|
|
168
172
|
const hasAdminAuth = isAdminRoute && /(?:requireAdmin|adminOnly|orgRole|org:admin|isAdmin|checkRole|requireRole)/i.test(code);
|
|
@@ -340,6 +344,7 @@ export function formatFindingsJson(findings, extra) {
|
|
|
340
344
|
return JSON.stringify({
|
|
341
345
|
summary: {
|
|
342
346
|
total: findings.length, critical, high, medium, low,
|
|
347
|
+
// blocked: true when critical or high findings exist (would fail --fail-on high)
|
|
343
348
|
blocked: critical > 0 || high > 0,
|
|
344
349
|
...extra,
|
|
345
350
|
},
|
|
@@ -362,19 +367,68 @@ export function checkCode(code, language, framework, filePath, configDir, format
|
|
|
362
367
|
}
|
|
363
368
|
function formatCleanReport(language, framework) {
|
|
364
369
|
const ctx = framework ? ` (${framework})` : "";
|
|
370
|
+
const tips = getLanguageTips(language, framework);
|
|
365
371
|
return [
|
|
366
372
|
`# GuardVibe Security Report`,
|
|
367
373
|
``,
|
|
368
374
|
`**Language:** ${language}${ctx}`,
|
|
369
375
|
`**Status:** No security issues detected`,
|
|
370
376
|
``,
|
|
371
|
-
`
|
|
372
|
-
|
|
373
|
-
`- Validate all user input with schemas (zod, joi)`,
|
|
374
|
-
`- Use environment variables for secrets`,
|
|
375
|
-
`- Add rate limiting to API endpoints`,
|
|
377
|
+
`Tips for ${language}${ctx}:`,
|
|
378
|
+
...tips.map(t => `- ${t}`),
|
|
376
379
|
].join("\n");
|
|
377
380
|
}
|
|
381
|
+
function getLanguageTips(language, framework) {
|
|
382
|
+
if (framework === "nextjs" || framework === "next")
|
|
383
|
+
return [
|
|
384
|
+
"Use `server-only` imports in files with secrets or DB access",
|
|
385
|
+
"Validate Server Action inputs with zod schemas",
|
|
386
|
+
"Set `serverActions.allowedOrigins` in next.config",
|
|
387
|
+
"Add security headers via `headers()` in next.config",
|
|
388
|
+
];
|
|
389
|
+
if (framework === "express" || framework === "fastify" || framework === "hono")
|
|
390
|
+
return [
|
|
391
|
+
"Add rate limiting middleware to auth and write endpoints",
|
|
392
|
+
"Use helmet() for security headers",
|
|
393
|
+
"Validate request body with zod or joi before processing",
|
|
394
|
+
"Never reflect user input in error responses",
|
|
395
|
+
];
|
|
396
|
+
if (language === "python")
|
|
397
|
+
return [
|
|
398
|
+
"Use parameterized queries — never f-strings in SQL",
|
|
399
|
+
"Add `Depends(get_current_user)` to protected routes",
|
|
400
|
+
"Pin dependency versions in requirements.txt",
|
|
401
|
+
"Use `secrets.compare_digest()` for token comparison",
|
|
402
|
+
];
|
|
403
|
+
if (language === "sql")
|
|
404
|
+
return [
|
|
405
|
+
"Use `SECURITY INVOKER` on views to respect RLS",
|
|
406
|
+
"Avoid `GRANT ALL` — use least-privilege permissions",
|
|
407
|
+
"Add `IF EXISTS` to destructive DDL for safety",
|
|
408
|
+
"Use parameterized queries in application code",
|
|
409
|
+
];
|
|
410
|
+
if (language === "dockerfile")
|
|
411
|
+
return [
|
|
412
|
+
"Use specific image tags, never `latest`",
|
|
413
|
+
"Run as non-root user with `USER` directive",
|
|
414
|
+
"Use multi-stage builds to minimize attack surface",
|
|
415
|
+
"Don't copy `.env` or secrets into the image",
|
|
416
|
+
];
|
|
417
|
+
if (language === "yaml" || language === "terraform")
|
|
418
|
+
return [
|
|
419
|
+
"Never hardcode secrets in config — use env vars or secrets manager",
|
|
420
|
+
"Pin action/provider versions to specific SHA or tag",
|
|
421
|
+
"Use least-privilege IAM policies",
|
|
422
|
+
"Enable audit logging for infrastructure changes",
|
|
423
|
+
];
|
|
424
|
+
// Default for JS/TS
|
|
425
|
+
return [
|
|
426
|
+
"Keep dependencies updated (`npm audit`)",
|
|
427
|
+
"Validate all user input with schemas (zod, joi)",
|
|
428
|
+
"Use environment variables for secrets",
|
|
429
|
+
"Use `textContent` instead of `innerHTML` for user data",
|
|
430
|
+
];
|
|
431
|
+
}
|
|
378
432
|
function formatReport(findings, language, framework) {
|
|
379
433
|
const ctx = framework ? ` (${framework})` : "";
|
|
380
434
|
// Severity ordering
|
|
@@ -126,7 +126,7 @@ export function checkProject(files, format = "markdown", rules) {
|
|
|
126
126
|
actionItems.forEach((item, i) => {
|
|
127
127
|
const fileCount = item.files.size;
|
|
128
128
|
const fileLabel = fileCount === 1 ? "1 file" : `${fileCount} files`;
|
|
129
|
-
lines.push(`${i + 1}. **[${item.rule.severity.toUpperCase()}] ${item.rule.name}** (${item.rule.id}) — ${item.count} occurrences in ${fileLabel}`, ` ${item.rule.fix}`, ``);
|
|
129
|
+
lines.push(`${i + 1}. **[${item.rule.severity.toUpperCase()}] ${item.rule.name}** (${item.rule.id}) — ${item.count} ${item.count === 1 ? "occurrence" : "occurrences"} in ${fileLabel}`, ` ${item.rule.fix}`, ``);
|
|
130
130
|
});
|
|
131
131
|
}
|
|
132
132
|
lines.push(`---`, ``);
|
|
@@ -220,7 +220,7 @@ export function scanDirectory(path, recursive = true, exclude = [], format = "ma
|
|
|
220
220
|
actionItems.forEach((item, i) => {
|
|
221
221
|
const fileCount = item.files.size;
|
|
222
222
|
const fileLabel = fileCount === 1 ? "1 file" : `${fileCount} files`;
|
|
223
|
-
lines.push(`${i + 1}. **[${item.rule.severity.toUpperCase()}] ${item.rule.name}** (${item.rule.id}) — ${item.count} occurrences in ${fileLabel}`, ` ${item.rule.fix}`, ``);
|
|
223
|
+
lines.push(`${i + 1}. **[${item.rule.severity.toUpperCase()}] ${item.rule.name}** (${item.rule.id}) — ${item.count} ${item.count === 1 ? "occurrence" : "occurrences"} in ${fileLabel}`, ` ${item.rule.fix}`, ``);
|
|
224
224
|
});
|
|
225
225
|
lines.push(`---`, ``);
|
|
226
226
|
for (const result of scanResults) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "guardvibe",
|
|
3
|
-
"version": "1.9.
|
|
3
|
+
"version": "1.9.6",
|
|
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": {
|