@quantracode/vibecheck 0.0.1 → 0.0.2
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 +6 -6
- package/dist/index.d.ts +0 -2
- package/dist/index.js +7902 -8
- package/package.json +13 -7
- package/dist/__tests__/cli.test.d.ts +0 -2
- package/dist/__tests__/cli.test.d.ts.map +0 -1
- package/dist/__tests__/cli.test.js +0 -243
- package/dist/__tests__/fixtures/safe-app/app/api/users/route.js +0 -36
- package/dist/__tests__/fixtures/vulnerable-app/app/api/users/route.js +0 -28
- package/dist/__tests__/fixtures/vulnerable-app/lib/config.d.ts +0 -4
- package/dist/__tests__/fixtures/vulnerable-app/lib/config.d.ts.map +0 -1
- package/dist/__tests__/fixtures/vulnerable-app/lib/config.js +0 -6
- package/dist/__tests__/scanners/env-config.test.d.ts +0 -2
- package/dist/__tests__/scanners/env-config.test.d.ts.map +0 -1
- package/dist/__tests__/scanners/env-config.test.js +0 -142
- package/dist/__tests__/scanners/nextjs-middleware.test.d.ts +0 -2
- package/dist/__tests__/scanners/nextjs-middleware.test.d.ts.map +0 -1
- package/dist/__tests__/scanners/nextjs-middleware.test.js +0 -193
- package/dist/__tests__/scanners/scanner-packs.test.d.ts +0 -2
- package/dist/__tests__/scanners/scanner-packs.test.d.ts.map +0 -1
- package/dist/__tests__/scanners/scanner-packs.test.js +0 -126
- package/dist/__tests__/scanners/unused-security-imports.test.d.ts +0 -2
- package/dist/__tests__/scanners/unused-security-imports.test.d.ts.map +0 -1
- package/dist/__tests__/scanners/unused-security-imports.test.js +0 -145
- package/dist/commands/demo-artifact.d.ts +0 -7
- package/dist/commands/demo-artifact.d.ts.map +0 -1
- package/dist/commands/demo-artifact.js +0 -322
- package/dist/commands/evaluate.d.ts +0 -30
- package/dist/commands/evaluate.d.ts.map +0 -1
- package/dist/commands/evaluate.js +0 -258
- package/dist/commands/explain.d.ts +0 -12
- package/dist/commands/explain.d.ts.map +0 -1
- package/dist/commands/explain.js +0 -214
- package/dist/commands/index.d.ts +0 -7
- package/dist/commands/index.d.ts.map +0 -1
- package/dist/commands/index.js +0 -6
- package/dist/commands/intent.d.ts +0 -21
- package/dist/commands/intent.d.ts.map +0 -1
- package/dist/commands/intent.js +0 -192
- package/dist/commands/scan.d.ts +0 -44
- package/dist/commands/scan.d.ts.map +0 -1
- package/dist/commands/scan.js +0 -497
- package/dist/commands/waivers.d.ts +0 -30
- package/dist/commands/waivers.d.ts.map +0 -1
- package/dist/commands/waivers.js +0 -249
- package/dist/index.d.ts.map +0 -1
- package/dist/phase3/index.d.ts +0 -11
- package/dist/phase3/index.d.ts.map +0 -1
- package/dist/phase3/index.js +0 -12
- package/dist/phase3/intent-miner.d.ts +0 -32
- package/dist/phase3/intent-miner.d.ts.map +0 -1
- package/dist/phase3/intent-miner.js +0 -323
- package/dist/phase3/proof-trace-builder.d.ts +0 -42
- package/dist/phase3/proof-trace-builder.d.ts.map +0 -1
- package/dist/phase3/proof-trace-builder.js +0 -441
- package/dist/phase3/scanners/auth-by-ui-server-gap.d.ts +0 -15
- package/dist/phase3/scanners/auth-by-ui-server-gap.d.ts.map +0 -1
- package/dist/phase3/scanners/auth-by-ui-server-gap.js +0 -237
- package/dist/phase3/scanners/comment-claim-unproven.d.ts +0 -14
- package/dist/phase3/scanners/comment-claim-unproven.d.ts.map +0 -1
- package/dist/phase3/scanners/comment-claim-unproven.js +0 -161
- package/dist/phase3/scanners/index.d.ts +0 -31
- package/dist/phase3/scanners/index.d.ts.map +0 -1
- package/dist/phase3/scanners/index.js +0 -40
- package/dist/phase3/scanners/middleware-assumed-not-matching.d.ts +0 -14
- package/dist/phase3/scanners/middleware-assumed-not-matching.d.ts.map +0 -1
- package/dist/phase3/scanners/middleware-assumed-not-matching.js +0 -172
- package/dist/phase3/scanners/validation-claimed-missing.d.ts +0 -15
- package/dist/phase3/scanners/validation-claimed-missing.d.ts.map +0 -1
- package/dist/phase3/scanners/validation-claimed-missing.js +0 -204
- package/dist/scanners/abuse/compute-abuse.d.ts +0 -20
- package/dist/scanners/abuse/compute-abuse.d.ts.map +0 -1
- package/dist/scanners/abuse/compute-abuse.js +0 -509
- package/dist/scanners/abuse/index.d.ts +0 -12
- package/dist/scanners/abuse/index.d.ts.map +0 -1
- package/dist/scanners/abuse/index.js +0 -15
- package/dist/scanners/auth/index.d.ts +0 -5
- package/dist/scanners/auth/index.d.ts.map +0 -1
- package/dist/scanners/auth/index.js +0 -10
- package/dist/scanners/auth/middleware-gap.d.ts +0 -22
- package/dist/scanners/auth/middleware-gap.d.ts.map +0 -1
- package/dist/scanners/auth/middleware-gap.js +0 -203
- package/dist/scanners/auth/unprotected-api-route.d.ts +0 -12
- package/dist/scanners/auth/unprotected-api-route.d.ts.map +0 -1
- package/dist/scanners/auth/unprotected-api-route.js +0 -126
- package/dist/scanners/config/index.d.ts +0 -5
- package/dist/scanners/config/index.d.ts.map +0 -1
- package/dist/scanners/config/index.js +0 -10
- package/dist/scanners/config/insecure-defaults.d.ts +0 -12
- package/dist/scanners/config/insecure-defaults.d.ts.map +0 -1
- package/dist/scanners/config/insecure-defaults.js +0 -77
- package/dist/scanners/config/undocumented-env.d.ts +0 -24
- package/dist/scanners/config/undocumented-env.d.ts.map +0 -1
- package/dist/scanners/config/undocumented-env.js +0 -159
- package/dist/scanners/crypto/index.d.ts +0 -6
- package/dist/scanners/crypto/index.d.ts.map +0 -1
- package/dist/scanners/crypto/index.js +0 -11
- package/dist/scanners/crypto/jwt-decode-unverified.d.ts +0 -14
- package/dist/scanners/crypto/jwt-decode-unverified.d.ts.map +0 -1
- package/dist/scanners/crypto/jwt-decode-unverified.js +0 -87
- package/dist/scanners/crypto/math-random-tokens.d.ts +0 -13
- package/dist/scanners/crypto/math-random-tokens.d.ts.map +0 -1
- package/dist/scanners/crypto/math-random-tokens.js +0 -80
- package/dist/scanners/crypto/weak-hashing.d.ts +0 -11
- package/dist/scanners/crypto/weak-hashing.d.ts.map +0 -1
- package/dist/scanners/crypto/weak-hashing.js +0 -95
- package/dist/scanners/env-config.d.ts +0 -24
- package/dist/scanners/env-config.d.ts.map +0 -1
- package/dist/scanners/env-config.js +0 -164
- package/dist/scanners/hallucinations/index.d.ts +0 -4
- package/dist/scanners/hallucinations/index.d.ts.map +0 -1
- package/dist/scanners/hallucinations/index.js +0 -8
- package/dist/scanners/hallucinations/unused-security-imports.d.ts +0 -36
- package/dist/scanners/hallucinations/unused-security-imports.d.ts.map +0 -1
- package/dist/scanners/hallucinations/unused-security-imports.js +0 -309
- package/dist/scanners/helpers/ast-helpers.d.ts +0 -6
- package/dist/scanners/helpers/ast-helpers.d.ts.map +0 -1
- package/dist/scanners/helpers/ast-helpers.js +0 -945
- package/dist/scanners/helpers/context-builder.d.ts +0 -17
- package/dist/scanners/helpers/context-builder.d.ts.map +0 -1
- package/dist/scanners/helpers/context-builder.js +0 -148
- package/dist/scanners/helpers/index.d.ts +0 -3
- package/dist/scanners/helpers/index.d.ts.map +0 -1
- package/dist/scanners/helpers/index.js +0 -2
- package/dist/scanners/index.d.ts +0 -30
- package/dist/scanners/index.d.ts.map +0 -1
- package/dist/scanners/index.js +0 -102
- package/dist/scanners/middleware/index.d.ts +0 -4
- package/dist/scanners/middleware/index.d.ts.map +0 -1
- package/dist/scanners/middleware/index.js +0 -7
- package/dist/scanners/middleware/missing-rate-limit.d.ts +0 -13
- package/dist/scanners/middleware/missing-rate-limit.d.ts.map +0 -1
- package/dist/scanners/middleware/missing-rate-limit.js +0 -140
- package/dist/scanners/network/cors-misconfiguration.d.ts +0 -14
- package/dist/scanners/network/cors-misconfiguration.d.ts.map +0 -1
- package/dist/scanners/network/cors-misconfiguration.js +0 -89
- package/dist/scanners/network/index.d.ts +0 -7
- package/dist/scanners/network/index.d.ts.map +0 -1
- package/dist/scanners/network/index.js +0 -18
- package/dist/scanners/network/missing-timeout.d.ts +0 -15
- package/dist/scanners/network/missing-timeout.d.ts.map +0 -1
- package/dist/scanners/network/missing-timeout.js +0 -93
- package/dist/scanners/network/open-redirect.d.ts +0 -15
- package/dist/scanners/network/open-redirect.d.ts.map +0 -1
- package/dist/scanners/network/open-redirect.js +0 -88
- package/dist/scanners/network/ssrf-prone-fetch.d.ts +0 -12
- package/dist/scanners/network/ssrf-prone-fetch.d.ts.map +0 -1
- package/dist/scanners/network/ssrf-prone-fetch.js +0 -90
- package/dist/scanners/nextjs-middleware.d.ts +0 -26
- package/dist/scanners/nextjs-middleware.d.ts.map +0 -1
- package/dist/scanners/nextjs-middleware.js +0 -246
- package/dist/scanners/privacy/debug-flags.d.ts +0 -13
- package/dist/scanners/privacy/debug-flags.d.ts.map +0 -1
- package/dist/scanners/privacy/debug-flags.js +0 -124
- package/dist/scanners/privacy/index.d.ts +0 -6
- package/dist/scanners/privacy/index.d.ts.map +0 -1
- package/dist/scanners/privacy/index.js +0 -11
- package/dist/scanners/privacy/over-broad-response.d.ts +0 -15
- package/dist/scanners/privacy/over-broad-response.d.ts.map +0 -1
- package/dist/scanners/privacy/over-broad-response.js +0 -109
- package/dist/scanners/privacy/sensitive-logging.d.ts +0 -11
- package/dist/scanners/privacy/sensitive-logging.d.ts.map +0 -1
- package/dist/scanners/privacy/sensitive-logging.js +0 -78
- package/dist/scanners/types.d.ts +0 -456
- package/dist/scanners/types.d.ts.map +0 -1
- package/dist/scanners/types.js +0 -16
- package/dist/scanners/unused-security-imports.d.ts +0 -34
- package/dist/scanners/unused-security-imports.d.ts.map +0 -1
- package/dist/scanners/unused-security-imports.js +0 -206
- package/dist/scanners/uploads/index.d.ts +0 -5
- package/dist/scanners/uploads/index.d.ts.map +0 -1
- package/dist/scanners/uploads/index.js +0 -9
- package/dist/scanners/uploads/missing-constraints.d.ts +0 -15
- package/dist/scanners/uploads/missing-constraints.d.ts.map +0 -1
- package/dist/scanners/uploads/missing-constraints.js +0 -109
- package/dist/scanners/uploads/public-path.d.ts +0 -11
- package/dist/scanners/uploads/public-path.d.ts.map +0 -1
- package/dist/scanners/uploads/public-path.js +0 -87
- package/dist/scanners/validation/client-side-only.d.ts +0 -14
- package/dist/scanners/validation/client-side-only.d.ts.map +0 -1
- package/dist/scanners/validation/client-side-only.js +0 -140
- package/dist/scanners/validation/ignored-validation.d.ts +0 -12
- package/dist/scanners/validation/ignored-validation.d.ts.map +0 -1
- package/dist/scanners/validation/ignored-validation.js +0 -119
- package/dist/scanners/validation/index.d.ts +0 -5
- package/dist/scanners/validation/index.d.ts.map +0 -1
- package/dist/scanners/validation/index.js +0 -9
- package/dist/utils/exclude-patterns.d.ts +0 -35
- package/dist/utils/exclude-patterns.d.ts.map +0 -1
- package/dist/utils/exclude-patterns.js +0 -78
- package/dist/utils/file-utils.d.ts +0 -37
- package/dist/utils/file-utils.d.ts.map +0 -1
- package/dist/utils/file-utils.js +0 -77
- package/dist/utils/fingerprint.d.ts +0 -25
- package/dist/utils/fingerprint.d.ts.map +0 -1
- package/dist/utils/fingerprint.js +0 -28
- package/dist/utils/git-info.d.ts +0 -14
- package/dist/utils/git-info.d.ts.map +0 -1
- package/dist/utils/git-info.js +0 -55
- package/dist/utils/index.d.ts +0 -4
- package/dist/utils/index.d.ts.map +0 -1
- package/dist/utils/index.js +0 -3
- package/dist/utils/progress.d.ts +0 -42
- package/dist/utils/progress.d.ts.map +0 -1
- package/dist/utils/progress.js +0 -165
- package/dist/utils/sarif-formatter.d.ts +0 -92
- package/dist/utils/sarif-formatter.d.ts.map +0 -1
- package/dist/utils/sarif-formatter.js +0 -172
package/dist/commands/scan.js
DELETED
|
@@ -1,497 +0,0 @@
|
|
|
1
|
-
import path from "node:path";
|
|
2
|
-
import { ARTIFACT_VERSION, computeSummary, validateArtifact, } from "@vibecheck/schema";
|
|
3
|
-
import { writeFileSync, resolvePath, ensureDir, } from "../utils/file-utils.js";
|
|
4
|
-
import { hashPath } from "../utils/fingerprint.js";
|
|
5
|
-
import { getGitInfo, getRepoName } from "../utils/git-info.js";
|
|
6
|
-
import { ALL_SCANNERS, ALL_SCANNER_PACKS, buildScanContext, severityMeetsThreshold, } from "../scanners/index.js";
|
|
7
|
-
import { buildRouteMap, buildMiddlewareMap, buildAllProofTraces, calculateCoverage, mineAllIntentClaims, } from "../phase3/index.js";
|
|
8
|
-
import { toSarif, sarifToJson } from "../utils/sarif-formatter.js";
|
|
9
|
-
import { ScanProgress, Spinner } from "../utils/progress.js";
|
|
10
|
-
const DEFAULT_OUTPUT_DIR = "vibecheck-artifacts";
|
|
11
|
-
const DEFAULT_OUTPUT_FILE = "vibecheck-scan.json";
|
|
12
|
-
/**
|
|
13
|
-
* Normalize path for Windows compatibility
|
|
14
|
-
*/
|
|
15
|
-
function normalizePath(p) {
|
|
16
|
-
// Use forward slashes for consistency, but respect platform separators for filesystem ops
|
|
17
|
-
return p.replace(/\\/g, "/");
|
|
18
|
-
}
|
|
19
|
-
/**
|
|
20
|
-
* Get output file path with correct extension
|
|
21
|
-
*/
|
|
22
|
-
function getOutputPath(basePath, format, targetDir) {
|
|
23
|
-
let outputPath = basePath;
|
|
24
|
-
// If path is relative, resolve against target directory
|
|
25
|
-
if (!path.isAbsolute(outputPath)) {
|
|
26
|
-
outputPath = resolvePath(targetDir, outputPath);
|
|
27
|
-
}
|
|
28
|
-
// Handle directory vs file path
|
|
29
|
-
const ext = path.extname(outputPath);
|
|
30
|
-
if (!ext) {
|
|
31
|
-
// It's a directory, add default filename
|
|
32
|
-
const filename = format === "sarif" ? "vibecheck-scan.sarif" : "vibecheck-scan.json";
|
|
33
|
-
outputPath = path.join(outputPath, filename);
|
|
34
|
-
}
|
|
35
|
-
else if (format === "sarif" && ext === ".json") {
|
|
36
|
-
// Replace .json with .sarif for sarif format
|
|
37
|
-
outputPath = outputPath.replace(/\.json$/, ".sarif");
|
|
38
|
-
}
|
|
39
|
-
else if (format === "json" && ext === ".sarif") {
|
|
40
|
-
// Replace .sarif with .json for json format
|
|
41
|
-
outputPath = outputPath.replace(/\.sarif$/, ".json");
|
|
42
|
-
}
|
|
43
|
-
return outputPath;
|
|
44
|
-
}
|
|
45
|
-
/**
|
|
46
|
-
* Run all scanners with file-level progress tracking
|
|
47
|
-
*/
|
|
48
|
-
async function runScannersWithProgress(baseContext, totalFiles) {
|
|
49
|
-
const allFindings = [];
|
|
50
|
-
const seenFingerprints = new Set();
|
|
51
|
-
// Initialize progress display with file count
|
|
52
|
-
const progress = new ScanProgress(ALL_SCANNER_PACKS.map((pack) => ({
|
|
53
|
-
id: pack.id,
|
|
54
|
-
name: pack.name,
|
|
55
|
-
scannerCount: pack.scanners.length,
|
|
56
|
-
})), totalFiles);
|
|
57
|
-
// Create file progress callback
|
|
58
|
-
const onFileProgress = (file, processed, total) => {
|
|
59
|
-
progress.onFileProgress(file, processed, total);
|
|
60
|
-
};
|
|
61
|
-
// Rebuild context with progress callback
|
|
62
|
-
const contextOptions = {
|
|
63
|
-
onFileProgress,
|
|
64
|
-
};
|
|
65
|
-
const context = await buildScanContext(baseContext.repoRoot, {
|
|
66
|
-
...contextOptions,
|
|
67
|
-
excludePatterns: [],
|
|
68
|
-
includeTests: false,
|
|
69
|
-
});
|
|
70
|
-
// Copy over the file index from base context (already computed)
|
|
71
|
-
Object.assign(context, {
|
|
72
|
-
fileIndex: baseContext.fileIndex,
|
|
73
|
-
repoMeta: baseContext.repoMeta,
|
|
74
|
-
frameworkHints: baseContext.frameworkHints,
|
|
75
|
-
prismaSchemaInfo: baseContext.prismaSchemaInfo,
|
|
76
|
-
});
|
|
77
|
-
progress.start();
|
|
78
|
-
// Iterate through packs for progress tracking
|
|
79
|
-
for (let packIndex = 0; packIndex < ALL_SCANNER_PACKS.length; packIndex++) {
|
|
80
|
-
const pack = ALL_SCANNER_PACKS[packIndex];
|
|
81
|
-
progress.startPack(packIndex);
|
|
82
|
-
for (let scannerIndex = 0; scannerIndex < pack.scanners.length; scannerIndex++) {
|
|
83
|
-
const scanner = pack.scanners[scannerIndex];
|
|
84
|
-
progress.startScanner(packIndex, scannerIndex);
|
|
85
|
-
try {
|
|
86
|
-
const findings = await scanner(context);
|
|
87
|
-
let newFindingsCount = 0;
|
|
88
|
-
// Dedupe by fingerprint
|
|
89
|
-
for (const finding of findings) {
|
|
90
|
-
if (!seenFingerprints.has(finding.fingerprint)) {
|
|
91
|
-
seenFingerprints.add(finding.fingerprint);
|
|
92
|
-
allFindings.push(finding);
|
|
93
|
-
newFindingsCount++;
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
progress.completeScanner(packIndex, newFindingsCount);
|
|
97
|
-
}
|
|
98
|
-
catch (error) {
|
|
99
|
-
// Log error but continue with other scanners
|
|
100
|
-
progress.completeScanner(packIndex, 0);
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
progress.completePack(packIndex);
|
|
104
|
-
}
|
|
105
|
-
progress.stop();
|
|
106
|
-
return allFindings;
|
|
107
|
-
}
|
|
108
|
-
/**
|
|
109
|
-
* Create scan artifact from findings
|
|
110
|
-
*/
|
|
111
|
-
function createArtifact(findings, targetDir, fileCount, repoName, metrics, phase3Data) {
|
|
112
|
-
const summary = computeSummary(findings);
|
|
113
|
-
const gitInfo = getGitInfo(targetDir);
|
|
114
|
-
const name = repoName ?? getRepoName(targetDir);
|
|
115
|
-
const artifact = {
|
|
116
|
-
artifactVersion: ARTIFACT_VERSION,
|
|
117
|
-
generatedAt: new Date().toISOString(),
|
|
118
|
-
tool: {
|
|
119
|
-
name: "vibecheck",
|
|
120
|
-
version: "0.0.1",
|
|
121
|
-
},
|
|
122
|
-
repo: {
|
|
123
|
-
name,
|
|
124
|
-
rootPathHash: hashPath(targetDir),
|
|
125
|
-
git: gitInfo,
|
|
126
|
-
},
|
|
127
|
-
summary,
|
|
128
|
-
findings,
|
|
129
|
-
};
|
|
130
|
-
if (metrics) {
|
|
131
|
-
artifact.metrics = {
|
|
132
|
-
filesScanned: metrics.filesScanned,
|
|
133
|
-
linesOfCode: 0, // Not implemented yet
|
|
134
|
-
scanDurationMs: metrics.scanDurationMs,
|
|
135
|
-
rulesExecuted: ALL_SCANNERS.length,
|
|
136
|
-
};
|
|
137
|
-
}
|
|
138
|
-
// Add Phase 3 data if provided
|
|
139
|
-
if (phase3Data) {
|
|
140
|
-
artifact.routeMap = phase3Data.routeMap;
|
|
141
|
-
artifact.middlewareMap = phase3Data.middlewareMap;
|
|
142
|
-
artifact.intentMap = phase3Data.intentMap;
|
|
143
|
-
artifact.proofTraces = phase3Data.proofTraces;
|
|
144
|
-
// Add coverage to metrics
|
|
145
|
-
if (artifact.metrics) {
|
|
146
|
-
artifact.metrics = {
|
|
147
|
-
...artifact.metrics,
|
|
148
|
-
...phase3Data.coverageMetrics,
|
|
149
|
-
};
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
return artifact;
|
|
153
|
-
}
|
|
154
|
-
/**
|
|
155
|
-
* Format severity for console output with color
|
|
156
|
-
*/
|
|
157
|
-
function formatSeverity(severity) {
|
|
158
|
-
const colors = {
|
|
159
|
-
critical: "\x1b[35m", // Magenta
|
|
160
|
-
high: "\x1b[31m", // Red
|
|
161
|
-
medium: "\x1b[33m", // Yellow
|
|
162
|
-
low: "\x1b[36m", // Cyan
|
|
163
|
-
info: "\x1b[90m", // Gray
|
|
164
|
-
};
|
|
165
|
-
const reset = "\x1b[0m";
|
|
166
|
-
return `${colors[severity]}${severity.toUpperCase()}${reset}`;
|
|
167
|
-
}
|
|
168
|
-
/**
|
|
169
|
-
* Print scan summary to console
|
|
170
|
-
*/
|
|
171
|
-
function printSummary(artifact, options) {
|
|
172
|
-
const { summary, findings } = artifact;
|
|
173
|
-
console.log("\n" + "=".repeat(60));
|
|
174
|
-
console.log("VibeCheck Scan Complete");
|
|
175
|
-
console.log("=".repeat(60));
|
|
176
|
-
console.log(`\nScanner packs: ${ALL_SCANNER_PACKS.map((p) => p.name).join(", ")}`);
|
|
177
|
-
console.log(`Total scanners: ${ALL_SCANNERS.length}`);
|
|
178
|
-
console.log(`Total findings: ${summary.totalFindings}`);
|
|
179
|
-
if (options.exclude.length > 0) {
|
|
180
|
-
console.log(`Custom excludes: ${options.exclude.length} pattern(s)`);
|
|
181
|
-
}
|
|
182
|
-
if (options.includeTests) {
|
|
183
|
-
console.log("Test files: included");
|
|
184
|
-
}
|
|
185
|
-
if (summary.totalFindings > 0) {
|
|
186
|
-
console.log("\nBy severity:");
|
|
187
|
-
if (summary.bySeverity.critical > 0) {
|
|
188
|
-
console.log(` ${formatSeverity("critical")}: ${summary.bySeverity.critical}`);
|
|
189
|
-
}
|
|
190
|
-
if (summary.bySeverity.high > 0) {
|
|
191
|
-
console.log(` ${formatSeverity("high")}: ${summary.bySeverity.high}`);
|
|
192
|
-
}
|
|
193
|
-
if (summary.bySeverity.medium > 0) {
|
|
194
|
-
console.log(` ${formatSeverity("medium")}: ${summary.bySeverity.medium}`);
|
|
195
|
-
}
|
|
196
|
-
if (summary.bySeverity.low > 0) {
|
|
197
|
-
console.log(` ${formatSeverity("low")}: ${summary.bySeverity.low}`);
|
|
198
|
-
}
|
|
199
|
-
if (summary.bySeverity.info > 0) {
|
|
200
|
-
console.log(` ${formatSeverity("info")}: ${summary.bySeverity.info}`);
|
|
201
|
-
}
|
|
202
|
-
console.log("\nTop findings:");
|
|
203
|
-
const topFindings = findings
|
|
204
|
-
.sort((a, b) => {
|
|
205
|
-
const order = { critical: 4, high: 3, medium: 2, low: 1, info: 0 };
|
|
206
|
-
return order[b.severity] - order[a.severity];
|
|
207
|
-
})
|
|
208
|
-
.slice(0, 5);
|
|
209
|
-
for (const finding of topFindings) {
|
|
210
|
-
console.log(` [${formatSeverity(finding.severity)}] ${finding.title}`);
|
|
211
|
-
if (finding.evidence[0]) {
|
|
212
|
-
console.log(` ${normalizePath(finding.evidence[0].file)}:${finding.evidence[0].startLine}`);
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
console.log("");
|
|
217
|
-
}
|
|
218
|
-
/**
|
|
219
|
-
* Check if any finding meets the fail threshold
|
|
220
|
-
*/
|
|
221
|
-
function shouldFail(findings, threshold) {
|
|
222
|
-
if (threshold === "off") {
|
|
223
|
-
return false;
|
|
224
|
-
}
|
|
225
|
-
return findings.some((f) => severityMeetsThreshold(f.severity, threshold));
|
|
226
|
-
}
|
|
227
|
-
/**
|
|
228
|
-
* Validate format option
|
|
229
|
-
*/
|
|
230
|
-
function validateFormat(format) {
|
|
231
|
-
const validFormats = ["json", "sarif", "both"];
|
|
232
|
-
if (!validFormats.includes(format)) {
|
|
233
|
-
console.error(`\x1b[31mError: Invalid format "${format}". Valid options: ${validFormats.join(", ")}\x1b[0m`);
|
|
234
|
-
process.exit(1);
|
|
235
|
-
}
|
|
236
|
-
return format;
|
|
237
|
-
}
|
|
238
|
-
/**
|
|
239
|
-
* Validate fail-on option
|
|
240
|
-
*/
|
|
241
|
-
function validateFailOn(failOn) {
|
|
242
|
-
const validThresholds = ["off", "info", "low", "medium", "high", "critical"];
|
|
243
|
-
if (!validThresholds.includes(failOn)) {
|
|
244
|
-
console.error(`\x1b[31mError: Invalid fail-on value "${failOn}". Valid options: ${validThresholds.join(", ")}\x1b[0m`);
|
|
245
|
-
process.exit(1);
|
|
246
|
-
}
|
|
247
|
-
return failOn;
|
|
248
|
-
}
|
|
249
|
-
/**
|
|
250
|
-
* Execute the scan command
|
|
251
|
-
*/
|
|
252
|
-
export async function executeScan(targetDir, options) {
|
|
253
|
-
const absoluteTarget = resolvePath(targetDir);
|
|
254
|
-
// Validate options
|
|
255
|
-
const format = validateFormat(options.format);
|
|
256
|
-
const failOn = validateFailOn(options.failOn);
|
|
257
|
-
// Handle --changed flag
|
|
258
|
-
if (options.changed) {
|
|
259
|
-
console.warn("\x1b[33mWarning: --changed flag is not implemented yet\x1b[0m");
|
|
260
|
-
}
|
|
261
|
-
console.log(`\n\x1b[36m\x1b[1m ╭${"─".repeat(88)}╮\x1b[0m`);
|
|
262
|
-
console.log(`\x1b[36m │\x1b[0m \x1b[1mVIBECHECK\x1b[0m ${" ".repeat(68)}\x1b[90mv0.0.1\x1b[0m \x1b[36m│\x1b[0m`);
|
|
263
|
-
console.log(`\x1b[36m │\x1b[0m \x1b[90m${normalizePath(absoluteTarget).slice(0, 84).padEnd(84)}\x1b[0m \x1b[36m│\x1b[0m`);
|
|
264
|
-
console.log(`\x1b[36m ╰${"─".repeat(88)}╯\x1b[0m\n`);
|
|
265
|
-
const startTime = Date.now();
|
|
266
|
-
// Build scan context with file index and helpers
|
|
267
|
-
const contextSpinner = new Spinner("Indexing source files");
|
|
268
|
-
contextSpinner.start();
|
|
269
|
-
// First build context without progress callback to get file count
|
|
270
|
-
const contextOptions = {
|
|
271
|
-
excludePatterns: options.exclude,
|
|
272
|
-
includeTests: options.includeTests,
|
|
273
|
-
};
|
|
274
|
-
const initialContext = await buildScanContext(absoluteTarget, contextOptions);
|
|
275
|
-
const totalFiles = initialContext.fileIndex.allSourceFiles.length;
|
|
276
|
-
contextSpinner.succeed(`Indexed ${totalFiles} source files`);
|
|
277
|
-
console.log(`\x1b[90m ├─ API routes: ${initialContext.fileIndex.apiRouteFiles.length}\x1b[0m`);
|
|
278
|
-
console.log(`\x1b[90m ├─ Config files: ${initialContext.fileIndex.configFiles.length}\x1b[0m`);
|
|
279
|
-
console.log(`\x1b[90m └─ Framework: ${initialContext.repoMeta.framework}\x1b[0m`);
|
|
280
|
-
// Run scanners with progress tracking
|
|
281
|
-
const findings = await runScannersWithProgress(initialContext, totalFiles);
|
|
282
|
-
const endTime = Date.now();
|
|
283
|
-
const scanDurationMs = endTime - startTime;
|
|
284
|
-
// Build Phase 3 data - enabled by default
|
|
285
|
-
let phase3Data;
|
|
286
|
-
const shouldEmitPhase3 = options.emitRouteMap || options.emitIntents || options.emitTraces;
|
|
287
|
-
if (shouldEmitPhase3) {
|
|
288
|
-
const intentSpinner = new Spinner("Building route map and proof traces");
|
|
289
|
-
intentSpinner.start();
|
|
290
|
-
const routeMapRaw = buildRouteMap(initialContext);
|
|
291
|
-
const middlewareMapRaw = buildMiddlewareMap(initialContext);
|
|
292
|
-
const intentMapRaw = mineAllIntentClaims(initialContext, routeMapRaw);
|
|
293
|
-
const proofTracesRaw = buildAllProofTraces(initialContext, routeMapRaw);
|
|
294
|
-
const coverageRaw = calculateCoverage(routeMapRaw, proofTracesRaw, middlewareMapRaw);
|
|
295
|
-
// Convert to schema format
|
|
296
|
-
const proofTracesRecord = {};
|
|
297
|
-
for (const [key, value] of proofTracesRaw) {
|
|
298
|
-
proofTracesRecord[key] = {
|
|
299
|
-
summary: value.authProven
|
|
300
|
-
? "Auth proven"
|
|
301
|
-
: value.middlewareCovered
|
|
302
|
-
? "Protected by middleware"
|
|
303
|
-
: "No protection proven",
|
|
304
|
-
nodes: value.steps.map((step) => ({
|
|
305
|
-
kind: "handler",
|
|
306
|
-
label: step.label,
|
|
307
|
-
file: step.file,
|
|
308
|
-
line: step.line,
|
|
309
|
-
})),
|
|
310
|
-
};
|
|
311
|
-
}
|
|
312
|
-
// Build middleware coverage
|
|
313
|
-
const allMatchers = middlewareMapRaw.flatMap((m) => m.matchers);
|
|
314
|
-
const middlewareCoverage = routeMapRaw.map((route) => {
|
|
315
|
-
const isRouteCovered = allMatchers.some((matcher) => {
|
|
316
|
-
const pattern = matcher.replace(/\*/g, ".*").replace(/\/:path\*/g, "/.*");
|
|
317
|
-
try {
|
|
318
|
-
return new RegExp(`^${pattern}`).test(route.path);
|
|
319
|
-
}
|
|
320
|
-
catch {
|
|
321
|
-
return route.path.startsWith(matcher.replace(/\/:path\*$/, ""));
|
|
322
|
-
}
|
|
323
|
-
});
|
|
324
|
-
return {
|
|
325
|
-
routeId: route.routeId,
|
|
326
|
-
covered: isRouteCovered,
|
|
327
|
-
};
|
|
328
|
-
});
|
|
329
|
-
// Calculate coverage stats
|
|
330
|
-
const stateChangingMethods = ["POST", "PUT", "PATCH", "DELETE"];
|
|
331
|
-
const stateChangingRoutes = routeMapRaw.filter((r) => stateChangingMethods.includes(r.method));
|
|
332
|
-
const protectedRoutes = stateChangingRoutes.filter((r) => {
|
|
333
|
-
const trace = proofTracesRaw.get(r.routeId);
|
|
334
|
-
return trace?.authProven || trace?.middlewareCovered;
|
|
335
|
-
});
|
|
336
|
-
const validatedRoutes = routeMapRaw
|
|
337
|
-
.filter((r) => ["POST", "PUT", "PATCH"].includes(r.method))
|
|
338
|
-
.filter((r) => proofTracesRaw.get(r.routeId)?.validationProven);
|
|
339
|
-
const coveredApiRoutes = middlewareCoverage.filter((c) => c.covered);
|
|
340
|
-
// Build phase3Data with optional fields based on flags
|
|
341
|
-
phase3Data = {
|
|
342
|
-
routeMap: options.emitRouteMap ? { routes: routeMapRaw } : { routes: [] },
|
|
343
|
-
middlewareMap: options.emitRouteMap ? {
|
|
344
|
-
middlewareFile: middlewareMapRaw[0]?.file,
|
|
345
|
-
matcher: allMatchers,
|
|
346
|
-
coverage: middlewareCoverage,
|
|
347
|
-
} : { matcher: [], coverage: [] },
|
|
348
|
-
intentMap: options.emitIntents ? { intents: intentMapRaw } : { intents: [] },
|
|
349
|
-
proofTraces: options.emitTraces ? proofTracesRecord : {},
|
|
350
|
-
coverageMetrics: {
|
|
351
|
-
authCoverage: {
|
|
352
|
-
totalStateChanging: stateChangingRoutes.length,
|
|
353
|
-
protectedCount: protectedRoutes.length,
|
|
354
|
-
unprotectedCount: stateChangingRoutes.length - protectedRoutes.length,
|
|
355
|
-
},
|
|
356
|
-
validationCoverage: {
|
|
357
|
-
totalStateChanging: routeMapRaw.filter((r) => ["POST", "PUT", "PATCH"].includes(r.method)).length,
|
|
358
|
-
validatedCount: validatedRoutes.length,
|
|
359
|
-
},
|
|
360
|
-
middlewareCoverage: {
|
|
361
|
-
totalApiRoutes: routeMapRaw.length,
|
|
362
|
-
coveredApiRoutes: coveredApiRoutes.length,
|
|
363
|
-
},
|
|
364
|
-
},
|
|
365
|
-
};
|
|
366
|
-
const parts = [];
|
|
367
|
-
if (options.emitRouteMap)
|
|
368
|
-
parts.push(`${routeMapRaw.length} routes`);
|
|
369
|
-
if (options.emitIntents)
|
|
370
|
-
parts.push(`${intentMapRaw.length} intents`);
|
|
371
|
-
if (options.emitTraces)
|
|
372
|
-
parts.push(`${Object.keys(proofTracesRecord).length} traces`);
|
|
373
|
-
intentSpinner.succeed(`Built Phase 3 data (${parts.join(", ")})`);
|
|
374
|
-
console.log(`\x1b[90m └─ Auth coverage: ${Math.round(coverageRaw.authCoverage * 100)}%\x1b[0m`);
|
|
375
|
-
}
|
|
376
|
-
// Create artifact
|
|
377
|
-
const artifact = createArtifact(findings, absoluteTarget, initialContext.fileIndex.allSourceFiles.length, options.repoName, {
|
|
378
|
-
filesScanned: initialContext.fileIndex.allSourceFiles.length,
|
|
379
|
-
scanDurationMs,
|
|
380
|
-
}, phase3Data);
|
|
381
|
-
// Validate artifact before writing
|
|
382
|
-
try {
|
|
383
|
-
validateArtifact(artifact);
|
|
384
|
-
}
|
|
385
|
-
catch (error) {
|
|
386
|
-
console.error("\x1b[31mError: Generated artifact failed validation\x1b[0m");
|
|
387
|
-
console.error(error);
|
|
388
|
-
return 1;
|
|
389
|
-
}
|
|
390
|
-
// Write output file(s)
|
|
391
|
-
const outputFiles = [];
|
|
392
|
-
if (format === "json" || format === "both") {
|
|
393
|
-
const jsonPath = getOutputPath(options.out, "json", absoluteTarget);
|
|
394
|
-
ensureDir(path.dirname(jsonPath));
|
|
395
|
-
writeFileSync(jsonPath, JSON.stringify(artifact, null, 2));
|
|
396
|
-
outputFiles.push(jsonPath);
|
|
397
|
-
}
|
|
398
|
-
if (format === "sarif" || format === "both") {
|
|
399
|
-
const sarifPath = getOutputPath(options.out, "sarif", absoluteTarget);
|
|
400
|
-
ensureDir(path.dirname(sarifPath));
|
|
401
|
-
const sarifLog = toSarif(artifact);
|
|
402
|
-
writeFileSync(sarifPath, sarifToJson(sarifLog));
|
|
403
|
-
outputFiles.push(sarifPath);
|
|
404
|
-
}
|
|
405
|
-
if (outputFiles.length > 0) {
|
|
406
|
-
console.log("\nArtifact(s) written to:");
|
|
407
|
-
for (const f of outputFiles) {
|
|
408
|
-
console.log(` ${normalizePath(f)}`);
|
|
409
|
-
}
|
|
410
|
-
}
|
|
411
|
-
// Print summary
|
|
412
|
-
printSummary(artifact, options);
|
|
413
|
-
// Check fail threshold
|
|
414
|
-
if (shouldFail(findings, failOn)) {
|
|
415
|
-
console.log(`\x1b[31mFailing: Found findings with severity >= ${failOn}\x1b[0m`);
|
|
416
|
-
return 1;
|
|
417
|
-
}
|
|
418
|
-
return 0;
|
|
419
|
-
}
|
|
420
|
-
/**
|
|
421
|
-
* Collect multiple --exclude values into an array
|
|
422
|
-
*/
|
|
423
|
-
function collectExcludes(value, previous) {
|
|
424
|
-
return previous.concat([value]);
|
|
425
|
-
}
|
|
426
|
-
/**
|
|
427
|
-
* Register scan command with commander
|
|
428
|
-
*/
|
|
429
|
-
export function registerScanCommand(program) {
|
|
430
|
-
program
|
|
431
|
-
.command("scan [target]")
|
|
432
|
-
.description("Scan a directory for security issues")
|
|
433
|
-
.option("-t, --target <path>", "Target directory to scan (alternative to positional argument)")
|
|
434
|
-
.option("-o, --out <path>", "Output file or directory path", path.join(DEFAULT_OUTPUT_DIR, DEFAULT_OUTPUT_FILE))
|
|
435
|
-
.option("-f, --format <format>", "Output format: json, sarif, or both", "json")
|
|
436
|
-
.option("--repo-name <name>", "Override repository name")
|
|
437
|
-
.option("--fail-on <threshold>", "Exit with non-zero if findings >= threshold (off, info, low, medium, high, critical)", "high")
|
|
438
|
-
.option("-e, --exclude <glob>", "Glob pattern to exclude (repeatable)", collectExcludes, [])
|
|
439
|
-
.option("--include-tests", "Include test files in scan (excluded by default)")
|
|
440
|
-
.option("--changed", "Only scan changed files (not implemented)")
|
|
441
|
-
.option("--emit-route-map", "Include route map in output (default: true)")
|
|
442
|
-
.option("--no-emit-route-map", "Exclude route map from output")
|
|
443
|
-
.option("--emit-intents", "Include intent claims in output (default: true)")
|
|
444
|
-
.option("--no-emit-intents", "Exclude intent claims from output")
|
|
445
|
-
.option("--emit-traces", "Include proof traces in output (default: true)")
|
|
446
|
-
.option("--no-emit-traces", "Exclude proof traces from output")
|
|
447
|
-
.addHelpText("after", `
|
|
448
|
-
Quickstart (no install):
|
|
449
|
-
$ npx vibecheck scan --fail-on off --out vibecheck-scan.json
|
|
450
|
-
$ pnpm dlx vibecheck scan --fail-on off --out vibecheck-scan.json
|
|
451
|
-
|
|
452
|
-
Examples:
|
|
453
|
-
$ vibecheck scan Scan current directory
|
|
454
|
-
$ vibecheck scan ./my-app Scan specific directory
|
|
455
|
-
$ vibecheck scan --target ./my-app Same as above (explicit target)
|
|
456
|
-
$ vibecheck scan --target ../myapp --out scan.json Scan another folder
|
|
457
|
-
$ vibecheck scan --format sarif Output in SARIF format
|
|
458
|
-
$ vibecheck scan --format both Output both JSON and SARIF
|
|
459
|
-
$ vibecheck scan -o ./reports Custom output directory
|
|
460
|
-
$ vibecheck scan --fail-on medium Fail on medium or higher
|
|
461
|
-
$ vibecheck scan --fail-on off Never fail based on findings
|
|
462
|
-
$ vibecheck scan -e "**/legacy/**" Exclude legacy directory
|
|
463
|
-
$ vibecheck scan -e "**/*.old.ts" -e "**/v1/**" Multiple excludes
|
|
464
|
-
$ vibecheck scan --include-tests Include test files
|
|
465
|
-
$ vibecheck scan --no-emit-intents Skip intent mining
|
|
466
|
-
$ vibecheck scan --no-emit-traces Skip proof trace building
|
|
467
|
-
|
|
468
|
-
Windows-safe output paths:
|
|
469
|
-
$ vibecheck scan --out vibecheck-scan.json Relative path (recommended)
|
|
470
|
-
$ vibecheck scan --out ./reports/scan.json Subdirectory path
|
|
471
|
-
|
|
472
|
-
Default excludes:
|
|
473
|
-
node_modules, dist, .git, build, .next, coverage, .turbo, .cache,
|
|
474
|
-
__tests__, *.test.*, *.spec.*, test/, tests/, fixtures/, __mocks__,
|
|
475
|
-
cypress/, e2e/, *.stories.*
|
|
476
|
-
`)
|
|
477
|
-
.action(async (positionalTarget, cmdOptions) => {
|
|
478
|
-
// Prefer --target over positional, fall back to cwd
|
|
479
|
-
const targetDir = cmdOptions.target
|
|
480
|
-
?? positionalTarget
|
|
481
|
-
?? process.cwd();
|
|
482
|
-
const options = {
|
|
483
|
-
out: cmdOptions.out,
|
|
484
|
-
format: cmdOptions.format,
|
|
485
|
-
repoName: cmdOptions.repoName,
|
|
486
|
-
failOn: cmdOptions.failOn,
|
|
487
|
-
changed: Boolean(cmdOptions.changed),
|
|
488
|
-
emitRouteMap: cmdOptions.emitRouteMap !== false, // default true
|
|
489
|
-
emitIntents: cmdOptions.emitIntents !== false, // default true
|
|
490
|
-
emitTraces: cmdOptions.emitTraces !== false, // default true
|
|
491
|
-
exclude: cmdOptions.exclude,
|
|
492
|
-
includeTests: Boolean(cmdOptions.includeTests),
|
|
493
|
-
};
|
|
494
|
-
const exitCode = await executeScan(targetDir, options);
|
|
495
|
-
process.exit(exitCode);
|
|
496
|
-
});
|
|
497
|
-
}
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import type { Command } from "commander";
|
|
2
|
-
/**
|
|
3
|
-
* Initialize a new waivers file
|
|
4
|
-
*/
|
|
5
|
-
export declare function executeWaiversInit(filepath: string, force: boolean): number;
|
|
6
|
-
/**
|
|
7
|
-
* Add a waiver to the file
|
|
8
|
-
*/
|
|
9
|
-
export declare function executeWaiversAdd(filepath: string, options: {
|
|
10
|
-
fingerprint?: string;
|
|
11
|
-
ruleId?: string;
|
|
12
|
-
pathPattern?: string;
|
|
13
|
-
reason: string;
|
|
14
|
-
createdBy: string;
|
|
15
|
-
expiresAt?: string;
|
|
16
|
-
ticketRef?: string;
|
|
17
|
-
}): number;
|
|
18
|
-
/**
|
|
19
|
-
* List waivers in the file
|
|
20
|
-
*/
|
|
21
|
-
export declare function executeWaiversList(filepath: string, verbose: boolean): number;
|
|
22
|
-
/**
|
|
23
|
-
* Remove a waiver from the file
|
|
24
|
-
*/
|
|
25
|
-
export declare function executeWaiversRemove(filepath: string, waiverId: string): number;
|
|
26
|
-
/**
|
|
27
|
-
* Register waivers command with subcommands
|
|
28
|
-
*/
|
|
29
|
-
export declare function registerWaiversCommand(program: Command): void;
|
|
30
|
-
//# sourceMappingURL=waivers.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"waivers.d.ts","sourceRoot":"","sources":["../../src/commands/waivers.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAqDzC;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,MAAM,CAc3E;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE;IACP,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,GACA,MAAM,CAsER;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,MAAM,CA0C7E;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAe/E;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAkF7D"}
|