guardvibe 3.0.9 → 3.0.11

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.
@@ -34,7 +34,7 @@ export const advancedSecurityRules = [
34
34
  severity: "low",
35
35
  owasp: "API4:2023 Unrestricted Resource Consumption",
36
36
  description: "API endpoint reads request body without explicit size limit. Note: Next.js/Vercel applies a default 4.5MB limit, so this is informational for those platforms. For custom servers, attackers can send large payloads to exhaust memory.",
37
- pattern: /export\s+(?:async\s+)?function\s+(?:POST|PUT|PATCH)\s*\([^)]*\)\s*\{(?:(?!content-length|maxBodySize|limit|MAX_)[\s\S]){5,}?(?:req\.json|req\.text|req\.body|req\.formData|request\.json|request\.text)\s*\(\s*\)/g,
37
+ pattern: /export\s+(?:async\s+)?function\s+(?:POST|PUT|PATCH)\s*\([^)]*\)\s*\{(?:(?!content-length|maxBodySize|limit|MAX_|parseBody|checkBodySize|bodyParser|bodyLimit|sizeLimit)[\s\S]){5,}?(?:req\.json|req\.text|req\.body|req\.formData|request\.json|request\.text)\s*\(\s*\)/g,
38
38
  languages: ["javascript", "typescript"],
39
39
  fix: "Check Content-Length header before parsing body, or use a body parser with size limit.",
40
40
  fixCode: '// Check body size before parsing\nexport async function POST(req: Request) {\n const contentLength = parseInt(req.headers.get("content-length") || "0");\n if (contentLength > 1024 * 1024) { // 1MB limit\n return new Response("Payload too large", { status: 413 });\n }\n const body = await req.json();\n}',
@@ -188,7 +188,7 @@ export const advancedSecurityRules = [
188
188
  severity: "medium",
189
189
  owasp: "A05:2025 Security Misconfiguration",
190
190
  description: "Security headers are configured but Referrer-Policy is missing. Without it, the full URL (including query parameters with tokens/IDs) is sent to external sites in the Referer header.",
191
- pattern: /(?:async\s+)?headers\s*\(\s*\)\s*\{[\s\S]{10,}?(?:X-Frame-Options|Strict-Transport-Security|Content-Security-Policy)(?:(?!Referrer-Policy)[\s\S]){10,}?\}/g,
191
+ pattern: /(?:async\s+)?headers\s*\(\s*\)(?=[\s\S]*(?:X-Frame-Options|Strict-Transport-Security|Content-Security-Policy))(?![\s\S]*Referrer-Policy)/g,
192
192
  languages: ["javascript", "typescript"],
193
193
  fix: "Add Referrer-Policy: strict-origin-when-cross-origin to your security headers.",
194
194
  fixCode: '{ key: "Referrer-Policy", value: "strict-origin-when-cross-origin" }',
@@ -201,7 +201,7 @@ export const advancedSecurityRules = [
201
201
  severity: "medium",
202
202
  owasp: "A05:2025 Security Misconfiguration",
203
203
  description: "Security headers are configured but Permissions-Policy is missing. Without it, embedded iframes and scripts can access camera, microphone, geolocation, and other sensitive browser APIs.",
204
- pattern: /(?:async\s+)?headers\s*\(\s*\)\s*\{[\s\S]{10,}?(?:X-Frame-Options|Strict-Transport-Security|Content-Security-Policy)(?:(?!Permissions-Policy)[\s\S]){10,}?\}/g,
204
+ pattern: /(?:async\s+)?headers\s*\(\s*\)(?=[\s\S]*(?:X-Frame-Options|Strict-Transport-Security|Content-Security-Policy))(?![\s\S]*Permissions-Policy)/g,
205
205
  languages: ["javascript", "typescript"],
206
206
  fix: "Add Permissions-Policy header to restrict browser API access.",
207
207
  fixCode: '{ key: "Permissions-Policy", value: "camera=(), microphone=(), geolocation=()" }',
@@ -330,7 +330,7 @@ export const advancedSecurityRules = [
330
330
  severity: "medium",
331
331
  owasp: "A01:2025 Broken Access Control",
332
332
  description: "POST/PUT/PATCH/DELETE route handler performs database mutations without CSRF token verification. Cross-site requests from malicious pages can trick authenticated users into performing unwanted actions.",
333
- pattern: /export\s+(?:async\s+)?function\s+(?:POST|PUT|PATCH|DELETE)\s*\([^)]*\)\s*\{(?:(?!csrf|csrfToken|CSRF|x-csrf|verifyCsrf|validateCsrf|anti.?forgery)[\s\S]){10,}?(?:\.create\s*\(|\.update\s*\(|\.delete\s*\(|\.insert\s*\(|\.upsert\s*\()/g,
333
+ pattern: /export\s+(?:async\s+)?function\s+(?:POST|PUT|PATCH|DELETE)\s*\([^)]*\)\s*\{(?:(?!csrf|csrfToken|CSRF|x-csrf|verifyCsrf|validateCsrf|anti.?forgery|requireAdmin|requireAuth|checkAuth|withAuth|protectRoute|authenticate|x-csrf-protection)[\s\S]){10,}?(?:\.create\s*\(|\.update\s*\(|\.delete\s*\(|\.insert\s*\(|\.upsert\s*\()/g,
334
334
  languages: ["javascript", "typescript"],
335
335
  fix: "Add CSRF token verification to state-changing endpoints.",
336
336
  fixCode: '// Verify CSRF token from header\nexport async function POST(req: Request) {\n const csrfToken = req.headers.get("x-csrf-token");\n if (!verifyCsrfToken(csrfToken)) {\n return new Response("CSRF validation failed", { status: 403 });\n }\n}',
@@ -269,7 +269,7 @@ export const modernStackRules = [
269
269
  severity: "high",
270
270
  owasp: "A05:2025 Security Misconfiguration",
271
271
  description: "Next.js app does not set a Content-Security-Policy header. CSP is the strongest defense against XSS — without it, injected scripts run freely in your users' browsers.",
272
- pattern: /(?:async\s+)?headers\s*\(\s*\)\s*\{(?:(?!Content-Security-Policy)[\s\S]){20,}?\}/g,
272
+ pattern: /(?:async\s+)?headers\s*\(\s*\)(?=[\s\S]*(?:X-Frame-Options|Strict-Transport-Security|X-Content-Type-Options))(?![\s\S]*Content-Security-Policy)/g,
273
273
  languages: ["javascript", "typescript"],
274
274
  fix: "Add a Content-Security-Policy header in next.config.ts headers().",
275
275
  fixCode: "// next.config.ts\nasync headers() {\n return [{\n source: '/(.*)',\n headers: [{\n key: 'Content-Security-Policy',\n value: \"default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:;\"\n }]\n }];\n}",
@@ -476,7 +476,7 @@ export const modernStackRules = [
476
476
  severity: "high",
477
477
  owasp: "A01:2025 Broken Access Control",
478
478
  description: "User-uploaded file's original filename is used directly for storage without sanitization. Attackers can use directory traversal (../../etc/passwd), null bytes (file.php%00.jpg), double extensions (file.jpg.exe), or Unicode tricks to overwrite files, bypass type checks, or achieve remote code execution.",
479
- pattern: /(?:file\.name|originalname|filename|req\.file\.originalname|formData\.get\s*\(\s*['"]file['"])\s*[\s\S]{0,100}?(?:writeFile|createWriteStream|save|upload|putObject|mv\s*\(|rename|storage)/gi,
479
+ pattern: /(?:file\.name|originalname|req\.file\.originalname|formData\.get\s*\(\s*['"]file['"])(?:(?!randomUUID|uuid|nanoid|sanitize|safeFilename|safeName|allowedExt|allowedExtensions|\.replace\s*\(\s*\/\[)[\s\S]){0,100}?(?:writeFile|createWriteStream|save|upload|putObject|mv\s*\(|rename|storage)/gi,
480
480
  languages: ["javascript", "typescript"],
481
481
  fix: "Generate a random filename (UUID/nanoid) and validate the extension against an allowlist. Never use the original filename for storage.",
482
482
  fixCode: 'import { randomUUID } from "crypto";\nimport path from "path";\n\n// Generate safe filename\nconst ext = path.extname(file.name).toLowerCase();\nconst ALLOWED_EXT = [".jpg", ".jpeg", ".png", ".webp", ".pdf"];\nif (!ALLOWED_EXT.includes(ext)) throw new Error("Invalid file type");\nconst safeName = `${randomUUID()}${ext}`;\nawait fs.writeFile(`/uploads/${safeName}`, buffer);',
@@ -66,7 +66,7 @@ export const nextjsRules = [
66
66
  severity: "medium",
67
67
  owasp: "A05:2025 Security Misconfiguration",
68
68
  description: "next.config is missing important security headers (Content-Security-Policy, Strict-Transport-Security, X-Frame-Options).",
69
- pattern: /(?:async\s+)?headers\s*\(\s*\)\s*\{(?:(?!X-Frame-Options|Strict-Transport-Security|Content-Security-Policy)[\s\S]){10,}?\}/g,
69
+ pattern: /(?:async\s+)?headers\s*\(\s*\)(?![\s\S]*(?:X-Frame-Options|Strict-Transport-Security|Content-Security-Policy))/g,
70
70
  languages: ["javascript", "typescript"],
71
71
  fix: "Add security headers in next.config.ts headers() function.",
72
72
  fixCode: '// next.config.ts\nasync headers() {\n return [{\n source: "/(.*)",\n headers: [\n { key: "X-Frame-Options", value: "DENY" },\n { key: "X-Content-Type-Options", value: "nosniff" },\n { key: "Strict-Transport-Security", value: "max-age=63072000; includeSubDomains" },\n ]\n }];\n}',
@@ -291,12 +291,115 @@ export async function runFullAudit(path, options) {
291
291
  actionItems,
292
292
  };
293
293
  }
294
+ function buildInlineRemediationPlan(result) {
295
+ const sectionConfig = {
296
+ secrets: {
297
+ priority: 1,
298
+ tool: "scan_secrets",
299
+ actions: [
300
+ "Call scan_secrets with format: json to list all secrets with file locations",
301
+ "For EACH secret: move to environment variable, add file to .gitignore",
302
+ "Rotate any API keys/tokens that were committed — they are compromised",
303
+ "Call scan_secrets_history to check git history for previously committed secrets",
304
+ "Re-run scan_secrets to confirm 0 secrets remain",
305
+ ],
306
+ },
307
+ code: {
308
+ priority: 2,
309
+ tool: "scan_directory",
310
+ actions: [
311
+ "Call scan_directory with format: json to get full finding list with fix suggestions",
312
+ "Fix ALL critical and high severity findings using fix_code for each file",
313
+ "Call verify_fix after each fix to confirm the vulnerability is resolved",
314
+ "Re-run scan_directory to confirm findings are resolved",
315
+ ],
316
+ },
317
+ dependencies: {
318
+ priority: 3,
319
+ tool: "scan_dependencies",
320
+ actions: [
321
+ "Call scan_dependencies with format: json to list vulnerable packages with CVE details",
322
+ "Run npm audit fix or npm update <package> for each vulnerable dependency",
323
+ "If a package is abandoned, find an alternative with check_package_health",
324
+ "Re-run scan_dependencies to confirm 0 CVEs remain",
325
+ ],
326
+ },
327
+ config: {
328
+ priority: 4,
329
+ tool: "audit_config",
330
+ actions: [
331
+ "Call audit_config with format: json to list all config issues",
332
+ "Call explain_remediation for each finding to get specific fix guidance",
333
+ "Apply fixes to next.config, middleware, .env, vercel.json, etc.",
334
+ "Re-run audit_config to confirm config issues are resolved",
335
+ ],
336
+ },
337
+ taint: {
338
+ priority: 5,
339
+ tool: "analyze_cross_file_dataflow",
340
+ actions: [
341
+ "Call analyze_cross_file_dataflow to trace tainted data flows from source to sink",
342
+ "Add input validation (zod/joi) at each source, or output encoding at each sink",
343
+ "Re-run analyze_cross_file_dataflow to confirm tainted flows are resolved",
344
+ ],
345
+ },
346
+ "auth-coverage": {
347
+ priority: 6,
348
+ tool: "auth_coverage",
349
+ actions: [
350
+ "Call auth_coverage with format: json to list all unprotected routes",
351
+ "Add auth guard (Clerk/NextAuth/Supabase) to each unprotected route",
352
+ "If a route is intentionally public, document it in .guardviberc authExceptions",
353
+ "Re-run auth_coverage to confirm all routes are protected or documented",
354
+ ],
355
+ },
356
+ };
357
+ const steps = [];
358
+ for (const section of result.sections) {
359
+ if (section.findings === 0)
360
+ continue;
361
+ const config = sectionConfig[section.name];
362
+ if (!config)
363
+ continue;
364
+ steps.push({
365
+ section: section.name,
366
+ priority: config.priority,
367
+ findings: section.findings,
368
+ critical: section.critical,
369
+ high: section.high,
370
+ tool: config.tool,
371
+ actions: config.actions,
372
+ });
373
+ }
374
+ steps.sort((a, b) => a.priority - b.priority);
375
+ return steps;
376
+ }
294
377
  // --- Formatter ---
295
378
  /**
296
379
  * Format audit result as markdown, JSON, or terminal-friendly output.
297
380
  */
298
381
  export function formatAuditResult(result, format) {
299
382
  if (format === "json") {
383
+ // Embed remediation plan directly in JSON when verdict is not PASS
384
+ if (result.verdict !== "PASS") {
385
+ const plan = buildInlineRemediationPlan(result);
386
+ return JSON.stringify({
387
+ ...result,
388
+ remediationPlan: {
389
+ totalSectionsWithFindings: plan.length,
390
+ warning: `MANDATORY: You must fix ALL ${plan.length} sections below. Do NOT fix only the code section and skip the rest. Work through each section in priority order. After completing ALL sections, call verify_remediation to confirm.`,
391
+ rules: [
392
+ "Do NOT dismiss findings as 'false positive' without running the section-specific tool first and verifying the finding is genuinely wrong.",
393
+ "Do NOT say 'npm audit is clean' to skip dependencies — GuardVibe uses the OSV database which may detect CVEs that npm audit misses.",
394
+ "Do NOT say 'already in .gitignore' to skip secrets — if GuardVibe detects secrets, they exist in the scanned files and must be removed or moved to env vars.",
395
+ "Do NOT skip auth-coverage by saying routes are 'intentionally public' — either add auth guards or add them to .guardviberc authExceptions so the scanner recognizes them.",
396
+ "After fixing each section, re-run the section-specific tool to confirm the finding count dropped. If it didn't drop, your fix didn't work.",
397
+ "Only call verify_remediation AFTER you have worked through ALL sections and confirmed each one improved.",
398
+ ],
399
+ steps: plan,
400
+ },
401
+ });
402
+ }
300
403
  return JSON.stringify(result);
301
404
  }
302
405
  if (format === "terminal") {
@@ -360,6 +463,35 @@ export function formatAuditResult(result, format) {
360
463
  lines.push(`- ${item}`);
361
464
  }
362
465
  }
466
+ // Embed remediation plan when verdict is not PASS
467
+ if (result.verdict !== "PASS") {
468
+ const plan = buildInlineRemediationPlan(result);
469
+ if (plan.length > 0) {
470
+ lines.push(``);
471
+ lines.push(`## Mandatory Remediation Plan`);
472
+ lines.push(``);
473
+ lines.push(`> **IMPORTANT: You MUST fix ALL ${plan.length} sections below. Do NOT fix only the code section and skip the rest. Work through each section in priority order. After completing ALL sections, call \`verify_remediation\` to confirm.**`);
474
+ lines.push(``);
475
+ lines.push(`**Rules:**`);
476
+ lines.push(`- Do NOT dismiss findings as "false positive" without running the section-specific tool first`);
477
+ lines.push(`- Do NOT say "npm audit is clean" to skip dependencies — GuardVibe uses OSV which may detect CVEs npm misses`);
478
+ lines.push(`- Do NOT say "already in .gitignore" to skip secrets — if detected, they exist in scanned files`);
479
+ lines.push(`- After fixing each section, re-run the section tool to confirm finding count dropped`);
480
+ lines.push(`- Only call verify_remediation AFTER all sections show improvement`);
481
+ lines.push(``);
482
+ for (const step of plan) {
483
+ lines.push(`### Step ${step.priority}: ${step.section} (${step.findings} findings — ${step.critical} critical, ${step.high} high)`);
484
+ lines.push(``);
485
+ for (let i = 0; i < step.actions.length; i++) {
486
+ lines.push(`${i + 1}. ${step.actions[i]}`);
487
+ }
488
+ lines.push(``);
489
+ }
490
+ lines.push(`### Final verification`);
491
+ lines.push(``);
492
+ lines.push(`After completing ALL steps above, call \`verify_remediation\` to confirm every section was addressed. Do NOT declare remediation complete until verify_remediation returns "complete".`);
493
+ }
494
+ }
363
495
  lines.push(``);
364
496
  lines.push(`---`);
365
497
  lines.push(`Timestamp: ${result.timestamp}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "guardvibe",
3
- "version": "3.0.9",
3
+ "version": "3.0.11",
4
4
  "mcpName": "io.github.goklab/guardvibe",
5
5
  "description": "Security MCP for vibe coding. 335 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",