@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 @@
|
|
|
1
|
+
{"version":3,"file":"cli.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/cli.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { executeScan } from "../commands/scan.js";
|
|
3
|
+
import { executeExplain } from "../commands/explain.js";
|
|
4
|
+
import { ScanArtifactSchema } from "@vibecheck/schema";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import fs from "node:fs";
|
|
7
|
+
import os from "node:os";
|
|
8
|
+
describe("scan command", () => {
|
|
9
|
+
it("produces valid artifact", async () => {
|
|
10
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "vibecheck-test-"));
|
|
11
|
+
const outputPath = path.join(tmpDir, "output", "scan.json");
|
|
12
|
+
// Create a simple test file
|
|
13
|
+
fs.writeFileSync(path.join(tmpDir, "app.ts"), `const url = process.env.DATABASE_URL;`);
|
|
14
|
+
try {
|
|
15
|
+
const options = {
|
|
16
|
+
out: outputPath,
|
|
17
|
+
format: "json",
|
|
18
|
+
failOn: "critical", // Don't fail on high so we can test output
|
|
19
|
+
changed: false,
|
|
20
|
+
};
|
|
21
|
+
// Capture console output
|
|
22
|
+
const originalLog = console.log;
|
|
23
|
+
const logs = [];
|
|
24
|
+
console.log = (...args) => logs.push(args.join(" "));
|
|
25
|
+
await executeScan(tmpDir, options);
|
|
26
|
+
console.log = originalLog;
|
|
27
|
+
// Read and validate output
|
|
28
|
+
const content = fs.readFileSync(outputPath, "utf-8");
|
|
29
|
+
const artifact = JSON.parse(content);
|
|
30
|
+
const result = ScanArtifactSchema.safeParse(artifact);
|
|
31
|
+
expect(result.success).toBe(true);
|
|
32
|
+
if (result.success) {
|
|
33
|
+
expect(result.data.artifactVersion).toBe("0.1");
|
|
34
|
+
expect(result.data.tool.name).toBe("vibecheck");
|
|
35
|
+
expect(result.data.findings.length).toBeGreaterThan(0);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
finally {
|
|
39
|
+
fs.rmSync(tmpDir, { recursive: true });
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
it("includes repo info when available", async () => {
|
|
43
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "vibecheck-test-"));
|
|
44
|
+
const outputPath = path.join(tmpDir, "scan.json");
|
|
45
|
+
fs.writeFileSync(path.join(tmpDir, "app.ts"), `const x = 1;`);
|
|
46
|
+
try {
|
|
47
|
+
const options = {
|
|
48
|
+
out: outputPath,
|
|
49
|
+
format: "json",
|
|
50
|
+
repoName: "test-repo",
|
|
51
|
+
failOn: "critical",
|
|
52
|
+
changed: false,
|
|
53
|
+
};
|
|
54
|
+
const originalLog = console.log;
|
|
55
|
+
console.log = () => { };
|
|
56
|
+
await executeScan(tmpDir, options);
|
|
57
|
+
console.log = originalLog;
|
|
58
|
+
const artifact = JSON.parse(fs.readFileSync(outputPath, "utf-8"));
|
|
59
|
+
expect(artifact.repo.name).toBe("test-repo");
|
|
60
|
+
}
|
|
61
|
+
finally {
|
|
62
|
+
fs.rmSync(tmpDir, { recursive: true });
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
it("includes metrics in output", async () => {
|
|
66
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "vibecheck-test-"));
|
|
67
|
+
const outputPath = path.join(tmpDir, "scan.json");
|
|
68
|
+
fs.writeFileSync(path.join(tmpDir, "app.ts"), `const x = 1;`);
|
|
69
|
+
try {
|
|
70
|
+
const options = {
|
|
71
|
+
out: outputPath,
|
|
72
|
+
format: "json",
|
|
73
|
+
failOn: "critical",
|
|
74
|
+
changed: false,
|
|
75
|
+
};
|
|
76
|
+
const originalLog = console.log;
|
|
77
|
+
console.log = () => { };
|
|
78
|
+
await executeScan(tmpDir, options);
|
|
79
|
+
console.log = originalLog;
|
|
80
|
+
const artifact = JSON.parse(fs.readFileSync(outputPath, "utf-8"));
|
|
81
|
+
expect(artifact.metrics).toBeDefined();
|
|
82
|
+
expect(artifact.metrics.filesScanned).toBe(1);
|
|
83
|
+
expect(artifact.metrics.scanDurationMs).toBeGreaterThanOrEqual(0);
|
|
84
|
+
}
|
|
85
|
+
finally {
|
|
86
|
+
fs.rmSync(tmpDir, { recursive: true });
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
it("exits with non-zero when findings exceed threshold", async () => {
|
|
90
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "vibecheck-test-"));
|
|
91
|
+
const outputPath = path.join(tmpDir, "scan.json");
|
|
92
|
+
// Create file with high severity finding
|
|
93
|
+
fs.writeFileSync(path.join(tmpDir, "app.ts"), `const key = process.env.API_SECRET_KEY;`);
|
|
94
|
+
try {
|
|
95
|
+
const options = {
|
|
96
|
+
out: outputPath,
|
|
97
|
+
format: "json",
|
|
98
|
+
failOn: "high",
|
|
99
|
+
changed: false,
|
|
100
|
+
};
|
|
101
|
+
const originalLog = console.log;
|
|
102
|
+
console.log = () => { };
|
|
103
|
+
const exitCode = await executeScan(tmpDir, options);
|
|
104
|
+
console.log = originalLog;
|
|
105
|
+
expect(exitCode).toBe(1);
|
|
106
|
+
}
|
|
107
|
+
finally {
|
|
108
|
+
fs.rmSync(tmpDir, { recursive: true });
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
describe("explain command", () => {
|
|
113
|
+
it("reads and displays artifact", async () => {
|
|
114
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "vibecheck-test-"));
|
|
115
|
+
const artifactPath = path.join(tmpDir, "scan.json");
|
|
116
|
+
const artifact = {
|
|
117
|
+
artifactVersion: "0.1",
|
|
118
|
+
generatedAt: new Date().toISOString(),
|
|
119
|
+
tool: { name: "vibecheck", version: "0.0.1" },
|
|
120
|
+
summary: {
|
|
121
|
+
totalFindings: 1,
|
|
122
|
+
bySeverity: { critical: 0, high: 1, medium: 0, low: 0, info: 0 },
|
|
123
|
+
byCategory: {
|
|
124
|
+
auth: 1,
|
|
125
|
+
validation: 0,
|
|
126
|
+
middleware: 0,
|
|
127
|
+
secrets: 0,
|
|
128
|
+
injection: 0,
|
|
129
|
+
privacy: 0,
|
|
130
|
+
config: 0,
|
|
131
|
+
other: 0,
|
|
132
|
+
},
|
|
133
|
+
},
|
|
134
|
+
findings: [
|
|
135
|
+
{
|
|
136
|
+
id: "test-001",
|
|
137
|
+
severity: "high",
|
|
138
|
+
confidence: 0.9,
|
|
139
|
+
category: "auth",
|
|
140
|
+
ruleId: "VC-AUTH-001",
|
|
141
|
+
title: "Test finding",
|
|
142
|
+
description: "Test description",
|
|
143
|
+
evidence: [
|
|
144
|
+
{ file: "test.ts", startLine: 1, endLine: 1, label: "Test label" },
|
|
145
|
+
],
|
|
146
|
+
remediation: { recommendedFix: "Fix it" },
|
|
147
|
+
fingerprint: "abc123",
|
|
148
|
+
},
|
|
149
|
+
],
|
|
150
|
+
};
|
|
151
|
+
fs.writeFileSync(artifactPath, JSON.stringify(artifact));
|
|
152
|
+
try {
|
|
153
|
+
const originalLog = console.log;
|
|
154
|
+
const logs = [];
|
|
155
|
+
console.log = (...args) => logs.push(args.join(" "));
|
|
156
|
+
const exitCode = await executeExplain(artifactPath, { limit: 5 });
|
|
157
|
+
console.log = originalLog;
|
|
158
|
+
expect(exitCode).toBe(0);
|
|
159
|
+
expect(logs.some((l) => l.includes("VIBECHECK SCAN REPORT"))).toBe(true);
|
|
160
|
+
expect(logs.some((l) => l.includes("Test finding"))).toBe(true);
|
|
161
|
+
}
|
|
162
|
+
finally {
|
|
163
|
+
fs.rmSync(tmpDir, { recursive: true });
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
it("returns error for invalid artifact", async () => {
|
|
167
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "vibecheck-test-"));
|
|
168
|
+
const artifactPath = path.join(tmpDir, "invalid.json");
|
|
169
|
+
fs.writeFileSync(artifactPath, JSON.stringify({ invalid: true }));
|
|
170
|
+
try {
|
|
171
|
+
const originalLog = console.log;
|
|
172
|
+
const originalError = console.error;
|
|
173
|
+
console.log = () => { };
|
|
174
|
+
console.error = () => { };
|
|
175
|
+
const exitCode = await executeExplain(artifactPath, { limit: 5 });
|
|
176
|
+
console.log = originalLog;
|
|
177
|
+
console.error = originalError;
|
|
178
|
+
expect(exitCode).toBe(1);
|
|
179
|
+
}
|
|
180
|
+
finally {
|
|
181
|
+
fs.rmSync(tmpDir, { recursive: true });
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
describe("artifact output shape", () => {
|
|
186
|
+
it("has correct structure", async () => {
|
|
187
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "vibecheck-test-"));
|
|
188
|
+
const outputPath = path.join(tmpDir, "scan.json");
|
|
189
|
+
fs.writeFileSync(path.join(tmpDir, "app.ts"), `import { z } from "zod";
|
|
190
|
+
// unused
|
|
191
|
+
`);
|
|
192
|
+
try {
|
|
193
|
+
const options = {
|
|
194
|
+
out: outputPath,
|
|
195
|
+
format: "json",
|
|
196
|
+
failOn: "critical",
|
|
197
|
+
changed: false,
|
|
198
|
+
};
|
|
199
|
+
const originalLog = console.log;
|
|
200
|
+
console.log = () => { };
|
|
201
|
+
await executeScan(tmpDir, options);
|
|
202
|
+
console.log = originalLog;
|
|
203
|
+
const artifact = JSON.parse(fs.readFileSync(outputPath, "utf-8"));
|
|
204
|
+
// Check top-level structure
|
|
205
|
+
expect(artifact).toHaveProperty("artifactVersion", "0.1");
|
|
206
|
+
expect(artifact).toHaveProperty("generatedAt");
|
|
207
|
+
expect(artifact).toHaveProperty("tool");
|
|
208
|
+
expect(artifact).toHaveProperty("summary");
|
|
209
|
+
expect(artifact).toHaveProperty("findings");
|
|
210
|
+
// Check tool info
|
|
211
|
+
expect(artifact.tool).toHaveProperty("name", "vibecheck");
|
|
212
|
+
expect(artifact.tool).toHaveProperty("version");
|
|
213
|
+
// Check summary structure
|
|
214
|
+
expect(artifact.summary).toHaveProperty("totalFindings");
|
|
215
|
+
expect(artifact.summary).toHaveProperty("bySeverity");
|
|
216
|
+
expect(artifact.summary).toHaveProperty("byCategory");
|
|
217
|
+
// Check finding structure (if any)
|
|
218
|
+
if (artifact.findings.length > 0) {
|
|
219
|
+
const finding = artifact.findings[0];
|
|
220
|
+
expect(finding).toHaveProperty("id");
|
|
221
|
+
expect(finding).toHaveProperty("severity");
|
|
222
|
+
expect(finding).toHaveProperty("confidence");
|
|
223
|
+
expect(finding).toHaveProperty("category");
|
|
224
|
+
expect(finding).toHaveProperty("ruleId");
|
|
225
|
+
expect(finding).toHaveProperty("title");
|
|
226
|
+
expect(finding).toHaveProperty("description");
|
|
227
|
+
expect(finding).toHaveProperty("evidence");
|
|
228
|
+
expect(finding).toHaveProperty("remediation");
|
|
229
|
+
expect(finding).toHaveProperty("fingerprint");
|
|
230
|
+
// Check ruleId format
|
|
231
|
+
expect(finding.ruleId).toMatch(/^VC-[A-Z]+-\d{3}$/);
|
|
232
|
+
// Check evidence structure
|
|
233
|
+
expect(finding.evidence[0]).toHaveProperty("file");
|
|
234
|
+
expect(finding.evidence[0]).toHaveProperty("startLine");
|
|
235
|
+
expect(finding.evidence[0]).toHaveProperty("endLine");
|
|
236
|
+
expect(finding.evidence[0]).toHaveProperty("label");
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
finally {
|
|
240
|
+
fs.rmSync(tmpDir, { recursive: true });
|
|
241
|
+
}
|
|
242
|
+
});
|
|
243
|
+
});
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { prisma } from "@/lib/prisma";
|
|
2
|
+
import { getServerSession } from "next-auth";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
const userSchema = z.object({
|
|
5
|
+
name: z.string(),
|
|
6
|
+
email: z.string().email(),
|
|
7
|
+
});
|
|
8
|
+
// Safe: Has auth check
|
|
9
|
+
export async function POST(request) {
|
|
10
|
+
const session = await getServerSession();
|
|
11
|
+
if (!session) {
|
|
12
|
+
return Response.json({ error: "Unauthorized" }, { status: 401 });
|
|
13
|
+
}
|
|
14
|
+
const body = await request.json();
|
|
15
|
+
// Safe: Validation result is used
|
|
16
|
+
const validatedData = userSchema.parse(body);
|
|
17
|
+
const user = await prisma.user.create({
|
|
18
|
+
data: validatedData,
|
|
19
|
+
});
|
|
20
|
+
// Safe: Not logging sensitive data
|
|
21
|
+
console.log("Created user:", { id: user.id, timestamp: Date.now() });
|
|
22
|
+
return Response.json(user);
|
|
23
|
+
}
|
|
24
|
+
// Safe: Has auth check
|
|
25
|
+
export async function DELETE(request) {
|
|
26
|
+
const session = await getServerSession();
|
|
27
|
+
if (!session) {
|
|
28
|
+
return Response.json({ error: "Unauthorized" }, { status: 401 });
|
|
29
|
+
}
|
|
30
|
+
const { searchParams } = new URL(request.url);
|
|
31
|
+
const id = searchParams.get("id");
|
|
32
|
+
await prisma.user.delete({
|
|
33
|
+
where: { id },
|
|
34
|
+
});
|
|
35
|
+
return Response.json({ success: true });
|
|
36
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { prisma } from "@/lib/prisma";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
const userSchema = z.object({
|
|
4
|
+
name: z.string(),
|
|
5
|
+
email: z.string().email(),
|
|
6
|
+
});
|
|
7
|
+
// VC-AUTH-001: No auth check before database write
|
|
8
|
+
export async function POST(request) {
|
|
9
|
+
const body = await request.json();
|
|
10
|
+
// VC-VAL-001: Validation called but result ignored
|
|
11
|
+
userSchema.parse(body);
|
|
12
|
+
// Using raw body instead of validated data
|
|
13
|
+
const user = await prisma.user.create({
|
|
14
|
+
data: body,
|
|
15
|
+
});
|
|
16
|
+
// VC-PRIV-001: Logging sensitive data
|
|
17
|
+
console.log("Created user:", { email: body.email, password: body.password });
|
|
18
|
+
return Response.json(user);
|
|
19
|
+
}
|
|
20
|
+
// VC-AUTH-001: Critical - delete without auth
|
|
21
|
+
export async function DELETE(request) {
|
|
22
|
+
const { searchParams } = new URL(request.url);
|
|
23
|
+
const id = searchParams.get("id");
|
|
24
|
+
await prisma.user.delete({
|
|
25
|
+
where: { id },
|
|
26
|
+
});
|
|
27
|
+
return Response.json({ success: true });
|
|
28
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../../../../src/__tests__/fixtures/vulnerable-app/lib/config.ts"],"names":[],"mappings":"AACA,eAAO,MAAM,UAAU,QAA6C,CAAC;AAGrE,eAAO,MAAM,cAAc,QAA8C,CAAC;AAG1E,eAAO,MAAM,OAAO,QAAiD,CAAC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// VC-CONFIG-002: Insecure default for critical secret
|
|
2
|
+
export const JWT_SECRET = process.env.JWT_SECRET || "dev-secret-123";
|
|
3
|
+
// VC-CONFIG-002: Another insecure default
|
|
4
|
+
export const SESSION_SECRET = process.env.SESSION_SECRET ?? "session-dev";
|
|
5
|
+
// Safe - not a secret
|
|
6
|
+
export const API_URL = process.env.API_URL || "http://localhost:3000";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"env-config.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/scanners/env-config.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { parseEnvExample, findEnvUsages, scanEnvConfig, } from "../../scanners/env-config.js";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import fs from "node:fs";
|
|
5
|
+
import os from "node:os";
|
|
6
|
+
describe("parseEnvExample", () => {
|
|
7
|
+
it("parses simple env file", () => {
|
|
8
|
+
const content = `
|
|
9
|
+
DATABASE_URL=
|
|
10
|
+
API_KEY=your-key-here
|
|
11
|
+
SECRET_TOKEN=
|
|
12
|
+
`;
|
|
13
|
+
const vars = parseEnvExample(content);
|
|
14
|
+
expect(vars.has("DATABASE_URL")).toBe(true);
|
|
15
|
+
expect(vars.has("API_KEY")).toBe(true);
|
|
16
|
+
expect(vars.has("SECRET_TOKEN")).toBe(true);
|
|
17
|
+
});
|
|
18
|
+
it("ignores comments", () => {
|
|
19
|
+
const content = `
|
|
20
|
+
# This is a comment
|
|
21
|
+
DATABASE_URL=
|
|
22
|
+
# API_KEY=should-be-ignored
|
|
23
|
+
`;
|
|
24
|
+
const vars = parseEnvExample(content);
|
|
25
|
+
expect(vars.has("DATABASE_URL")).toBe(true);
|
|
26
|
+
expect(vars.has("API_KEY")).toBe(false);
|
|
27
|
+
});
|
|
28
|
+
it("handles empty lines", () => {
|
|
29
|
+
const content = `
|
|
30
|
+
|
|
31
|
+
DATABASE_URL=
|
|
32
|
+
|
|
33
|
+
API_KEY=
|
|
34
|
+
|
|
35
|
+
`;
|
|
36
|
+
const vars = parseEnvExample(content);
|
|
37
|
+
expect(vars.size).toBe(2);
|
|
38
|
+
});
|
|
39
|
+
it("handles vars without values", () => {
|
|
40
|
+
const content = `DATABASE_URL`;
|
|
41
|
+
const vars = parseEnvExample(content);
|
|
42
|
+
expect(vars.has("DATABASE_URL")).toBe(true);
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
describe("findEnvUsages", () => {
|
|
46
|
+
it("finds process.env.VAR usage", () => {
|
|
47
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "vibecheck-test-"));
|
|
48
|
+
const filePath = path.join(tmpDir, "test.ts");
|
|
49
|
+
fs.writeFileSync(filePath, `
|
|
50
|
+
const url = process.env.DATABASE_URL;
|
|
51
|
+
const key = process.env.API_KEY;
|
|
52
|
+
`);
|
|
53
|
+
try {
|
|
54
|
+
const usages = findEnvUsages(["test.ts"], tmpDir);
|
|
55
|
+
expect(usages).toHaveLength(2);
|
|
56
|
+
expect(usages[0].name).toBe("DATABASE_URL");
|
|
57
|
+
expect(usages[1].name).toBe("API_KEY");
|
|
58
|
+
}
|
|
59
|
+
finally {
|
|
60
|
+
fs.rmSync(tmpDir, { recursive: true });
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
it("finds bracket notation usage", () => {
|
|
64
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "vibecheck-test-"));
|
|
65
|
+
const filePath = path.join(tmpDir, "test.ts");
|
|
66
|
+
fs.writeFileSync(filePath, `const url = process.env["DATABASE_URL"];`);
|
|
67
|
+
try {
|
|
68
|
+
const usages = findEnvUsages(["test.ts"], tmpDir);
|
|
69
|
+
expect(usages).toHaveLength(1);
|
|
70
|
+
expect(usages[0].name).toBe("DATABASE_URL");
|
|
71
|
+
}
|
|
72
|
+
finally {
|
|
73
|
+
fs.rmSync(tmpDir, { recursive: true });
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
it("returns correct line numbers", () => {
|
|
77
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "vibecheck-test-"));
|
|
78
|
+
const filePath = path.join(tmpDir, "test.ts");
|
|
79
|
+
fs.writeFileSync(filePath, `// line 1
|
|
80
|
+
// line 2
|
|
81
|
+
const x = process.env.MY_VAR; // line 3
|
|
82
|
+
`);
|
|
83
|
+
try {
|
|
84
|
+
const usages = findEnvUsages(["test.ts"], tmpDir);
|
|
85
|
+
expect(usages[0].line).toBe(3);
|
|
86
|
+
}
|
|
87
|
+
finally {
|
|
88
|
+
fs.rmSync(tmpDir, { recursive: true });
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
describe("scanEnvConfig", () => {
|
|
93
|
+
it("creates findings for undocumented env vars", async () => {
|
|
94
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "vibecheck-test-"));
|
|
95
|
+
const filePath = path.join(tmpDir, "app.ts");
|
|
96
|
+
fs.writeFileSync(filePath, `const url = process.env.DATABASE_URL;`);
|
|
97
|
+
try {
|
|
98
|
+
const context = {
|
|
99
|
+
targetDir: tmpDir,
|
|
100
|
+
sourceFiles: ["app.ts"],
|
|
101
|
+
};
|
|
102
|
+
const findings = await scanEnvConfig(context);
|
|
103
|
+
expect(findings).toHaveLength(1);
|
|
104
|
+
expect(findings[0].ruleId).toBe("VC-CONFIG-001");
|
|
105
|
+
expect(findings[0].title).toContain("DATABASE_URL");
|
|
106
|
+
}
|
|
107
|
+
finally {
|
|
108
|
+
fs.rmSync(tmpDir, { recursive: true });
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
it("does not create findings for documented vars", async () => {
|
|
112
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "vibecheck-test-"));
|
|
113
|
+
fs.writeFileSync(path.join(tmpDir, "app.ts"), `const url = process.env.DATABASE_URL;`);
|
|
114
|
+
fs.writeFileSync(path.join(tmpDir, ".env.example"), "DATABASE_URL=");
|
|
115
|
+
try {
|
|
116
|
+
const context = {
|
|
117
|
+
targetDir: tmpDir,
|
|
118
|
+
sourceFiles: ["app.ts"],
|
|
119
|
+
};
|
|
120
|
+
const findings = await scanEnvConfig(context);
|
|
121
|
+
expect(findings).toHaveLength(0);
|
|
122
|
+
}
|
|
123
|
+
finally {
|
|
124
|
+
fs.rmSync(tmpDir, { recursive: true });
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
it("marks SECRET-like vars as high severity", async () => {
|
|
128
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "vibecheck-test-"));
|
|
129
|
+
fs.writeFileSync(path.join(tmpDir, "app.ts"), `const key = process.env.API_SECRET_KEY;`);
|
|
130
|
+
try {
|
|
131
|
+
const context = {
|
|
132
|
+
targetDir: tmpDir,
|
|
133
|
+
sourceFiles: ["app.ts"],
|
|
134
|
+
};
|
|
135
|
+
const findings = await scanEnvConfig(context);
|
|
136
|
+
expect(findings[0].severity).toBe("high");
|
|
137
|
+
}
|
|
138
|
+
finally {
|
|
139
|
+
fs.rmSync(tmpDir, { recursive: true });
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nextjs-middleware.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/scanners/nextjs-middleware.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { parseMatcherConfig, matcherCoversApi, scanNextjsMiddleware, } from "../../scanners/nextjs-middleware.js";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import fs from "node:fs";
|
|
5
|
+
import os from "node:os";
|
|
6
|
+
describe("parseMatcherConfig", () => {
|
|
7
|
+
it("parses single string matcher", () => {
|
|
8
|
+
const content = `
|
|
9
|
+
export const config = {
|
|
10
|
+
matcher: '/dashboard/:path*'
|
|
11
|
+
};
|
|
12
|
+
`;
|
|
13
|
+
const matchers = parseMatcherConfig(content);
|
|
14
|
+
expect(matchers).toEqual(["/dashboard/:path*"]);
|
|
15
|
+
});
|
|
16
|
+
it("parses array matcher", () => {
|
|
17
|
+
const content = `
|
|
18
|
+
export const config = {
|
|
19
|
+
matcher: ['/dashboard/:path*', '/admin/:path*']
|
|
20
|
+
};
|
|
21
|
+
`;
|
|
22
|
+
const matchers = parseMatcherConfig(content);
|
|
23
|
+
expect(matchers).toEqual(["/dashboard/:path*", "/admin/:path*"]);
|
|
24
|
+
});
|
|
25
|
+
it("returns null when no config export", () => {
|
|
26
|
+
const content = `
|
|
27
|
+
export function middleware(request) {
|
|
28
|
+
return NextResponse.next();
|
|
29
|
+
}
|
|
30
|
+
`;
|
|
31
|
+
const matchers = parseMatcherConfig(content);
|
|
32
|
+
expect(matchers).toBeNull();
|
|
33
|
+
});
|
|
34
|
+
it("parses complex negation pattern", () => {
|
|
35
|
+
const content = `
|
|
36
|
+
export const config = {
|
|
37
|
+
matcher: '/((?!_next/static|_next/image|favicon.ico).*)'
|
|
38
|
+
};
|
|
39
|
+
`;
|
|
40
|
+
const matchers = parseMatcherConfig(content);
|
|
41
|
+
expect(matchers).toEqual(["/((?!_next/static|_next/image|favicon.ico).*)"]);
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
describe("matcherCoversApi", () => {
|
|
45
|
+
it("returns true for direct /api match", () => {
|
|
46
|
+
expect(matcherCoversApi(["/api"])).toBe(true);
|
|
47
|
+
expect(matcherCoversApi(["/api/:path*"])).toBe(true);
|
|
48
|
+
});
|
|
49
|
+
it("returns true for patterns starting with /api", () => {
|
|
50
|
+
expect(matcherCoversApi(["/api/users/:path*"])).toBe(true);
|
|
51
|
+
});
|
|
52
|
+
it("returns true for catch-all patterns", () => {
|
|
53
|
+
expect(matcherCoversApi(["/:path*"])).toBe(true);
|
|
54
|
+
});
|
|
55
|
+
it("returns true for negation patterns not excluding api", () => {
|
|
56
|
+
expect(matcherCoversApi(["/((?!_next/static|_next/image|favicon.ico).*)"])).toBe(true);
|
|
57
|
+
});
|
|
58
|
+
it("returns false for dashboard-only patterns", () => {
|
|
59
|
+
expect(matcherCoversApi(["/dashboard/:path*"])).toBe(false);
|
|
60
|
+
});
|
|
61
|
+
it("returns false for unrelated patterns", () => {
|
|
62
|
+
expect(matcherCoversApi(["/admin", "/profile"])).toBe(false);
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
describe("scanNextjsMiddleware", () => {
|
|
66
|
+
function createNextProject(tmpDir, options = {}) {
|
|
67
|
+
// Create package.json
|
|
68
|
+
const pkg = {
|
|
69
|
+
name: "test-app",
|
|
70
|
+
dependencies: {
|
|
71
|
+
next: "14.0.0",
|
|
72
|
+
...(options.hasNextAuth && { "next-auth": "4.0.0" }),
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
fs.writeFileSync(path.join(tmpDir, "package.json"), JSON.stringify(pkg));
|
|
76
|
+
// Create middleware if specified
|
|
77
|
+
if (options.hasMiddleware && options.middlewareContent) {
|
|
78
|
+
fs.writeFileSync(path.join(tmpDir, "middleware.ts"), options.middlewareContent);
|
|
79
|
+
}
|
|
80
|
+
// Create API routes if specified
|
|
81
|
+
if (options.hasApiRoutes) {
|
|
82
|
+
const apiDir = path.join(tmpDir, "app", "api", "users");
|
|
83
|
+
fs.mkdirSync(apiDir, { recursive: true });
|
|
84
|
+
fs.writeFileSync(path.join(apiDir, "route.ts"), `export async function GET() { return Response.json({}); }`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
it("returns no findings for non-Next.js project", async () => {
|
|
88
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "vibecheck-test-"));
|
|
89
|
+
try {
|
|
90
|
+
fs.writeFileSync(path.join(tmpDir, "package.json"), JSON.stringify({ name: "test" }));
|
|
91
|
+
const context = {
|
|
92
|
+
targetDir: tmpDir,
|
|
93
|
+
sourceFiles: [],
|
|
94
|
+
};
|
|
95
|
+
const findings = await scanNextjsMiddleware(context);
|
|
96
|
+
expect(findings).toHaveLength(0);
|
|
97
|
+
}
|
|
98
|
+
finally {
|
|
99
|
+
fs.rmSync(tmpDir, { recursive: true });
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
it("returns no findings when no API routes exist", async () => {
|
|
103
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "vibecheck-test-"));
|
|
104
|
+
try {
|
|
105
|
+
createNextProject(tmpDir, { hasApiRoutes: false });
|
|
106
|
+
const context = {
|
|
107
|
+
targetDir: tmpDir,
|
|
108
|
+
sourceFiles: [],
|
|
109
|
+
};
|
|
110
|
+
const findings = await scanNextjsMiddleware(context);
|
|
111
|
+
expect(findings).toHaveLength(0);
|
|
112
|
+
}
|
|
113
|
+
finally {
|
|
114
|
+
fs.rmSync(tmpDir, { recursive: true });
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
it("finds missing middleware with next-auth", async () => {
|
|
118
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "vibecheck-test-"));
|
|
119
|
+
try {
|
|
120
|
+
createNextProject(tmpDir, {
|
|
121
|
+
hasNextAuth: true,
|
|
122
|
+
hasApiRoutes: true,
|
|
123
|
+
});
|
|
124
|
+
const context = {
|
|
125
|
+
targetDir: tmpDir,
|
|
126
|
+
sourceFiles: [],
|
|
127
|
+
};
|
|
128
|
+
const findings = await scanNextjsMiddleware(context);
|
|
129
|
+
expect(findings).toHaveLength(1);
|
|
130
|
+
expect(findings[0].ruleId).toBe("VC-AUTH-INFO-001");
|
|
131
|
+
expect(findings[0].severity).toBe("medium");
|
|
132
|
+
}
|
|
133
|
+
finally {
|
|
134
|
+
fs.rmSync(tmpDir, { recursive: true });
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
it("finds middleware not covering API routes", async () => {
|
|
138
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "vibecheck-test-"));
|
|
139
|
+
try {
|
|
140
|
+
createNextProject(tmpDir, {
|
|
141
|
+
hasMiddleware: true,
|
|
142
|
+
middlewareContent: `
|
|
143
|
+
export function middleware(request) {
|
|
144
|
+
return NextResponse.next();
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export const config = {
|
|
148
|
+
matcher: '/dashboard/:path*'
|
|
149
|
+
};
|
|
150
|
+
`,
|
|
151
|
+
hasApiRoutes: true,
|
|
152
|
+
});
|
|
153
|
+
const context = {
|
|
154
|
+
targetDir: tmpDir,
|
|
155
|
+
sourceFiles: [],
|
|
156
|
+
};
|
|
157
|
+
const findings = await scanNextjsMiddleware(context);
|
|
158
|
+
expect(findings).toHaveLength(1);
|
|
159
|
+
expect(findings[0].ruleId).toBe("VC-MW-001");
|
|
160
|
+
expect(findings[0].severity).toBe("high");
|
|
161
|
+
}
|
|
162
|
+
finally {
|
|
163
|
+
fs.rmSync(tmpDir, { recursive: true });
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
it("returns no findings when middleware covers API", async () => {
|
|
167
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "vibecheck-test-"));
|
|
168
|
+
try {
|
|
169
|
+
createNextProject(tmpDir, {
|
|
170
|
+
hasMiddleware: true,
|
|
171
|
+
middlewareContent: `
|
|
172
|
+
export function middleware(request) {
|
|
173
|
+
return NextResponse.next();
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export const config = {
|
|
177
|
+
matcher: ['/dashboard/:path*', '/api/:path*']
|
|
178
|
+
};
|
|
179
|
+
`,
|
|
180
|
+
hasApiRoutes: true,
|
|
181
|
+
});
|
|
182
|
+
const context = {
|
|
183
|
+
targetDir: tmpDir,
|
|
184
|
+
sourceFiles: [],
|
|
185
|
+
};
|
|
186
|
+
const findings = await scanNextjsMiddleware(context);
|
|
187
|
+
expect(findings).toHaveLength(0);
|
|
188
|
+
}
|
|
189
|
+
finally {
|
|
190
|
+
fs.rmSync(tmpDir, { recursive: true });
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
});
|