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.
- package/build/data/rules/nextjs.js +1 -1
- package/build/tools/full-audit.js +50 -50
- package/package.json +1 -1
|
@@ -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
|
-
|
|
173
|
-
const
|
|
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
|
-
|
|
183
|
-
|
|
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
|
-
|
|
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.
|
|
241
|
-
description: (f.description ??
|
|
242
|
-
fix: (f.fix ??
|
|
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
|
|
246
|
-
|
|
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
|
-
"
|
|
382
|
-
"For
|
|
383
|
-
"
|
|
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 above — each 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
|
-
"
|
|
393
|
-
"
|
|
394
|
-
"
|
|
395
|
-
"Re-run full scan to confirm total code findings dropped",
|
|
406
|
+
"Look at sectionFindings above — each 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
|
-
"
|
|
403
|
-
"Run `npm
|
|
404
|
-
"
|
|
405
|
-
"Re-run `npx guardvibe audit` and confirm dependency findings dropped to 0",
|
|
415
|
+
"Look at sectionFindings above — each 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
|
-
"
|
|
413
|
-
"
|
|
414
|
-
"
|
|
415
|
-
"Re-run audit and confirm config findings dropped",
|
|
424
|
+
"Look at sectionFindings above — each 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
|
-
"
|
|
423
|
-
"
|
|
424
|
-
"
|
|
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
|
-
"
|
|
432
|
-
"
|
|
433
|
-
"
|
|
434
|
-
"Re-run `npx guardvibe auth-coverage --format json` and confirm unprotected count matches your authExceptions count",
|
|
442
|
+
"Look at sectionFindings above — each 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: `
|
|
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
|
-
"
|
|
474
|
-
"
|
|
475
|
-
"
|
|
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 sectionFindings — not 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(`> **
|
|
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.
|
|
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",
|