guardvibe 2.7.3 → 2.7.4
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/scan.js
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import { readFileSync, writeFileSync, existsSync, mkdirSync, statSync } from "fs";
|
|
6
6
|
import { resolve, extname, basename, join, dirname } from "path";
|
|
7
7
|
import { parseArgs, shouldFail, validateFormat, getOutputPath, getStringFlag } from "./args.js";
|
|
8
|
+
import { securityBanner } from "../utils/banner.js";
|
|
8
9
|
function safeWriteOutput(outputFile, result) {
|
|
9
10
|
const dir = dirname(outputFile);
|
|
10
11
|
if (!existsSync(dir)) {
|
|
@@ -137,6 +138,10 @@ export async function runDiffScan(base, flags) {
|
|
|
137
138
|
lines.push(` Fix: ${f.fix}`);
|
|
138
139
|
}
|
|
139
140
|
}
|
|
141
|
+
const critical = allFindings.filter(f => f.severity === "critical").length;
|
|
142
|
+
const high = allFindings.filter(f => f.severity === "high").length;
|
|
143
|
+
const medium = allFindings.filter(f => f.severity === "medium").length;
|
|
144
|
+
lines.push(securityBanner({ total: allFindings.length, critical, high, medium, filesScanned: changedFiles.length, context: "Diff" }));
|
|
140
145
|
result = lines.join("\n");
|
|
141
146
|
}
|
|
142
147
|
if (outputFile) {
|
package/build/server/types.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { securityBanner, bannerFields } from "../utils/banner.js";
|
|
1
2
|
// ── Secret Redaction ───────────────────────────────────────────────
|
|
2
3
|
const SECRET_PATTERNS = [
|
|
3
4
|
// Anthropic & OpenAI keys
|
|
@@ -86,10 +87,12 @@ export function formatHostFindings(findings, scannedFiles, skippedFiles, format,
|
|
|
86
87
|
const medium = findings.filter(f => f.severity === "medium").length;
|
|
87
88
|
const low = findings.filter(f => f.severity === "low").length;
|
|
88
89
|
const info = findings.filter(f => f.severity === "info").length;
|
|
90
|
+
const { grade, score } = bannerFields({ total: findings.length, critical, high, medium, low, filesScanned: scannedFiles.length });
|
|
89
91
|
return JSON.stringify({
|
|
90
92
|
summary: {
|
|
91
93
|
total: findings.length,
|
|
92
94
|
critical, high, medium, low, info,
|
|
95
|
+
grade, score,
|
|
93
96
|
scannedFiles: scannedFiles.length,
|
|
94
97
|
skippedFiles: skippedFiles.length,
|
|
95
98
|
},
|
|
@@ -126,5 +129,10 @@ export function formatHostFindings(findings, scannedFiles, skippedFiles, format,
|
|
|
126
129
|
for (const f of skippedFiles)
|
|
127
130
|
lines.push(`- \`${f}\``);
|
|
128
131
|
}
|
|
132
|
+
const critical = findings.filter(f => f.severity === "critical").length;
|
|
133
|
+
const high = findings.filter(f => f.severity === "high").length;
|
|
134
|
+
const medium = findings.filter(f => f.severity === "medium").length;
|
|
135
|
+
const low = findings.filter(f => f.severity === "low").length;
|
|
136
|
+
lines.push(securityBanner({ total: findings.length, critical, high, medium, low, filesScanned: scannedFiles.length, context: "Host Security" }));
|
|
129
137
|
return lines.join("\n");
|
|
130
138
|
}
|
|
@@ -2,6 +2,7 @@ import { basename } from "path";
|
|
|
2
2
|
import { owaspRules } from "../data/rules/index.js";
|
|
3
3
|
import { loadConfig } from "../utils/config.js";
|
|
4
4
|
import { loadIgnoreFile, isIgnored } from "../utils/ignore.js";
|
|
5
|
+
import { securityBanner } from "../utils/banner.js";
|
|
5
6
|
function parseSuppressionsFromCode(lines) {
|
|
6
7
|
const suppressions = [];
|
|
7
8
|
const pattern = /(?:\/\/|#|<!--)\s*guardvibe-ignore(?:-next-line)?\s*(VG\d+)?\s*(?:-->)?/i;
|
|
@@ -441,6 +442,7 @@ function formatCleanReport(language, framework) {
|
|
|
441
442
|
``,
|
|
442
443
|
`Tips for ${language}${ctx}:`,
|
|
443
444
|
...tips.map(t => `- ${t}`),
|
|
445
|
+
securityBanner({ total: 0, critical: 0, high: 0, medium: 0 }),
|
|
444
446
|
].join("\n");
|
|
445
447
|
}
|
|
446
448
|
function getLanguageTips(language, framework) {
|
|
@@ -549,6 +551,7 @@ function formatReport(findings, language, framework) {
|
|
|
549
551
|
}
|
|
550
552
|
}
|
|
551
553
|
}
|
|
554
|
+
lines.push(securityBanner({ total: allFindings.length, critical: criticalCount, high: highCount, medium: mediumCount }));
|
|
552
555
|
return lines.join("\n");
|
|
553
556
|
}
|
|
554
557
|
// ─── Buddy Format ────────────────────────────────────────────────
|
|
@@ -6,6 +6,7 @@ import { analyzeCode } from "./check-code.js";
|
|
|
6
6
|
import { loadConfig } from "../utils/config.js";
|
|
7
7
|
import { DEFAULT_EXCLUDES, EXTENSION_MAP, CONFIG_FILE_MAP } from "../utils/constants.js";
|
|
8
8
|
import { walkDirectory } from "../utils/walk-directory.js";
|
|
9
|
+
import { securityBanner } from "../utils/banner.js";
|
|
9
10
|
const require = createRequire(import.meta.url);
|
|
10
11
|
const pkg = require("../../package.json");
|
|
11
12
|
// GuardVibe version — used in scan metadata
|
|
@@ -240,5 +241,6 @@ export function scanDirectory(path, recursive = true, exclude = [], format = "ma
|
|
|
240
241
|
for (const s of skippedFiles)
|
|
241
242
|
lines.push(`- ${s}`);
|
|
242
243
|
}
|
|
244
|
+
lines.push(securityBanner({ total: totalIssues, critical: totalCritical, high: totalHigh, medium: totalMedium, score, grade, filesScanned: metadata.filesScanned }));
|
|
243
245
|
return lines.join("\n");
|
|
244
246
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { execFileSync } from "child_process";
|
|
2
2
|
import { extname, basename } from "path";
|
|
3
3
|
import { analyzeCode, formatFindingsJson } from "./check-code.js";
|
|
4
|
+
import { securityBanner } from "../utils/banner.js";
|
|
4
5
|
const EXTENSION_MAP = {
|
|
5
6
|
".js": "javascript", ".jsx": "javascript", ".mjs": "javascript", ".cjs": "javascript",
|
|
6
7
|
".ts": "typescript", ".tsx": "typescript", ".mts": "typescript", ".cts": "typescript",
|
|
@@ -135,5 +136,6 @@ export function scanStaged(cwd = process.cwd(), format = "markdown", rules) {
|
|
|
135
136
|
if (skippedFiles.length > 0) {
|
|
136
137
|
lines.push("", `*Skipped ${skippedFiles.length} files with unsupported extensions.*`);
|
|
137
138
|
}
|
|
139
|
+
lines.push(securityBanner({ total: totalIssues, critical: totalCritical, high: totalHigh, medium: totalMedium, score, grade, filesScanned: scannedCount, context: "Pre-Commit" }));
|
|
138
140
|
return lines.join("\n");
|
|
139
141
|
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared security summary banner — appended to the end of every CLI command output.
|
|
3
|
+
* Gives users an instant, human-readable security status in one line.
|
|
4
|
+
*/
|
|
5
|
+
export interface BannerInput {
|
|
6
|
+
/** Total findings count */
|
|
7
|
+
total: number;
|
|
8
|
+
critical: number;
|
|
9
|
+
high: number;
|
|
10
|
+
medium: number;
|
|
11
|
+
low?: number;
|
|
12
|
+
/** Numeric score 0-100 (optional — will be computed if not provided) */
|
|
13
|
+
score?: number;
|
|
14
|
+
/** Grade A-F (optional — will be computed from score) */
|
|
15
|
+
grade?: string;
|
|
16
|
+
/** Number of files scanned */
|
|
17
|
+
filesScanned?: number;
|
|
18
|
+
/** Context label for the banner (e.g., "Host Security", "Pre-Commit") */
|
|
19
|
+
context?: string;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Generate the summary banner line for terminal output.
|
|
23
|
+
*
|
|
24
|
+
* Examples:
|
|
25
|
+
* 🛡️ GuardVibe: A (95/100) — 0 critical, 0 high, 2 medium | 42 files scanned
|
|
26
|
+
* 🛡️ GuardVibe: F (12/100) — 5 critical, 3 high, 8 medium | 42 files scanned
|
|
27
|
+
* 🛡️ GuardVibe: Clean — no issues found | 6 files scanned
|
|
28
|
+
*/
|
|
29
|
+
export declare function securityBanner(input: BannerInput): string;
|
|
30
|
+
/**
|
|
31
|
+
* Generate the JSON summary banner object — added to JSON output's summary.
|
|
32
|
+
*/
|
|
33
|
+
export declare function bannerFields(input: BannerInput): {
|
|
34
|
+
grade: string;
|
|
35
|
+
score: number;
|
|
36
|
+
};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared security summary banner — appended to the end of every CLI command output.
|
|
3
|
+
* Gives users an instant, human-readable security status in one line.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Compute grade from score.
|
|
7
|
+
*/
|
|
8
|
+
function gradeFromScore(score) {
|
|
9
|
+
if (score >= 90)
|
|
10
|
+
return "A";
|
|
11
|
+
if (score >= 75)
|
|
12
|
+
return "B";
|
|
13
|
+
if (score >= 60)
|
|
14
|
+
return "C";
|
|
15
|
+
if (score >= 40)
|
|
16
|
+
return "D";
|
|
17
|
+
return "F";
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Compute score from findings if not provided.
|
|
21
|
+
* Uses weighted density formula consistent with scan-directory.ts.
|
|
22
|
+
*/
|
|
23
|
+
function computeScore(input) {
|
|
24
|
+
if (input.score !== undefined)
|
|
25
|
+
return input.score;
|
|
26
|
+
const files = Math.max(input.filesScanned ?? 1, 1);
|
|
27
|
+
const weighted = input.critical * 15 + input.high * 5 + input.medium * 0.5;
|
|
28
|
+
const density = weighted / files;
|
|
29
|
+
return Math.max(0, Math.min(100, Math.round(100 - Math.min(density, 5) * 20)));
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Generate the summary banner line for terminal output.
|
|
33
|
+
*
|
|
34
|
+
* Examples:
|
|
35
|
+
* 🛡️ GuardVibe: A (95/100) — 0 critical, 0 high, 2 medium | 42 files scanned
|
|
36
|
+
* 🛡️ GuardVibe: F (12/100) — 5 critical, 3 high, 8 medium | 42 files scanned
|
|
37
|
+
* 🛡️ GuardVibe: Clean — no issues found | 6 files scanned
|
|
38
|
+
*/
|
|
39
|
+
export function securityBanner(input) {
|
|
40
|
+
const score = computeScore(input);
|
|
41
|
+
const grade = input.grade ?? gradeFromScore(score);
|
|
42
|
+
const ctx = input.context ? ` ${input.context}:` : ":";
|
|
43
|
+
const filesPart = input.filesScanned !== undefined ? ` | ${input.filesScanned} files scanned` : "";
|
|
44
|
+
if (input.total === 0) {
|
|
45
|
+
return `\n🛡️ GuardVibe${ctx} Clean — no issues found${filesPart}`;
|
|
46
|
+
}
|
|
47
|
+
const parts = [];
|
|
48
|
+
if (input.critical > 0)
|
|
49
|
+
parts.push(`${input.critical} critical`);
|
|
50
|
+
if (input.high > 0)
|
|
51
|
+
parts.push(`${input.high} high`);
|
|
52
|
+
if (input.medium > 0)
|
|
53
|
+
parts.push(`${input.medium} medium`);
|
|
54
|
+
if ((input.low ?? 0) > 0)
|
|
55
|
+
parts.push(`${input.low} low`);
|
|
56
|
+
const breakdown = parts.join(", ");
|
|
57
|
+
return `\n🛡️ GuardVibe${ctx} ${grade} (${score}/100) — ${breakdown}${filesPart}`;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Generate the JSON summary banner object — added to JSON output's summary.
|
|
61
|
+
*/
|
|
62
|
+
export function bannerFields(input) {
|
|
63
|
+
const score = computeScore(input);
|
|
64
|
+
const grade = input.grade ?? gradeFromScore(score);
|
|
65
|
+
return { grade, score };
|
|
66
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "guardvibe",
|
|
3
|
-
"version": "2.7.
|
|
3
|
+
"version": "2.7.4",
|
|
4
4
|
"mcpName": "io.github.goklab/guardvibe",
|
|
5
5
|
"description": "Security MCP for vibe coding. 330 rules, 29 tools, CLI + doctor. Host security: CVE-2025-59536 hook injection, CVE-2026-21852 base URL hijack, MCP config audit, AI host hardening. 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",
|