@vibecheckai/cli 3.0.2 → 3.0.3
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/package.json +9 -1
- package/bin/cli-hygiene.js +0 -241
- package/bin/guardrail.js +0 -834
- package/bin/runners/cli-utils.js +0 -1070
- package/bin/runners/context/ai-task-decomposer.js +0 -337
- package/bin/runners/context/analyzer.js +0 -462
- package/bin/runners/context/api-contracts.js +0 -427
- package/bin/runners/context/context-diff.js +0 -342
- package/bin/runners/context/context-pruner.js +0 -291
- package/bin/runners/context/dependency-graph.js +0 -414
- package/bin/runners/context/generators/claude.js +0 -107
- package/bin/runners/context/generators/codex.js +0 -108
- package/bin/runners/context/generators/copilot.js +0 -119
- package/bin/runners/context/generators/cursor.js +0 -514
- package/bin/runners/context/generators/mcp.js +0 -151
- package/bin/runners/context/generators/windsurf.js +0 -180
- package/bin/runners/context/git-context.js +0 -302
- package/bin/runners/context/index.js +0 -1042
- package/bin/runners/context/insights.js +0 -173
- package/bin/runners/context/mcp-server/generate-rules.js +0 -337
- package/bin/runners/context/mcp-server/index.js +0 -1176
- package/bin/runners/context/mcp-server/package.json +0 -24
- package/bin/runners/context/memory.js +0 -200
- package/bin/runners/context/monorepo.js +0 -215
- package/bin/runners/context/multi-repo-federation.js +0 -404
- package/bin/runners/context/patterns.js +0 -253
- package/bin/runners/context/proof-context.js +0 -972
- package/bin/runners/context/security-scanner.js +0 -303
- package/bin/runners/context/semantic-search.js +0 -350
- package/bin/runners/context/shared.js +0 -264
- package/bin/runners/context/team-conventions.js +0 -310
- package/bin/runners/lib/ai-bridge.js +0 -416
- package/bin/runners/lib/analysis-core.js +0 -271
- package/bin/runners/lib/analyzers.js +0 -541
- package/bin/runners/lib/audit-bridge.js +0 -391
- package/bin/runners/lib/auth-truth.js +0 -193
- package/bin/runners/lib/auth.js +0 -215
- package/bin/runners/lib/backup.js +0 -62
- package/bin/runners/lib/billing.js +0 -107
- package/bin/runners/lib/claims.js +0 -118
- package/bin/runners/lib/cli-ui.js +0 -540
- package/bin/runners/lib/compliance-bridge-new.js +0 -0
- package/bin/runners/lib/compliance-bridge.js +0 -165
- package/bin/runners/lib/contracts/auth-contract.js +0 -194
- package/bin/runners/lib/contracts/env-contract.js +0 -178
- package/bin/runners/lib/contracts/external-contract.js +0 -198
- package/bin/runners/lib/contracts/guard.js +0 -168
- package/bin/runners/lib/contracts/index.js +0 -89
- package/bin/runners/lib/contracts/plan-validator.js +0 -311
- package/bin/runners/lib/contracts/route-contract.js +0 -192
- package/bin/runners/lib/detect.js +0 -89
- package/bin/runners/lib/doctor/autofix.js +0 -254
- package/bin/runners/lib/doctor/index.js +0 -37
- package/bin/runners/lib/doctor/modules/dependencies.js +0 -325
- package/bin/runners/lib/doctor/modules/index.js +0 -46
- package/bin/runners/lib/doctor/modules/network.js +0 -250
- package/bin/runners/lib/doctor/modules/project.js +0 -312
- package/bin/runners/lib/doctor/modules/runtime.js +0 -224
- package/bin/runners/lib/doctor/modules/security.js +0 -348
- package/bin/runners/lib/doctor/modules/system.js +0 -213
- package/bin/runners/lib/doctor/modules/vibecheck.js +0 -394
- package/bin/runners/lib/doctor/reporter.js +0 -262
- package/bin/runners/lib/doctor/service.js +0 -262
- package/bin/runners/lib/doctor/types.js +0 -113
- package/bin/runners/lib/doctor/ui.js +0 -263
- package/bin/runners/lib/doctor-enhanced.js +0 -233
- package/bin/runners/lib/doctor-v2.js +0 -608
- package/bin/runners/lib/enforcement.js +0 -72
|
@@ -1,391 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Audit Bridge - CLI Integration
|
|
3
|
-
*
|
|
4
|
-
* Provides a CommonJS wrapper for the audit trail functionality.
|
|
5
|
-
* Used by CLI runners to emit audit events.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
"use strict";
|
|
9
|
-
|
|
10
|
-
const path = require("path");
|
|
11
|
-
const fs = require("fs");
|
|
12
|
-
const crypto = require("crypto");
|
|
13
|
-
|
|
14
|
-
// Configuration
|
|
15
|
-
const AUDIT_DIR = ".vibecheck/audit";
|
|
16
|
-
const AUDIT_FILE = "audit.log.jsonl";
|
|
17
|
-
const GENESIS_HASH = "0".repeat(64);
|
|
18
|
-
|
|
19
|
-
// Tier from environment or default
|
|
20
|
-
function getCurrentTier() {
|
|
21
|
-
return process.env.VIBECHECK_TIER || "free";
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
// Get current actor from environment
|
|
25
|
-
function getCurrentActor() {
|
|
26
|
-
const userId = process.env.VIBECHECK_USER_ID || process.env.USER || process.env.USERNAME || "anonymous";
|
|
27
|
-
const userName = process.env.VIBECHECK_USER_NAME || process.env.USERNAME;
|
|
28
|
-
const userEmail = process.env.VIBECHECK_USER_EMAIL;
|
|
29
|
-
|
|
30
|
-
// Detect CI environment
|
|
31
|
-
if (process.env.CI || process.env.GITHUB_ACTIONS || process.env.GITLAB_CI) {
|
|
32
|
-
return {
|
|
33
|
-
id: process.env.GITHUB_ACTOR || process.env.GITLAB_USER_LOGIN || "ci-system",
|
|
34
|
-
type: "ci",
|
|
35
|
-
name: process.env.GITHUB_ACTOR || process.env.GITLAB_USER_NAME,
|
|
36
|
-
};
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
return {
|
|
40
|
-
id: userId,
|
|
41
|
-
type: "user",
|
|
42
|
-
name: userName,
|
|
43
|
-
email: userEmail,
|
|
44
|
-
};
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
// Redaction patterns for sensitive data
|
|
48
|
-
const REDACTION_PATTERNS = [
|
|
49
|
-
/(?:api[_-]?key|apikey|token|secret|password|pwd|auth)[=:]\s*['"]?([a-zA-Z0-9_\-]{16,})['"]?/gi,
|
|
50
|
-
/eyJ[a-zA-Z0-9_-]+\.eyJ[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+/g,
|
|
51
|
-
/(?:AKIA|ABIA|ACCA|ASIA)[A-Z0-9]{16}/g,
|
|
52
|
-
/(?:sk_live_|sk_test_|pk_live_|pk_test_)[a-zA-Z0-9]+/g,
|
|
53
|
-
];
|
|
54
|
-
|
|
55
|
-
function redactSensitive(input) {
|
|
56
|
-
if (typeof input !== "string") return input;
|
|
57
|
-
let result = input;
|
|
58
|
-
for (const pattern of REDACTION_PATTERNS) {
|
|
59
|
-
result = result.replace(pattern, "[REDACTED]");
|
|
60
|
-
}
|
|
61
|
-
return result;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
function redactMetadata(metadata, tier) {
|
|
65
|
-
if (!metadata) return undefined;
|
|
66
|
-
|
|
67
|
-
// Compliance+ gets full metadata (still redact secrets)
|
|
68
|
-
if (["compliance", "enterprise", "unlimited"].includes(tier)) {
|
|
69
|
-
return redactObject(metadata);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// Pro gets limited metadata
|
|
73
|
-
if (tier === "pro") {
|
|
74
|
-
return {
|
|
75
|
-
command: metadata.command,
|
|
76
|
-
score: metadata.score,
|
|
77
|
-
grade: metadata.grade,
|
|
78
|
-
issueCount: metadata.issueCount,
|
|
79
|
-
fixCount: metadata.fixCount,
|
|
80
|
-
durationMs: metadata.durationMs,
|
|
81
|
-
errorCode: metadata.errorCode,
|
|
82
|
-
};
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// Free/Starter get minimal
|
|
86
|
-
return {
|
|
87
|
-
score: metadata.score,
|
|
88
|
-
grade: metadata.grade,
|
|
89
|
-
};
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
function redactObject(obj) {
|
|
93
|
-
if (!obj || typeof obj !== "object") return obj;
|
|
94
|
-
const result = {};
|
|
95
|
-
for (const [key, value] of Object.entries(obj)) {
|
|
96
|
-
if (typeof value === "string") {
|
|
97
|
-
result[key] = redactSensitive(value);
|
|
98
|
-
} else if (Array.isArray(value)) {
|
|
99
|
-
result[key] = value.map((v) => (typeof v === "string" ? redactSensitive(v) : v));
|
|
100
|
-
} else if (typeof value === "object" && value !== null) {
|
|
101
|
-
result[key] = redactObject(value);
|
|
102
|
-
} else {
|
|
103
|
-
result[key] = value;
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
return result;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
// Compute SHA-256 hash
|
|
110
|
-
function computeHash(event) {
|
|
111
|
-
const payload = JSON.stringify({
|
|
112
|
-
id: event.id,
|
|
113
|
-
timestamp: event.timestamp,
|
|
114
|
-
actor: event.actor,
|
|
115
|
-
surface: event.surface,
|
|
116
|
-
action: event.action,
|
|
117
|
-
category: event.category,
|
|
118
|
-
target: event.target,
|
|
119
|
-
tier: event.tier,
|
|
120
|
-
result: event.result,
|
|
121
|
-
metadata: event.metadata,
|
|
122
|
-
prevHash: event.prevHash,
|
|
123
|
-
version: event.version,
|
|
124
|
-
});
|
|
125
|
-
return crypto.createHash("sha256").update(payload).digest("hex");
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
// Get audit file path
|
|
129
|
-
function getAuditFilePath(basePath = process.cwd()) {
|
|
130
|
-
return path.join(basePath, AUDIT_DIR, AUDIT_FILE);
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
// Ensure audit directory exists
|
|
134
|
-
function ensureAuditDir(basePath = process.cwd()) {
|
|
135
|
-
const dir = path.join(basePath, AUDIT_DIR);
|
|
136
|
-
if (!fs.existsSync(dir)) {
|
|
137
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
// Get last hash from audit log
|
|
142
|
-
function getLastHash(basePath = process.cwd()) {
|
|
143
|
-
const filePath = getAuditFilePath(basePath);
|
|
144
|
-
if (!fs.existsSync(filePath)) {
|
|
145
|
-
return GENESIS_HASH;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
const content = fs.readFileSync(filePath, "utf8");
|
|
149
|
-
const lines = content.split("\n").filter((line) => line.trim());
|
|
150
|
-
if (lines.length === 0) {
|
|
151
|
-
return GENESIS_HASH;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
try {
|
|
155
|
-
const lastEvent = JSON.parse(lines[lines.length - 1]);
|
|
156
|
-
return lastEvent.hash || GENESIS_HASH;
|
|
157
|
-
} catch {
|
|
158
|
-
return GENESIS_HASH;
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
// Create audit event
|
|
163
|
-
function createEvent(input, prevHash) {
|
|
164
|
-
const tier = getCurrentTier();
|
|
165
|
-
const id = crypto.randomUUID();
|
|
166
|
-
const timestamp = new Date().toISOString();
|
|
167
|
-
|
|
168
|
-
const eventWithoutHash = {
|
|
169
|
-
id,
|
|
170
|
-
timestamp,
|
|
171
|
-
actor: input.actor || getCurrentActor(),
|
|
172
|
-
surface: input.surface,
|
|
173
|
-
action: input.action,
|
|
174
|
-
category: input.category,
|
|
175
|
-
target: input.target,
|
|
176
|
-
tier,
|
|
177
|
-
result: input.result,
|
|
178
|
-
metadata: redactMetadata(input.metadata, tier),
|
|
179
|
-
prevHash,
|
|
180
|
-
version: 1,
|
|
181
|
-
};
|
|
182
|
-
|
|
183
|
-
const hash = computeHash(eventWithoutHash);
|
|
184
|
-
|
|
185
|
-
return {
|
|
186
|
-
...eventWithoutHash,
|
|
187
|
-
hash,
|
|
188
|
-
};
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
// Emit audit event
|
|
192
|
-
function emit(input, basePath = process.cwd()) {
|
|
193
|
-
try {
|
|
194
|
-
ensureAuditDir(basePath);
|
|
195
|
-
const prevHash = getLastHash(basePath);
|
|
196
|
-
const event = createEvent(input, prevHash);
|
|
197
|
-
|
|
198
|
-
const filePath = getAuditFilePath(basePath);
|
|
199
|
-
fs.appendFileSync(filePath, JSON.stringify(event) + "\n", "utf8");
|
|
200
|
-
|
|
201
|
-
return event;
|
|
202
|
-
} catch (err) {
|
|
203
|
-
// Silently fail - audit should not break main functionality
|
|
204
|
-
if (process.env.VIBECHECK_DEBUG) {
|
|
205
|
-
console.error("[audit] Failed to emit event:", err.message);
|
|
206
|
-
}
|
|
207
|
-
return null;
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
// Pre-defined actions
|
|
212
|
-
const AuditActions = {
|
|
213
|
-
SCAN_START: "scan.start",
|
|
214
|
-
SCAN_COMPLETE: "scan.complete",
|
|
215
|
-
SCAN_ERROR: "scan.error",
|
|
216
|
-
SHIP_CHECK: "ship.check",
|
|
217
|
-
SHIP_APPROVE: "ship.approve",
|
|
218
|
-
SHIP_REJECT: "ship.reject",
|
|
219
|
-
REALITY_START: "reality.start",
|
|
220
|
-
REALITY_COMPLETE: "reality.complete",
|
|
221
|
-
REALITY_ERROR: "reality.error",
|
|
222
|
-
AUTOPILOT_ENABLE: "autopilot.enable",
|
|
223
|
-
AUTOPILOT_DISABLE: "autopilot.disable",
|
|
224
|
-
AUTOPILOT_RUN: "autopilot.run",
|
|
225
|
-
AUTOPILOT_REPORT: "autopilot.report",
|
|
226
|
-
FIX_PLAN: "fix.plan",
|
|
227
|
-
FIX_APPLY: "fix.apply",
|
|
228
|
-
FIX_REVERT: "fix.revert",
|
|
229
|
-
GATE_CHECK: "gate.check",
|
|
230
|
-
GATE_PASS: "gate.pass",
|
|
231
|
-
GATE_FAIL: "gate.fail",
|
|
232
|
-
AUTH_LOGIN: "auth.login",
|
|
233
|
-
AUTH_LOGOUT: "auth.logout",
|
|
234
|
-
TOOL_INVOKE: "tool.invoke",
|
|
235
|
-
TOOL_COMPLETE: "tool.complete",
|
|
236
|
-
TOOL_ERROR: "tool.error",
|
|
237
|
-
};
|
|
238
|
-
|
|
239
|
-
// Convenience emitters
|
|
240
|
-
function emitScanStart(projectPath, args) {
|
|
241
|
-
return emit({
|
|
242
|
-
surface: "cli",
|
|
243
|
-
action: AuditActions.SCAN_START,
|
|
244
|
-
category: "scan",
|
|
245
|
-
target: { type: "project", path: projectPath },
|
|
246
|
-
result: "success",
|
|
247
|
-
metadata: { command: "scan", args, projectPath },
|
|
248
|
-
});
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
function emitScanComplete(projectPath, result, metadata) {
|
|
252
|
-
return emit({
|
|
253
|
-
surface: "cli",
|
|
254
|
-
action: AuditActions.SCAN_COMPLETE,
|
|
255
|
-
category: "scan",
|
|
256
|
-
target: { type: "project", path: projectPath },
|
|
257
|
-
result,
|
|
258
|
-
metadata: { command: "scan", projectPath, ...metadata },
|
|
259
|
-
});
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
function emitShipCheck(projectPath, result, metadata) {
|
|
263
|
-
return emit({
|
|
264
|
-
surface: "cli",
|
|
265
|
-
action: AuditActions.SHIP_CHECK,
|
|
266
|
-
category: "ship",
|
|
267
|
-
target: { type: "project", path: projectPath },
|
|
268
|
-
result,
|
|
269
|
-
metadata: { command: "ship", projectPath, ...metadata },
|
|
270
|
-
});
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
function emitRealityStart(url, flows) {
|
|
274
|
-
return emit({
|
|
275
|
-
surface: "cli",
|
|
276
|
-
action: AuditActions.REALITY_START,
|
|
277
|
-
category: "reality",
|
|
278
|
-
target: { type: "url", path: url },
|
|
279
|
-
result: "success",
|
|
280
|
-
metadata: { command: "reality", url, flows },
|
|
281
|
-
});
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
function emitRealityComplete(url, result, metadata) {
|
|
285
|
-
return emit({
|
|
286
|
-
surface: "cli",
|
|
287
|
-
action: AuditActions.REALITY_COMPLETE,
|
|
288
|
-
category: "reality",
|
|
289
|
-
target: { type: "url", path: url },
|
|
290
|
-
result,
|
|
291
|
-
metadata: { command: "reality", ...metadata },
|
|
292
|
-
});
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
function emitAutopilotAction(action, projectPath, result, metadata) {
|
|
296
|
-
const actionMap = {
|
|
297
|
-
enable: AuditActions.AUTOPILOT_ENABLE,
|
|
298
|
-
disable: AuditActions.AUTOPILOT_DISABLE,
|
|
299
|
-
run: AuditActions.AUTOPILOT_RUN,
|
|
300
|
-
report: AuditActions.AUTOPILOT_REPORT,
|
|
301
|
-
};
|
|
302
|
-
|
|
303
|
-
return emit({
|
|
304
|
-
surface: "cli",
|
|
305
|
-
action: actionMap[action] || action,
|
|
306
|
-
category: "autopilot",
|
|
307
|
-
target: { type: "project", path: projectPath },
|
|
308
|
-
result,
|
|
309
|
-
metadata: { command: "autopilot", projectPath, ...metadata },
|
|
310
|
-
});
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
function emitFixPlan(projectPath, result, metadata) {
|
|
314
|
-
return emit({
|
|
315
|
-
surface: "cli",
|
|
316
|
-
action: AuditActions.FIX_PLAN,
|
|
317
|
-
category: "fix",
|
|
318
|
-
target: { type: "project", path: projectPath },
|
|
319
|
-
result,
|
|
320
|
-
metadata: { command: "fix", projectPath, ...metadata },
|
|
321
|
-
});
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
function emitFixApply(projectPath, result, metadata) {
|
|
325
|
-
return emit({
|
|
326
|
-
surface: "cli",
|
|
327
|
-
action: AuditActions.FIX_APPLY,
|
|
328
|
-
category: "fix",
|
|
329
|
-
target: { type: "project", path: projectPath },
|
|
330
|
-
result,
|
|
331
|
-
metadata: { command: "fix", projectPath, ...metadata },
|
|
332
|
-
});
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
function emitGateCheck(projectPath, passed, metadata) {
|
|
336
|
-
return emit({
|
|
337
|
-
surface: "cli",
|
|
338
|
-
action: passed ? AuditActions.GATE_PASS : AuditActions.GATE_FAIL,
|
|
339
|
-
category: "gate",
|
|
340
|
-
target: { type: "project", path: projectPath },
|
|
341
|
-
result: passed ? "success" : "failure",
|
|
342
|
-
metadata: { command: "gate", projectPath, ...metadata },
|
|
343
|
-
});
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
function emitToolInvoke(surface, toolName, args, result, metadata) {
|
|
347
|
-
return emit({
|
|
348
|
-
surface,
|
|
349
|
-
action: AuditActions.TOOL_INVOKE,
|
|
350
|
-
category: "tool",
|
|
351
|
-
target: { type: "tool", name: toolName },
|
|
352
|
-
result,
|
|
353
|
-
metadata: { command: toolName, args, ...metadata },
|
|
354
|
-
});
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
function emitAuth(action, result, metadata) {
|
|
358
|
-
const actionMap = {
|
|
359
|
-
login: AuditActions.AUTH_LOGIN,
|
|
360
|
-
logout: AuditActions.AUTH_LOGOUT,
|
|
361
|
-
};
|
|
362
|
-
|
|
363
|
-
return emit({
|
|
364
|
-
surface: "cli",
|
|
365
|
-
action: actionMap[action] || action,
|
|
366
|
-
category: "auth",
|
|
367
|
-
target: { type: "auth" },
|
|
368
|
-
result,
|
|
369
|
-
metadata,
|
|
370
|
-
});
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
// Export the audit bridge
|
|
374
|
-
module.exports = {
|
|
375
|
-
emit,
|
|
376
|
-
AuditActions,
|
|
377
|
-
emitScanStart,
|
|
378
|
-
emitScanComplete,
|
|
379
|
-
emitShipCheck,
|
|
380
|
-
emitRealityStart,
|
|
381
|
-
emitRealityComplete,
|
|
382
|
-
emitAutopilotAction,
|
|
383
|
-
emitFixPlan,
|
|
384
|
-
emitFixApply,
|
|
385
|
-
emitGateCheck,
|
|
386
|
-
emitToolInvoke,
|
|
387
|
-
emitAuth,
|
|
388
|
-
getCurrentTier,
|
|
389
|
-
getCurrentActor,
|
|
390
|
-
getAuditFilePath,
|
|
391
|
-
};
|
|
@@ -1,193 +0,0 @@
|
|
|
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
|
-
};
|