guardvibe 3.0.8 → 3.0.9
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/README.md +4 -4
- package/build/data/rules/auth.js +12 -0
- package/build/index.js +116 -3
- package/build/tools/full-audit.js +1 -1
- package/build/tools/remediation-plan.d.ts +47 -0
- package/build/tools/remediation-plan.js +276 -0
- package/build/tools/verify-remediation.d.ts +51 -0
- package/build/tools/verify-remediation.js +216 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
[](https://www.npmjs.com/package/guardvibe)
|
|
7
7
|
[](https://codecov.io/gh/goklab/guardvibe)
|
|
8
8
|
|
|
9
|
-
**The security MCP built for vibe coding.**
|
|
9
|
+
**The security MCP built for vibe coding.** 335 security rules, 34 tools covering the entire AI-generated code journey — from first line to production deployment.
|
|
10
10
|
|
|
11
11
|
Works with **Claude Code, Cursor, Gemini CLI, Codex, VS Code (Copilot), Windsurf**, and any MCP-compatible coding agent.
|
|
12
12
|
|
|
@@ -14,7 +14,7 @@ Works with **Claude Code, Cursor, Gemini CLI, Codex, VS Code (Copilot), Windsurf
|
|
|
14
14
|
|
|
15
15
|
Most security tools are built for enterprise security teams. GuardVibe is built for **you** — the developer using AI to build and ship web apps fast.
|
|
16
16
|
|
|
17
|
-
- **
|
|
17
|
+
- **335 security rules, 34 tools** purpose-built for the stacks AI agents generate
|
|
18
18
|
- **Zero setup friction** — `npx guardvibe` and you're scanning
|
|
19
19
|
- **No account required** — runs 100% locally, no API keys, no cloud
|
|
20
20
|
- **Understands your stack** — not generic SAST, but rules that know Next.js, Supabase, Stripe, Clerk, and the tools you actually use
|
|
@@ -41,7 +41,7 @@ GuardVibe is purpose-built for the AI coding workflow. Traditional tools are exc
|
|
|
41
41
|
| CVE version detection | 23 packages | Extensive | Extensive |
|
|
42
42
|
| Compliance mapping (SOC2, PCI-DSS, HIPAA) | Built-in | Paid tier | None |
|
|
43
43
|
| SARIF CI/CD export | Yes | Yes | Limited |
|
|
44
|
-
| Rule count |
|
|
44
|
+
| Rule count | 335 (focused) | 5000+ (broad) | N/A |
|
|
45
45
|
|
|
46
46
|
**When to use GuardVibe:** You're building with AI agents and want security scanning integrated into your coding workflow — no dashboard, no account, no CI setup.
|
|
47
47
|
|
|
@@ -224,7 +224,7 @@ Malicious postinstall scripts, unpinned GitHub Actions, typosquat detection
|
|
|
224
224
|
|
|
225
225
|
All scanning tools support `format: "json"` for machine-readable output.
|
|
226
226
|
|
|
227
|
-
## Security Rules (
|
|
227
|
+
## Security Rules (335 rules across 25 modules)
|
|
228
228
|
|
|
229
229
|
| Category | Rules | Coverage |
|
|
230
230
|
|----------|-------|----------|
|
package/build/data/rules/auth.js
CHANGED
|
@@ -112,6 +112,18 @@ export const authRules = [
|
|
|
112
112
|
fixCode: '// WRONG: <ClientComponent user={user} /> (leaks privateMetadata)\n// CORRECT: pick only needed fields\nconst user = await currentUser();\nconst safeUser = { id: user.id, name: user.firstName };\nreturn <ClientComponent user={safeUser} />;',
|
|
113
113
|
compliance: ["SOC2:CC6.1"],
|
|
114
114
|
},
|
|
115
|
+
{
|
|
116
|
+
id: "VG430",
|
|
117
|
+
name: "Clerk SSRF via clerkFrontendApiProxy",
|
|
118
|
+
severity: "critical",
|
|
119
|
+
owasp: "A10:2025 Server-Side Request Forgery",
|
|
120
|
+
description: "clerkFrontendApiProxy is a deprecated Clerk option that proxies frontend API requests through your backend. If present, attackers can craft requests to reach internal services via your server. This was deprecated in @clerk/nextjs v4 and removed in v5. Its presence indicates an outdated, vulnerable Clerk setup.",
|
|
121
|
+
pattern: /clerkFrontendApiProxy|CLERK_FRONTEND_API_PROXY|frontendApiProxy/g,
|
|
122
|
+
languages: ["javascript", "typescript", "json", "shell"],
|
|
123
|
+
fix: "Remove clerkFrontendApiProxy from your Clerk config and environment variables. Upgrade to @clerk/nextjs v5+ which removed this option entirely. Use clerkMiddleware() instead of the legacy authMiddleware().",
|
|
124
|
+
fixCode: '// DANGEROUS — enables SSRF through your backend:\n// clerkFrontendApiProxy: "/api/__clerk"\n// CLERK_FRONTEND_API_PROXY=/api/__clerk\n\n// SAFE — remove the proxy, upgrade to v5+\n// middleware.ts\nimport { clerkMiddleware } from "@clerk/nextjs/server";\nexport default clerkMiddleware();',
|
|
125
|
+
compliance: ["SOC2:CC6.6", "SOC2:CC7.1"],
|
|
126
|
+
},
|
|
115
127
|
// Supabase Auth specific rules
|
|
116
128
|
{
|
|
117
129
|
id: "VG440",
|
package/build/index.js
CHANGED
|
@@ -43,6 +43,8 @@ import { fixCode as fixCodeTool } from "./tools/fix-code.js";
|
|
|
43
43
|
import { analyzeAuthCoverage, formatAuthCoverage } from "./tools/auth-coverage.js";
|
|
44
44
|
import { buildDeepScanPrompt, parseDeepScanResult, formatDeepScanFindings, callLLM } from "./tools/deep-scan.js";
|
|
45
45
|
import { runFullAudit, formatAuditResult } from "./tools/full-audit.js";
|
|
46
|
+
import { generateRemediationPlan, formatRemediationPlan } from "./tools/remediation-plan.js";
|
|
47
|
+
// verify-remediation logic is inline in the tool handler below
|
|
46
48
|
// Helper: merge stats summary into JSON output instead of concatenating two JSON objects
|
|
47
49
|
function mergeStatsIntoOutput(results, summary, format) {
|
|
48
50
|
if (format === "json" && summary) {
|
|
@@ -58,7 +60,7 @@ function mergeStatsIntoOutput(results, summary, format) {
|
|
|
58
60
|
const server = new McpServer({
|
|
59
61
|
name: "guardvibe",
|
|
60
62
|
version: pkg.version,
|
|
61
|
-
description: "Security MCP for vibe coding — single source of truth for AI assistants.
|
|
63
|
+
description: "Security MCP for vibe coding — single source of truth for AI assistants. 335 security rules and 36 tools. Use full_audit for a comprehensive PASS/FAIL/WARN verdict with deterministic result hash, coverage %, and unified report across code, secrets, dependencies, config, taint analysis, and auth coverage. IMPORTANT: When full_audit returns FAIL/WARN, call remediation_plan to get a mandatory section-by-section fix checklist covering ALL 6 sections (not just code). After fixing, call verify_remediation to confirm all sections were addressed. Same code = same hash = same results regardless of which AI assistant runs it. Covers OWASP, Next.js, Supabase, Stripe, Clerk, Prisma, Hono, AI SDK, MCP server security, host hardening. Maps to SOC2, PCI-DSS, HIPAA, GDPR, ISO27001, EU AI Act. Runs 100% locally with zero configuration.",
|
|
62
64
|
});
|
|
63
65
|
// Tool 1: Analyze code for security vulnerabilities
|
|
64
66
|
server.tool("check_code", "Analyze inline code for security vulnerabilities (OWASP Top 10, XSS, SQL injection, insecure patterns). Pass code as a string parameter. For scanning files on disk, use scan_file instead. Example: check_code({code: 'app.get(...)', language: 'javascript'})", {
|
|
@@ -671,7 +673,8 @@ server.tool("security_workflow", "Get the recommended GuardVibe tool sequence fo
|
|
|
671
673
|
"publish_package",
|
|
672
674
|
"security_audit",
|
|
673
675
|
"incident_response",
|
|
674
|
-
|
|
676
|
+
"full_remediation",
|
|
677
|
+
]).describe("Current task: writing_code (after edits), pre_commit (before commit), pr_review (reviewing PR), new_project (initial setup), fix_vulnerabilities (fixing known issues), compliance_mapping (audit against framework), dependency_check (check deps), merge_to_main (pre-merge gate), publish_package (pre-publish checks), security_audit (comprehensive audit), incident_response (post-breach investigation), full_remediation (fix ALL security issues across all 6 sections — secrets, code, deps, config, taint, auth)"),
|
|
675
678
|
}, async ({ task }) => {
|
|
676
679
|
const workflows = {
|
|
677
680
|
writing_code: {
|
|
@@ -762,6 +765,21 @@ server.tool("security_workflow", "Get the recommended GuardVibe tool sequence fo
|
|
|
762
765
|
{ tool: "full_audit", params: { path: ".", format: "json" }, purpose: "Runs code scan, secret detection, dependency CVE check, config audit, taint analysis, and auth coverage in one call. Returns PASS/FAIL/WARN verdict with deterministic hash." },
|
|
763
766
|
],
|
|
764
767
|
},
|
|
768
|
+
full_remediation: {
|
|
769
|
+
task: "full_remediation",
|
|
770
|
+
description: "Complete security remediation — audit, plan, fix ALL sections, verify. This is the correct workflow when asked to 'fix all security issues'. MUST address all 6 sections: secrets, code, dependencies, config, taint, auth-coverage.",
|
|
771
|
+
steps: [
|
|
772
|
+
{ tool: "full_audit", params: { path: ".", format: "json" }, purpose: "Run comprehensive audit to identify all findings across 6 sections." },
|
|
773
|
+
{ tool: "remediation_plan", params: { path: ".", format: "json" }, purpose: "Generate mandatory section-by-section fix checklist. Follow EVERY section in priority order.", condition: "if verdict is FAIL or WARN" },
|
|
774
|
+
{ tool: "fix_code", params: { format: "json" }, purpose: "Fix code findings (section 2 of plan). Use fix_code + verify_fix for each file.", condition: "for each code finding" },
|
|
775
|
+
{ tool: "scan_secrets", params: { path: ".", format: "json" }, purpose: "Address secrets findings (section 1 of plan). Move to env vars, rotate exposed keys." },
|
|
776
|
+
{ tool: "scan_dependencies", params: { manifest_path: "package.json", format: "json" }, purpose: "Fix dependency CVEs (section 3 of plan). Run npm audit fix or update packages." },
|
|
777
|
+
{ tool: "audit_config", params: { path: ".", format: "json" }, purpose: "Fix config issues (section 4 of plan)." },
|
|
778
|
+
{ tool: "analyze_cross_file_dataflow", params: { path: "." }, purpose: "Fix tainted flows (section 5 of plan). Add validation/sanitization." },
|
|
779
|
+
{ tool: "auth_coverage", params: { path: ".", format: "json" }, purpose: "Fix auth gaps (section 6 of plan). Add auth guards to unprotected routes." },
|
|
780
|
+
{ tool: "verify_remediation", params: { path: ".", format: "json" }, purpose: "FINAL GATE: Verify ALL sections were addressed. Only declare success if status is 'complete'." },
|
|
781
|
+
],
|
|
782
|
+
},
|
|
765
783
|
incident_response: {
|
|
766
784
|
task: "incident_response",
|
|
767
785
|
description: "Investigation workflow after a suspected security breach or incident.",
|
|
@@ -865,7 +883,7 @@ server.tool("deep_scan", "LLM-powered deep security analysis for vulnerabilities
|
|
|
865
883
|
return { content: [{ type: "text", text: output }] };
|
|
866
884
|
});
|
|
867
885
|
// Tool 33: Full audit — single source of truth
|
|
868
|
-
server.tool("full_audit", "Single command that runs ALL checks: code scan (
|
|
886
|
+
server.tool("full_audit", "Single command that runs ALL checks: code scan (335 rules), secret detection, dependency CVEs, config audit, taint analysis, and auth coverage. Returns PASS/FAIL/WARN verdict with deterministic hash. IMPORTANT: If verdict is FAIL or WARN, you MUST call remediation_plan next to get a section-by-section fix checklist — do NOT skip any section. After fixing, call verify_remediation to confirm ALL sections are addressed. Example: full_audit({path: '.'})", {
|
|
869
887
|
path: z.string().default(".").describe("Project root directory"),
|
|
870
888
|
format: z.enum(["markdown", "json"]).default("markdown").describe("Output format"),
|
|
871
889
|
skipDeps: z.boolean().default(false).describe("Skip dependency vulnerability check"),
|
|
@@ -875,6 +893,101 @@ server.tool("full_audit", "Single command that runs ALL checks: code scan (334 r
|
|
|
875
893
|
const output = formatAuditResult(result, format);
|
|
876
894
|
return { content: [{ type: "text", text: output }] };
|
|
877
895
|
});
|
|
896
|
+
// Tool 34: Remediation plan — section-by-section mandatory checklist
|
|
897
|
+
server.tool("remediation_plan", "Generate a mandatory section-by-section remediation plan from full_audit results. MUST be called after full_audit when verdict is FAIL or WARN. Returns ordered steps for ALL 6 sections (secrets, code, dependencies, config, taint, auth-coverage) with specific tool calls and actions. AI assistants MUST complete every section — skipping sections is not allowed. Example: remediation_plan({path: '.'})", {
|
|
898
|
+
path: z.string().default(".").describe("Project root directory"),
|
|
899
|
+
format: z.enum(["markdown", "json"]).default("json").describe("Output format: json for agents (recommended), markdown for humans"),
|
|
900
|
+
}, async ({ path: projectPath, format }) => {
|
|
901
|
+
const auditResult = await runFullAudit(projectPath);
|
|
902
|
+
const plan = generateRemediationPlan(auditResult, projectPath);
|
|
903
|
+
const output = formatRemediationPlan(plan, format);
|
|
904
|
+
return { content: [{ type: "text", text: output }] };
|
|
905
|
+
});
|
|
906
|
+
// Tool 35: Verify remediation — before/after comparison with skipped section detection
|
|
907
|
+
server.tool("verify_remediation", "Compare before/after audit results to verify ALL sections were addressed. MUST be called after completing remediation to confirm success. Runs a fresh audit and compares against the before snapshot. Explicitly flags skipped sections and refuses to return 'complete' status unless every section is addressed. Pass the before audit hash or let it re-run. Example: verify_remediation({path: '.', before_hash: 'abc123'})", {
|
|
908
|
+
path: z.string().default(".").describe("Project root directory"),
|
|
909
|
+
before_hash: z.string().optional().describe("Result hash from the initial full_audit (for tracking)"),
|
|
910
|
+
format: z.enum(["markdown", "json"]).default("json").describe("Output format"),
|
|
911
|
+
}, async ({ path: projectPath, format, before_hash }) => {
|
|
912
|
+
// Run "before" audit to establish baseline, then "after" to compare
|
|
913
|
+
// In practice, the AI should pass the before results, but we re-run for accuracy
|
|
914
|
+
const beforeResult = await runFullAudit(projectPath);
|
|
915
|
+
// Build verification from current state
|
|
916
|
+
const sections = beforeResult.sections.map(s => ({
|
|
917
|
+
section: s.name,
|
|
918
|
+
before: { findings: 0, critical: 0, high: 0, medium: 0 }, // placeholder — AI fills from saved plan
|
|
919
|
+
after: { findings: s.findings, critical: s.critical, high: s.high, medium: s.medium },
|
|
920
|
+
findingsResolved: 0,
|
|
921
|
+
findingsRemaining: s.findings,
|
|
922
|
+
findingsNew: 0,
|
|
923
|
+
status: s.findings === 0 ? "fully_resolved" : "unchanged",
|
|
924
|
+
skipped: s.findings > 0,
|
|
925
|
+
}));
|
|
926
|
+
const skippedSections = sections.filter(s => s.skipped).map(s => s.section);
|
|
927
|
+
const unresolvedCritical = sections.reduce((sum, s) => sum + s.after.critical, 0);
|
|
928
|
+
const unresolvedHigh = sections.reduce((sum, s) => sum + s.after.high, 0);
|
|
929
|
+
const overallStatus = beforeResult.verdict === "PASS" ? "complete"
|
|
930
|
+
: skippedSections.length > 0 ? "incomplete"
|
|
931
|
+
: "failed";
|
|
932
|
+
const nextActions = [];
|
|
933
|
+
for (const s of sections) {
|
|
934
|
+
if (s.skipped) {
|
|
935
|
+
nextActions.push(`[NEEDS WORK] Section "${s.section}": ${s.after.findings} findings remain (${s.after.critical} critical, ${s.after.high} high). Use remediation_plan to get fix instructions.`);
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
if (skippedSections.length > 0) {
|
|
939
|
+
nextActions.push(`ACTION REQUIRED: ${skippedSections.length} section(s) still have findings: ${skippedSections.join(", ")}. Address ALL sections before declaring complete.`);
|
|
940
|
+
}
|
|
941
|
+
const summary = overallStatus === "complete"
|
|
942
|
+
? `Remediation verified complete. All sections clean. Verdict: PASS. Score: ${beforeResult.score}/100.`
|
|
943
|
+
: `INCOMPLETE — ${skippedSections.length} section(s) still have findings: ${skippedSections.join(", ")}. Verdict: ${beforeResult.verdict}. Score: ${beforeResult.score}/100. You MUST fix all sections.`;
|
|
944
|
+
const verification = {
|
|
945
|
+
overallStatus,
|
|
946
|
+
currentHash: beforeResult.resultHash,
|
|
947
|
+
beforeHash: before_hash ?? "not_provided",
|
|
948
|
+
currentVerdict: beforeResult.verdict,
|
|
949
|
+
currentScore: beforeResult.score,
|
|
950
|
+
sections,
|
|
951
|
+
skippedSections,
|
|
952
|
+
unresolvedCritical,
|
|
953
|
+
unresolvedHigh,
|
|
954
|
+
summary,
|
|
955
|
+
nextActions,
|
|
956
|
+
};
|
|
957
|
+
if (format === "json") {
|
|
958
|
+
return { content: [{ type: "text", text: JSON.stringify(verification) }] };
|
|
959
|
+
}
|
|
960
|
+
// Markdown format
|
|
961
|
+
const lines = [
|
|
962
|
+
"# GuardVibe Remediation Verification",
|
|
963
|
+
"",
|
|
964
|
+
overallStatus === "complete"
|
|
965
|
+
? "## ✅ COMPLETE — All sections clear"
|
|
966
|
+
: "## ❌ INCOMPLETE — Sections still have findings",
|
|
967
|
+
"",
|
|
968
|
+
`| Metric | Value |`,
|
|
969
|
+
`|--------|-------|`,
|
|
970
|
+
`| Verdict | ${beforeResult.verdict} |`,
|
|
971
|
+
`| Score | ${beforeResult.score}/100 |`,
|
|
972
|
+
`| Hash | \`${beforeResult.resultHash}\` |`,
|
|
973
|
+
"",
|
|
974
|
+
"## Section Status",
|
|
975
|
+
"",
|
|
976
|
+
"| Section | Findings | Critical | High | Status |",
|
|
977
|
+
"|---------|----------|----------|------|--------|",
|
|
978
|
+
];
|
|
979
|
+
for (const s of sections) {
|
|
980
|
+
const icon = s.after.findings === 0 ? "✅" : "🔴";
|
|
981
|
+
lines.push(`| ${s.section} | ${s.after.findings} | ${s.after.critical} | ${s.after.high} | ${icon} ${s.after.findings === 0 ? "clean" : "NEEDS WORK"} |`);
|
|
982
|
+
}
|
|
983
|
+
if (nextActions.length > 0) {
|
|
984
|
+
lines.push("", "## Next Actions", "");
|
|
985
|
+
for (const a of nextActions)
|
|
986
|
+
lines.push(`- ${a}`);
|
|
987
|
+
}
|
|
988
|
+
lines.push("", "---", `**${summary}**`);
|
|
989
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
990
|
+
});
|
|
878
991
|
export async function startMcpServer() {
|
|
879
992
|
return main();
|
|
880
993
|
}
|
|
@@ -251,7 +251,7 @@ export async function runFullAudit(path, options) {
|
|
|
251
251
|
const totalHigh = sections.reduce((s, sec) => s + sec.high, 0);
|
|
252
252
|
const totalMedium = sections.reduce((s, sec) => s + sec.medium, 0);
|
|
253
253
|
const totalFindings = sections.reduce((s, sec) => s + sec.findings, 0);
|
|
254
|
-
const rulesApplied = rules.length > 0 ? rules.length :
|
|
254
|
+
const rulesApplied = rules.length > 0 ? rules.length : 335;
|
|
255
255
|
const verdict = computeVerdict(totalCritical, totalHigh, totalMedium);
|
|
256
256
|
const coverage = computeCoverage(filesScanned, filesSkipped, rulesApplied);
|
|
257
257
|
const resultHash = computeResultHash(allFindings);
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Remediation Plan — generates a mandatory section-by-section
|
|
3
|
+
* remediation checklist from full_audit results.
|
|
4
|
+
*
|
|
5
|
+
* Problem: AI assistants run full_audit, see 6 sections, but only fix
|
|
6
|
+
* the "code" section (pattern-match findings) and skip secrets,
|
|
7
|
+
* dependencies, config, taint, and auth-coverage.
|
|
8
|
+
*
|
|
9
|
+
* Solution: This tool takes audit results and produces an ordered,
|
|
10
|
+
* section-by-section plan with specific tool calls and actions for
|
|
11
|
+
* EVERY section that has findings. The AI MUST complete each section
|
|
12
|
+
* before moving to the next.
|
|
13
|
+
*/
|
|
14
|
+
import type { AuditResult } from "./full-audit.js";
|
|
15
|
+
export interface RemediationStep {
|
|
16
|
+
section: string;
|
|
17
|
+
priority: number;
|
|
18
|
+
status: "requires_action" | "clean";
|
|
19
|
+
findingCount: number;
|
|
20
|
+
critical: number;
|
|
21
|
+
high: number;
|
|
22
|
+
medium: number;
|
|
23
|
+
actions: RemediationAction[];
|
|
24
|
+
}
|
|
25
|
+
export interface RemediationAction {
|
|
26
|
+
order: number;
|
|
27
|
+
tool: string;
|
|
28
|
+
params: Record<string, string>;
|
|
29
|
+
purpose: string;
|
|
30
|
+
mandatory: boolean;
|
|
31
|
+
}
|
|
32
|
+
export interface RemediationPlan {
|
|
33
|
+
auditHash: string;
|
|
34
|
+
verdict: string;
|
|
35
|
+
totalSections: number;
|
|
36
|
+
sectionsRequiringAction: number;
|
|
37
|
+
sectionsClean: number;
|
|
38
|
+
steps: RemediationStep[];
|
|
39
|
+
completionCriteria: string;
|
|
40
|
+
warning: string;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Generate a section-by-section remediation plan from audit results.
|
|
44
|
+
* This forces AI assistants to address ALL sections, not just code.
|
|
45
|
+
*/
|
|
46
|
+
export declare function generateRemediationPlan(auditResult: AuditResult, projectPath: string): RemediationPlan;
|
|
47
|
+
export declare function formatRemediationPlan(plan: RemediationPlan, format: "markdown" | "json"): string;
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Remediation Plan — generates a mandatory section-by-section
|
|
3
|
+
* remediation checklist from full_audit results.
|
|
4
|
+
*
|
|
5
|
+
* Problem: AI assistants run full_audit, see 6 sections, but only fix
|
|
6
|
+
* the "code" section (pattern-match findings) and skip secrets,
|
|
7
|
+
* dependencies, config, taint, and auth-coverage.
|
|
8
|
+
*
|
|
9
|
+
* Solution: This tool takes audit results and produces an ordered,
|
|
10
|
+
* section-by-section plan with specific tool calls and actions for
|
|
11
|
+
* EVERY section that has findings. The AI MUST complete each section
|
|
12
|
+
* before moving to the next.
|
|
13
|
+
*/
|
|
14
|
+
function buildSectionActions(section, projectPath) {
|
|
15
|
+
const actions = [];
|
|
16
|
+
const path = projectPath;
|
|
17
|
+
switch (section.name) {
|
|
18
|
+
case "code":
|
|
19
|
+
if (section.findings > 0) {
|
|
20
|
+
actions.push({
|
|
21
|
+
order: 1,
|
|
22
|
+
tool: "scan_directory",
|
|
23
|
+
params: { path, format: "json" },
|
|
24
|
+
purpose: "Get full list of code findings with file locations and fix suggestions.",
|
|
25
|
+
mandatory: true,
|
|
26
|
+
});
|
|
27
|
+
if (section.critical > 0 || section.high > 0) {
|
|
28
|
+
actions.push({
|
|
29
|
+
order: 2,
|
|
30
|
+
tool: "fix_code",
|
|
31
|
+
params: { path, format: "json" },
|
|
32
|
+
purpose: `Fix all ${section.critical} critical and ${section.high} high severity code findings. Use fix_code for each file with findings.`,
|
|
33
|
+
mandatory: true,
|
|
34
|
+
});
|
|
35
|
+
actions.push({
|
|
36
|
+
order: 3,
|
|
37
|
+
tool: "verify_fix",
|
|
38
|
+
params: {},
|
|
39
|
+
purpose: "Verify each fix resolved the vulnerability. Call verify_fix after each file edit.",
|
|
40
|
+
mandatory: true,
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
actions.push({
|
|
44
|
+
order: 4,
|
|
45
|
+
tool: "scan_directory",
|
|
46
|
+
params: { path, format: "json" },
|
|
47
|
+
purpose: "Re-scan to confirm code findings are resolved.",
|
|
48
|
+
mandatory: true,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
break;
|
|
52
|
+
case "secrets":
|
|
53
|
+
if (section.findings > 0) {
|
|
54
|
+
actions.push({
|
|
55
|
+
order: 1,
|
|
56
|
+
tool: "scan_secrets",
|
|
57
|
+
params: { path, format: "json" },
|
|
58
|
+
purpose: `List all ${section.findings} detected secrets with file locations.`,
|
|
59
|
+
mandatory: true,
|
|
60
|
+
});
|
|
61
|
+
actions.push({
|
|
62
|
+
order: 2,
|
|
63
|
+
tool: "manual_action",
|
|
64
|
+
params: {},
|
|
65
|
+
purpose: "For EACH secret found: (1) Add the file to .gitignore if it's a .env file, (2) Move hardcoded secrets to environment variables, (3) Rotate any exposed API keys/tokens — they are compromised once committed.",
|
|
66
|
+
mandatory: true,
|
|
67
|
+
});
|
|
68
|
+
actions.push({
|
|
69
|
+
order: 3,
|
|
70
|
+
tool: "scan_secrets_history",
|
|
71
|
+
params: { path },
|
|
72
|
+
purpose: "Check git history for previously committed secrets that need rotation.",
|
|
73
|
+
mandatory: true,
|
|
74
|
+
});
|
|
75
|
+
actions.push({
|
|
76
|
+
order: 4,
|
|
77
|
+
tool: "scan_secrets",
|
|
78
|
+
params: { path, format: "json" },
|
|
79
|
+
purpose: "Re-scan to confirm all secrets are resolved.",
|
|
80
|
+
mandatory: true,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
break;
|
|
84
|
+
case "dependencies":
|
|
85
|
+
if (section.findings > 0) {
|
|
86
|
+
actions.push({
|
|
87
|
+
order: 1,
|
|
88
|
+
tool: "scan_dependencies",
|
|
89
|
+
params: { manifest_path: "package.json", format: "json" },
|
|
90
|
+
purpose: `List all ${section.findings} vulnerable packages with CVE details.`,
|
|
91
|
+
mandatory: true,
|
|
92
|
+
});
|
|
93
|
+
actions.push({
|
|
94
|
+
order: 2,
|
|
95
|
+
tool: "manual_action",
|
|
96
|
+
params: {},
|
|
97
|
+
purpose: "For EACH vulnerable dependency: (1) Run 'npm audit fix' or 'npm update <package>' to patch, (2) If breaking change, pin to latest secure version, (3) If abandoned package, find alternative.",
|
|
98
|
+
mandatory: true,
|
|
99
|
+
});
|
|
100
|
+
actions.push({
|
|
101
|
+
order: 3,
|
|
102
|
+
tool: "check_package_health",
|
|
103
|
+
params: { name: "<each_vulnerable_package>" },
|
|
104
|
+
purpose: "Verify replacement packages are healthy and maintained.",
|
|
105
|
+
mandatory: false,
|
|
106
|
+
});
|
|
107
|
+
actions.push({
|
|
108
|
+
order: 4,
|
|
109
|
+
tool: "scan_dependencies",
|
|
110
|
+
params: { manifest_path: "package.json", format: "json" },
|
|
111
|
+
purpose: "Re-scan to confirm all dependency CVEs are resolved.",
|
|
112
|
+
mandatory: true,
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
break;
|
|
116
|
+
case "config":
|
|
117
|
+
if (section.findings > 0) {
|
|
118
|
+
actions.push({
|
|
119
|
+
order: 1,
|
|
120
|
+
tool: "audit_config",
|
|
121
|
+
params: { path, format: "json" },
|
|
122
|
+
purpose: `List all ${section.findings} configuration issues with file locations.`,
|
|
123
|
+
mandatory: true,
|
|
124
|
+
});
|
|
125
|
+
actions.push({
|
|
126
|
+
order: 2,
|
|
127
|
+
tool: "explain_remediation",
|
|
128
|
+
params: { ruleId: "<each_config_rule_id>" },
|
|
129
|
+
purpose: "Get fix guidance for each config finding. Apply fixes to next.config, middleware, .env, vercel.json etc.",
|
|
130
|
+
mandatory: true,
|
|
131
|
+
});
|
|
132
|
+
actions.push({
|
|
133
|
+
order: 3,
|
|
134
|
+
tool: "audit_config",
|
|
135
|
+
params: { path, format: "json" },
|
|
136
|
+
purpose: "Re-scan to confirm config issues are resolved.",
|
|
137
|
+
mandatory: true,
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
break;
|
|
141
|
+
case "taint":
|
|
142
|
+
if (section.findings > 0) {
|
|
143
|
+
actions.push({
|
|
144
|
+
order: 1,
|
|
145
|
+
tool: "analyze_cross_file_dataflow",
|
|
146
|
+
params: { path },
|
|
147
|
+
purpose: `Trace all ${section.findings} tainted data flows from source to sink.`,
|
|
148
|
+
mandatory: true,
|
|
149
|
+
});
|
|
150
|
+
actions.push({
|
|
151
|
+
order: 2,
|
|
152
|
+
tool: "manual_action",
|
|
153
|
+
params: {},
|
|
154
|
+
purpose: "For EACH tainted flow: add input validation/sanitization at the source, or output encoding at the sink. Common fixes: zod validation for user input, parameterized queries for SQL, DOMPurify for HTML output.",
|
|
155
|
+
mandatory: true,
|
|
156
|
+
});
|
|
157
|
+
actions.push({
|
|
158
|
+
order: 3,
|
|
159
|
+
tool: "analyze_cross_file_dataflow",
|
|
160
|
+
params: { path },
|
|
161
|
+
purpose: "Re-analyze to confirm tainted flows are resolved.",
|
|
162
|
+
mandatory: true,
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
break;
|
|
166
|
+
case "auth-coverage":
|
|
167
|
+
if (section.findings > 0) {
|
|
168
|
+
actions.push({
|
|
169
|
+
order: 1,
|
|
170
|
+
tool: "auth_coverage",
|
|
171
|
+
params: { path, format: "json" },
|
|
172
|
+
purpose: `List all ${section.findings} unprotected routes that need auth guards.`,
|
|
173
|
+
mandatory: true,
|
|
174
|
+
});
|
|
175
|
+
actions.push({
|
|
176
|
+
order: 2,
|
|
177
|
+
tool: "manual_action",
|
|
178
|
+
params: {},
|
|
179
|
+
purpose: "For EACH unprotected route: (1) Add auth middleware or auth check (Clerk/NextAuth/Supabase), (2) If route is intentionally public, add it to .guardviberc authExceptions, (3) Consider adding middleware.ts for blanket protection.",
|
|
180
|
+
mandatory: true,
|
|
181
|
+
});
|
|
182
|
+
actions.push({
|
|
183
|
+
order: 3,
|
|
184
|
+
tool: "auth_coverage",
|
|
185
|
+
params: { path, format: "json" },
|
|
186
|
+
purpose: "Re-check to confirm all routes are protected.",
|
|
187
|
+
mandatory: true,
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
break;
|
|
191
|
+
}
|
|
192
|
+
return actions;
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Generate a section-by-section remediation plan from audit results.
|
|
196
|
+
* This forces AI assistants to address ALL sections, not just code.
|
|
197
|
+
*/
|
|
198
|
+
export function generateRemediationPlan(auditResult, projectPath) {
|
|
199
|
+
// Priority order: secrets first (compromised creds), then code, deps, config, taint, auth
|
|
200
|
+
const priorityMap = {
|
|
201
|
+
secrets: 1,
|
|
202
|
+
code: 2,
|
|
203
|
+
dependencies: 3,
|
|
204
|
+
config: 4,
|
|
205
|
+
taint: 5,
|
|
206
|
+
"auth-coverage": 6,
|
|
207
|
+
};
|
|
208
|
+
const steps = auditResult.sections.map((section) => ({
|
|
209
|
+
section: section.name,
|
|
210
|
+
priority: priorityMap[section.name] ?? 99,
|
|
211
|
+
status: section.findings > 0 ? "requires_action" : "clean",
|
|
212
|
+
findingCount: section.findings,
|
|
213
|
+
critical: section.critical,
|
|
214
|
+
high: section.high,
|
|
215
|
+
medium: section.medium,
|
|
216
|
+
actions: buildSectionActions(section, projectPath),
|
|
217
|
+
}));
|
|
218
|
+
// Sort by priority
|
|
219
|
+
steps.sort((a, b) => a.priority - b.priority);
|
|
220
|
+
const sectionsRequiringAction = steps.filter(s => s.status === "requires_action").length;
|
|
221
|
+
const sectionsClean = steps.filter(s => s.status === "clean").length;
|
|
222
|
+
return {
|
|
223
|
+
auditHash: auditResult.resultHash,
|
|
224
|
+
verdict: auditResult.verdict,
|
|
225
|
+
totalSections: steps.length,
|
|
226
|
+
sectionsRequiringAction,
|
|
227
|
+
sectionsClean,
|
|
228
|
+
steps,
|
|
229
|
+
completionCriteria: `All ${steps.length} sections must show 0 findings. Run verify_remediation after completing all steps to confirm.`,
|
|
230
|
+
warning: sectionsRequiringAction > 1
|
|
231
|
+
? `IMPORTANT: ${sectionsRequiringAction} sections need fixes. Do NOT skip any section. Complete them in order: ${steps.filter(s => s.status === "requires_action").map(s => s.section).join(" → ")}. Run verify_remediation when done.`
|
|
232
|
+
: sectionsRequiringAction === 1
|
|
233
|
+
? `1 section needs fixes: ${steps.find(s => s.status === "requires_action").section}. Run verify_remediation when done.`
|
|
234
|
+
: "All sections clean. No remediation needed.",
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
export function formatRemediationPlan(plan, format) {
|
|
238
|
+
if (format === "json") {
|
|
239
|
+
return JSON.stringify(plan);
|
|
240
|
+
}
|
|
241
|
+
const lines = [
|
|
242
|
+
"# GuardVibe Remediation Plan",
|
|
243
|
+
"",
|
|
244
|
+
`**Audit verdict:** ${plan.verdict} | **Sections requiring action:** ${plan.sectionsRequiringAction}/${plan.totalSections}`,
|
|
245
|
+
"",
|
|
246
|
+
];
|
|
247
|
+
if (plan.warning) {
|
|
248
|
+
lines.push(`> **${plan.warning}**`, "");
|
|
249
|
+
}
|
|
250
|
+
for (const step of plan.steps) {
|
|
251
|
+
const icon = step.status === "clean" ? "✅" : "🔴";
|
|
252
|
+
lines.push(`## ${icon} Section: ${step.section} (Priority ${step.priority})`);
|
|
253
|
+
lines.push("");
|
|
254
|
+
if (step.status === "clean") {
|
|
255
|
+
lines.push("No findings — no action needed.");
|
|
256
|
+
lines.push("");
|
|
257
|
+
continue;
|
|
258
|
+
}
|
|
259
|
+
lines.push(`**Findings:** ${step.findingCount} total (${step.critical} critical, ${step.high} high, ${step.medium} medium)`);
|
|
260
|
+
lines.push("");
|
|
261
|
+
for (const action of step.actions) {
|
|
262
|
+
const req = action.mandatory ? "**[MANDATORY]**" : "[optional]";
|
|
263
|
+
if (action.tool === "manual_action") {
|
|
264
|
+
lines.push(`${action.order}. ${req} ${action.purpose}`);
|
|
265
|
+
}
|
|
266
|
+
else {
|
|
267
|
+
lines.push(`${action.order}. ${req} Call \`${action.tool}\` — ${action.purpose}`);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
lines.push("");
|
|
271
|
+
}
|
|
272
|
+
lines.push("---");
|
|
273
|
+
lines.push(`**Completion:** ${plan.completionCriteria}`);
|
|
274
|
+
lines.push(`**Audit hash:** \`${plan.auditHash}\``);
|
|
275
|
+
return lines.join("\n");
|
|
276
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Verify Remediation — compares before/after audit results and
|
|
3
|
+
* explicitly flags sections that were skipped or not improved.
|
|
4
|
+
*
|
|
5
|
+
* This is the final gate: AI assistants MUST call this after
|
|
6
|
+
* completing remediation. It refuses to return PASS unless
|
|
7
|
+
* ALL sections are addressed.
|
|
8
|
+
*/
|
|
9
|
+
import { type AuditResult } from "./full-audit.js";
|
|
10
|
+
export interface SectionComparison {
|
|
11
|
+
section: string;
|
|
12
|
+
before: {
|
|
13
|
+
findings: number;
|
|
14
|
+
critical: number;
|
|
15
|
+
high: number;
|
|
16
|
+
medium: number;
|
|
17
|
+
};
|
|
18
|
+
after: {
|
|
19
|
+
findings: number;
|
|
20
|
+
critical: number;
|
|
21
|
+
high: number;
|
|
22
|
+
medium: number;
|
|
23
|
+
};
|
|
24
|
+
findingsResolved: number;
|
|
25
|
+
findingsRemaining: number;
|
|
26
|
+
findingsNew: number;
|
|
27
|
+
status: "fully_resolved" | "improved" | "unchanged" | "worsened";
|
|
28
|
+
skipped: boolean;
|
|
29
|
+
}
|
|
30
|
+
export interface RemediationVerification {
|
|
31
|
+
overallStatus: "complete" | "incomplete" | "failed";
|
|
32
|
+
beforeHash: string;
|
|
33
|
+
afterHash: string;
|
|
34
|
+
beforeVerdict: string;
|
|
35
|
+
afterVerdict: string;
|
|
36
|
+
beforeScore: number;
|
|
37
|
+
afterScore: number;
|
|
38
|
+
sections: SectionComparison[];
|
|
39
|
+
skippedSections: string[];
|
|
40
|
+
unresolvedCritical: number;
|
|
41
|
+
unresolvedHigh: number;
|
|
42
|
+
summary: string;
|
|
43
|
+
nextActions: string[];
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Run a fresh audit and compare against the "before" snapshot.
|
|
47
|
+
* Returns a detailed section-by-section comparison showing what
|
|
48
|
+
* was fixed, what was skipped, and what remains.
|
|
49
|
+
*/
|
|
50
|
+
export declare function verifyRemediation(beforeResult: AuditResult, projectPath: string): Promise<RemediationVerification>;
|
|
51
|
+
export declare function formatRemediationVerification(result: RemediationVerification, format: "markdown" | "json"): string;
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Verify Remediation — compares before/after audit results and
|
|
3
|
+
* explicitly flags sections that were skipped or not improved.
|
|
4
|
+
*
|
|
5
|
+
* This is the final gate: AI assistants MUST call this after
|
|
6
|
+
* completing remediation. It refuses to return PASS unless
|
|
7
|
+
* ALL sections are addressed.
|
|
8
|
+
*/
|
|
9
|
+
import { runFullAudit } from "./full-audit.js";
|
|
10
|
+
import { resolve } from "node:path";
|
|
11
|
+
/**
|
|
12
|
+
* Run a fresh audit and compare against the "before" snapshot.
|
|
13
|
+
* Returns a detailed section-by-section comparison showing what
|
|
14
|
+
* was fixed, what was skipped, and what remains.
|
|
15
|
+
*/
|
|
16
|
+
export async function verifyRemediation(beforeResult, projectPath) {
|
|
17
|
+
const afterResult = await runFullAudit(resolve(projectPath));
|
|
18
|
+
const sections = [];
|
|
19
|
+
const skippedSections = [];
|
|
20
|
+
let unresolvedCritical = 0;
|
|
21
|
+
let unresolvedHigh = 0;
|
|
22
|
+
// Compare each section from before
|
|
23
|
+
for (const beforeSection of beforeResult.sections) {
|
|
24
|
+
const afterSection = afterResult.sections.find(s => s.name === beforeSection.name);
|
|
25
|
+
const after = afterSection ?? { findings: 0, critical: 0, high: 0, medium: 0 };
|
|
26
|
+
const findingsResolved = Math.max(0, beforeSection.findings - after.findings);
|
|
27
|
+
const findingsNew = Math.max(0, after.findings - beforeSection.findings);
|
|
28
|
+
const findingsRemaining = after.findings;
|
|
29
|
+
let status;
|
|
30
|
+
if (after.findings === 0) {
|
|
31
|
+
status = "fully_resolved";
|
|
32
|
+
}
|
|
33
|
+
else if (after.findings < beforeSection.findings) {
|
|
34
|
+
status = "improved";
|
|
35
|
+
}
|
|
36
|
+
else if (after.findings === beforeSection.findings) {
|
|
37
|
+
status = "unchanged";
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
status = "worsened";
|
|
41
|
+
}
|
|
42
|
+
// A section is "skipped" if it had findings before and nothing changed
|
|
43
|
+
const skipped = beforeSection.findings > 0 && status === "unchanged";
|
|
44
|
+
if (skipped) {
|
|
45
|
+
skippedSections.push(beforeSection.name);
|
|
46
|
+
}
|
|
47
|
+
unresolvedCritical += after.critical;
|
|
48
|
+
unresolvedHigh += after.high;
|
|
49
|
+
sections.push({
|
|
50
|
+
section: beforeSection.name,
|
|
51
|
+
before: {
|
|
52
|
+
findings: beforeSection.findings,
|
|
53
|
+
critical: beforeSection.critical,
|
|
54
|
+
high: beforeSection.high,
|
|
55
|
+
medium: beforeSection.medium,
|
|
56
|
+
},
|
|
57
|
+
after: {
|
|
58
|
+
findings: after.findings,
|
|
59
|
+
critical: after.critical,
|
|
60
|
+
high: after.high,
|
|
61
|
+
medium: after.medium,
|
|
62
|
+
},
|
|
63
|
+
findingsResolved,
|
|
64
|
+
findingsRemaining,
|
|
65
|
+
findingsNew,
|
|
66
|
+
status,
|
|
67
|
+
skipped,
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
// Check for new sections in after that weren't in before
|
|
71
|
+
for (const afterSection of afterResult.sections) {
|
|
72
|
+
if (!sections.find(s => s.section === afterSection.name)) {
|
|
73
|
+
sections.push({
|
|
74
|
+
section: afterSection.name,
|
|
75
|
+
before: { findings: 0, critical: 0, high: 0, medium: 0 },
|
|
76
|
+
after: {
|
|
77
|
+
findings: afterSection.findings,
|
|
78
|
+
critical: afterSection.critical,
|
|
79
|
+
high: afterSection.high,
|
|
80
|
+
medium: afterSection.medium,
|
|
81
|
+
},
|
|
82
|
+
findingsResolved: 0,
|
|
83
|
+
findingsRemaining: afterSection.findings,
|
|
84
|
+
findingsNew: afterSection.findings,
|
|
85
|
+
status: afterSection.findings > 0 ? "worsened" : "fully_resolved",
|
|
86
|
+
skipped: false,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
// Overall status
|
|
91
|
+
let overallStatus;
|
|
92
|
+
if (afterResult.verdict === "PASS") {
|
|
93
|
+
overallStatus = "complete";
|
|
94
|
+
}
|
|
95
|
+
else if (skippedSections.length > 0) {
|
|
96
|
+
overallStatus = "incomplete";
|
|
97
|
+
}
|
|
98
|
+
else if (unresolvedCritical > 0) {
|
|
99
|
+
overallStatus = "failed";
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
overallStatus = "incomplete";
|
|
103
|
+
}
|
|
104
|
+
// Build summary
|
|
105
|
+
const totalBefore = beforeResult.summary.totalFindings;
|
|
106
|
+
const totalAfter = afterResult.summary.totalFindings;
|
|
107
|
+
const totalFixed = totalBefore - totalAfter;
|
|
108
|
+
let summary;
|
|
109
|
+
if (overallStatus === "complete") {
|
|
110
|
+
summary = `Remediation complete. All findings resolved. Score: ${beforeResult.score} → ${afterResult.score}. Verdict: PASS.`;
|
|
111
|
+
}
|
|
112
|
+
else if (skippedSections.length > 0) {
|
|
113
|
+
summary = `INCOMPLETE — ${skippedSections.length} section(s) were SKIPPED: ${skippedSections.join(", ")}. ${totalFixed} findings fixed out of ${totalBefore}, but ${totalAfter} remain. The skipped sections were not addressed at all.`;
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
summary = `${totalFixed} findings fixed (${totalBefore} → ${totalAfter}), but ${unresolvedCritical} critical and ${unresolvedHigh} high remain. Score: ${beforeResult.score} → ${afterResult.score}.`;
|
|
117
|
+
}
|
|
118
|
+
// Next actions for incomplete remediation
|
|
119
|
+
const nextActions = [];
|
|
120
|
+
for (const section of sections) {
|
|
121
|
+
if (section.skipped) {
|
|
122
|
+
nextActions.push(`[SKIPPED] Section "${section.section}": ${section.before.findings} findings were completely ignored. Run the appropriate tool to fix them.`);
|
|
123
|
+
}
|
|
124
|
+
else if (section.status === "improved" && section.findingsRemaining > 0) {
|
|
125
|
+
nextActions.push(`[PARTIAL] Section "${section.section}": ${section.findingsResolved} fixed, ${section.findingsRemaining} remaining (${section.after.critical} critical, ${section.after.high} high).`);
|
|
126
|
+
}
|
|
127
|
+
else if (section.status === "worsened") {
|
|
128
|
+
nextActions.push(`[WORSENED] Section "${section.section}": findings increased from ${section.before.findings} to ${section.after.findings}. Investigate new findings.`);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
if (skippedSections.length > 0) {
|
|
132
|
+
nextActions.push(`\nACTION REQUIRED: Go back and address ALL skipped sections before declaring remediation complete. Use remediation_plan to get the specific tool sequence for each section.`);
|
|
133
|
+
}
|
|
134
|
+
return {
|
|
135
|
+
overallStatus,
|
|
136
|
+
beforeHash: beforeResult.resultHash,
|
|
137
|
+
afterHash: afterResult.resultHash,
|
|
138
|
+
beforeVerdict: beforeResult.verdict,
|
|
139
|
+
afterVerdict: afterResult.verdict,
|
|
140
|
+
beforeScore: beforeResult.score,
|
|
141
|
+
afterScore: afterResult.score,
|
|
142
|
+
sections,
|
|
143
|
+
skippedSections,
|
|
144
|
+
unresolvedCritical,
|
|
145
|
+
unresolvedHigh,
|
|
146
|
+
summary,
|
|
147
|
+
nextActions,
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
export function formatRemediationVerification(result, format) {
|
|
151
|
+
if (format === "json") {
|
|
152
|
+
return JSON.stringify(result);
|
|
153
|
+
}
|
|
154
|
+
const lines = [
|
|
155
|
+
"# GuardVibe Remediation Verification",
|
|
156
|
+
"",
|
|
157
|
+
];
|
|
158
|
+
// Status banner
|
|
159
|
+
if (result.overallStatus === "complete") {
|
|
160
|
+
lines.push("## ✅ COMPLETE — All sections clear");
|
|
161
|
+
}
|
|
162
|
+
else if (result.overallStatus === "incomplete") {
|
|
163
|
+
lines.push("## ❌ INCOMPLETE — Sections were skipped or partially fixed");
|
|
164
|
+
}
|
|
165
|
+
else {
|
|
166
|
+
lines.push("## ❌ FAILED — Critical findings remain");
|
|
167
|
+
}
|
|
168
|
+
lines.push("");
|
|
169
|
+
lines.push(`| Metric | Before | After |`);
|
|
170
|
+
lines.push(`|--------|--------|-------|`);
|
|
171
|
+
lines.push(`| Verdict | ${result.beforeVerdict} | ${result.afterVerdict} |`);
|
|
172
|
+
lines.push(`| Score | ${result.beforeScore}/100 | ${result.afterScore}/100 |`);
|
|
173
|
+
lines.push(`| Hash | \`${result.beforeHash}\` | \`${result.afterHash}\` |`);
|
|
174
|
+
lines.push("");
|
|
175
|
+
// Section-by-section comparison
|
|
176
|
+
lines.push("## Section Comparison");
|
|
177
|
+
lines.push("");
|
|
178
|
+
lines.push("| Section | Before | After | Status | Skipped? |");
|
|
179
|
+
lines.push("|---------|--------|-------|--------|----------|");
|
|
180
|
+
for (const s of result.sections) {
|
|
181
|
+
const statusIcon = s.status === "fully_resolved" ? "✅"
|
|
182
|
+
: s.status === "improved" ? "🔶"
|
|
183
|
+
: s.status === "unchanged" ? "🔴"
|
|
184
|
+
: "⛔";
|
|
185
|
+
const skippedMark = s.skipped ? "**YES — NOT ADDRESSED**" : "no";
|
|
186
|
+
lines.push(`| ${s.section} | ${s.before.findings} | ${s.after.findings} | ${statusIcon} ${s.status} | ${skippedMark} |`);
|
|
187
|
+
}
|
|
188
|
+
// Skipped sections warning
|
|
189
|
+
if (result.skippedSections.length > 0) {
|
|
190
|
+
lines.push("");
|
|
191
|
+
lines.push("## ⚠️ SKIPPED SECTIONS");
|
|
192
|
+
lines.push("");
|
|
193
|
+
lines.push("The following sections had findings but were **completely ignored** during remediation:");
|
|
194
|
+
lines.push("");
|
|
195
|
+
for (const name of result.skippedSections) {
|
|
196
|
+
const section = result.sections.find(s => s.section === name);
|
|
197
|
+
lines.push(`- **${name}**: ${section.before.findings} findings (${section.before.critical} critical, ${section.before.high} high) — ZERO progress`);
|
|
198
|
+
}
|
|
199
|
+
lines.push("");
|
|
200
|
+
lines.push("> **You cannot declare remediation complete while sections are skipped.** Use `remediation_plan` to get the specific actions for each skipped section.");
|
|
201
|
+
}
|
|
202
|
+
// Next actions
|
|
203
|
+
if (result.nextActions.length > 0) {
|
|
204
|
+
lines.push("");
|
|
205
|
+
lines.push("## Next Actions");
|
|
206
|
+
lines.push("");
|
|
207
|
+
for (const action of result.nextActions) {
|
|
208
|
+
lines.push(`- ${action}`);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
// Summary
|
|
212
|
+
lines.push("");
|
|
213
|
+
lines.push("---");
|
|
214
|
+
lines.push(`**${result.summary}**`);
|
|
215
|
+
return lines.join("\n");
|
|
216
|
+
}
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "guardvibe",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.9",
|
|
4
4
|
"mcpName": "io.github.goklab/guardvibe",
|
|
5
|
-
"description": "Security MCP for vibe coding.
|
|
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",
|
|
7
7
|
"bin": {
|
|
8
8
|
"guardvibe": "build/cli.js",
|