@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
|
@@ -1,203 +0,0 @@
|
|
|
1
|
-
import { readFileSync, resolvePath } from "../../utils/file-utils.js";
|
|
2
|
-
import { generateFingerprint, generateFindingId } from "../../utils/fingerprint.js";
|
|
3
|
-
const RULE_MW_001 = "VC-MW-001";
|
|
4
|
-
const RULE_AUTH_INFO_001 = "VC-AUTH-INFO-001";
|
|
5
|
-
/**
|
|
6
|
-
* Parse matcher config from middleware file
|
|
7
|
-
*
|
|
8
|
-
* Looks for patterns like:
|
|
9
|
-
* - export const config = { matcher: '/api/:path*' }
|
|
10
|
-
* - export const config = { matcher: ['/api/:path*', '/admin/:path*'] }
|
|
11
|
-
*/
|
|
12
|
-
export function parseMatcherConfig(content) {
|
|
13
|
-
// Look for config export with matcher
|
|
14
|
-
const configMatch = content.match(/export\s+const\s+config\s*=\s*\{[^}]*matcher\s*:\s*([^}]+)\}/s);
|
|
15
|
-
if (!configMatch) {
|
|
16
|
-
return null; // No config export found
|
|
17
|
-
}
|
|
18
|
-
const matcherPart = configMatch[1];
|
|
19
|
-
// Check for array matcher: ['/path1', '/path2']
|
|
20
|
-
const arrayMatch = matcherPart.match(/\[([^\]]+)\]/);
|
|
21
|
-
if (arrayMatch) {
|
|
22
|
-
const items = arrayMatch[1];
|
|
23
|
-
const paths = [];
|
|
24
|
-
// Extract string literals
|
|
25
|
-
const stringMatches = items.matchAll(/['"]([^'"]+)['"]/g);
|
|
26
|
-
for (const m of stringMatches) {
|
|
27
|
-
paths.push(m[1]);
|
|
28
|
-
}
|
|
29
|
-
return paths;
|
|
30
|
-
}
|
|
31
|
-
// Check for single string matcher: '/path'
|
|
32
|
-
const stringMatch = matcherPart.match(/['"]([^'"]+)['"]/);
|
|
33
|
-
if (stringMatch) {
|
|
34
|
-
return [stringMatch[1]];
|
|
35
|
-
}
|
|
36
|
-
return null;
|
|
37
|
-
}
|
|
38
|
-
/**
|
|
39
|
-
* Check if any matcher pattern covers /api routes
|
|
40
|
-
*/
|
|
41
|
-
export function matcherCoversApi(matchers) {
|
|
42
|
-
for (const matcher of matchers) {
|
|
43
|
-
// Direct /api match
|
|
44
|
-
if (matcher === "/api" || matcher === "/api/:path*") {
|
|
45
|
-
return true;
|
|
46
|
-
}
|
|
47
|
-
// Pattern starts with /api
|
|
48
|
-
if (matcher.startsWith("/api/") || matcher.startsWith("/api:")) {
|
|
49
|
-
return true;
|
|
50
|
-
}
|
|
51
|
-
// Catch-all that would include /api
|
|
52
|
-
if (matcher === "/:path*" || matcher === "/(.*)" || matcher === "/(.*)") {
|
|
53
|
-
return true;
|
|
54
|
-
}
|
|
55
|
-
// Negation pattern - check if it's excluding something else but including api
|
|
56
|
-
// e.g., '/((?!_next/static|_next/image|favicon.ico).*)'
|
|
57
|
-
if (matcher.includes("(?!") && !matcher.includes("api")) {
|
|
58
|
-
return true;
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
return false;
|
|
62
|
-
}
|
|
63
|
-
/**
|
|
64
|
-
* VC-MW-001: Middleware matcher gap for /api coverage
|
|
65
|
-
* VC-AUTH-INFO-001: Missing middleware with next-auth
|
|
66
|
-
*
|
|
67
|
-
* Checks if middleware properly covers API routes
|
|
68
|
-
*/
|
|
69
|
-
export async function scanMiddlewareGap(context) {
|
|
70
|
-
const { repoRoot, fileIndex, repoMeta } = context;
|
|
71
|
-
const findings = [];
|
|
72
|
-
// Only run for Next.js projects
|
|
73
|
-
if (repoMeta.framework !== "next") {
|
|
74
|
-
return findings;
|
|
75
|
-
}
|
|
76
|
-
const hasApiRoutes = fileIndex.apiRouteFiles.length > 0;
|
|
77
|
-
if (!hasApiRoutes) {
|
|
78
|
-
return findings; // No API routes to protect
|
|
79
|
-
}
|
|
80
|
-
const middlewarePath = fileIndex.middlewareFile;
|
|
81
|
-
if (!middlewarePath) {
|
|
82
|
-
// No middleware file exists
|
|
83
|
-
if (repoMeta.hasNextAuth) {
|
|
84
|
-
// Has next-auth but no middleware - likely missing protection
|
|
85
|
-
const evidence = [
|
|
86
|
-
{
|
|
87
|
-
file: "package.json",
|
|
88
|
-
startLine: 1,
|
|
89
|
-
endLine: 1,
|
|
90
|
-
label: "next-auth dependency present",
|
|
91
|
-
},
|
|
92
|
-
...fileIndex.apiRouteFiles.slice(0, 3).map((route) => ({
|
|
93
|
-
file: route,
|
|
94
|
-
startLine: 1,
|
|
95
|
-
endLine: 1,
|
|
96
|
-
label: "API route without middleware protection",
|
|
97
|
-
})),
|
|
98
|
-
];
|
|
99
|
-
const fingerprint = generateFingerprint({
|
|
100
|
-
ruleId: RULE_AUTH_INFO_001,
|
|
101
|
-
file: "middleware.ts",
|
|
102
|
-
symbol: "missing",
|
|
103
|
-
});
|
|
104
|
-
findings.push({
|
|
105
|
-
id: generateFindingId({
|
|
106
|
-
ruleId: RULE_AUTH_INFO_001,
|
|
107
|
-
file: "middleware.ts",
|
|
108
|
-
symbol: "missing",
|
|
109
|
-
}),
|
|
110
|
-
severity: "medium",
|
|
111
|
-
confidence: 0.7,
|
|
112
|
-
category: "auth",
|
|
113
|
-
ruleId: RULE_AUTH_INFO_001,
|
|
114
|
-
title: "Next.js middleware missing with next-auth dependency",
|
|
115
|
-
description: `This Next.js project uses next-auth but has no middleware.ts file. API routes (${fileIndex.apiRouteFiles.length} found) may lack server-side authentication enforcement. While next-auth provides session management, middleware is recommended for edge-level protection.`,
|
|
116
|
-
evidence,
|
|
117
|
-
remediation: {
|
|
118
|
-
recommendedFix: "Create a middleware.ts file that checks authentication for protected routes. See: https://next-auth.js.org/configuration/nextjs#middleware",
|
|
119
|
-
patch: `// middleware.ts
|
|
120
|
-
import { withAuth } from "next-auth/middleware";
|
|
121
|
-
|
|
122
|
-
export default withAuth({
|
|
123
|
-
callbacks: {
|
|
124
|
-
authorized: ({ token }) => !!token,
|
|
125
|
-
},
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
export const config = {
|
|
129
|
-
matcher: ["/api/:path*", "/dashboard/:path*"],
|
|
130
|
-
};`,
|
|
131
|
-
},
|
|
132
|
-
links: {
|
|
133
|
-
owasp: "https://owasp.org/Top10/A01_2021-Broken_Access_Control/",
|
|
134
|
-
},
|
|
135
|
-
fingerprint,
|
|
136
|
-
});
|
|
137
|
-
}
|
|
138
|
-
return findings;
|
|
139
|
-
}
|
|
140
|
-
// Middleware exists - check if it covers API routes
|
|
141
|
-
const middlewareContent = readFileSync(resolvePath(repoRoot, middlewarePath));
|
|
142
|
-
if (!middlewareContent) {
|
|
143
|
-
return findings;
|
|
144
|
-
}
|
|
145
|
-
const matchers = parseMatcherConfig(middlewareContent);
|
|
146
|
-
// If no matcher config, middleware applies to all routes (except static)
|
|
147
|
-
if (matchers === null) {
|
|
148
|
-
return findings; // Likely covers everything
|
|
149
|
-
}
|
|
150
|
-
// Check if matchers cover /api
|
|
151
|
-
if (!matcherCoversApi(matchers)) {
|
|
152
|
-
const lines = middlewareContent.split("\n");
|
|
153
|
-
let configLine = 1;
|
|
154
|
-
for (let i = 0; i < lines.length; i++) {
|
|
155
|
-
if (lines[i].includes("config") && lines[i].includes("matcher")) {
|
|
156
|
-
configLine = i + 1;
|
|
157
|
-
break;
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
const evidence = [
|
|
161
|
-
{
|
|
162
|
-
file: middlewarePath,
|
|
163
|
-
startLine: configLine,
|
|
164
|
-
endLine: configLine,
|
|
165
|
-
snippet: `matcher: ${JSON.stringify(matchers)}`,
|
|
166
|
-
label: "Middleware matcher does not include /api routes",
|
|
167
|
-
},
|
|
168
|
-
...fileIndex.apiRouteFiles.slice(0, 2).map((route) => ({
|
|
169
|
-
file: route,
|
|
170
|
-
startLine: 1,
|
|
171
|
-
endLine: 1,
|
|
172
|
-
label: "API route not covered by middleware",
|
|
173
|
-
})),
|
|
174
|
-
];
|
|
175
|
-
const fingerprint = generateFingerprint({
|
|
176
|
-
ruleId: RULE_MW_001,
|
|
177
|
-
file: middlewarePath,
|
|
178
|
-
symbol: "matcher",
|
|
179
|
-
});
|
|
180
|
-
findings.push({
|
|
181
|
-
id: generateFindingId({
|
|
182
|
-
ruleId: RULE_MW_001,
|
|
183
|
-
file: middlewarePath,
|
|
184
|
-
symbol: "matcher",
|
|
185
|
-
}),
|
|
186
|
-
severity: "high",
|
|
187
|
-
confidence: 0.85,
|
|
188
|
-
category: "middleware",
|
|
189
|
-
ruleId: RULE_MW_001,
|
|
190
|
-
title: "Next.js middleware matcher does not cover API routes",
|
|
191
|
-
description: `The middleware.ts file has a matcher configuration that does not include /api routes. Found ${fileIndex.apiRouteFiles.length} API route(s) that are not protected by middleware. Current matcher: ${JSON.stringify(matchers)}`,
|
|
192
|
-
evidence,
|
|
193
|
-
remediation: {
|
|
194
|
-
recommendedFix: `Update the middleware matcher to include API routes. Example: matcher: ['/((?!_next/static|_next/image|favicon.ico).*)', '/api/:path*']`,
|
|
195
|
-
},
|
|
196
|
-
links: {
|
|
197
|
-
owasp: "https://owasp.org/Top10/A01_2021-Broken_Access_Control/",
|
|
198
|
-
},
|
|
199
|
-
fingerprint,
|
|
200
|
-
});
|
|
201
|
-
}
|
|
202
|
-
return findings;
|
|
203
|
-
}
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import type { Finding } from "@vibecheck/schema";
|
|
2
|
-
import type { ScanContext } from "../types.js";
|
|
3
|
-
/**
|
|
4
|
-
* VC-AUTH-001: Unprotected state-changing API route
|
|
5
|
-
*
|
|
6
|
-
* Identifies Next.js App Router route handlers that:
|
|
7
|
-
* 1. Export POST/PUT/PATCH/DELETE methods
|
|
8
|
-
* 2. Contain database write/delete operations or export functionality
|
|
9
|
-
* 3. Do NOT contain any server-side auth checks
|
|
10
|
-
*/
|
|
11
|
-
export declare function scanUnprotectedApiRoutes(context: ScanContext): Promise<Finding[]>;
|
|
12
|
-
//# sourceMappingURL=unprotected-api-route.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"unprotected-api-route.d.ts","sourceRoot":"","sources":["../../../src/scanners/auth/unprotected-api-route.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAA0B,MAAM,mBAAmB,CAAC;AACzE,OAAO,KAAK,EAAE,WAAW,EAAU,MAAM,aAAa,CAAC;AAqCvD;;;;;;;GAOG;AACH,wBAAsB,wBAAwB,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC,CAqGvF"}
|
|
@@ -1,126 +0,0 @@
|
|
|
1
|
-
import { resolvePath } from "../../utils/file-utils.js";
|
|
2
|
-
import { generateFingerprint, generateFindingId } from "../../utils/fingerprint.js";
|
|
3
|
-
const RULE_ID = "VC-AUTH-001";
|
|
4
|
-
/**
|
|
5
|
-
* Methods that modify state and require authentication
|
|
6
|
-
*/
|
|
7
|
-
const STATE_CHANGING_METHODS = ["POST", "PUT", "PATCH", "DELETE"];
|
|
8
|
-
/**
|
|
9
|
-
* Extract route path from file path
|
|
10
|
-
* e.g., app/api/users/route.ts -> /api/users
|
|
11
|
-
*/
|
|
12
|
-
function extractRoutePath(filePath) {
|
|
13
|
-
// Normalize slashes
|
|
14
|
-
const normalized = filePath.replace(/\\/g, "/");
|
|
15
|
-
// Extract from app/api/... pattern
|
|
16
|
-
const match = normalized.match(/(?:app|src\/app)(\/api\/[^/]+(?:\/[^/]+)*?)\/route\.[tj]sx?$/);
|
|
17
|
-
if (match) {
|
|
18
|
-
return match[1];
|
|
19
|
-
}
|
|
20
|
-
// Fallback: just return the file path
|
|
21
|
-
return normalized;
|
|
22
|
-
}
|
|
23
|
-
/**
|
|
24
|
-
* Determine severity based on sink type
|
|
25
|
-
*/
|
|
26
|
-
function getSeverity(sinks) {
|
|
27
|
-
const hasCritical = sinks.some((s) => s.isCritical);
|
|
28
|
-
return hasCritical ? "critical" : "high";
|
|
29
|
-
}
|
|
30
|
-
/**
|
|
31
|
-
* VC-AUTH-001: Unprotected state-changing API route
|
|
32
|
-
*
|
|
33
|
-
* Identifies Next.js App Router route handlers that:
|
|
34
|
-
* 1. Export POST/PUT/PATCH/DELETE methods
|
|
35
|
-
* 2. Contain database write/delete operations or export functionality
|
|
36
|
-
* 3. Do NOT contain any server-side auth checks
|
|
37
|
-
*/
|
|
38
|
-
export async function scanUnprotectedApiRoutes(context) {
|
|
39
|
-
const { repoRoot, fileIndex, helpers, repoMeta } = context;
|
|
40
|
-
const findings = [];
|
|
41
|
-
// Only scan Next.js projects
|
|
42
|
-
if (repoMeta.framework !== "next") {
|
|
43
|
-
return findings;
|
|
44
|
-
}
|
|
45
|
-
// Scan API route files
|
|
46
|
-
for (const relPath of fileIndex.apiRouteFiles) {
|
|
47
|
-
const absPath = resolvePath(repoRoot, relPath);
|
|
48
|
-
const sourceFile = helpers.parseFile(absPath);
|
|
49
|
-
if (!sourceFile)
|
|
50
|
-
continue;
|
|
51
|
-
const handlers = helpers.findRouteHandlers(sourceFile);
|
|
52
|
-
for (const handler of handlers) {
|
|
53
|
-
// Only check state-changing methods
|
|
54
|
-
if (!STATE_CHANGING_METHODS.includes(handler.method)) {
|
|
55
|
-
continue;
|
|
56
|
-
}
|
|
57
|
-
// Check for auth
|
|
58
|
-
const hasAuth = helpers.containsAuthCheck(handler.functionNode);
|
|
59
|
-
if (hasAuth) {
|
|
60
|
-
continue;
|
|
61
|
-
}
|
|
62
|
-
// Find database sinks
|
|
63
|
-
const sinks = helpers.findDbSinks(handler.functionNode);
|
|
64
|
-
// Only flag if there are dangerous sinks
|
|
65
|
-
if (sinks.length === 0) {
|
|
66
|
-
continue;
|
|
67
|
-
}
|
|
68
|
-
const routePath = extractRoutePath(relPath);
|
|
69
|
-
const severity = getSeverity(sinks);
|
|
70
|
-
const evidence = [
|
|
71
|
-
{
|
|
72
|
-
file: relPath,
|
|
73
|
-
startLine: handler.startLine,
|
|
74
|
-
endLine: handler.endLine,
|
|
75
|
-
snippet: helpers.getNodeText(handler.functionNode).slice(0, 200) + "...",
|
|
76
|
-
label: `Unprotected ${handler.method} handler`,
|
|
77
|
-
},
|
|
78
|
-
...sinks.slice(0, 2).map((sink) => ({
|
|
79
|
-
file: relPath,
|
|
80
|
-
startLine: sink.line,
|
|
81
|
-
endLine: sink.line,
|
|
82
|
-
snippet: sink.snippet,
|
|
83
|
-
label: `${sink.kind} ${sink.operation} operation without auth check`,
|
|
84
|
-
})),
|
|
85
|
-
];
|
|
86
|
-
const fingerprint = generateFingerprint({
|
|
87
|
-
ruleId: RULE_ID,
|
|
88
|
-
file: relPath,
|
|
89
|
-
symbol: handler.method,
|
|
90
|
-
route: routePath,
|
|
91
|
-
});
|
|
92
|
-
const sinkOperations = sinks.map((s) => `${s.kind}.${s.operation}`).join(", ");
|
|
93
|
-
findings.push({
|
|
94
|
-
id: generateFindingId({
|
|
95
|
-
ruleId: RULE_ID,
|
|
96
|
-
file: relPath,
|
|
97
|
-
symbol: handler.method,
|
|
98
|
-
}),
|
|
99
|
-
ruleId: RULE_ID,
|
|
100
|
-
title: `Unprotected ${handler.method} route: ${routePath}`,
|
|
101
|
-
description: `The API route ${routePath} exports a ${handler.method} handler that performs database operations (${sinkOperations}) without any authentication checks. An attacker could invoke this endpoint directly to modify or delete data without authorization.`,
|
|
102
|
-
severity,
|
|
103
|
-
confidence: 0.88,
|
|
104
|
-
category: "auth",
|
|
105
|
-
evidence,
|
|
106
|
-
remediation: {
|
|
107
|
-
recommendedFix: `Add authentication to the ${handler.method} handler. Check for a valid session using getServerSession(), auth(), or similar before performing database operations.`,
|
|
108
|
-
patch: `// Add at the start of your handler:
|
|
109
|
-
const session = await getServerSession(authOptions);
|
|
110
|
-
if (!session) {
|
|
111
|
-
return new Response(JSON.stringify({ error: "Unauthorized" }), {
|
|
112
|
-
status: 401,
|
|
113
|
-
headers: { "Content-Type": "application/json" }
|
|
114
|
-
});
|
|
115
|
-
}`,
|
|
116
|
-
},
|
|
117
|
-
links: {
|
|
118
|
-
owasp: "https://owasp.org/Top10/A01_2021-Broken_Access_Control/",
|
|
119
|
-
cwe: "https://cwe.mitre.org/data/definitions/306.html",
|
|
120
|
-
},
|
|
121
|
-
fingerprint,
|
|
122
|
-
});
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
return findings;
|
|
126
|
-
}
|
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
import type { ScannerPack } from "../types.js";
|
|
2
|
-
export declare const configPack: ScannerPack;
|
|
3
|
-
export { scanUndocumentedEnv, parseEnvExample, findEnvUsages } from "./undocumented-env.js";
|
|
4
|
-
export { scanInsecureDefaults } from "./insecure-defaults.js";
|
|
5
|
-
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/scanners/config/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAI/C,eAAO,MAAM,UAAU,EAAE,WAIxB,CAAC;AAGF,OAAO,EAAE,mBAAmB,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC5F,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC"}
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { scanUndocumentedEnv } from "./undocumented-env.js";
|
|
2
|
-
import { scanInsecureDefaults } from "./insecure-defaults.js";
|
|
3
|
-
export const configPack = {
|
|
4
|
-
id: "config",
|
|
5
|
-
name: "Configuration & Secrets",
|
|
6
|
-
scanners: [scanUndocumentedEnv, scanInsecureDefaults],
|
|
7
|
-
};
|
|
8
|
-
// Re-export for testing
|
|
9
|
-
export { scanUndocumentedEnv, parseEnvExample, findEnvUsages } from "./undocumented-env.js";
|
|
10
|
-
export { scanInsecureDefaults } from "./insecure-defaults.js";
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import type { Finding } from "@vibecheck/schema";
|
|
2
|
-
import type { ScanContext } from "../types.js";
|
|
3
|
-
/**
|
|
4
|
-
* VC-CONFIG-002: Insecure default secret fallback
|
|
5
|
-
*
|
|
6
|
-
* Detects patterns:
|
|
7
|
-
* - process.env.* ?? "dev"
|
|
8
|
-
* - process.env.* || "dev"
|
|
9
|
-
* - Hardcoded secret strings assigned to SECRET/TOKEN/KEY/PASSWORD vars
|
|
10
|
-
*/
|
|
11
|
-
export declare function scanInsecureDefaults(context: ScanContext): Promise<Finding[]>;
|
|
12
|
-
//# sourceMappingURL=insecure-defaults.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"insecure-defaults.d.ts","sourceRoot":"","sources":["../../../src/scanners/config/insecure-defaults.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAA0B,MAAM,mBAAmB,CAAC;AACzE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAM/C;;;;;;;GAOG;AACH,wBAAsB,oBAAoB,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC,CAwEnF"}
|
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
import { resolvePath } from "../../utils/file-utils.js";
|
|
2
|
-
import { generateFingerprint, generateFindingId } from "../../utils/fingerprint.js";
|
|
3
|
-
const RULE_ID = "VC-CONFIG-002";
|
|
4
|
-
/**
|
|
5
|
-
* VC-CONFIG-002: Insecure default secret fallback
|
|
6
|
-
*
|
|
7
|
-
* Detects patterns:
|
|
8
|
-
* - process.env.* ?? "dev"
|
|
9
|
-
* - process.env.* || "dev"
|
|
10
|
-
* - Hardcoded secret strings assigned to SECRET/TOKEN/KEY/PASSWORD vars
|
|
11
|
-
*/
|
|
12
|
-
export async function scanInsecureDefaults(context) {
|
|
13
|
-
const { repoRoot, fileIndex, helpers } = context;
|
|
14
|
-
const findings = [];
|
|
15
|
-
// Scan all source files
|
|
16
|
-
for (const relPath of fileIndex.allSourceFiles) {
|
|
17
|
-
const absPath = resolvePath(repoRoot, relPath);
|
|
18
|
-
const sourceFile = helpers.parseFile(absPath);
|
|
19
|
-
if (!sourceFile)
|
|
20
|
-
continue;
|
|
21
|
-
const insecureDefaults = helpers.findInsecureDefaults(sourceFile);
|
|
22
|
-
for (const def of insecureDefaults) {
|
|
23
|
-
const severity = def.isCritical ? "critical" : "medium";
|
|
24
|
-
const evidence = [
|
|
25
|
-
{
|
|
26
|
-
file: relPath,
|
|
27
|
-
startLine: def.line,
|
|
28
|
-
endLine: def.line,
|
|
29
|
-
snippet: def.snippet,
|
|
30
|
-
label: def.isCritical
|
|
31
|
-
? `Critical secret with hardcoded fallback: ${def.envVar}`
|
|
32
|
-
: `Environment variable with hardcoded fallback: ${def.envVar}`,
|
|
33
|
-
},
|
|
34
|
-
];
|
|
35
|
-
const fingerprint = generateFingerprint({
|
|
36
|
-
ruleId: RULE_ID,
|
|
37
|
-
file: relPath,
|
|
38
|
-
symbol: def.envVar,
|
|
39
|
-
startLine: def.line,
|
|
40
|
-
});
|
|
41
|
-
findings.push({
|
|
42
|
-
id: generateFindingId({
|
|
43
|
-
ruleId: RULE_ID,
|
|
44
|
-
file: relPath,
|
|
45
|
-
symbol: def.envVar,
|
|
46
|
-
startLine: def.line,
|
|
47
|
-
}),
|
|
48
|
-
ruleId: RULE_ID,
|
|
49
|
-
title: `Insecure default for ${def.envVar}`,
|
|
50
|
-
description: def.isCritical
|
|
51
|
-
? `The secret ${def.envVar} has a hardcoded fallback value "${def.fallbackValue.slice(0, 20)}${def.fallbackValue.length > 20 ? "..." : ""}". This is a critical security issue as the fallback value may be used in production if the environment variable is not set, potentially exposing a weak or predictable secret.`
|
|
52
|
-
: `The environment variable ${def.envVar} has a hardcoded fallback value. While this may be intended for development, it could lead to insecure defaults being used in production.`,
|
|
53
|
-
severity,
|
|
54
|
-
confidence: def.isCritical ? 0.95 : 0.8,
|
|
55
|
-
category: "config",
|
|
56
|
-
evidence,
|
|
57
|
-
remediation: {
|
|
58
|
-
recommendedFix: `Remove the hardcoded fallback and require the environment variable to be explicitly set. Add validation at startup to fail fast if required secrets are missing.`,
|
|
59
|
-
patch: `// Instead of:
|
|
60
|
-
const secret = process.env.${def.envVar} || "hardcoded";
|
|
61
|
-
|
|
62
|
-
// Do:
|
|
63
|
-
const secret = process.env.${def.envVar};
|
|
64
|
-
if (!secret) {
|
|
65
|
-
throw new Error("${def.envVar} environment variable is required");
|
|
66
|
-
}`,
|
|
67
|
-
},
|
|
68
|
-
links: {
|
|
69
|
-
cwe: "https://cwe.mitre.org/data/definitions/798.html",
|
|
70
|
-
owasp: "https://owasp.org/Top10/A07_2021-Identification_and_Authentication_Failures/",
|
|
71
|
-
},
|
|
72
|
-
fingerprint,
|
|
73
|
-
});
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
return findings;
|
|
77
|
-
}
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import type { Finding } from "@vibecheck/schema";
|
|
2
|
-
import type { ScanContext } from "../types.js";
|
|
3
|
-
interface EnvUsage {
|
|
4
|
-
name: string;
|
|
5
|
-
file: string;
|
|
6
|
-
line: number;
|
|
7
|
-
snippet: string;
|
|
8
|
-
}
|
|
9
|
-
/**
|
|
10
|
-
* Parse .env.example file and extract defined variable names
|
|
11
|
-
*/
|
|
12
|
-
export declare function parseEnvExample(content: string): Set<string>;
|
|
13
|
-
/**
|
|
14
|
-
* Find all process.env usages in source files
|
|
15
|
-
*/
|
|
16
|
-
export declare function findEnvUsages(sourceFiles: string[], targetDir: string): EnvUsage[];
|
|
17
|
-
/**
|
|
18
|
-
* VC-CONFIG-001: Undocumented environment variable
|
|
19
|
-
*
|
|
20
|
-
* Finds process.env usage and checks if variables are documented in .env.example
|
|
21
|
-
*/
|
|
22
|
-
export declare function scanUndocumentedEnv(context: ScanContext): Promise<Finding[]>;
|
|
23
|
-
export {};
|
|
24
|
-
//# sourceMappingURL=undocumented-env.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"undocumented-env.d.ts","sourceRoot":"","sources":["../../../src/scanners/config/undocumented-env.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAA0B,MAAM,mBAAmB,CAAC;AAGzE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAoB/C,UAAU,QAAQ;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,CAiB5D;AAED;;GAEG;AACH,wBAAgB,aAAa,CAC3B,WAAW,EAAE,MAAM,EAAE,EACrB,SAAS,EAAE,MAAM,GAChB,QAAQ,EAAE,CA6CZ;AASD;;;;GAIG;AACH,wBAAsB,mBAAmB,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC,CAmFlF"}
|
|
@@ -1,159 +0,0 @@
|
|
|
1
|
-
import { readFileSync, resolvePath } from "../../utils/file-utils.js";
|
|
2
|
-
import { generateFingerprint, generateFindingId } from "../../utils/fingerprint.js";
|
|
3
|
-
const RULE_ID = "VC-CONFIG-001";
|
|
4
|
-
/**
|
|
5
|
-
* Patterns that indicate a secret-like environment variable
|
|
6
|
-
*/
|
|
7
|
-
const SECRET_PATTERNS = /SECRET|KEY|TOKEN|PASSWORD|PRIVATE|CREDENTIAL|API_KEY|AUTH/i;
|
|
8
|
-
/**
|
|
9
|
-
* Regex to find process.env.VARIABLE_NAME usage
|
|
10
|
-
* Captures the variable name in group 1
|
|
11
|
-
*/
|
|
12
|
-
const PROCESS_ENV_REGEX = /process\.env\.([A-Z][A-Z0-9_]*)/g;
|
|
13
|
-
/**
|
|
14
|
-
* Regex to find process.env["VARIABLE"] or process.env['VARIABLE'] usage
|
|
15
|
-
*/
|
|
16
|
-
const PROCESS_ENV_BRACKET_REGEX = /process\.env\[['"]([A-Z][A-Z0-9_]*)['"]\]/g;
|
|
17
|
-
/**
|
|
18
|
-
* Parse .env.example file and extract defined variable names
|
|
19
|
-
*/
|
|
20
|
-
export function parseEnvExample(content) {
|
|
21
|
-
const vars = new Set();
|
|
22
|
-
const lines = content.split("\n");
|
|
23
|
-
for (const line of lines) {
|
|
24
|
-
const trimmed = line.trim();
|
|
25
|
-
// Skip comments and empty lines
|
|
26
|
-
if (trimmed.startsWith("#") || trimmed === "")
|
|
27
|
-
continue;
|
|
28
|
-
// Match VAR_NAME= or VAR_NAME (without value)
|
|
29
|
-
const match = trimmed.match(/^([A-Z][A-Z0-9_]*)(?:=|$)/);
|
|
30
|
-
if (match) {
|
|
31
|
-
vars.add(match[1]);
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
return vars;
|
|
35
|
-
}
|
|
36
|
-
/**
|
|
37
|
-
* Find all process.env usages in source files
|
|
38
|
-
*/
|
|
39
|
-
export function findEnvUsages(sourceFiles, targetDir) {
|
|
40
|
-
const usages = [];
|
|
41
|
-
for (const relFile of sourceFiles) {
|
|
42
|
-
const absPath = resolvePath(targetDir, relFile);
|
|
43
|
-
const content = readFileSync(absPath);
|
|
44
|
-
if (!content)
|
|
45
|
-
continue;
|
|
46
|
-
const lines = content.split("\n");
|
|
47
|
-
// Find dot notation: process.env.VAR_NAME
|
|
48
|
-
for (const match of content.matchAll(PROCESS_ENV_REGEX)) {
|
|
49
|
-
const varName = match[1];
|
|
50
|
-
const index = match.index;
|
|
51
|
-
// Find line number
|
|
52
|
-
const beforeMatch = content.slice(0, index);
|
|
53
|
-
const lineNumber = beforeMatch.split("\n").length;
|
|
54
|
-
usages.push({
|
|
55
|
-
name: varName,
|
|
56
|
-
file: relFile,
|
|
57
|
-
line: lineNumber,
|
|
58
|
-
snippet: lines[lineNumber - 1]?.trim() ?? "",
|
|
59
|
-
});
|
|
60
|
-
}
|
|
61
|
-
// Find bracket notation: process.env["VAR_NAME"]
|
|
62
|
-
for (const match of content.matchAll(PROCESS_ENV_BRACKET_REGEX)) {
|
|
63
|
-
const varName = match[1];
|
|
64
|
-
const index = match.index;
|
|
65
|
-
const beforeMatch = content.slice(0, index);
|
|
66
|
-
const lineNumber = beforeMatch.split("\n").length;
|
|
67
|
-
usages.push({
|
|
68
|
-
name: varName,
|
|
69
|
-
file: relFile,
|
|
70
|
-
line: lineNumber,
|
|
71
|
-
snippet: lines[lineNumber - 1]?.trim() ?? "",
|
|
72
|
-
});
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
return usages;
|
|
76
|
-
}
|
|
77
|
-
/**
|
|
78
|
-
* Determine severity based on variable name
|
|
79
|
-
*/
|
|
80
|
-
function getSeverity(varName) {
|
|
81
|
-
return SECRET_PATTERNS.test(varName) ? "high" : "medium";
|
|
82
|
-
}
|
|
83
|
-
/**
|
|
84
|
-
* VC-CONFIG-001: Undocumented environment variable
|
|
85
|
-
*
|
|
86
|
-
* Finds process.env usage and checks if variables are documented in .env.example
|
|
87
|
-
*/
|
|
88
|
-
export async function scanUndocumentedEnv(context) {
|
|
89
|
-
const { repoRoot, fileIndex } = context;
|
|
90
|
-
const findings = [];
|
|
91
|
-
// Check for .env.example
|
|
92
|
-
const envExamplePath = resolvePath(repoRoot, ".env.example");
|
|
93
|
-
const envExampleContent = readFileSync(envExamplePath);
|
|
94
|
-
// Get documented env vars (empty set if no .env.example)
|
|
95
|
-
const documentedVars = envExampleContent
|
|
96
|
-
? parseEnvExample(envExampleContent)
|
|
97
|
-
: new Set();
|
|
98
|
-
// Find all env usages
|
|
99
|
-
const usages = findEnvUsages(fileIndex.allSourceFiles, repoRoot);
|
|
100
|
-
// Group usages by variable name
|
|
101
|
-
const usagesByVar = new Map();
|
|
102
|
-
for (const usage of usages) {
|
|
103
|
-
const existing = usagesByVar.get(usage.name) ?? [];
|
|
104
|
-
existing.push(usage);
|
|
105
|
-
usagesByVar.set(usage.name, existing);
|
|
106
|
-
}
|
|
107
|
-
// Check each variable
|
|
108
|
-
for (const [varName, varUsages] of usagesByVar) {
|
|
109
|
-
if (documentedVars.has(varName)) {
|
|
110
|
-
continue; // Variable is documented, no finding
|
|
111
|
-
}
|
|
112
|
-
const severity = getSeverity(varName);
|
|
113
|
-
const firstUsage = varUsages[0];
|
|
114
|
-
const evidence = varUsages.slice(0, 3).map((u) => ({
|
|
115
|
-
file: u.file,
|
|
116
|
-
startLine: u.line,
|
|
117
|
-
endLine: u.line,
|
|
118
|
-
snippet: u.snippet,
|
|
119
|
-
label: `Usage of process.env.${varName}`,
|
|
120
|
-
}));
|
|
121
|
-
if (varUsages.length > 3) {
|
|
122
|
-
evidence.push({
|
|
123
|
-
file: varUsages[3].file,
|
|
124
|
-
startLine: varUsages[3].line,
|
|
125
|
-
endLine: varUsages[3].line,
|
|
126
|
-
label: `...and ${varUsages.length - 3} more usages`,
|
|
127
|
-
});
|
|
128
|
-
}
|
|
129
|
-
const fingerprint = generateFingerprint({
|
|
130
|
-
ruleId: RULE_ID,
|
|
131
|
-
file: firstUsage.file,
|
|
132
|
-
symbol: varName,
|
|
133
|
-
});
|
|
134
|
-
const hasEnvExample = envExampleContent !== null;
|
|
135
|
-
findings.push({
|
|
136
|
-
id: generateFindingId({
|
|
137
|
-
ruleId: RULE_ID,
|
|
138
|
-
file: firstUsage.file,
|
|
139
|
-
symbol: varName,
|
|
140
|
-
}),
|
|
141
|
-
severity,
|
|
142
|
-
confidence: 0.85,
|
|
143
|
-
category: "config",
|
|
144
|
-
ruleId: RULE_ID,
|
|
145
|
-
title: `Undocumented environment variable: ${varName}`,
|
|
146
|
-
description: hasEnvExample
|
|
147
|
-
? `The environment variable "${varName}" is used in the codebase but is not listed in .env.example. This can lead to deployment issues or confusion for other developers.`
|
|
148
|
-
: `The environment variable "${varName}" is used but no .env.example file exists. Consider creating one to document required configuration.`,
|
|
149
|
-
evidence,
|
|
150
|
-
remediation: {
|
|
151
|
-
recommendedFix: hasEnvExample
|
|
152
|
-
? `Add "${varName}=" to .env.example with an appropriate default or placeholder value.`
|
|
153
|
-
: `Create a .env.example file and add "${varName}=" with documentation.`,
|
|
154
|
-
},
|
|
155
|
-
fingerprint,
|
|
156
|
-
});
|
|
157
|
-
}
|
|
158
|
-
return findings;
|
|
159
|
-
}
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
import type { ScannerPack } from "../types.js";
|
|
2
|
-
export declare const cryptoPack: ScannerPack;
|
|
3
|
-
export { scanMathRandomTokens } from "./math-random-tokens.js";
|
|
4
|
-
export { scanJwtDecodeUnverified } from "./jwt-decode-unverified.js";
|
|
5
|
-
export { scanWeakHashing } from "./weak-hashing.js";
|
|
6
|
-
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/scanners/crypto/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAK/C,eAAO,MAAM,UAAU,EAAE,WAIxB,CAAC;AAEF,OAAO,EAAE,oBAAoB,EAAE,MAAM,yBAAyB,CAAC;AAC/D,OAAO,EAAE,uBAAuB,EAAE,MAAM,4BAA4B,CAAC;AACrE,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC"}
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import { scanMathRandomTokens } from "./math-random-tokens.js";
|
|
2
|
-
import { scanJwtDecodeUnverified } from "./jwt-decode-unverified.js";
|
|
3
|
-
import { scanWeakHashing } from "./weak-hashing.js";
|
|
4
|
-
export const cryptoPack = {
|
|
5
|
-
id: "crypto",
|
|
6
|
-
name: "Cryptography Security",
|
|
7
|
-
scanners: [scanMathRandomTokens, scanJwtDecodeUnverified, scanWeakHashing],
|
|
8
|
-
};
|
|
9
|
-
export { scanMathRandomTokens } from "./math-random-tokens.js";
|
|
10
|
-
export { scanJwtDecodeUnverified } from "./jwt-decode-unverified.js";
|
|
11
|
-
export { scanWeakHashing } from "./weak-hashing.js";
|