@vibecheckai/cli 3.2.4 → 3.2.6
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/bin/.generated +25 -25
- package/bin/dev/run-v2-torture.js +30 -30
- package/bin/runners/lib/__tests__/entitlements-v2.test.js +295 -295
- package/bin/runners/lib/api-client.js +269 -0
- package/bin/runners/lib/auth-truth.js +193 -193
- package/bin/runners/lib/backup.js +62 -62
- package/bin/runners/lib/billing.js +107 -107
- package/bin/runners/lib/claims.js +118 -118
- package/bin/runners/lib/cli-ui.js +540 -540
- package/bin/runners/lib/contracts/auth-contract.js +202 -202
- package/bin/runners/lib/contracts/env-contract.js +181 -181
- package/bin/runners/lib/contracts/external-contract.js +206 -206
- package/bin/runners/lib/contracts/guard.js +168 -168
- package/bin/runners/lib/contracts/index.js +89 -89
- package/bin/runners/lib/contracts/plan-validator.js +311 -311
- package/bin/runners/lib/contracts/route-contract.js +199 -199
- package/bin/runners/lib/contracts.js +804 -804
- package/bin/runners/lib/detect.js +89 -89
- package/bin/runners/lib/doctor/autofix.js +254 -254
- package/bin/runners/lib/doctor/index.js +37 -37
- package/bin/runners/lib/doctor/modules/dependencies.js +325 -325
- package/bin/runners/lib/doctor/modules/index.js +46 -46
- package/bin/runners/lib/doctor/modules/network.js +250 -250
- package/bin/runners/lib/doctor/modules/project.js +312 -312
- package/bin/runners/lib/doctor/modules/runtime.js +224 -224
- package/bin/runners/lib/doctor/modules/security.js +348 -348
- package/bin/runners/lib/doctor/modules/system.js +213 -213
- package/bin/runners/lib/doctor/modules/vibecheck.js +394 -394
- package/bin/runners/lib/doctor/reporter.js +262 -262
- package/bin/runners/lib/doctor/service.js +262 -262
- package/bin/runners/lib/doctor/types.js +113 -113
- package/bin/runners/lib/doctor/ui.js +263 -263
- package/bin/runners/lib/doctor-v2.js +608 -608
- package/bin/runners/lib/drift.js +425 -425
- package/bin/runners/lib/enforcement.js +72 -72
- package/bin/runners/lib/enterprise-detect.js +603 -603
- package/bin/runners/lib/enterprise-init.js +942 -942
- package/bin/runners/lib/env-resolver.js +417 -417
- package/bin/runners/lib/env-template.js +66 -66
- package/bin/runners/lib/env.js +189 -189
- package/bin/runners/lib/extractors/client-calls.js +990 -990
- package/bin/runners/lib/extractors/fastify-route-dump.js +573 -573
- package/bin/runners/lib/extractors/fastify-routes.js +426 -426
- package/bin/runners/lib/extractors/index.js +363 -363
- package/bin/runners/lib/extractors/next-routes.js +524 -524
- package/bin/runners/lib/extractors/proof-graph.js +431 -431
- package/bin/runners/lib/extractors/route-matcher.js +451 -451
- package/bin/runners/lib/extractors/truthpack-v2.js +377 -377
- package/bin/runners/lib/extractors/ui-bindings.js +547 -547
- package/bin/runners/lib/findings-schema.js +281 -281
- package/bin/runners/lib/firewall-prompt.js +50 -50
- package/bin/runners/lib/graph/graph-builder.js +265 -265
- package/bin/runners/lib/graph/html-renderer.js +413 -413
- package/bin/runners/lib/graph/index.js +32 -32
- package/bin/runners/lib/graph/runtime-collector.js +215 -215
- package/bin/runners/lib/graph/static-extractor.js +518 -518
- package/bin/runners/lib/html-report.js +650 -650
- package/bin/runners/lib/llm.js +75 -75
- package/bin/runners/lib/meter.js +61 -61
- package/bin/runners/lib/missions/evidence.js +126 -126
- package/bin/runners/lib/patch.js +40 -40
- package/bin/runners/lib/permissions/auth-model.js +213 -213
- package/bin/runners/lib/permissions/idor-prover.js +205 -205
- package/bin/runners/lib/permissions/index.js +45 -45
- package/bin/runners/lib/permissions/matrix-builder.js +198 -198
- package/bin/runners/lib/pkgjson.js +28 -28
- package/bin/runners/lib/policy.js +295 -295
- package/bin/runners/lib/preflight.js +142 -142
- package/bin/runners/lib/reality/correlation-detectors.js +359 -359
- package/bin/runners/lib/reality/index.js +318 -318
- package/bin/runners/lib/reality/request-hashing.js +416 -416
- package/bin/runners/lib/reality/request-mapper.js +453 -453
- package/bin/runners/lib/reality/safety-rails.js +463 -463
- package/bin/runners/lib/reality/semantic-snapshot.js +408 -408
- package/bin/runners/lib/reality/toast-detector.js +393 -393
- package/bin/runners/lib/reality-findings.js +84 -84
- package/bin/runners/lib/receipts.js +179 -179
- package/bin/runners/lib/redact.js +29 -29
- package/bin/runners/lib/replay/capsule-manager.js +154 -154
- package/bin/runners/lib/replay/index.js +263 -263
- package/bin/runners/lib/replay/player.js +348 -348
- package/bin/runners/lib/replay/recorder.js +331 -331
- package/bin/runners/lib/report.js +135 -135
- package/bin/runners/lib/route-detection.js +1140 -1140
- package/bin/runners/lib/sandbox/index.js +59 -59
- package/bin/runners/lib/sandbox/proof-chain.js +399 -399
- package/bin/runners/lib/sandbox/sandbox-runner.js +205 -205
- package/bin/runners/lib/sandbox/worktree.js +174 -174
- package/bin/runners/lib/schema-validator.js +350 -350
- package/bin/runners/lib/schemas/contracts.schema.json +160 -160
- package/bin/runners/lib/schemas/finding.schema.json +100 -100
- package/bin/runners/lib/schemas/mission-pack.schema.json +206 -206
- package/bin/runners/lib/schemas/proof-graph.schema.json +176 -176
- package/bin/runners/lib/schemas/reality-report.schema.json +162 -162
- package/bin/runners/lib/schemas/share-pack.schema.json +180 -180
- package/bin/runners/lib/schemas/ship-report.schema.json +117 -117
- package/bin/runners/lib/schemas/truthpack-v2.schema.json +303 -303
- package/bin/runners/lib/schemas/validator.js +438 -438
- package/bin/runners/lib/score-history.js +282 -282
- package/bin/runners/lib/share-pack.js +239 -239
- package/bin/runners/lib/snippets.js +67 -67
- package/bin/runners/lib/upsell.js +510 -510
- package/bin/runners/lib/usage.js +153 -153
- package/bin/runners/lib/validate-patch.js +156 -156
- package/bin/runners/lib/verdict-engine.js +628 -628
- package/bin/runners/reality/engine.js +917 -917
- package/bin/runners/reality/flows.js +122 -122
- package/bin/runners/reality/report.js +378 -378
- package/bin/runners/reality/session.js +193 -193
- package/bin/runners/runAgent.d.ts +5 -0
- package/bin/runners/runFirewall.d.ts +5 -0
- package/bin/runners/runFirewallHook.d.ts +5 -0
- package/bin/runners/runGuard.js +168 -168
- package/bin/runners/runScan.js +82 -0
- package/bin/runners/runTruth.d.ts +5 -0
- package/bin/vibecheck.js +45 -20
- package/mcp-server/index.js +85 -0
- package/mcp-server/lib/api-client.js +269 -0
- package/mcp-server/package.json +1 -1
- package/mcp-server/tier-auth.js +173 -113
- package/mcp-server/tools/index.js +72 -72
- package/mcp-server/vibecheck-mcp-server-3.2.0.tgz +0 -0
- package/package.json +1 -1
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API Client for Vibecheck Dashboard Integration
|
|
3
|
+
*
|
|
4
|
+
* Handles communication with the web dashboard API for:
|
|
5
|
+
* - Creating scan records
|
|
6
|
+
* - Updating scan progress
|
|
7
|
+
* - Submitting scan results
|
|
8
|
+
* - Broadcasting real-time updates
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
"use strict";
|
|
12
|
+
|
|
13
|
+
const https = require("https");
|
|
14
|
+
const http = require("http");
|
|
15
|
+
const { logger } = require("./logger");
|
|
16
|
+
|
|
17
|
+
// Configuration
|
|
18
|
+
const API_BASE_URL = process.env.VIBECHECK_API_URL || "https://api.vibecheckai.dev";
|
|
19
|
+
const API_TIMEOUT = 30000; // 30 seconds
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Make HTTP request to API
|
|
23
|
+
*/
|
|
24
|
+
async function makeRequest(path, options = {}) {
|
|
25
|
+
const url = new URL(path, API_BASE_URL);
|
|
26
|
+
const isHttps = url.protocol === "https:";
|
|
27
|
+
const client = isHttps ? https : http;
|
|
28
|
+
|
|
29
|
+
const defaultOptions = {
|
|
30
|
+
method: "POST",
|
|
31
|
+
headers: {
|
|
32
|
+
"Content-Type": "application/json",
|
|
33
|
+
"User-Agent": "vibecheck-cli/3.3.0",
|
|
34
|
+
},
|
|
35
|
+
timeout: API_TIMEOUT,
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const requestOptions = {
|
|
39
|
+
...defaultOptions,
|
|
40
|
+
...options,
|
|
41
|
+
hostname: url.hostname,
|
|
42
|
+
port: url.port || (isHttps ? 443 : 80),
|
|
43
|
+
path: url.pathname + url.search,
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
// Add Authorization header if API key is available
|
|
47
|
+
const apiKey = process.env.VIBECHECK_API_KEY || getApiKey();
|
|
48
|
+
if (apiKey) {
|
|
49
|
+
requestOptions.headers.Authorization = `Bearer ${apiKey}`;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return new Promise((resolve, reject) => {
|
|
53
|
+
const req = client.request(requestOptions, (res) => {
|
|
54
|
+
let data = "";
|
|
55
|
+
|
|
56
|
+
res.on("data", (chunk) => {
|
|
57
|
+
data += chunk;
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
res.on("end", () => {
|
|
61
|
+
try {
|
|
62
|
+
const jsonData = data ? JSON.parse(data) : {};
|
|
63
|
+
resolve({
|
|
64
|
+
ok: res.statusCode >= 200 && res.statusCode < 300,
|
|
65
|
+
status: res.statusCode,
|
|
66
|
+
data: jsonData,
|
|
67
|
+
});
|
|
68
|
+
} catch (err) {
|
|
69
|
+
resolve({
|
|
70
|
+
ok: false,
|
|
71
|
+
status: res.statusCode,
|
|
72
|
+
data: { error: "Invalid JSON response" },
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
req.on("error", (err) => {
|
|
79
|
+
logger.debug(`API request failed: ${err.message}`);
|
|
80
|
+
resolve({
|
|
81
|
+
ok: false,
|
|
82
|
+
error: err.message,
|
|
83
|
+
data: { error: err.message },
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
req.on("timeout", () => {
|
|
88
|
+
req.destroy();
|
|
89
|
+
resolve({
|
|
90
|
+
ok: false,
|
|
91
|
+
error: "Request timeout",
|
|
92
|
+
data: { error: "Request timeout" },
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
if (options.body) {
|
|
97
|
+
req.write(JSON.stringify(options.body));
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
req.end();
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Get API key from environment or config
|
|
106
|
+
*/
|
|
107
|
+
function getApiKey() {
|
|
108
|
+
// Try various environment variables
|
|
109
|
+
const envVars = [
|
|
110
|
+
"VIBECHECK_API_KEY",
|
|
111
|
+
"VIBECHECK_TOKEN",
|
|
112
|
+
"API_KEY",
|
|
113
|
+
"TOKEN",
|
|
114
|
+
];
|
|
115
|
+
|
|
116
|
+
for (const envVar of envVars) {
|
|
117
|
+
if (process.env[envVar]) {
|
|
118
|
+
return process.env[envVar];
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Try to read from config file
|
|
123
|
+
try {
|
|
124
|
+
const fs = require("fs");
|
|
125
|
+
const path = require("path");
|
|
126
|
+
const os = require("os");
|
|
127
|
+
|
|
128
|
+
const configPaths = [
|
|
129
|
+
path.join(os.homedir(), ".vibecheck", "config.json"),
|
|
130
|
+
path.join(process.cwd(), ".vibecheckrc.json"),
|
|
131
|
+
path.join(process.cwd(), "vibecheck.config.json"),
|
|
132
|
+
];
|
|
133
|
+
|
|
134
|
+
for (const configPath of configPaths) {
|
|
135
|
+
if (fs.existsSync(configPath)) {
|
|
136
|
+
const config = JSON.parse(fs.readFileSync(configPath, "utf8"));
|
|
137
|
+
if (config.apiKey || config.token) {
|
|
138
|
+
return config.apiKey || config.token;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
} catch (err) {
|
|
143
|
+
// Ignore config file errors
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Create a new scan record
|
|
151
|
+
*/
|
|
152
|
+
async function createScan(options) {
|
|
153
|
+
const {
|
|
154
|
+
repositoryId,
|
|
155
|
+
repositoryUrl,
|
|
156
|
+
localPath,
|
|
157
|
+
branch = "main",
|
|
158
|
+
enableLLM = false,
|
|
159
|
+
} = options;
|
|
160
|
+
|
|
161
|
+
const response = await makeRequest("/api/scans", {
|
|
162
|
+
body: {
|
|
163
|
+
repositoryId,
|
|
164
|
+
repositoryUrl,
|
|
165
|
+
localPath,
|
|
166
|
+
branch,
|
|
167
|
+
enableLLM,
|
|
168
|
+
},
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
if (!response.ok) {
|
|
172
|
+
throw new Error(`Failed to create scan: ${response.data?.error || response.error}`);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return response.data.data;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Update scan progress
|
|
180
|
+
*/
|
|
181
|
+
async function updateScanProgress(scanId, progress, status = null) {
|
|
182
|
+
const response = await makeRequest(`/api/scans/${scanId}/progress`, {
|
|
183
|
+
body: {
|
|
184
|
+
progress,
|
|
185
|
+
status,
|
|
186
|
+
},
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
if (!response.ok) {
|
|
190
|
+
logger.debug(`Failed to update scan progress: ${response.data?.error || response.error}`);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return response.data;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Submit scan results
|
|
198
|
+
*/
|
|
199
|
+
async function submitScanResults(scanId, results) {
|
|
200
|
+
const {
|
|
201
|
+
verdict,
|
|
202
|
+
score,
|
|
203
|
+
findings,
|
|
204
|
+
filesScanned,
|
|
205
|
+
linesScanned,
|
|
206
|
+
durationMs,
|
|
207
|
+
metadata = {},
|
|
208
|
+
} = results;
|
|
209
|
+
|
|
210
|
+
const response = await makeRequest(`/api/scans/${scanId}/complete`, {
|
|
211
|
+
body: {
|
|
212
|
+
verdict,
|
|
213
|
+
score,
|
|
214
|
+
findings,
|
|
215
|
+
filesScanned,
|
|
216
|
+
linesScanned,
|
|
217
|
+
durationMs,
|
|
218
|
+
metadata,
|
|
219
|
+
},
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
if (!response.ok) {
|
|
223
|
+
throw new Error(`Failed to submit scan results: ${response.data?.error || response.error}`);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return response.data;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Report scan error
|
|
231
|
+
*/
|
|
232
|
+
async function reportScanError(scanId, error) {
|
|
233
|
+
const response = await makeRequest(`/api/scans/${scanId}/error`, {
|
|
234
|
+
body: {
|
|
235
|
+
error: error.message || String(error),
|
|
236
|
+
stack: error.stack,
|
|
237
|
+
},
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
if (!response.ok) {
|
|
241
|
+
logger.debug(`Failed to report scan error: ${response.data?.error || response.error}`);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
return response.data;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Check if API is available
|
|
249
|
+
*/
|
|
250
|
+
async function isApiAvailable() {
|
|
251
|
+
try {
|
|
252
|
+
const response = await makeRequest("/api/health", {
|
|
253
|
+
method: "GET",
|
|
254
|
+
timeout: 5000,
|
|
255
|
+
});
|
|
256
|
+
return response.ok;
|
|
257
|
+
} catch (err) {
|
|
258
|
+
return false;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
module.exports = {
|
|
263
|
+
createScan,
|
|
264
|
+
updateScanProgress,
|
|
265
|
+
submitScanResults,
|
|
266
|
+
reportScanError,
|
|
267
|
+
isApiAvailable,
|
|
268
|
+
getApiKey,
|
|
269
|
+
};
|
|
@@ -1,193 +1,193 @@
|
|
|
1
|
-
// bin/runners/lib/auth-truth.js
|
|
2
|
-
// Auth Truth v1 - Detects auth/protection patterns in Next + Fastify codebases
|
|
3
|
-
const fg = require("fast-glob");
|
|
4
|
-
const fs = require("fs");
|
|
5
|
-
const path = require("path");
|
|
6
|
-
const crypto = require("crypto");
|
|
7
|
-
|
|
8
|
-
function sha256(text) {
|
|
9
|
-
return "sha256:" + crypto.createHash("sha256").update(text).digest("hex");
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
function safeRead(fileAbs) {
|
|
13
|
-
return fs.readFileSync(fileAbs, "utf8");
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
function evidenceFromLine({ fileAbs, repoRoot, lineNo, reason }) {
|
|
17
|
-
const fileRel = path.relative(repoRoot, fileAbs).replace(/\\/g, "/");
|
|
18
|
-
const lines = safeRead(fileAbs).split(/\r?\n/);
|
|
19
|
-
const idx = Math.max(0, Math.min(lines.length - 1, lineNo - 1));
|
|
20
|
-
const snippet = lines[idx] || "";
|
|
21
|
-
return {
|
|
22
|
-
id: `ev_${crypto.randomBytes(4).toString("hex")}`,
|
|
23
|
-
file: fileRel,
|
|
24
|
-
lines: `${lineNo}-${lineNo}`,
|
|
25
|
-
snippetHash: sha256(snippet),
|
|
26
|
-
reason
|
|
27
|
-
};
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function findLineMatches(code, regex) {
|
|
31
|
-
const out = [];
|
|
32
|
-
const lines = code.split(/\r?\n/);
|
|
33
|
-
for (let i = 0; i < lines.length; i++) {
|
|
34
|
-
if (regex.test(lines[i])) out.push(i + 1);
|
|
35
|
-
}
|
|
36
|
-
return out;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function guessAuthSignalsFromCode(code) {
|
|
40
|
-
const signals = [];
|
|
41
|
-
|
|
42
|
-
const patterns = [
|
|
43
|
-
{ key: "next_middleware", rx: /\bNextResponse\.(redirect|rewrite)\b/ },
|
|
44
|
-
{ key: "next_auth", rx: /\bgetServerSession\b|\bNextAuth\b|\bauth\(\)\b/ },
|
|
45
|
-
{ key: "clerk", rx: /\bclerkMiddleware\b|\bauthMiddleware\b|@clerk\/nextjs/ },
|
|
46
|
-
{ key: "supabase", rx: /\bcreateRouteHandlerClient\b|\bcreateServerClient\b|@supabase/ },
|
|
47
|
-
{ key: "jwt_verify", rx: /\b(jwtVerify|verifyJWT|verifyToken|authorization|bearer)\b/i },
|
|
48
|
-
{ key: "session", rx: /\b(session|cookie|setCookie|getCookie)\b/i },
|
|
49
|
-
{ key: "rbac", rx: /\b(role|roles|permissions|rbac|isAdmin|adminOnly)\b/i },
|
|
50
|
-
{ key: "fastify_hook", rx: /\.addHook\(\s*['"](onRequest|preHandler|preValidation)['"]/ },
|
|
51
|
-
{ key: "fastify_jwt", rx: /@fastify\/jwt|fastify-jwt|fastify\.jwt/i },
|
|
52
|
-
];
|
|
53
|
-
|
|
54
|
-
for (const p of patterns) {
|
|
55
|
-
if (p.rx.test(code)) signals.push(p.key);
|
|
56
|
-
}
|
|
57
|
-
return Array.from(new Set(signals));
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
async function resolveNextMiddleware(repoRoot) {
|
|
61
|
-
const candidates = await fg(
|
|
62
|
-
["middleware.@(ts|js)", "src/middleware.@(ts|js)"],
|
|
63
|
-
{ cwd: repoRoot, absolute: true, onlyFiles: true }
|
|
64
|
-
);
|
|
65
|
-
|
|
66
|
-
const middlewares = [];
|
|
67
|
-
|
|
68
|
-
for (const fileAbs of candidates) {
|
|
69
|
-
const code = safeRead(fileAbs);
|
|
70
|
-
const fileRel = path.relative(repoRoot, fileAbs).replace(/\\/g, "/");
|
|
71
|
-
|
|
72
|
-
const matcherLines = findLineMatches(code, /\bmatcher\b/);
|
|
73
|
-
const redirectLines = findLineMatches(code, /\bNextResponse\.(redirect|rewrite)\b/);
|
|
74
|
-
|
|
75
|
-
const evidence = [];
|
|
76
|
-
for (const ln of matcherLines.slice(0, 5)) {
|
|
77
|
-
evidence.push(evidenceFromLine({ fileAbs, repoRoot, lineNo: ln, reason: "Next middleware matcher config" }));
|
|
78
|
-
}
|
|
79
|
-
for (const ln of redirectLines.slice(0, 5)) {
|
|
80
|
-
evidence.push(evidenceFromLine({ fileAbs, repoRoot, lineNo: ln, reason: "Next middleware redirect/rewrite" }));
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
const matcher = [];
|
|
84
|
-
const matcherBlock = code.match(/matcher\s*:\s*(\[[\s\S]*?\])/);
|
|
85
|
-
if (matcherBlock && matcherBlock[1]) {
|
|
86
|
-
const raw = matcherBlock[1];
|
|
87
|
-
const strings = Array.from(raw.matchAll(/['"`]([^'"`]+)['"`]/g)).map(m => m[1]);
|
|
88
|
-
matcher.push(...strings);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
middlewares.push({
|
|
92
|
-
file: fileRel,
|
|
93
|
-
matcher,
|
|
94
|
-
signals: guessAuthSignalsFromCode(code),
|
|
95
|
-
evidence
|
|
96
|
-
});
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
return middlewares;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
async function resolveFastifyAuthSignals(repoRoot, truthpackRoutes) {
|
|
103
|
-
const handlerFiles = new Set((truthpackRoutes || []).map(r => r.handler).filter(Boolean));
|
|
104
|
-
const signals = [];
|
|
105
|
-
const evidence = [];
|
|
106
|
-
|
|
107
|
-
for (const fileRel of handlerFiles) {
|
|
108
|
-
const fileAbs = path.join(repoRoot, fileRel);
|
|
109
|
-
if (!fs.existsSync(fileAbs)) continue;
|
|
110
|
-
|
|
111
|
-
const code = safeRead(fileAbs);
|
|
112
|
-
const sigs = guessAuthSignalsFromCode(code);
|
|
113
|
-
if (!sigs.length) continue;
|
|
114
|
-
|
|
115
|
-
for (const s of sigs) signals.push({ type: s, file: fileRel });
|
|
116
|
-
|
|
117
|
-
const authLinePatterns = [
|
|
118
|
-
{ rx: /\.addHook\(\s*['"](onRequest|preHandler|preValidation)['"]/, reason: "Fastify hook likely used for auth" },
|
|
119
|
-
{ rx: /\b(jwtVerify|authorization|bearer)\b/i, reason: "JWT/Authorization verification signal" },
|
|
120
|
-
{ rx: /@fastify\/jwt|fastify\.jwt/i, reason: "Fastify JWT plugin signal" },
|
|
121
|
-
{ rx: /\b(isAdmin|adminOnly|permissions|rbac)\b/i, reason: "RBAC/permissions signal" },
|
|
122
|
-
];
|
|
123
|
-
|
|
124
|
-
const lines = code.split(/\r?\n/);
|
|
125
|
-
for (let i = 0; i < lines.length; i++) {
|
|
126
|
-
const line = lines[i];
|
|
127
|
-
for (const p of authLinePatterns) {
|
|
128
|
-
if (p.rx.test(line)) {
|
|
129
|
-
evidence.push({
|
|
130
|
-
id: `ev_${crypto.randomBytes(4).toString("hex")}`,
|
|
131
|
-
file: fileRel,
|
|
132
|
-
lines: `${i + 1}-${i + 1}`,
|
|
133
|
-
snippetHash: sha256(line),
|
|
134
|
-
reason: p.reason
|
|
135
|
-
});
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
if (evidence.length > 30) break;
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
const uniqueTypes = Array.from(new Set(signals.map(s => s.type)));
|
|
143
|
-
|
|
144
|
-
return {
|
|
145
|
-
signalTypes: uniqueTypes,
|
|
146
|
-
signals,
|
|
147
|
-
evidence
|
|
148
|
-
};
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
function matcherCoversPath(matcherList, p) {
|
|
152
|
-
if (!Array.isArray(matcherList) || !matcherList.length) return false;
|
|
153
|
-
const pathStr = p.startsWith("/") ? p : `/${p}`;
|
|
154
|
-
|
|
155
|
-
return matcherList.some(m => {
|
|
156
|
-
if (!m) return false;
|
|
157
|
-
|
|
158
|
-
if (m.includes(":path*")) {
|
|
159
|
-
const prefix = m.split(":path*")[0].replace(/\/$/, "");
|
|
160
|
-
return pathStr.startsWith(prefix || "/");
|
|
161
|
-
}
|
|
162
|
-
if (m.includes("(.*)")) {
|
|
163
|
-
const prefix = m.split("(.*)")[0].replace(/\/$/, "");
|
|
164
|
-
return pathStr.startsWith(prefix || "/");
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
if (m === pathStr) return true;
|
|
168
|
-
if (pathStr.startsWith(m.endsWith("/") ? m : m + "/")) return true;
|
|
169
|
-
|
|
170
|
-
return false;
|
|
171
|
-
});
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
async function buildAuthTruth(repoRoot, routesServer) {
|
|
175
|
-
const middlewares = await resolveNextMiddleware(repoRoot);
|
|
176
|
-
const matchers = middlewares.flatMap(mw => mw.matcher || []);
|
|
177
|
-
const fastify = await resolveFastifyAuthSignals(repoRoot, routesServer);
|
|
178
|
-
|
|
179
|
-
return {
|
|
180
|
-
nextMiddleware: middlewares,
|
|
181
|
-
nextMatcherPatterns: matchers,
|
|
182
|
-
fastify,
|
|
183
|
-
helpers: {
|
|
184
|
-
matcherCoversPath: "runtime-only"
|
|
185
|
-
}
|
|
186
|
-
};
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
module.exports = {
|
|
190
|
-
buildAuthTruth,
|
|
191
|
-
matcherCoversPath,
|
|
192
|
-
guessAuthSignalsFromCode
|
|
193
|
-
};
|
|
1
|
+
// bin/runners/lib/auth-truth.js
|
|
2
|
+
// Auth Truth v1 - Detects auth/protection patterns in Next + Fastify codebases
|
|
3
|
+
const fg = require("fast-glob");
|
|
4
|
+
const fs = require("fs");
|
|
5
|
+
const path = require("path");
|
|
6
|
+
const crypto = require("crypto");
|
|
7
|
+
|
|
8
|
+
function sha256(text) {
|
|
9
|
+
return "sha256:" + crypto.createHash("sha256").update(text).digest("hex");
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function safeRead(fileAbs) {
|
|
13
|
+
return fs.readFileSync(fileAbs, "utf8");
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function evidenceFromLine({ fileAbs, repoRoot, lineNo, reason }) {
|
|
17
|
+
const fileRel = path.relative(repoRoot, fileAbs).replace(/\\/g, "/");
|
|
18
|
+
const lines = safeRead(fileAbs).split(/\r?\n/);
|
|
19
|
+
const idx = Math.max(0, Math.min(lines.length - 1, lineNo - 1));
|
|
20
|
+
const snippet = lines[idx] || "";
|
|
21
|
+
return {
|
|
22
|
+
id: `ev_${crypto.randomBytes(4).toString("hex")}`,
|
|
23
|
+
file: fileRel,
|
|
24
|
+
lines: `${lineNo}-${lineNo}`,
|
|
25
|
+
snippetHash: sha256(snippet),
|
|
26
|
+
reason
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function findLineMatches(code, regex) {
|
|
31
|
+
const out = [];
|
|
32
|
+
const lines = code.split(/\r?\n/);
|
|
33
|
+
for (let i = 0; i < lines.length; i++) {
|
|
34
|
+
if (regex.test(lines[i])) out.push(i + 1);
|
|
35
|
+
}
|
|
36
|
+
return out;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function guessAuthSignalsFromCode(code) {
|
|
40
|
+
const signals = [];
|
|
41
|
+
|
|
42
|
+
const patterns = [
|
|
43
|
+
{ key: "next_middleware", rx: /\bNextResponse\.(redirect|rewrite)\b/ },
|
|
44
|
+
{ key: "next_auth", rx: /\bgetServerSession\b|\bNextAuth\b|\bauth\(\)\b/ },
|
|
45
|
+
{ key: "clerk", rx: /\bclerkMiddleware\b|\bauthMiddleware\b|@clerk\/nextjs/ },
|
|
46
|
+
{ key: "supabase", rx: /\bcreateRouteHandlerClient\b|\bcreateServerClient\b|@supabase/ },
|
|
47
|
+
{ key: "jwt_verify", rx: /\b(jwtVerify|verifyJWT|verifyToken|authorization|bearer)\b/i },
|
|
48
|
+
{ key: "session", rx: /\b(session|cookie|setCookie|getCookie)\b/i },
|
|
49
|
+
{ key: "rbac", rx: /\b(role|roles|permissions|rbac|isAdmin|adminOnly)\b/i },
|
|
50
|
+
{ key: "fastify_hook", rx: /\.addHook\(\s*['"](onRequest|preHandler|preValidation)['"]/ },
|
|
51
|
+
{ key: "fastify_jwt", rx: /@fastify\/jwt|fastify-jwt|fastify\.jwt/i },
|
|
52
|
+
];
|
|
53
|
+
|
|
54
|
+
for (const p of patterns) {
|
|
55
|
+
if (p.rx.test(code)) signals.push(p.key);
|
|
56
|
+
}
|
|
57
|
+
return Array.from(new Set(signals));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async function resolveNextMiddleware(repoRoot) {
|
|
61
|
+
const candidates = await fg(
|
|
62
|
+
["middleware.@(ts|js)", "src/middleware.@(ts|js)"],
|
|
63
|
+
{ cwd: repoRoot, absolute: true, onlyFiles: true }
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
const middlewares = [];
|
|
67
|
+
|
|
68
|
+
for (const fileAbs of candidates) {
|
|
69
|
+
const code = safeRead(fileAbs);
|
|
70
|
+
const fileRel = path.relative(repoRoot, fileAbs).replace(/\\/g, "/");
|
|
71
|
+
|
|
72
|
+
const matcherLines = findLineMatches(code, /\bmatcher\b/);
|
|
73
|
+
const redirectLines = findLineMatches(code, /\bNextResponse\.(redirect|rewrite)\b/);
|
|
74
|
+
|
|
75
|
+
const evidence = [];
|
|
76
|
+
for (const ln of matcherLines.slice(0, 5)) {
|
|
77
|
+
evidence.push(evidenceFromLine({ fileAbs, repoRoot, lineNo: ln, reason: "Next middleware matcher config" }));
|
|
78
|
+
}
|
|
79
|
+
for (const ln of redirectLines.slice(0, 5)) {
|
|
80
|
+
evidence.push(evidenceFromLine({ fileAbs, repoRoot, lineNo: ln, reason: "Next middleware redirect/rewrite" }));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const matcher = [];
|
|
84
|
+
const matcherBlock = code.match(/matcher\s*:\s*(\[[\s\S]*?\])/);
|
|
85
|
+
if (matcherBlock && matcherBlock[1]) {
|
|
86
|
+
const raw = matcherBlock[1];
|
|
87
|
+
const strings = Array.from(raw.matchAll(/['"`]([^'"`]+)['"`]/g)).map(m => m[1]);
|
|
88
|
+
matcher.push(...strings);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
middlewares.push({
|
|
92
|
+
file: fileRel,
|
|
93
|
+
matcher,
|
|
94
|
+
signals: guessAuthSignalsFromCode(code),
|
|
95
|
+
evidence
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return middlewares;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async function resolveFastifyAuthSignals(repoRoot, truthpackRoutes) {
|
|
103
|
+
const handlerFiles = new Set((truthpackRoutes || []).map(r => r.handler).filter(Boolean));
|
|
104
|
+
const signals = [];
|
|
105
|
+
const evidence = [];
|
|
106
|
+
|
|
107
|
+
for (const fileRel of handlerFiles) {
|
|
108
|
+
const fileAbs = path.join(repoRoot, fileRel);
|
|
109
|
+
if (!fs.existsSync(fileAbs)) continue;
|
|
110
|
+
|
|
111
|
+
const code = safeRead(fileAbs);
|
|
112
|
+
const sigs = guessAuthSignalsFromCode(code);
|
|
113
|
+
if (!sigs.length) continue;
|
|
114
|
+
|
|
115
|
+
for (const s of sigs) signals.push({ type: s, file: fileRel });
|
|
116
|
+
|
|
117
|
+
const authLinePatterns = [
|
|
118
|
+
{ rx: /\.addHook\(\s*['"](onRequest|preHandler|preValidation)['"]/, reason: "Fastify hook likely used for auth" },
|
|
119
|
+
{ rx: /\b(jwtVerify|authorization|bearer)\b/i, reason: "JWT/Authorization verification signal" },
|
|
120
|
+
{ rx: /@fastify\/jwt|fastify\.jwt/i, reason: "Fastify JWT plugin signal" },
|
|
121
|
+
{ rx: /\b(isAdmin|adminOnly|permissions|rbac)\b/i, reason: "RBAC/permissions signal" },
|
|
122
|
+
];
|
|
123
|
+
|
|
124
|
+
const lines = code.split(/\r?\n/);
|
|
125
|
+
for (let i = 0; i < lines.length; i++) {
|
|
126
|
+
const line = lines[i];
|
|
127
|
+
for (const p of authLinePatterns) {
|
|
128
|
+
if (p.rx.test(line)) {
|
|
129
|
+
evidence.push({
|
|
130
|
+
id: `ev_${crypto.randomBytes(4).toString("hex")}`,
|
|
131
|
+
file: fileRel,
|
|
132
|
+
lines: `${i + 1}-${i + 1}`,
|
|
133
|
+
snippetHash: sha256(line),
|
|
134
|
+
reason: p.reason
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
if (evidence.length > 30) break;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const uniqueTypes = Array.from(new Set(signals.map(s => s.type)));
|
|
143
|
+
|
|
144
|
+
return {
|
|
145
|
+
signalTypes: uniqueTypes,
|
|
146
|
+
signals,
|
|
147
|
+
evidence
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function matcherCoversPath(matcherList, p) {
|
|
152
|
+
if (!Array.isArray(matcherList) || !matcherList.length) return false;
|
|
153
|
+
const pathStr = p.startsWith("/") ? p : `/${p}`;
|
|
154
|
+
|
|
155
|
+
return matcherList.some(m => {
|
|
156
|
+
if (!m) return false;
|
|
157
|
+
|
|
158
|
+
if (m.includes(":path*")) {
|
|
159
|
+
const prefix = m.split(":path*")[0].replace(/\/$/, "");
|
|
160
|
+
return pathStr.startsWith(prefix || "/");
|
|
161
|
+
}
|
|
162
|
+
if (m.includes("(.*)")) {
|
|
163
|
+
const prefix = m.split("(.*)")[0].replace(/\/$/, "");
|
|
164
|
+
return pathStr.startsWith(prefix || "/");
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (m === pathStr) return true;
|
|
168
|
+
if (pathStr.startsWith(m.endsWith("/") ? m : m + "/")) return true;
|
|
169
|
+
|
|
170
|
+
return false;
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
async function buildAuthTruth(repoRoot, routesServer) {
|
|
175
|
+
const middlewares = await resolveNextMiddleware(repoRoot);
|
|
176
|
+
const matchers = middlewares.flatMap(mw => mw.matcher || []);
|
|
177
|
+
const fastify = await resolveFastifyAuthSignals(repoRoot, routesServer);
|
|
178
|
+
|
|
179
|
+
return {
|
|
180
|
+
nextMiddleware: middlewares,
|
|
181
|
+
nextMatcherPatterns: matchers,
|
|
182
|
+
fastify,
|
|
183
|
+
helpers: {
|
|
184
|
+
matcherCoversPath: "runtime-only"
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
module.exports = {
|
|
190
|
+
buildAuthTruth,
|
|
191
|
+
matcherCoversPath,
|
|
192
|
+
guessAuthSignalsFromCode
|
|
193
|
+
};
|