guardvibe 3.0.15 → 3.0.17

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.
@@ -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\S]*(?:X-Frame-Options|Strict-Transport-Security|Content-Security-Policy))/g,
69
+ pattern: /(?:async\s+)?headers\s*\(\s*\)\s*\{[\s\S]{0,20}return\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}',
@@ -169,8 +169,14 @@ export async function runFullAudit(path, options) {
169
169
  const secretsJson = scanSecrets(projectRoot, true, "json");
170
170
  const parsed = safeJsonParse(secretsJson);
171
171
  if (parsed) {
172
- const counts = parseSectionCounts(parsed);
173
- const secretFindings = (parsed.findings ?? []).map((f) => ({
172
+ // Filter out gitignored secrets — they're local dev files, not a security risk
173
+ const rawFindings = parsed.findings ?? [];
174
+ const actionableFindings = rawFindings.filter((f) => f.gitStatus !== "ignored");
175
+ const ignoredCount = rawFindings.length - actionableFindings.length;
176
+ const actionableCritical = actionableFindings.filter((f) => f.severity === "critical").length;
177
+ const actionableHigh = actionableFindings.filter((f) => f.severity === "high").length;
178
+ const actionableMedium = actionableFindings.length - actionableCritical - actionableHigh;
179
+ const secretFindings = actionableFindings.map((f) => ({
174
180
  ruleId: `SECRET:${(f.provider ?? "unknown")}`,
175
181
  severity: (f.severity ?? "high"),
176
182
  file: (f.file ?? ""),
@@ -179,8 +185,15 @@ export async function runFullAudit(path, options) {
179
185
  description: (f.match ?? f.description ?? ""),
180
186
  fix: "Move this secret to an environment variable and ensure the file is in .gitignore",
181
187
  }));
182
- sections.push({ name: "secrets", status: "ok", ...counts, details: counts.findings === 0 ? "No secrets found" : `${counts.findings} secret(s) detected`, sectionFindings: secretFindings });
183
- for (const f of parsed.findings ?? []) {
188
+ const detailText = actionableFindings.length === 0
189
+ ? (ignoredCount > 0 ? `${ignoredCount} secret(s) in .gitignore (safe)` : "No secrets found")
190
+ : `${actionableFindings.length} secret(s) detected${ignoredCount > 0 ? ` (${ignoredCount} in .gitignore excluded)` : ""}`;
191
+ sections.push({
192
+ name: "secrets", status: "ok",
193
+ findings: actionableFindings.length, critical: actionableCritical, high: actionableHigh, medium: actionableMedium,
194
+ details: detailText, sectionFindings: secretFindings,
195
+ });
196
+ for (const f of actionableFindings) {
184
197
  allFindings.push({ ruleId: `SECRET:${f.provider ?? "unknown"}`, severity: f.severity, file: f.file ?? "", line: f.line ?? 0 });
185
198
  }
186
199
  }
@@ -232,18 +245,21 @@ export async function runFullAudit(path, options) {
232
245
  const parsed = safeJsonParse(configJson);
233
246
  if (parsed) {
234
247
  const counts = parseSectionCounts(parsed);
235
- const configFindings = (parsed.findings ?? []).map((f) => ({
248
+ // auditConfig uses "issues" key, not "findings"
249
+ const rawIssues = parsed.issues ?? parsed.findings ?? [];
250
+ const configFindings = rawIssues.map((f) => ({
236
251
  ruleId: (f.id ?? f.ruleId ?? "CONFIG"),
237
252
  severity: (f.severity ?? "medium"),
238
- file: (f.file ?? ""),
253
+ file: (Array.isArray(f.files) && f.files.length > 0 ? f.files[0] : (f.file ?? "")),
239
254
  line: (f.line ?? 0),
240
- name: (f.name ?? f.description ?? ""),
241
- description: (f.description ?? f.details ?? ""),
242
- fix: (f.fix ?? f.remediation ?? ""),
255
+ name: (f.title ?? f.name ?? ""),
256
+ description: (f.description ?? ""),
257
+ fix: (f.fix ?? ""),
243
258
  }));
244
259
  sections.push({ name: "config", status: "ok", ...counts, details: counts.findings === 0 ? "Config secure" : `${counts.findings} config issue(s)`, sectionFindings: configFindings });
245
- for (const f of parsed.findings ?? []) {
246
- allFindings.push({ ruleId: f.id ?? f.ruleId ?? "CONFIG", severity: f.severity ?? "medium", file: f.file ?? "", line: f.line ?? 0 });
260
+ for (const f of rawIssues) {
261
+ const file = Array.isArray(f.files) && f.files.length > 0 ? f.files[0] : (f.file ?? "");
262
+ allFindings.push({ ruleId: f.id ?? f.ruleId ?? "CONFIG", severity: f.severity ?? "medium", file, line: f.line ?? 0 });
247
263
  }
248
264
  }
249
265
  }
@@ -378,60 +394,54 @@ function buildInlineRemediationPlan(result) {
378
394
  priority: 1,
379
395
  tool: "scan_secrets",
380
396
  actions: [
381
- "MCP: Call scan_secrets with format: json OR CLI: run `npx guardvibe scan --format json` and look for SECRET: findings in the output",
382
- "For EACH secret found: (a) if it's in a .env file, ensure .env* is in .gitignore, (b) if it's hardcoded in source code, move it to an environment variable, (c) NEVER dismiss secrets as 'already in .gitignore' — if GuardVibe found it, the FILE exists and contains real values",
383
- "Rotate any API keys/tokens that were committed to git history — run `git log --all -p -S 'YOUR_KEY_PREFIX' -- '*.env*'` to check",
384
- "MCP: Call scan_secrets_history — OR CLI: run `npx guardvibe scan --format json` to verify",
385
- "Re-run the scan and confirm secret count dropped to 0. If it didn't, your fix didn't work — try again",
397
+ "Look at sectionFindings aboveeach has file, line, description",
398
+ "For each: move hardcoded secret to .env, add file to .gitignore",
399
+ "Verify: run `npx guardvibe audit --format json 2>&1 | head -1` secrets count must be 0",
386
400
  ],
387
401
  },
388
402
  code: {
389
403
  priority: 2,
390
404
  tool: "scan_directory",
391
405
  actions: [
392
- "MCP: Call scan_directory with format: json OR CLI: run `npx guardvibe scan --format json` to get full finding list",
393
- "Fix ALL critical and high severity findings. Read each flagged file, understand the vulnerability, apply the fix suggested in the finding",
394
- "After fixing each file, re-scan it: MCP: Call scan_file — OR CLI: run `npx guardvibe check <file> --format json` to confirm the finding is gone",
395
- "Re-run full scan to confirm total code findings dropped",
406
+ "Look at sectionFindings aboveeach has ruleId, file, line, fix",
407
+ "Read each file, apply the fix. Run `npx guardvibe explain <RULE_ID>` if unclear",
408
+ "Verify each file: `npx guardvibe check <file> --format json` must show 0 findings",
396
409
  ],
397
410
  },
398
411
  dependencies: {
399
412
  priority: 3,
400
413
  tool: "scan_dependencies",
401
414
  actions: [
402
- "MCP: Call scan_dependencies with format: json OR CLI: run `npx guardvibe scan --format json` and check dependency findings, also run `npm audit`",
403
- "Run `npm audit fix` to auto-fix what's possible. If that doesn't work, run `npm update <package>` for each vulnerable package",
404
- "If a package can't be updated (breaking changes), find an alternative or pin to a patched version",
405
- "Re-run `npx guardvibe audit` and confirm dependency findings dropped to 0",
415
+ "Look at sectionFindings aboveeach has package name and CVE",
416
+ "Run `npm update <package>` for each. If already latest, check for alternative package",
417
+ "Verify: re-run audit dependencies count must drop",
406
418
  ],
407
419
  },
408
420
  config: {
409
421
  priority: 4,
410
422
  tool: "audit_config",
411
423
  actions: [
412
- "MCP: Call audit_config with format: json OR CLI: run `npx guardvibe audit --format json` and parse the config section details",
413
- "Common config fixes: add missing security headers in next.config.ts (CSP, HSTS, X-Frame-Options, Referrer-Policy, Permissions-Policy), set poweredByHeader: false, configure CORS properly",
414
- "MCP: Call explain_remediation for each rule ID — OR CLI: run `npx guardvibe explain <RULE_ID>` to get specific fix guidance",
415
- "Re-run audit and confirm config findings dropped",
424
+ "Look at sectionFindings aboveeach has ruleId, file, title, fix",
425
+ "Run `npx guardvibe explain <RULE_ID>` for each finding to get exact fix code",
426
+ "Apply fixes to the listed files. Verify: re-run audit config count must drop",
416
427
  ],
417
428
  },
418
429
  taint: {
419
430
  priority: 5,
420
431
  tool: "analyze_cross_file_dataflow",
421
432
  actions: [
422
- "MCP: Call analyze_cross_file_dataflow — OR CLI: run `npx guardvibe audit --format json` and parse the taint section. Look for user input (URL params, form data, req.body) flowing to dangerous sinks (SQL, HTML, file system)",
423
- "Fix each tainted flow: add Zod/joi validation at the input source, use parameterized queries for SQL, use sanitizeUrl/DOMPurify for HTML output, validate file paths",
424
- "Re-run audit and confirm taint findings dropped to 0",
433
+ "Look at sectionFindings above each shows source file:line sink file:line",
434
+ "Add input validation (Zod) at source, or sanitization at sink",
435
+ "Verify: re-run audit taint count must be 0",
425
436
  ],
426
437
  },
427
438
  "auth-coverage": {
428
439
  priority: 6,
429
440
  tool: "auth_coverage",
430
441
  actions: [
431
- "MCP: Call auth_coverage with format: json OR CLI: run `npx guardvibe auth-coverage --format json` to list all unprotected routes",
432
- "For each unprotected route: (a) if it needs auth, add middleware or auth guard (Clerk/NextAuth/Supabase), (b) if it's intentionally public (homepage, blog, about, etc.), add it to .guardviberc file under authExceptions with a reason",
433
- "Create or update .guardviberc in project root: {\"authExceptions\": [{\"path\": \"/blog\", \"reason\": \"Public page\"}]}",
434
- "Re-run `npx guardvibe auth-coverage --format json` and confirm unprotected count matches your authExceptions count",
442
+ "Look at sectionFindings aboveeach shows unprotected route path and file",
443
+ "Add auth guard, or add to .guardviberc authExceptions: {\"path\": \"/route\", \"reason\": \"Public\"}",
444
+ "Verify: `npx guardvibe auth-coverage --format json` unprotected must be 0",
435
445
  ],
436
446
  },
437
447
  };
@@ -468,14 +478,11 @@ export function formatAuditResult(result, format) {
468
478
  ...result,
469
479
  remediationPlan: {
470
480
  totalSectionsWithFindings: plan.length,
471
- 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.`,
481
+ warning: `FIX ALL ${plan.length} SECTIONS. Each section has sectionFindings with exact file, line, and fix. Process every finding. After ALL sections: re-run audit to verify.`,
472
482
  rules: [
473
- "Do NOT dismiss findings as 'false positive' without running the section-specific tool first and verifying the finding is genuinely wrong.",
474
- "Do NOT say 'npm audit is clean' to skip dependencies GuardVibe uses the OSV database which may detect CVEs that npm audit misses.",
475
- "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.",
476
- "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.",
477
- "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.",
478
- "Only call verify_remediation AFTER you have worked through ALL sections and confirmed each one improved.",
483
+ "NEVER use 'npx guardvibe diff' use 'npx guardvibe audit --format json 2>&1 | head -1'",
484
+ "Fix EVERY finding in sectionFindingsnot just one section",
485
+ "After fixing, re-run audit and verify finding count dropped",
479
486
  ],
480
487
  steps: plan,
481
488
  },
@@ -551,14 +558,7 @@ export function formatAuditResult(result, format) {
551
558
  lines.push(``);
552
559
  lines.push(`## Mandatory Remediation Plan`);
553
560
  lines.push(``);
554
- 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.**`);
555
- lines.push(``);
556
- lines.push(`**Rules:**`);
557
- lines.push(`- Do NOT dismiss findings as "false positive" without running the section-specific tool first`);
558
- lines.push(`- Do NOT say "npm audit is clean" to skip dependencies — GuardVibe uses OSV which may detect CVEs npm misses`);
559
- lines.push(`- Do NOT say "already in .gitignore" to skip secrets — if detected, they exist in scanned files`);
560
- lines.push(`- After fixing each section, re-run the section tool to confirm finding count dropped`);
561
- lines.push(`- Only call verify_remediation AFTER all sections show improvement`);
561
+ lines.push(`> **FIX ALL ${plan.length} SECTIONS. Each section's sectionFindings has exact file, line, and fix. Process every finding. Re-run audit after ALL sections to verify.**`);
562
562
  lines.push(``);
563
563
  for (const step of plan) {
564
564
  lines.push(`### Step ${step.priority}: ${step.section} (${step.findings} findings — ${step.critical} critical, ${step.high} high)`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "guardvibe",
3
- "version": "3.0.15",
3
+ "version": "3.0.17",
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",