@quantracode/vibecheck 0.0.1
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/LICENSE +21 -0
- package/README.md +839 -0
- package/dist/__tests__/cli.test.d.ts +2 -0
- package/dist/__tests__/cli.test.d.ts.map +1 -0
- package/dist/__tests__/cli.test.js +243 -0
- package/dist/__tests__/fixtures/safe-app/app/api/users/route.js +36 -0
- package/dist/__tests__/fixtures/vulnerable-app/app/api/users/route.js +28 -0
- package/dist/__tests__/fixtures/vulnerable-app/lib/config.d.ts +4 -0
- package/dist/__tests__/fixtures/vulnerable-app/lib/config.d.ts.map +1 -0
- package/dist/__tests__/fixtures/vulnerable-app/lib/config.js +6 -0
- package/dist/__tests__/scanners/env-config.test.d.ts +2 -0
- package/dist/__tests__/scanners/env-config.test.d.ts.map +1 -0
- package/dist/__tests__/scanners/env-config.test.js +142 -0
- package/dist/__tests__/scanners/nextjs-middleware.test.d.ts +2 -0
- package/dist/__tests__/scanners/nextjs-middleware.test.d.ts.map +1 -0
- package/dist/__tests__/scanners/nextjs-middleware.test.js +193 -0
- package/dist/__tests__/scanners/scanner-packs.test.d.ts +2 -0
- package/dist/__tests__/scanners/scanner-packs.test.d.ts.map +1 -0
- package/dist/__tests__/scanners/scanner-packs.test.js +126 -0
- package/dist/__tests__/scanners/unused-security-imports.test.d.ts +2 -0
- package/dist/__tests__/scanners/unused-security-imports.test.d.ts.map +1 -0
- package/dist/__tests__/scanners/unused-security-imports.test.js +145 -0
- package/dist/commands/demo-artifact.d.ts +7 -0
- package/dist/commands/demo-artifact.d.ts.map +1 -0
- package/dist/commands/demo-artifact.js +322 -0
- package/dist/commands/evaluate.d.ts +30 -0
- package/dist/commands/evaluate.d.ts.map +1 -0
- package/dist/commands/evaluate.js +258 -0
- package/dist/commands/explain.d.ts +12 -0
- package/dist/commands/explain.d.ts.map +1 -0
- package/dist/commands/explain.js +214 -0
- package/dist/commands/index.d.ts +7 -0
- package/dist/commands/index.d.ts.map +1 -0
- package/dist/commands/index.js +6 -0
- package/dist/commands/intent.d.ts +21 -0
- package/dist/commands/intent.d.ts.map +1 -0
- package/dist/commands/intent.js +192 -0
- package/dist/commands/scan.d.ts +44 -0
- package/dist/commands/scan.d.ts.map +1 -0
- package/dist/commands/scan.js +497 -0
- package/dist/commands/waivers.d.ts +30 -0
- package/dist/commands/waivers.d.ts.map +1 -0
- package/dist/commands/waivers.js +249 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +17 -0
- package/dist/phase3/index.d.ts +11 -0
- package/dist/phase3/index.d.ts.map +1 -0
- package/dist/phase3/index.js +12 -0
- package/dist/phase3/intent-miner.d.ts +32 -0
- package/dist/phase3/intent-miner.d.ts.map +1 -0
- package/dist/phase3/intent-miner.js +323 -0
- package/dist/phase3/proof-trace-builder.d.ts +42 -0
- package/dist/phase3/proof-trace-builder.d.ts.map +1 -0
- package/dist/phase3/proof-trace-builder.js +441 -0
- package/dist/phase3/scanners/auth-by-ui-server-gap.d.ts +15 -0
- package/dist/phase3/scanners/auth-by-ui-server-gap.d.ts.map +1 -0
- package/dist/phase3/scanners/auth-by-ui-server-gap.js +237 -0
- package/dist/phase3/scanners/comment-claim-unproven.d.ts +14 -0
- package/dist/phase3/scanners/comment-claim-unproven.d.ts.map +1 -0
- package/dist/phase3/scanners/comment-claim-unproven.js +161 -0
- package/dist/phase3/scanners/index.d.ts +31 -0
- package/dist/phase3/scanners/index.d.ts.map +1 -0
- package/dist/phase3/scanners/index.js +40 -0
- package/dist/phase3/scanners/middleware-assumed-not-matching.d.ts +14 -0
- package/dist/phase3/scanners/middleware-assumed-not-matching.d.ts.map +1 -0
- package/dist/phase3/scanners/middleware-assumed-not-matching.js +172 -0
- package/dist/phase3/scanners/validation-claimed-missing.d.ts +15 -0
- package/dist/phase3/scanners/validation-claimed-missing.d.ts.map +1 -0
- package/dist/phase3/scanners/validation-claimed-missing.js +204 -0
- package/dist/scanners/abuse/compute-abuse.d.ts +20 -0
- package/dist/scanners/abuse/compute-abuse.d.ts.map +1 -0
- package/dist/scanners/abuse/compute-abuse.js +509 -0
- package/dist/scanners/abuse/index.d.ts +12 -0
- package/dist/scanners/abuse/index.d.ts.map +1 -0
- package/dist/scanners/abuse/index.js +15 -0
- package/dist/scanners/auth/index.d.ts +5 -0
- package/dist/scanners/auth/index.d.ts.map +1 -0
- package/dist/scanners/auth/index.js +10 -0
- package/dist/scanners/auth/middleware-gap.d.ts +22 -0
- package/dist/scanners/auth/middleware-gap.d.ts.map +1 -0
- package/dist/scanners/auth/middleware-gap.js +203 -0
- package/dist/scanners/auth/unprotected-api-route.d.ts +12 -0
- package/dist/scanners/auth/unprotected-api-route.d.ts.map +1 -0
- package/dist/scanners/auth/unprotected-api-route.js +126 -0
- package/dist/scanners/config/index.d.ts +5 -0
- package/dist/scanners/config/index.d.ts.map +1 -0
- package/dist/scanners/config/index.js +10 -0
- package/dist/scanners/config/insecure-defaults.d.ts +12 -0
- package/dist/scanners/config/insecure-defaults.d.ts.map +1 -0
- package/dist/scanners/config/insecure-defaults.js +77 -0
- package/dist/scanners/config/undocumented-env.d.ts +24 -0
- package/dist/scanners/config/undocumented-env.d.ts.map +1 -0
- package/dist/scanners/config/undocumented-env.js +159 -0
- package/dist/scanners/crypto/index.d.ts +6 -0
- package/dist/scanners/crypto/index.d.ts.map +1 -0
- package/dist/scanners/crypto/index.js +11 -0
- package/dist/scanners/crypto/jwt-decode-unverified.d.ts +14 -0
- package/dist/scanners/crypto/jwt-decode-unverified.d.ts.map +1 -0
- package/dist/scanners/crypto/jwt-decode-unverified.js +87 -0
- package/dist/scanners/crypto/math-random-tokens.d.ts +13 -0
- package/dist/scanners/crypto/math-random-tokens.d.ts.map +1 -0
- package/dist/scanners/crypto/math-random-tokens.js +80 -0
- package/dist/scanners/crypto/weak-hashing.d.ts +11 -0
- package/dist/scanners/crypto/weak-hashing.d.ts.map +1 -0
- package/dist/scanners/crypto/weak-hashing.js +95 -0
- package/dist/scanners/env-config.d.ts +24 -0
- package/dist/scanners/env-config.d.ts.map +1 -0
- package/dist/scanners/env-config.js +164 -0
- package/dist/scanners/hallucinations/index.d.ts +4 -0
- package/dist/scanners/hallucinations/index.d.ts.map +1 -0
- package/dist/scanners/hallucinations/index.js +8 -0
- package/dist/scanners/hallucinations/unused-security-imports.d.ts +36 -0
- package/dist/scanners/hallucinations/unused-security-imports.d.ts.map +1 -0
- package/dist/scanners/hallucinations/unused-security-imports.js +309 -0
- package/dist/scanners/helpers/ast-helpers.d.ts +6 -0
- package/dist/scanners/helpers/ast-helpers.d.ts.map +1 -0
- package/dist/scanners/helpers/ast-helpers.js +945 -0
- package/dist/scanners/helpers/context-builder.d.ts +17 -0
- package/dist/scanners/helpers/context-builder.d.ts.map +1 -0
- package/dist/scanners/helpers/context-builder.js +148 -0
- package/dist/scanners/helpers/index.d.ts +3 -0
- package/dist/scanners/helpers/index.d.ts.map +1 -0
- package/dist/scanners/helpers/index.js +2 -0
- package/dist/scanners/index.d.ts +30 -0
- package/dist/scanners/index.d.ts.map +1 -0
- package/dist/scanners/index.js +102 -0
- package/dist/scanners/middleware/index.d.ts +4 -0
- package/dist/scanners/middleware/index.d.ts.map +1 -0
- package/dist/scanners/middleware/index.js +7 -0
- package/dist/scanners/middleware/missing-rate-limit.d.ts +13 -0
- package/dist/scanners/middleware/missing-rate-limit.d.ts.map +1 -0
- package/dist/scanners/middleware/missing-rate-limit.js +140 -0
- package/dist/scanners/network/cors-misconfiguration.d.ts +14 -0
- package/dist/scanners/network/cors-misconfiguration.d.ts.map +1 -0
- package/dist/scanners/network/cors-misconfiguration.js +89 -0
- package/dist/scanners/network/index.d.ts +7 -0
- package/dist/scanners/network/index.d.ts.map +1 -0
- package/dist/scanners/network/index.js +18 -0
- package/dist/scanners/network/missing-timeout.d.ts +15 -0
- package/dist/scanners/network/missing-timeout.d.ts.map +1 -0
- package/dist/scanners/network/missing-timeout.js +93 -0
- package/dist/scanners/network/open-redirect.d.ts +15 -0
- package/dist/scanners/network/open-redirect.d.ts.map +1 -0
- package/dist/scanners/network/open-redirect.js +88 -0
- package/dist/scanners/network/ssrf-prone-fetch.d.ts +12 -0
- package/dist/scanners/network/ssrf-prone-fetch.d.ts.map +1 -0
- package/dist/scanners/network/ssrf-prone-fetch.js +90 -0
- package/dist/scanners/nextjs-middleware.d.ts +26 -0
- package/dist/scanners/nextjs-middleware.d.ts.map +1 -0
- package/dist/scanners/nextjs-middleware.js +246 -0
- package/dist/scanners/privacy/debug-flags.d.ts +13 -0
- package/dist/scanners/privacy/debug-flags.d.ts.map +1 -0
- package/dist/scanners/privacy/debug-flags.js +124 -0
- package/dist/scanners/privacy/index.d.ts +6 -0
- package/dist/scanners/privacy/index.d.ts.map +1 -0
- package/dist/scanners/privacy/index.js +11 -0
- package/dist/scanners/privacy/over-broad-response.d.ts +15 -0
- package/dist/scanners/privacy/over-broad-response.d.ts.map +1 -0
- package/dist/scanners/privacy/over-broad-response.js +109 -0
- package/dist/scanners/privacy/sensitive-logging.d.ts +11 -0
- package/dist/scanners/privacy/sensitive-logging.d.ts.map +1 -0
- package/dist/scanners/privacy/sensitive-logging.js +78 -0
- package/dist/scanners/types.d.ts +456 -0
- package/dist/scanners/types.d.ts.map +1 -0
- package/dist/scanners/types.js +16 -0
- package/dist/scanners/unused-security-imports.d.ts +34 -0
- package/dist/scanners/unused-security-imports.d.ts.map +1 -0
- package/dist/scanners/unused-security-imports.js +206 -0
- package/dist/scanners/uploads/index.d.ts +5 -0
- package/dist/scanners/uploads/index.d.ts.map +1 -0
- package/dist/scanners/uploads/index.js +9 -0
- package/dist/scanners/uploads/missing-constraints.d.ts +15 -0
- package/dist/scanners/uploads/missing-constraints.d.ts.map +1 -0
- package/dist/scanners/uploads/missing-constraints.js +109 -0
- package/dist/scanners/uploads/public-path.d.ts +11 -0
- package/dist/scanners/uploads/public-path.d.ts.map +1 -0
- package/dist/scanners/uploads/public-path.js +87 -0
- package/dist/scanners/validation/client-side-only.d.ts +14 -0
- package/dist/scanners/validation/client-side-only.d.ts.map +1 -0
- package/dist/scanners/validation/client-side-only.js +140 -0
- package/dist/scanners/validation/ignored-validation.d.ts +12 -0
- package/dist/scanners/validation/ignored-validation.d.ts.map +1 -0
- package/dist/scanners/validation/ignored-validation.js +119 -0
- package/dist/scanners/validation/index.d.ts +5 -0
- package/dist/scanners/validation/index.d.ts.map +1 -0
- package/dist/scanners/validation/index.js +9 -0
- package/dist/utils/exclude-patterns.d.ts +35 -0
- package/dist/utils/exclude-patterns.d.ts.map +1 -0
- package/dist/utils/exclude-patterns.js +78 -0
- package/dist/utils/file-utils.d.ts +37 -0
- package/dist/utils/file-utils.d.ts.map +1 -0
- package/dist/utils/file-utils.js +77 -0
- package/dist/utils/fingerprint.d.ts +25 -0
- package/dist/utils/fingerprint.d.ts.map +1 -0
- package/dist/utils/fingerprint.js +28 -0
- package/dist/utils/git-info.d.ts +14 -0
- package/dist/utils/git-info.d.ts.map +1 -0
- package/dist/utils/git-info.js +55 -0
- package/dist/utils/index.d.ts +4 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +3 -0
- package/dist/utils/progress.d.ts +42 -0
- package/dist/utils/progress.d.ts.map +1 -0
- package/dist/utils/progress.js +165 -0
- package/dist/utils/sarif-formatter.d.ts +92 -0
- package/dist/utils/sarif-formatter.d.ts.map +1 -0
- package/dist/utils/sarif-formatter.js +172 -0
- package/package.json +66 -0
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { readFileSync, fileExists, resolvePath, writeFileSync, ensureDir } from "../utils/file-utils.js";
|
|
3
|
+
import { evaluate, PROFILE_NAMES, PROFILE_DESCRIPTIONS, WaiversFileSchema, PolicyConfigSchema, } from "@vibecheck/policy";
|
|
4
|
+
import { validateArtifact } from "@vibecheck/schema";
|
|
5
|
+
/**
|
|
6
|
+
* Load and validate scan artifact from file
|
|
7
|
+
*/
|
|
8
|
+
function loadArtifact(filepath) {
|
|
9
|
+
const absolutePath = resolvePath(filepath);
|
|
10
|
+
if (!fileExists(absolutePath)) {
|
|
11
|
+
throw new Error(`Artifact file not found: ${filepath}`);
|
|
12
|
+
}
|
|
13
|
+
const content = readFileSync(absolutePath);
|
|
14
|
+
if (!content) {
|
|
15
|
+
throw new Error(`Failed to read artifact file: ${filepath}`);
|
|
16
|
+
}
|
|
17
|
+
const data = JSON.parse(content);
|
|
18
|
+
// Validate the artifact
|
|
19
|
+
return validateArtifact(data);
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Load and validate waivers file
|
|
23
|
+
*/
|
|
24
|
+
function loadWaivers(filepath) {
|
|
25
|
+
const absolutePath = resolvePath(filepath);
|
|
26
|
+
if (!fileExists(absolutePath)) {
|
|
27
|
+
throw new Error(`Waivers file not found: ${filepath}`);
|
|
28
|
+
}
|
|
29
|
+
const content = readFileSync(absolutePath);
|
|
30
|
+
if (!content) {
|
|
31
|
+
throw new Error(`Failed to read waivers file: ${filepath}`);
|
|
32
|
+
}
|
|
33
|
+
const data = JSON.parse(content);
|
|
34
|
+
const result = WaiversFileSchema.safeParse(data);
|
|
35
|
+
if (!result.success) {
|
|
36
|
+
throw new Error(`Invalid waivers file: ${result.error.message}`);
|
|
37
|
+
}
|
|
38
|
+
return result.data.waivers;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Load and validate policy config file
|
|
42
|
+
*/
|
|
43
|
+
function loadConfig(filepath) {
|
|
44
|
+
const absolutePath = resolvePath(filepath);
|
|
45
|
+
if (!fileExists(absolutePath)) {
|
|
46
|
+
throw new Error(`Config file not found: ${filepath}`);
|
|
47
|
+
}
|
|
48
|
+
const content = readFileSync(absolutePath);
|
|
49
|
+
if (!content) {
|
|
50
|
+
throw new Error(`Failed to read config file: ${filepath}`);
|
|
51
|
+
}
|
|
52
|
+
const data = JSON.parse(content);
|
|
53
|
+
const result = PolicyConfigSchema.safeParse(data);
|
|
54
|
+
if (!result.success) {
|
|
55
|
+
throw new Error(`Invalid config file: ${result.error.message}`);
|
|
56
|
+
}
|
|
57
|
+
return result.data;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Format severity for console output with color
|
|
61
|
+
*/
|
|
62
|
+
function formatSeverity(severity) {
|
|
63
|
+
const colors = {
|
|
64
|
+
critical: "\x1b[35m", // Magenta
|
|
65
|
+
high: "\x1b[31m", // Red
|
|
66
|
+
medium: "\x1b[33m", // Yellow
|
|
67
|
+
low: "\x1b[36m", // Cyan
|
|
68
|
+
info: "\x1b[90m", // Gray
|
|
69
|
+
};
|
|
70
|
+
const reset = "\x1b[0m";
|
|
71
|
+
return `${colors[severity]}${severity.toUpperCase()}${reset}`;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Format status for console output with color
|
|
75
|
+
*/
|
|
76
|
+
function formatStatus(status) {
|
|
77
|
+
const colors = {
|
|
78
|
+
pass: "\x1b[32m", // Green
|
|
79
|
+
warn: "\x1b[33m", // Yellow
|
|
80
|
+
fail: "\x1b[31m", // Red
|
|
81
|
+
};
|
|
82
|
+
const reset = "\x1b[0m";
|
|
83
|
+
return `${colors[status]}${status.toUpperCase()}${reset}`;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Print evaluation summary to console
|
|
87
|
+
*/
|
|
88
|
+
function printSummary(report) {
|
|
89
|
+
console.log("\n" + "=".repeat(60));
|
|
90
|
+
console.log("VibeCheck Policy Evaluation");
|
|
91
|
+
console.log("=".repeat(60));
|
|
92
|
+
console.log(`\nProfile: ${report.profileName ?? "custom"}`);
|
|
93
|
+
console.log(`Status: ${formatStatus(report.status)}`);
|
|
94
|
+
console.log(`Exit Code: ${report.exitCode}`);
|
|
95
|
+
console.log("\nSummary:");
|
|
96
|
+
console.log(` Total active findings: ${report.summary.total}`);
|
|
97
|
+
console.log(` Waived findings: ${report.summary.waived}`);
|
|
98
|
+
console.log(` Ignored by override: ${report.summary.ignored}`);
|
|
99
|
+
if (report.summary.total > 0) {
|
|
100
|
+
console.log("\nBy severity:");
|
|
101
|
+
if (report.summary.bySeverity.critical > 0) {
|
|
102
|
+
console.log(` ${formatSeverity("critical")}: ${report.summary.bySeverity.critical}`);
|
|
103
|
+
}
|
|
104
|
+
if (report.summary.bySeverity.high > 0) {
|
|
105
|
+
console.log(` ${formatSeverity("high")}: ${report.summary.bySeverity.high}`);
|
|
106
|
+
}
|
|
107
|
+
if (report.summary.bySeverity.medium > 0) {
|
|
108
|
+
console.log(` ${formatSeverity("medium")}: ${report.summary.bySeverity.medium}`);
|
|
109
|
+
}
|
|
110
|
+
if (report.summary.bySeverity.low > 0) {
|
|
111
|
+
console.log(` ${formatSeverity("low")}: ${report.summary.bySeverity.low}`);
|
|
112
|
+
}
|
|
113
|
+
if (report.summary.bySeverity.info > 0) {
|
|
114
|
+
console.log(` ${formatSeverity("info")}: ${report.summary.bySeverity.info}`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
// Show thresholds
|
|
118
|
+
console.log("\nThresholds:");
|
|
119
|
+
console.log(` Fail on severity >= ${report.thresholds.failOnSeverity} (confidence >= ${report.thresholds.minConfidenceForFail})`);
|
|
120
|
+
console.log(` Warn on severity >= ${report.thresholds.warnOnSeverity} (confidence >= ${report.thresholds.minConfidenceForWarn})`);
|
|
121
|
+
if (report.thresholds.maxFindings > 0) {
|
|
122
|
+
console.log(` Max findings: ${report.thresholds.maxFindings}`);
|
|
123
|
+
}
|
|
124
|
+
// Show regression if present
|
|
125
|
+
if (report.regression) {
|
|
126
|
+
console.log("\nRegression:");
|
|
127
|
+
console.log(` New findings: ${report.regression.newFindings.length}`);
|
|
128
|
+
console.log(` Resolved findings: ${report.regression.resolvedFindings.length}`);
|
|
129
|
+
console.log(` Severity regressions: ${report.regression.severityRegressions.length}`);
|
|
130
|
+
console.log(` Net change: ${report.regression.netChange > 0 ? "+" : ""}${report.regression.netChange}`);
|
|
131
|
+
}
|
|
132
|
+
// Show reasons
|
|
133
|
+
if (report.reasons.length > 0) {
|
|
134
|
+
console.log("\nReasons:");
|
|
135
|
+
for (const reason of report.reasons) {
|
|
136
|
+
const statusIcon = reason.status === "fail" ? "\x1b[31m✗\x1b[0m"
|
|
137
|
+
: reason.status === "warn" ? "\x1b[33m!\x1b[0m"
|
|
138
|
+
: "\x1b[32m✓\x1b[0m";
|
|
139
|
+
console.log(` ${statusIcon} ${reason.message}`);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
// Show waived findings if any
|
|
143
|
+
if (report.waivedFindings.length > 0) {
|
|
144
|
+
console.log("\nWaived findings:");
|
|
145
|
+
for (const wf of report.waivedFindings.slice(0, 5)) {
|
|
146
|
+
const expiredNote = wf.expired ? " (EXPIRED)" : "";
|
|
147
|
+
console.log(` - [${wf.finding.ruleId}] ${wf.finding.title}${expiredNote}`);
|
|
148
|
+
console.log(` Reason: ${wf.waiver.reason}`);
|
|
149
|
+
}
|
|
150
|
+
if (report.waivedFindings.length > 5) {
|
|
151
|
+
console.log(` ... and ${report.waivedFindings.length - 5} more`);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
console.log("");
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Execute the evaluate command
|
|
158
|
+
*/
|
|
159
|
+
export async function executeEvaluate(options) {
|
|
160
|
+
// Load artifact
|
|
161
|
+
const artifact = loadArtifact(options.artifact);
|
|
162
|
+
// Load baseline if provided
|
|
163
|
+
let baseline;
|
|
164
|
+
if (options.baseline) {
|
|
165
|
+
baseline = loadArtifact(options.baseline);
|
|
166
|
+
}
|
|
167
|
+
// Load config if provided, otherwise use profile
|
|
168
|
+
let config;
|
|
169
|
+
if (options.config) {
|
|
170
|
+
config = loadConfig(options.config);
|
|
171
|
+
}
|
|
172
|
+
// Load waivers if provided
|
|
173
|
+
let waivers = [];
|
|
174
|
+
if (options.waivers) {
|
|
175
|
+
waivers = loadWaivers(options.waivers);
|
|
176
|
+
}
|
|
177
|
+
// Run evaluation
|
|
178
|
+
const report = evaluate({
|
|
179
|
+
artifact,
|
|
180
|
+
baseline,
|
|
181
|
+
config,
|
|
182
|
+
profile: config ? undefined : options.profile,
|
|
183
|
+
waivers,
|
|
184
|
+
artifactPath: options.artifact,
|
|
185
|
+
});
|
|
186
|
+
// Output report
|
|
187
|
+
if (options.out) {
|
|
188
|
+
const outPath = resolvePath(options.out);
|
|
189
|
+
ensureDir(path.dirname(outPath));
|
|
190
|
+
writeFileSync(outPath, JSON.stringify(report, null, 2));
|
|
191
|
+
if (!options.quiet) {
|
|
192
|
+
console.log(`Policy report written to: ${outPath}`);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
// Print summary unless quiet mode
|
|
196
|
+
if (!options.quiet) {
|
|
197
|
+
printSummary(report);
|
|
198
|
+
}
|
|
199
|
+
else {
|
|
200
|
+
// In quiet mode, just output JSON
|
|
201
|
+
if (!options.out) {
|
|
202
|
+
console.log(JSON.stringify(report, null, 2));
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
return report.exitCode;
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Register evaluate command with commander
|
|
209
|
+
*/
|
|
210
|
+
export function registerEvaluateCommand(program) {
|
|
211
|
+
program
|
|
212
|
+
.command("evaluate")
|
|
213
|
+
.description("Evaluate a scan artifact against a policy")
|
|
214
|
+
.requiredOption("-a, --artifact <path>", "Path to scan artifact (JSON)")
|
|
215
|
+
.option("-b, --baseline <path>", "Path to baseline artifact for regression detection")
|
|
216
|
+
.option("-p, --profile <name>", `Policy profile (${PROFILE_NAMES.join(", ")})`, "startup")
|
|
217
|
+
.option("-c, --config <path>", "Path to policy config file (overrides profile)")
|
|
218
|
+
.option("-w, --waivers <path>", "Path to waivers file")
|
|
219
|
+
.option("-o, --out <path>", "Output report to file")
|
|
220
|
+
.option("-q, --quiet", "JSON output only (no console summary)")
|
|
221
|
+
.addHelpText("after", `
|
|
222
|
+
Profiles:
|
|
223
|
+
${PROFILE_NAMES.map((name) => ` ${name}: ${PROFILE_DESCRIPTIONS[name]}`).join("\n")}
|
|
224
|
+
|
|
225
|
+
Examples:
|
|
226
|
+
$ vibecheck evaluate -a ./vibecheck-scan.json
|
|
227
|
+
$ vibecheck evaluate -a ./scan.json -p strict
|
|
228
|
+
$ vibecheck evaluate -a ./scan.json -b ./baseline.json
|
|
229
|
+
$ vibecheck evaluate -a ./scan.json -w ./waivers.json
|
|
230
|
+
$ vibecheck evaluate -a ./scan.json -c ./policy.json
|
|
231
|
+
$ vibecheck evaluate -a ./scan.json -o ./report.json -q
|
|
232
|
+
`)
|
|
233
|
+
.action(async (cmdOptions) => {
|
|
234
|
+
// Validate profile
|
|
235
|
+
const profileStr = cmdOptions.profile;
|
|
236
|
+
if (!PROFILE_NAMES.includes(profileStr)) {
|
|
237
|
+
console.error(`\x1b[31mError: Invalid profile "${profileStr}". Valid options: ${PROFILE_NAMES.join(", ")}\x1b[0m`);
|
|
238
|
+
process.exit(1);
|
|
239
|
+
}
|
|
240
|
+
const options = {
|
|
241
|
+
artifact: cmdOptions.artifact,
|
|
242
|
+
baseline: cmdOptions.baseline,
|
|
243
|
+
profile: profileStr,
|
|
244
|
+
config: cmdOptions.config,
|
|
245
|
+
waivers: cmdOptions.waivers,
|
|
246
|
+
out: cmdOptions.out,
|
|
247
|
+
quiet: Boolean(cmdOptions.quiet),
|
|
248
|
+
};
|
|
249
|
+
try {
|
|
250
|
+
const exitCode = await executeEvaluate(options);
|
|
251
|
+
process.exit(exitCode);
|
|
252
|
+
}
|
|
253
|
+
catch (error) {
|
|
254
|
+
console.error(`\x1b[31mError: ${error instanceof Error ? error.message : String(error)}\x1b[0m`);
|
|
255
|
+
process.exit(1);
|
|
256
|
+
}
|
|
257
|
+
});
|
|
258
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Command } from "commander";
|
|
2
|
+
/**
|
|
3
|
+
* Execute the explain command
|
|
4
|
+
*/
|
|
5
|
+
export declare function executeExplain(artifactPath: string, options: {
|
|
6
|
+
limit: number;
|
|
7
|
+
}): Promise<number>;
|
|
8
|
+
/**
|
|
9
|
+
* Register explain command with commander
|
|
10
|
+
*/
|
|
11
|
+
export declare function registerExplainCommand(program: Command): void;
|
|
12
|
+
//# sourceMappingURL=explain.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"explain.d.ts","sourceRoot":"","sources":["../../src/commands/explain.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA0MzC;;GAEG;AACH,wBAAsB,cAAc,CAClC,YAAY,EAAE,MAAM,EACpB,OAAO,EAAE;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,GACzB,OAAO,CAAC,MAAM,CAAC,CAkCjB;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAU7D"}
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import { validateArtifact, } from "@vibecheck/schema";
|
|
2
|
+
import { readFileSync, resolvePath } from "../utils/file-utils.js";
|
|
3
|
+
/**
|
|
4
|
+
* Format severity for console output with color
|
|
5
|
+
*/
|
|
6
|
+
function formatSeverity(severity) {
|
|
7
|
+
const colors = {
|
|
8
|
+
critical: "\x1b[35m", // Magenta
|
|
9
|
+
high: "\x1b[31m", // Red
|
|
10
|
+
medium: "\x1b[33m", // Yellow
|
|
11
|
+
low: "\x1b[36m", // Cyan
|
|
12
|
+
info: "\x1b[90m", // Gray
|
|
13
|
+
};
|
|
14
|
+
const reset = "\x1b[0m";
|
|
15
|
+
return `${colors[severity]}${severity.toUpperCase().padEnd(8)}${reset}`;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Format category for display
|
|
19
|
+
*/
|
|
20
|
+
function formatCategory(category) {
|
|
21
|
+
return `\x1b[34m${category}\x1b[0m`;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Print a horizontal divider
|
|
25
|
+
*/
|
|
26
|
+
function divider(char = "-", length = 70) {
|
|
27
|
+
return char.repeat(length);
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Print artifact summary
|
|
31
|
+
*/
|
|
32
|
+
function printSummary(artifact) {
|
|
33
|
+
const { summary, tool, repo, generatedAt, metrics } = artifact;
|
|
34
|
+
console.log("\n" + divider("="));
|
|
35
|
+
console.log("VIBECHECK SCAN REPORT");
|
|
36
|
+
console.log(divider("="));
|
|
37
|
+
console.log(`\nTool: ${tool.name} v${tool.version}`);
|
|
38
|
+
if (repo) {
|
|
39
|
+
console.log(`Repository: ${repo.name}`);
|
|
40
|
+
if (repo.git?.branch) {
|
|
41
|
+
console.log(`Branch: ${repo.git.branch}`);
|
|
42
|
+
}
|
|
43
|
+
if (repo.git?.commit) {
|
|
44
|
+
console.log(`Commit: ${repo.git.commit.slice(0, 8)}`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
console.log(`Generated: ${new Date(generatedAt).toLocaleString()}`);
|
|
48
|
+
if (metrics) {
|
|
49
|
+
console.log(`\nScan Metrics:`);
|
|
50
|
+
console.log(` Files scanned: ${metrics.filesScanned}`);
|
|
51
|
+
console.log(` Duration: ${metrics.scanDurationMs.toFixed(0)}ms`);
|
|
52
|
+
console.log(` Rules run: ${metrics.rulesExecuted}`);
|
|
53
|
+
}
|
|
54
|
+
console.log(`\n${divider()}`);
|
|
55
|
+
console.log("SUMMARY");
|
|
56
|
+
console.log(divider());
|
|
57
|
+
console.log(`\nTotal Findings: ${summary.totalFindings}`);
|
|
58
|
+
if (summary.totalFindings > 0) {
|
|
59
|
+
console.log("\nBy Severity:");
|
|
60
|
+
const severities = ["critical", "high", "medium", "low", "info"];
|
|
61
|
+
for (const sev of severities) {
|
|
62
|
+
const count = summary.bySeverity[sev];
|
|
63
|
+
if (count > 0) {
|
|
64
|
+
const bar = "\u2588".repeat(Math.min(count * 2, 40));
|
|
65
|
+
console.log(` ${formatSeverity(sev)} ${count.toString().padStart(3)} ${bar}`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
console.log("\nBy Category:");
|
|
69
|
+
const categories = Object.entries(summary.byCategory)
|
|
70
|
+
.filter(([, count]) => count > 0)
|
|
71
|
+
.sort(([, a], [, b]) => b - a);
|
|
72
|
+
for (const [cat, count] of categories) {
|
|
73
|
+
console.log(` ${formatCategory(cat.padEnd(12))} ${count}`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Sort findings by severity (highest first)
|
|
79
|
+
*/
|
|
80
|
+
function sortBySeverity(findings) {
|
|
81
|
+
const order = {
|
|
82
|
+
critical: 4,
|
|
83
|
+
high: 3,
|
|
84
|
+
medium: 2,
|
|
85
|
+
low: 1,
|
|
86
|
+
info: 0,
|
|
87
|
+
};
|
|
88
|
+
return [...findings].sort((a, b) => order[b.severity] - order[a.severity]);
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Print detailed finding information
|
|
92
|
+
*/
|
|
93
|
+
function printFinding(finding, index) {
|
|
94
|
+
console.log(`\n${divider()}`);
|
|
95
|
+
console.log(`FINDING #${index + 1}: ${finding.title}`);
|
|
96
|
+
console.log(divider());
|
|
97
|
+
console.log(`\nSeverity: ${formatSeverity(finding.severity)}`);
|
|
98
|
+
console.log(`Category: ${formatCategory(finding.category)}`);
|
|
99
|
+
console.log(`Rule: ${finding.ruleId}`);
|
|
100
|
+
console.log(`Confidence: ${(finding.confidence * 100).toFixed(0)}%`);
|
|
101
|
+
console.log(`ID: ${finding.id}`);
|
|
102
|
+
console.log(`\nDescription:`);
|
|
103
|
+
console.log(` ${finding.description}`);
|
|
104
|
+
console.log(`\nEvidence:`);
|
|
105
|
+
for (const ev of finding.evidence) {
|
|
106
|
+
console.log(` - ${ev.file}:${ev.startLine}-${ev.endLine}`);
|
|
107
|
+
console.log(` ${ev.label}`);
|
|
108
|
+
if (ev.snippet) {
|
|
109
|
+
console.log(` \x1b[90m${ev.snippet}\x1b[0m`);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
if (finding.claim) {
|
|
113
|
+
console.log(`\nClaim:`);
|
|
114
|
+
console.log(` Type: ${finding.claim.type}`);
|
|
115
|
+
console.log(` Source: ${finding.claim.source}`);
|
|
116
|
+
console.log(` Strength: ${finding.claim.strength}`);
|
|
117
|
+
console.log(` Evidence: "${finding.claim.textEvidence}"`);
|
|
118
|
+
}
|
|
119
|
+
if (finding.proof) {
|
|
120
|
+
console.log(`\nProof Trace:`);
|
|
121
|
+
console.log(` ${finding.proof.summary}`);
|
|
122
|
+
for (const node of finding.proof.nodes) {
|
|
123
|
+
const loc = node.file ? ` (${node.file}:${node.line})` : "";
|
|
124
|
+
console.log(` [${node.kind}] ${node.label}${loc}`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
console.log(`\nRemediation:`);
|
|
128
|
+
console.log(` ${finding.remediation.recommendedFix}`);
|
|
129
|
+
if (finding.remediation.patch) {
|
|
130
|
+
console.log(`\n Suggested patch:`);
|
|
131
|
+
console.log(` \x1b[90m${finding.remediation.patch}\x1b[0m`);
|
|
132
|
+
}
|
|
133
|
+
if (finding.links) {
|
|
134
|
+
console.log(`\nReferences:`);
|
|
135
|
+
if (finding.links.owasp) {
|
|
136
|
+
console.log(` OWASP: ${finding.links.owasp}`);
|
|
137
|
+
}
|
|
138
|
+
if (finding.links.cwe) {
|
|
139
|
+
console.log(` CWE: ${finding.links.cwe}`);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Print top findings with details
|
|
145
|
+
*/
|
|
146
|
+
function printTopFindings(artifact, limit) {
|
|
147
|
+
const { findings } = artifact;
|
|
148
|
+
if (findings.length === 0) {
|
|
149
|
+
console.log("\n\x1b[32mNo findings to display.\x1b[0m");
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
const sorted = sortBySeverity(findings);
|
|
153
|
+
const top = sorted.slice(0, limit);
|
|
154
|
+
console.log(`\n${divider("=")}`);
|
|
155
|
+
console.log(`TOP ${top.length} FINDINGS`);
|
|
156
|
+
console.log(divider("="));
|
|
157
|
+
for (let i = 0; i < top.length; i++) {
|
|
158
|
+
printFinding(top[i], i);
|
|
159
|
+
}
|
|
160
|
+
if (findings.length > limit) {
|
|
161
|
+
console.log(`\n${divider()}`);
|
|
162
|
+
console.log(`\x1b[90m... and ${findings.length - limit} more findings\x1b[0m`);
|
|
163
|
+
}
|
|
164
|
+
console.log("");
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Execute the explain command
|
|
168
|
+
*/
|
|
169
|
+
export async function executeExplain(artifactPath, options) {
|
|
170
|
+
const absolutePath = resolvePath(artifactPath);
|
|
171
|
+
// Read artifact file
|
|
172
|
+
const content = readFileSync(absolutePath);
|
|
173
|
+
if (!content) {
|
|
174
|
+
console.error(`\x1b[31mError: Could not read file: ${absolutePath}\x1b[0m`);
|
|
175
|
+
return 1;
|
|
176
|
+
}
|
|
177
|
+
// Parse JSON
|
|
178
|
+
let json;
|
|
179
|
+
try {
|
|
180
|
+
json = JSON.parse(content);
|
|
181
|
+
}
|
|
182
|
+
catch {
|
|
183
|
+
console.error(`\x1b[31mError: Invalid JSON in file: ${absolutePath}\x1b[0m`);
|
|
184
|
+
return 1;
|
|
185
|
+
}
|
|
186
|
+
// Validate artifact
|
|
187
|
+
let artifact;
|
|
188
|
+
try {
|
|
189
|
+
artifact = validateArtifact(json);
|
|
190
|
+
}
|
|
191
|
+
catch (error) {
|
|
192
|
+
console.error(`\x1b[31mError: Invalid artifact format\x1b[0m`);
|
|
193
|
+
console.error(error);
|
|
194
|
+
return 1;
|
|
195
|
+
}
|
|
196
|
+
// Print report
|
|
197
|
+
printSummary(artifact);
|
|
198
|
+
printTopFindings(artifact, options.limit);
|
|
199
|
+
return 0;
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Register explain command with commander
|
|
203
|
+
*/
|
|
204
|
+
export function registerExplainCommand(program) {
|
|
205
|
+
program
|
|
206
|
+
.command("explain <artifactPath>")
|
|
207
|
+
.description("Display a human-readable summary of a scan artifact")
|
|
208
|
+
.option("-l, --limit <number>", "Maximum findings to display", "5")
|
|
209
|
+
.action(async (artifactPath, options) => {
|
|
210
|
+
const limit = parseInt(options.limit, 10) || 5;
|
|
211
|
+
const exitCode = await executeExplain(artifactPath, { limit });
|
|
212
|
+
process.exit(exitCode);
|
|
213
|
+
});
|
|
214
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { registerScanCommand, executeScan, type ScanOptions } from "./scan.js";
|
|
2
|
+
export { registerExplainCommand, executeExplain } from "./explain.js";
|
|
3
|
+
export { registerDemoArtifactCommand, executeDemoArtifact } from "./demo-artifact.js";
|
|
4
|
+
export { registerIntentCommand, executeIntent, type IntentOptions } from "./intent.js";
|
|
5
|
+
export { registerEvaluateCommand, executeEvaluate, type EvaluateOptions } from "./evaluate.js";
|
|
6
|
+
export { registerWaiversCommand } from "./waivers.js";
|
|
7
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/commands/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,WAAW,EAAE,KAAK,WAAW,EAAE,MAAM,WAAW,CAAC;AAC/E,OAAO,EAAE,sBAAsB,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AACtE,OAAO,EAAE,2BAA2B,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AACtF,OAAO,EAAE,qBAAqB,EAAE,aAAa,EAAE,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AACvF,OAAO,EAAE,uBAAuB,EAAE,eAAe,EAAE,KAAK,eAAe,EAAE,MAAM,eAAe,CAAC;AAC/F,OAAO,EAAE,sBAAsB,EAAE,MAAM,cAAc,CAAC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { registerScanCommand, executeScan } from "./scan.js";
|
|
2
|
+
export { registerExplainCommand, executeExplain } from "./explain.js";
|
|
3
|
+
export { registerDemoArtifactCommand, executeDemoArtifact } from "./demo-artifact.js";
|
|
4
|
+
export { registerIntentCommand, executeIntent } from "./intent.js";
|
|
5
|
+
export { registerEvaluateCommand, executeEvaluate } from "./evaluate.js";
|
|
6
|
+
export { registerWaiversCommand } from "./waivers.js";
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Intent Command
|
|
3
|
+
*
|
|
4
|
+
* Generates a security intent map baseline for the codebase.
|
|
5
|
+
* Useful for tracking drift over time.
|
|
6
|
+
*/
|
|
7
|
+
import type { Command } from "commander";
|
|
8
|
+
export interface IntentOptions {
|
|
9
|
+
out: string;
|
|
10
|
+
format: string;
|
|
11
|
+
repoName?: string;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Execute the intent command
|
|
15
|
+
*/
|
|
16
|
+
export declare function executeIntent(targetDir: string, options: IntentOptions): Promise<number>;
|
|
17
|
+
/**
|
|
18
|
+
* Register intent command with commander
|
|
19
|
+
*/
|
|
20
|
+
export declare function registerIntentCommand(program: Command): void;
|
|
21
|
+
//# sourceMappingURL=intent.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"intent.d.ts","sourceRoot":"","sources":["../../src/commands/intent.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAqBzC,MAAM,WAAW,aAAa;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAuCD;;GAEG;AACH,wBAAsB,aAAa,CACjC,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,aAAa,GACrB,OAAO,CAAC,MAAM,CAAC,CA8DjB;AA+ID;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAgB5D"}
|