@vibecheckai/cli 3.2.2 → 3.2.4
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/ENHANCEMENT_GUIDE.md +121 -121
- package/bin/runners/lib/__tests__/entitlements-v2.test.js +295 -295
- package/bin/runners/lib/agent-firewall/ai/false-positive-analyzer.js +474 -0
- package/bin/runners/lib/agent-firewall/claims/extractor.js +117 -28
- package/bin/runners/lib/agent-firewall/evidence/env-evidence.js +23 -14
- package/bin/runners/lib/agent-firewall/evidence/route-evidence.js +72 -1
- package/bin/runners/lib/agent-firewall/interceptor/base.js +2 -2
- package/bin/runners/lib/agent-firewall/policy/default-policy.json +6 -0
- package/bin/runners/lib/agent-firewall/policy/engine.js +34 -3
- package/bin/runners/lib/agent-firewall/policy/rules/fake-success.js +29 -4
- package/bin/runners/lib/agent-firewall/policy/rules/ghost-route.js +12 -0
- package/bin/runners/lib/agent-firewall/truthpack/loader.js +21 -0
- package/bin/runners/lib/agent-firewall/utils/ignore-checker.js +118 -0
- package/bin/runners/lib/analyzers.js +606 -325
- 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/engines/accessibility-engine.js +190 -0
- package/bin/runners/lib/engines/api-consistency-engine.js +162 -0
- package/bin/runners/lib/engines/ast-cache.js +99 -0
- package/bin/runners/lib/engines/code-quality-engine.js +255 -0
- package/bin/runners/lib/engines/console-logs-engine.js +115 -0
- package/bin/runners/lib/engines/cross-file-analysis-engine.js +268 -0
- package/bin/runners/lib/engines/dead-code-engine.js +198 -0
- package/bin/runners/lib/engines/deprecated-api-engine.js +226 -0
- package/bin/runners/lib/engines/empty-catch-engine.js +150 -0
- package/bin/runners/lib/engines/file-filter.js +131 -0
- package/bin/runners/lib/engines/hardcoded-secrets-engine.js +251 -0
- package/bin/runners/lib/engines/mock-data-engine.js +272 -0
- package/bin/runners/lib/engines/parallel-processor.js +71 -0
- package/bin/runners/lib/engines/performance-issues-engine.js +265 -0
- package/bin/runners/lib/engines/security-vulnerabilities-engine.js +243 -0
- package/bin/runners/lib/engines/todo-fixme-engine.js +115 -0
- package/bin/runners/lib/engines/type-aware-engine.js +152 -0
- package/bin/runners/lib/engines/unsafe-regex-engine.js +225 -0
- package/bin/runners/lib/engines/vibecheck-engines/README.md +53 -0
- package/bin/runners/lib/engines/vibecheck-engines/index.js +15 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/ast-cache.js +164 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/code-quality-engine.js +291 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/console-logs-engine.js +83 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/dead-code-engine.js +198 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/deprecated-api-engine.js +275 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/empty-catch-engine.js +167 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/file-filter.js +217 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/hardcoded-secrets-engine.js +139 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/mock-data-engine.js +140 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/parallel-processor.js +164 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/performance-issues-engine.js +234 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/type-aware-engine.js +217 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/unsafe-regex-engine.js +78 -0
- package/bin/runners/lib/engines/vibecheck-engines/package.json +13 -0
- 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/global-flags.js +213 -213
- 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/interactive-menu.js +1496 -1496
- 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-output.js +187 -187
- 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/scan-output.js +525 -190
- 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/status-output.js +253 -253
- package/bin/runners/lib/terminal-ui.js +351 -271
- 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/runGuard.js +168 -168
- package/bin/runners/runProof.zip +0 -0
- package/bin/runners/runProve.js +8 -0
- package/bin/runners/runReality.js +14 -0
- package/bin/runners/runScan.js +17 -1
- package/bin/runners/runTruth.js +15 -3
- package/mcp-server/tier-auth.js +4 -4
- package/mcp-server/tools/index.js +72 -72
- package/package.json +1 -1
|
@@ -1,199 +1,199 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Route Contract Builder
|
|
3
|
-
* Builds routes.json contract from truthpack
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
"use strict";
|
|
7
|
-
|
|
8
|
-
const crypto = require("crypto");
|
|
9
|
-
|
|
10
|
-
function sha256(text) {
|
|
11
|
-
return crypto.createHash("sha256").update(text).digest("hex").slice(0, 16);
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Build routes contract from truthpack
|
|
16
|
-
*/
|
|
17
|
-
function buildRouteContract(truthpack) {
|
|
18
|
-
const contract = {
|
|
19
|
-
version: "1.0.0",
|
|
20
|
-
generatedAt: new Date().toISOString(),
|
|
21
|
-
routes: []
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
const serverRoutes = truthpack?.routes?.server || [];
|
|
25
|
-
|
|
26
|
-
for (const route of serverRoutes) {
|
|
27
|
-
const routeSpec = {
|
|
28
|
-
id: `route_${sha256(route.method + "_" + route.path)}`,
|
|
29
|
-
method: route.method,
|
|
30
|
-
path: route.path,
|
|
31
|
-
handler: route.handler || "unknown",
|
|
32
|
-
auth: inferAuthRequirement(route, truthpack),
|
|
33
|
-
roles: inferRoles(route, truthpack),
|
|
34
|
-
confidence: route.confidence || "med",
|
|
35
|
-
evidence: route.evidence || []
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
contract.routes.push(routeSpec);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
// Deterministic output: sort routes by method + path
|
|
42
|
-
contract.routes.sort((a, b) => {
|
|
43
|
-
const keyA = `${a.method}_${a.path}`;
|
|
44
|
-
const keyB = `${b.method}_${b.path}`;
|
|
45
|
-
return keyA.localeCompare(keyB);
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
return contract;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Infer auth requirement from route and truthpack
|
|
53
|
-
*/
|
|
54
|
-
function inferAuthRequirement(route, truthpack) {
|
|
55
|
-
const authPatterns = truthpack?.auth?.nextMatcherPatterns || [];
|
|
56
|
-
const path = route.path;
|
|
57
|
-
|
|
58
|
-
// Check if path matches any protected pattern
|
|
59
|
-
for (const pattern of authPatterns) {
|
|
60
|
-
if (matchesPattern(path, pattern)) {
|
|
61
|
-
return "required";
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
// Check for auth hooks in Fastify
|
|
66
|
-
if (route.hooks?.includes("onRequest") || route.hooks?.includes("preHandler")) {
|
|
67
|
-
return "required";
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// Check for public API patterns
|
|
71
|
-
if (path.includes("/public/") || path.includes("/health") || path.includes("/status")) {
|
|
72
|
-
return "none";
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
return "optional";
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Infer roles from route metadata
|
|
80
|
-
*/
|
|
81
|
-
function inferRoles(route, truthpack) {
|
|
82
|
-
const roles = [];
|
|
83
|
-
|
|
84
|
-
// Check for admin patterns in path
|
|
85
|
-
if (route.path.includes("/admin")) {
|
|
86
|
-
roles.push("admin");
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// Check handler for role patterns
|
|
90
|
-
const handler = route.handler || "";
|
|
91
|
-
if (handler.includes("admin")) {
|
|
92
|
-
roles.push("admin");
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
return roles.length > 0 ? roles : undefined;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
function matchesPattern(path, pattern) {
|
|
99
|
-
// Simple pattern matching
|
|
100
|
-
const normPattern = pattern.replace(/\*/g, ".*").replace(/\//g, "\\/");
|
|
101
|
-
try {
|
|
102
|
-
const rx = new RegExp(`^${normPattern}`, "i");
|
|
103
|
-
return rx.test(path);
|
|
104
|
-
} catch {
|
|
105
|
-
return false;
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* Validate code against route contract
|
|
111
|
-
*/
|
|
112
|
-
function validateAgainstRouteContract(contract, clientRefs) {
|
|
113
|
-
const violations = [];
|
|
114
|
-
const contractPaths = new Map(contract.routes.map(r => [`${r.method}_${r.path}`, r]));
|
|
115
|
-
|
|
116
|
-
for (const ref of clientRefs) {
|
|
117
|
-
const key = `${ref.method}_${ref.path}`;
|
|
118
|
-
const wildcardKey = `*_${ref.path}`;
|
|
119
|
-
|
|
120
|
-
if (!contractPaths.has(key) && !contractPaths.has(wildcardKey)) {
|
|
121
|
-
// Check parameterized match
|
|
122
|
-
const match = findParameterizedMatch(contract.routes, ref.method, ref.path);
|
|
123
|
-
|
|
124
|
-
if (!match) {
|
|
125
|
-
violations.push({
|
|
126
|
-
type: "undeclared_route",
|
|
127
|
-
severity: "BLOCK",
|
|
128
|
-
route: { method: ref.method, path: ref.path },
|
|
129
|
-
source: ref.source,
|
|
130
|
-
message: `Route ${ref.method} ${ref.path} used in client but not declared in contract`,
|
|
131
|
-
evidence: ref.evidence || []
|
|
132
|
-
});
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
return violations;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
function findParameterizedMatch(routes, method, path) {
|
|
141
|
-
for (const r of routes) {
|
|
142
|
-
if (r.method !== "*" && r.method !== method) continue;
|
|
143
|
-
if (matchesParameterized(r.path, path)) return r;
|
|
144
|
-
}
|
|
145
|
-
return null;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
function matchesParameterized(pattern, actual) {
|
|
149
|
-
const patternParts = pattern.split("/").filter(Boolean);
|
|
150
|
-
const actualParts = actual.split("/").filter(Boolean);
|
|
151
|
-
|
|
152
|
-
if (patternParts.length !== actualParts.length) return false;
|
|
153
|
-
|
|
154
|
-
for (let i = 0; i < patternParts.length; i++) {
|
|
155
|
-
const p = patternParts[i];
|
|
156
|
-
if (p.startsWith(":") || p.startsWith("*")) continue;
|
|
157
|
-
if (p !== actualParts[i]) return false;
|
|
158
|
-
}
|
|
159
|
-
return true;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
/**
|
|
163
|
-
* Diff two route contracts
|
|
164
|
-
*/
|
|
165
|
-
function diffRouteContracts(before, after) {
|
|
166
|
-
const diff = {
|
|
167
|
-
added: [],
|
|
168
|
-
removed: [],
|
|
169
|
-
changed: []
|
|
170
|
-
};
|
|
171
|
-
|
|
172
|
-
const beforeMap = new Map(before.routes.map(r => [r.id, r]));
|
|
173
|
-
const afterMap = new Map(after.routes.map(r => [r.id, r]));
|
|
174
|
-
|
|
175
|
-
for (const [id, route] of afterMap) {
|
|
176
|
-
if (!beforeMap.has(id)) {
|
|
177
|
-
diff.added.push(route);
|
|
178
|
-
} else {
|
|
179
|
-
const prev = beforeMap.get(id);
|
|
180
|
-
if (prev.auth !== route.auth || JSON.stringify(prev.roles) !== JSON.stringify(route.roles)) {
|
|
181
|
-
diff.changed.push({ before: prev, after: route });
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
for (const [id, route] of beforeMap) {
|
|
187
|
-
if (!afterMap.has(id)) {
|
|
188
|
-
diff.removed.push(route);
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
return diff;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
module.exports = {
|
|
196
|
-
buildRouteContract,
|
|
197
|
-
validateAgainstRouteContract,
|
|
198
|
-
diffRouteContracts
|
|
199
|
-
};
|
|
1
|
+
/**
|
|
2
|
+
* Route Contract Builder
|
|
3
|
+
* Builds routes.json contract from truthpack
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
"use strict";
|
|
7
|
+
|
|
8
|
+
const crypto = require("crypto");
|
|
9
|
+
|
|
10
|
+
function sha256(text) {
|
|
11
|
+
return crypto.createHash("sha256").update(text).digest("hex").slice(0, 16);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Build routes contract from truthpack
|
|
16
|
+
*/
|
|
17
|
+
function buildRouteContract(truthpack) {
|
|
18
|
+
const contract = {
|
|
19
|
+
version: "1.0.0",
|
|
20
|
+
generatedAt: new Date().toISOString(),
|
|
21
|
+
routes: []
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const serverRoutes = truthpack?.routes?.server || [];
|
|
25
|
+
|
|
26
|
+
for (const route of serverRoutes) {
|
|
27
|
+
const routeSpec = {
|
|
28
|
+
id: `route_${sha256(route.method + "_" + route.path)}`,
|
|
29
|
+
method: route.method,
|
|
30
|
+
path: route.path,
|
|
31
|
+
handler: route.handler || "unknown",
|
|
32
|
+
auth: inferAuthRequirement(route, truthpack),
|
|
33
|
+
roles: inferRoles(route, truthpack),
|
|
34
|
+
confidence: route.confidence || "med",
|
|
35
|
+
evidence: route.evidence || []
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
contract.routes.push(routeSpec);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Deterministic output: sort routes by method + path
|
|
42
|
+
contract.routes.sort((a, b) => {
|
|
43
|
+
const keyA = `${a.method}_${a.path}`;
|
|
44
|
+
const keyB = `${b.method}_${b.path}`;
|
|
45
|
+
return keyA.localeCompare(keyB);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
return contract;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Infer auth requirement from route and truthpack
|
|
53
|
+
*/
|
|
54
|
+
function inferAuthRequirement(route, truthpack) {
|
|
55
|
+
const authPatterns = truthpack?.auth?.nextMatcherPatterns || [];
|
|
56
|
+
const path = route.path;
|
|
57
|
+
|
|
58
|
+
// Check if path matches any protected pattern
|
|
59
|
+
for (const pattern of authPatterns) {
|
|
60
|
+
if (matchesPattern(path, pattern)) {
|
|
61
|
+
return "required";
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Check for auth hooks in Fastify
|
|
66
|
+
if (route.hooks?.includes("onRequest") || route.hooks?.includes("preHandler")) {
|
|
67
|
+
return "required";
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Check for public API patterns
|
|
71
|
+
if (path.includes("/public/") || path.includes("/health") || path.includes("/status")) {
|
|
72
|
+
return "none";
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return "optional";
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Infer roles from route metadata
|
|
80
|
+
*/
|
|
81
|
+
function inferRoles(route, truthpack) {
|
|
82
|
+
const roles = [];
|
|
83
|
+
|
|
84
|
+
// Check for admin patterns in path
|
|
85
|
+
if (route.path.includes("/admin")) {
|
|
86
|
+
roles.push("admin");
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Check handler for role patterns
|
|
90
|
+
const handler = route.handler || "";
|
|
91
|
+
if (handler.includes("admin")) {
|
|
92
|
+
roles.push("admin");
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return roles.length > 0 ? roles : undefined;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function matchesPattern(path, pattern) {
|
|
99
|
+
// Simple pattern matching
|
|
100
|
+
const normPattern = pattern.replace(/\*/g, ".*").replace(/\//g, "\\/");
|
|
101
|
+
try {
|
|
102
|
+
const rx = new RegExp(`^${normPattern}`, "i");
|
|
103
|
+
return rx.test(path);
|
|
104
|
+
} catch {
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Validate code against route contract
|
|
111
|
+
*/
|
|
112
|
+
function validateAgainstRouteContract(contract, clientRefs) {
|
|
113
|
+
const violations = [];
|
|
114
|
+
const contractPaths = new Map(contract.routes.map(r => [`${r.method}_${r.path}`, r]));
|
|
115
|
+
|
|
116
|
+
for (const ref of clientRefs) {
|
|
117
|
+
const key = `${ref.method}_${ref.path}`;
|
|
118
|
+
const wildcardKey = `*_${ref.path}`;
|
|
119
|
+
|
|
120
|
+
if (!contractPaths.has(key) && !contractPaths.has(wildcardKey)) {
|
|
121
|
+
// Check parameterized match
|
|
122
|
+
const match = findParameterizedMatch(contract.routes, ref.method, ref.path);
|
|
123
|
+
|
|
124
|
+
if (!match) {
|
|
125
|
+
violations.push({
|
|
126
|
+
type: "undeclared_route",
|
|
127
|
+
severity: "BLOCK",
|
|
128
|
+
route: { method: ref.method, path: ref.path },
|
|
129
|
+
source: ref.source,
|
|
130
|
+
message: `Route ${ref.method} ${ref.path} used in client but not declared in contract`,
|
|
131
|
+
evidence: ref.evidence || []
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return violations;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function findParameterizedMatch(routes, method, path) {
|
|
141
|
+
for (const r of routes) {
|
|
142
|
+
if (r.method !== "*" && r.method !== method) continue;
|
|
143
|
+
if (matchesParameterized(r.path, path)) return r;
|
|
144
|
+
}
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function matchesParameterized(pattern, actual) {
|
|
149
|
+
const patternParts = pattern.split("/").filter(Boolean);
|
|
150
|
+
const actualParts = actual.split("/").filter(Boolean);
|
|
151
|
+
|
|
152
|
+
if (patternParts.length !== actualParts.length) return false;
|
|
153
|
+
|
|
154
|
+
for (let i = 0; i < patternParts.length; i++) {
|
|
155
|
+
const p = patternParts[i];
|
|
156
|
+
if (p.startsWith(":") || p.startsWith("*")) continue;
|
|
157
|
+
if (p !== actualParts[i]) return false;
|
|
158
|
+
}
|
|
159
|
+
return true;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Diff two route contracts
|
|
164
|
+
*/
|
|
165
|
+
function diffRouteContracts(before, after) {
|
|
166
|
+
const diff = {
|
|
167
|
+
added: [],
|
|
168
|
+
removed: [],
|
|
169
|
+
changed: []
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
const beforeMap = new Map(before.routes.map(r => [r.id, r]));
|
|
173
|
+
const afterMap = new Map(after.routes.map(r => [r.id, r]));
|
|
174
|
+
|
|
175
|
+
for (const [id, route] of afterMap) {
|
|
176
|
+
if (!beforeMap.has(id)) {
|
|
177
|
+
diff.added.push(route);
|
|
178
|
+
} else {
|
|
179
|
+
const prev = beforeMap.get(id);
|
|
180
|
+
if (prev.auth !== route.auth || JSON.stringify(prev.roles) !== JSON.stringify(route.roles)) {
|
|
181
|
+
diff.changed.push({ before: prev, after: route });
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
for (const [id, route] of beforeMap) {
|
|
187
|
+
if (!afterMap.has(id)) {
|
|
188
|
+
diff.removed.push(route);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return diff;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
module.exports = {
|
|
196
|
+
buildRouteContract,
|
|
197
|
+
validateAgainstRouteContract,
|
|
198
|
+
diffRouteContracts
|
|
199
|
+
};
|