@vibecheckai/cli 3.8.0 → 3.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/runners/lib/agent-firewall/enforcement/index.js +98 -98
- package/bin/runners/lib/agent-firewall/enforcement/mode.js +318 -318
- package/bin/runners/lib/agent-firewall/enforcement/orchestrator.js +484 -484
- package/bin/runners/lib/agent-firewall/enforcement/proof-artifact.js +418 -418
- package/bin/runners/lib/agent-firewall/enforcement/verdict-v2.js +333 -333
- package/bin/runners/lib/agent-firewall/intent/alignment-engine.js +634 -622
- package/bin/runners/lib/agent-firewall/intent/index.js +102 -102
- package/bin/runners/lib/agent-firewall/intent/schema.js +352 -352
- package/bin/runners/lib/agent-firewall/intent/store.js +283 -283
- package/bin/runners/lib/agent-firewall/interceptor/base.js +7 -3
- package/bin/runners/lib/engine/ast-cache.js +210 -210
- package/bin/runners/lib/engine/auth-extractor.js +211 -211
- package/bin/runners/lib/engine/billing-extractor.js +112 -112
- package/bin/runners/lib/engine/enforcement-extractor.js +100 -100
- package/bin/runners/lib/engine/env-extractor.js +207 -207
- package/bin/runners/lib/engine/express-extractor.js +208 -208
- package/bin/runners/lib/engine/extractors.js +849 -849
- package/bin/runners/lib/engine/index.js +207 -207
- package/bin/runners/lib/engine/repo-index.js +514 -514
- package/bin/runners/lib/engine/types.js +124 -124
- package/bin/runners/lib/unified-cli-output.js +16 -0
- package/bin/runners/runCI.js +353 -0
- package/bin/runners/runCheckpoint.js +2 -2
- package/bin/runners/runIntent.js +906 -906
- package/bin/runners/runPacks.js +2089 -2089
- package/bin/runners/runReality.js +178 -1
- package/bin/runners/runShield.js +1282 -1282
- package/mcp-server/handlers/index.ts +2 -2
- package/mcp-server/handlers/tool-handler.ts +47 -8
- package/mcp-server/lib/executor.ts +5 -5
- package/mcp-server/lib/index.ts +14 -4
- package/mcp-server/lib/sandbox.test.ts +4 -4
- package/mcp-server/lib/sandbox.ts +2 -2
- package/mcp-server/package.json +1 -1
- package/mcp-server/registry.test.ts +18 -12
- package/mcp-server/tsconfig.json +1 -0
- package/package.json +2 -1
|
@@ -1,211 +1,211 @@
|
|
|
1
|
-
// bin/runners/lib/engine/auth-extractor.js
|
|
2
|
-
// Optimized auth/middleware extraction using RepoIndex
|
|
3
|
-
|
|
4
|
-
const path = require("path");
|
|
5
|
-
const fs = require("fs");
|
|
6
|
-
const crypto = require("crypto");
|
|
7
|
-
|
|
8
|
-
function sha256(text) {
|
|
9
|
-
return "sha256:" + crypto.createHash("sha256").update(text).digest("hex");
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
function evidenceFromContent(content, fileRel, lineNo, reason) {
|
|
13
|
-
if (!content) return null;
|
|
14
|
-
const lines = content.split(/\r?\n/);
|
|
15
|
-
const idx = Math.max(0, Math.min(lines.length - 1, lineNo - 1));
|
|
16
|
-
const snippet = lines[idx] || "";
|
|
17
|
-
return {
|
|
18
|
-
id: `ev_${crypto.randomBytes(4).toString("hex")}`,
|
|
19
|
-
file: fileRel,
|
|
20
|
-
lines: `${lineNo}-${lineNo}`,
|
|
21
|
-
snippetHash: sha256(snippet),
|
|
22
|
-
reason
|
|
23
|
-
};
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
function findLineMatches(code, regex) {
|
|
27
|
-
const out = [];
|
|
28
|
-
const lines = code.split(/\r?\n/);
|
|
29
|
-
for (let i = 0; i < lines.length; i++) {
|
|
30
|
-
if (regex.test(lines[i])) out.push(i + 1);
|
|
31
|
-
}
|
|
32
|
-
return out;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function guessAuthSignalsFromCode(code) {
|
|
36
|
-
const signals = [];
|
|
37
|
-
|
|
38
|
-
const patterns = [
|
|
39
|
-
{ key: "next_middleware", rx: /\bNextResponse\.(redirect|rewrite)\b/ },
|
|
40
|
-
{ key: "next_auth", rx: /\bgetServerSession\b|\bNextAuth\b|\bauth\(\)\b/ },
|
|
41
|
-
{ key: "clerk", rx: /\bclerkMiddleware\b|\bauthMiddleware\b|@clerk\/nextjs/ },
|
|
42
|
-
{ key: "supabase", rx: /\bcreateRouteHandlerClient\b|\bcreateServerClient\b|@supabase/ },
|
|
43
|
-
{ key: "jwt_verify", rx: /\b(jwtVerify|verifyJWT|verifyToken|authorization|bearer)\b/i },
|
|
44
|
-
{ key: "session", rx: /\b(session|cookie|setCookie|getCookie)\b/i },
|
|
45
|
-
{ key: "rbac", rx: /\b(role|roles|permissions|rbac|isAdmin|adminOnly)\b/i },
|
|
46
|
-
{ key: "fastify_hook", rx: /\.addHook\(\s*['"](onRequest|preHandler|preValidation)['"]/ },
|
|
47
|
-
{ key: "fastify_jwt", rx: /@fastify\/jwt|fastify-jwt|fastify\.jwt/i },
|
|
48
|
-
];
|
|
49
|
-
|
|
50
|
-
for (const p of patterns) {
|
|
51
|
-
if (p.rx.test(code)) signals.push(p.key);
|
|
52
|
-
}
|
|
53
|
-
return Array.from(new Set(signals));
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Resolve Next.js middleware using RepoIndex
|
|
58
|
-
* @param {import('./repo-index').RepoIndex} index
|
|
59
|
-
* @returns {Array}
|
|
60
|
-
*/
|
|
61
|
-
function extractNextMiddleware(index) {
|
|
62
|
-
// Find middleware.ts/js files
|
|
63
|
-
const middlewareFiles = index.files.filter(f =>
|
|
64
|
-
/^(src\/)?middleware\.(ts|tsx|js|jsx)$/.test(f.rel)
|
|
65
|
-
);
|
|
66
|
-
|
|
67
|
-
const middlewares = [];
|
|
68
|
-
|
|
69
|
-
for (const file of middlewareFiles) {
|
|
70
|
-
const content = index.getContent(file.abs);
|
|
71
|
-
if (!content) continue;
|
|
72
|
-
|
|
73
|
-
const matcherLines = findLineMatches(content, /\bmatcher\b/);
|
|
74
|
-
const redirectLines = findLineMatches(content, /\bNextResponse\.(redirect|rewrite)\b/);
|
|
75
|
-
|
|
76
|
-
const evidence = [];
|
|
77
|
-
for (const ln of matcherLines.slice(0, 5)) {
|
|
78
|
-
evidence.push(evidenceFromContent(content, file.rel, ln, "Next middleware matcher config"));
|
|
79
|
-
}
|
|
80
|
-
for (const ln of redirectLines.slice(0, 5)) {
|
|
81
|
-
evidence.push(evidenceFromContent(content, file.rel, ln, "Next middleware redirect/rewrite"));
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
const matcher = [];
|
|
85
|
-
const matcherBlock = content.match(/matcher\s*:\s*(\[[\s\S]*?\])/);
|
|
86
|
-
if (matcherBlock && matcherBlock[1]) {
|
|
87
|
-
const raw = matcherBlock[1];
|
|
88
|
-
const strings = Array.from(raw.matchAll(/['"`]([^'"`]+)['"`]/g)).map(m => m[1]);
|
|
89
|
-
matcher.push(...strings);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
middlewares.push({
|
|
93
|
-
file: file.rel,
|
|
94
|
-
matcher,
|
|
95
|
-
signals: guessAuthSignalsFromCode(content),
|
|
96
|
-
evidence
|
|
97
|
-
});
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
return middlewares;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/**
|
|
104
|
-
* Resolve Fastify auth signals using RepoIndex
|
|
105
|
-
* @param {import('./repo-index').RepoIndex} index
|
|
106
|
-
* @param {Array} truthpackRoutes - Server routes from truthpack
|
|
107
|
-
* @returns {Object}
|
|
108
|
-
*/
|
|
109
|
-
function extractFastifyAuthSignals(index, truthpackRoutes) {
|
|
110
|
-
const handlerFiles = new Set((truthpackRoutes || []).map(r => r.handler).filter(Boolean));
|
|
111
|
-
const signals = [];
|
|
112
|
-
const evidence = [];
|
|
113
|
-
|
|
114
|
-
for (const fileRel of handlerFiles) {
|
|
115
|
-
// Try to get content from index first (fast path)
|
|
116
|
-
const fileAbs = path.join(index.repoRoot, fileRel);
|
|
117
|
-
let content = index.getContent(fileAbs);
|
|
118
|
-
|
|
119
|
-
// Fall back to direct read if not in index
|
|
120
|
-
if (!content) {
|
|
121
|
-
try {
|
|
122
|
-
content = fs.readFileSync(fileAbs, "utf8");
|
|
123
|
-
} catch {
|
|
124
|
-
continue;
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
const sigs = guessAuthSignalsFromCode(content);
|
|
129
|
-
if (!sigs.length) continue;
|
|
130
|
-
|
|
131
|
-
for (const s of sigs) signals.push({ type: s, file: fileRel });
|
|
132
|
-
|
|
133
|
-
const authLinePatterns = [
|
|
134
|
-
{ rx: /\.addHook\(\s*['"](onRequest|preHandler|preValidation)['"]/, reason: "Fastify hook likely used for auth" },
|
|
135
|
-
{ rx: /\b(jwtVerify|authorization|bearer)\b/i, reason: "JWT/Authorization verification signal" },
|
|
136
|
-
{ rx: /@fastify\/jwt|fastify\.jwt/i, reason: "Fastify JWT plugin signal" },
|
|
137
|
-
{ rx: /\b(isAdmin|adminOnly|permissions|rbac)\b/i, reason: "RBAC/permissions signal" },
|
|
138
|
-
];
|
|
139
|
-
|
|
140
|
-
const lines = content.split(/\r?\n/);
|
|
141
|
-
for (let i = 0; i < lines.length; i++) {
|
|
142
|
-
const line = lines[i];
|
|
143
|
-
for (const p of authLinePatterns) {
|
|
144
|
-
if (p.rx.test(line)) {
|
|
145
|
-
evidence.push(evidenceFromContent(content, fileRel, i + 1, p.reason));
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
if (evidence.length > 30) break;
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
const uniqueTypes = Array.from(new Set(signals.map(s => s.type)));
|
|
153
|
-
|
|
154
|
-
return {
|
|
155
|
-
signalTypes: uniqueTypes,
|
|
156
|
-
signals,
|
|
157
|
-
evidence
|
|
158
|
-
};
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
function matcherCoversPath(matcherList, p) {
|
|
162
|
-
if (!Array.isArray(matcherList) || !matcherList.length) return false;
|
|
163
|
-
const pathStr = p.startsWith("/") ? p : `/${p}`;
|
|
164
|
-
|
|
165
|
-
return matcherList.some(m => {
|
|
166
|
-
if (!m) return false;
|
|
167
|
-
|
|
168
|
-
if (m.includes(":path*")) {
|
|
169
|
-
const prefix = m.split(":path*")[0].replace(/\/$/, "");
|
|
170
|
-
return pathStr.startsWith(prefix || "/");
|
|
171
|
-
}
|
|
172
|
-
if (m.includes("(.*)")) {
|
|
173
|
-
const prefix = m.split("(.*)")[0].replace(/\/$/, "");
|
|
174
|
-
return pathStr.startsWith(prefix || "/");
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
if (m === pathStr) return true;
|
|
178
|
-
if (pathStr.startsWith(m.endsWith("/") ? m : m + "/")) return true;
|
|
179
|
-
|
|
180
|
-
return false;
|
|
181
|
-
});
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
/**
|
|
185
|
-
* Build auth truth using RepoIndex (optimized)
|
|
186
|
-
* @param {import('./repo-index').RepoIndex} index
|
|
187
|
-
* @param {Array} serverRoutes
|
|
188
|
-
* @returns {Object}
|
|
189
|
-
*/
|
|
190
|
-
function buildAuthTruthV2(index, serverRoutes) {
|
|
191
|
-
const middlewares = extractNextMiddleware(index);
|
|
192
|
-
const matchers = middlewares.flatMap(mw => mw.matcher || []);
|
|
193
|
-
const fastify = extractFastifyAuthSignals(index, serverRoutes);
|
|
194
|
-
|
|
195
|
-
return {
|
|
196
|
-
nextMiddleware: middlewares,
|
|
197
|
-
nextMatcherPatterns: matchers,
|
|
198
|
-
fastify,
|
|
199
|
-
helpers: {
|
|
200
|
-
matcherCoversPath: "runtime-only"
|
|
201
|
-
}
|
|
202
|
-
};
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
module.exports = {
|
|
206
|
-
buildAuthTruthV2,
|
|
207
|
-
extractNextMiddleware,
|
|
208
|
-
extractFastifyAuthSignals,
|
|
209
|
-
matcherCoversPath,
|
|
210
|
-
guessAuthSignalsFromCode,
|
|
211
|
-
};
|
|
1
|
+
// bin/runners/lib/engine/auth-extractor.js
|
|
2
|
+
// Optimized auth/middleware extraction using RepoIndex
|
|
3
|
+
|
|
4
|
+
const path = require("path");
|
|
5
|
+
const fs = require("fs");
|
|
6
|
+
const crypto = require("crypto");
|
|
7
|
+
|
|
8
|
+
function sha256(text) {
|
|
9
|
+
return "sha256:" + crypto.createHash("sha256").update(text).digest("hex");
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function evidenceFromContent(content, fileRel, lineNo, reason) {
|
|
13
|
+
if (!content) return null;
|
|
14
|
+
const lines = content.split(/\r?\n/);
|
|
15
|
+
const idx = Math.max(0, Math.min(lines.length - 1, lineNo - 1));
|
|
16
|
+
const snippet = lines[idx] || "";
|
|
17
|
+
return {
|
|
18
|
+
id: `ev_${crypto.randomBytes(4).toString("hex")}`,
|
|
19
|
+
file: fileRel,
|
|
20
|
+
lines: `${lineNo}-${lineNo}`,
|
|
21
|
+
snippetHash: sha256(snippet),
|
|
22
|
+
reason
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function findLineMatches(code, regex) {
|
|
27
|
+
const out = [];
|
|
28
|
+
const lines = code.split(/\r?\n/);
|
|
29
|
+
for (let i = 0; i < lines.length; i++) {
|
|
30
|
+
if (regex.test(lines[i])) out.push(i + 1);
|
|
31
|
+
}
|
|
32
|
+
return out;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function guessAuthSignalsFromCode(code) {
|
|
36
|
+
const signals = [];
|
|
37
|
+
|
|
38
|
+
const patterns = [
|
|
39
|
+
{ key: "next_middleware", rx: /\bNextResponse\.(redirect|rewrite)\b/ },
|
|
40
|
+
{ key: "next_auth", rx: /\bgetServerSession\b|\bNextAuth\b|\bauth\(\)\b/ },
|
|
41
|
+
{ key: "clerk", rx: /\bclerkMiddleware\b|\bauthMiddleware\b|@clerk\/nextjs/ },
|
|
42
|
+
{ key: "supabase", rx: /\bcreateRouteHandlerClient\b|\bcreateServerClient\b|@supabase/ },
|
|
43
|
+
{ key: "jwt_verify", rx: /\b(jwtVerify|verifyJWT|verifyToken|authorization|bearer)\b/i },
|
|
44
|
+
{ key: "session", rx: /\b(session|cookie|setCookie|getCookie)\b/i },
|
|
45
|
+
{ key: "rbac", rx: /\b(role|roles|permissions|rbac|isAdmin|adminOnly)\b/i },
|
|
46
|
+
{ key: "fastify_hook", rx: /\.addHook\(\s*['"](onRequest|preHandler|preValidation)['"]/ },
|
|
47
|
+
{ key: "fastify_jwt", rx: /@fastify\/jwt|fastify-jwt|fastify\.jwt/i },
|
|
48
|
+
];
|
|
49
|
+
|
|
50
|
+
for (const p of patterns) {
|
|
51
|
+
if (p.rx.test(code)) signals.push(p.key);
|
|
52
|
+
}
|
|
53
|
+
return Array.from(new Set(signals));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Resolve Next.js middleware using RepoIndex
|
|
58
|
+
* @param {import('./repo-index').RepoIndex} index
|
|
59
|
+
* @returns {Array}
|
|
60
|
+
*/
|
|
61
|
+
function extractNextMiddleware(index) {
|
|
62
|
+
// Find middleware.ts/js files
|
|
63
|
+
const middlewareFiles = index.files.filter(f =>
|
|
64
|
+
/^(src\/)?middleware\.(ts|tsx|js|jsx)$/.test(f.rel)
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
const middlewares = [];
|
|
68
|
+
|
|
69
|
+
for (const file of middlewareFiles) {
|
|
70
|
+
const content = index.getContent(file.abs);
|
|
71
|
+
if (!content) continue;
|
|
72
|
+
|
|
73
|
+
const matcherLines = findLineMatches(content, /\bmatcher\b/);
|
|
74
|
+
const redirectLines = findLineMatches(content, /\bNextResponse\.(redirect|rewrite)\b/);
|
|
75
|
+
|
|
76
|
+
const evidence = [];
|
|
77
|
+
for (const ln of matcherLines.slice(0, 5)) {
|
|
78
|
+
evidence.push(evidenceFromContent(content, file.rel, ln, "Next middleware matcher config"));
|
|
79
|
+
}
|
|
80
|
+
for (const ln of redirectLines.slice(0, 5)) {
|
|
81
|
+
evidence.push(evidenceFromContent(content, file.rel, ln, "Next middleware redirect/rewrite"));
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const matcher = [];
|
|
85
|
+
const matcherBlock = content.match(/matcher\s*:\s*(\[[\s\S]*?\])/);
|
|
86
|
+
if (matcherBlock && matcherBlock[1]) {
|
|
87
|
+
const raw = matcherBlock[1];
|
|
88
|
+
const strings = Array.from(raw.matchAll(/['"`]([^'"`]+)['"`]/g)).map(m => m[1]);
|
|
89
|
+
matcher.push(...strings);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
middlewares.push({
|
|
93
|
+
file: file.rel,
|
|
94
|
+
matcher,
|
|
95
|
+
signals: guessAuthSignalsFromCode(content),
|
|
96
|
+
evidence
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return middlewares;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Resolve Fastify auth signals using RepoIndex
|
|
105
|
+
* @param {import('./repo-index').RepoIndex} index
|
|
106
|
+
* @param {Array} truthpackRoutes - Server routes from truthpack
|
|
107
|
+
* @returns {Object}
|
|
108
|
+
*/
|
|
109
|
+
function extractFastifyAuthSignals(index, truthpackRoutes) {
|
|
110
|
+
const handlerFiles = new Set((truthpackRoutes || []).map(r => r.handler).filter(Boolean));
|
|
111
|
+
const signals = [];
|
|
112
|
+
const evidence = [];
|
|
113
|
+
|
|
114
|
+
for (const fileRel of handlerFiles) {
|
|
115
|
+
// Try to get content from index first (fast path)
|
|
116
|
+
const fileAbs = path.join(index.repoRoot, fileRel);
|
|
117
|
+
let content = index.getContent(fileAbs);
|
|
118
|
+
|
|
119
|
+
// Fall back to direct read if not in index
|
|
120
|
+
if (!content) {
|
|
121
|
+
try {
|
|
122
|
+
content = fs.readFileSync(fileAbs, "utf8");
|
|
123
|
+
} catch {
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const sigs = guessAuthSignalsFromCode(content);
|
|
129
|
+
if (!sigs.length) continue;
|
|
130
|
+
|
|
131
|
+
for (const s of sigs) signals.push({ type: s, file: fileRel });
|
|
132
|
+
|
|
133
|
+
const authLinePatterns = [
|
|
134
|
+
{ rx: /\.addHook\(\s*['"](onRequest|preHandler|preValidation)['"]/, reason: "Fastify hook likely used for auth" },
|
|
135
|
+
{ rx: /\b(jwtVerify|authorization|bearer)\b/i, reason: "JWT/Authorization verification signal" },
|
|
136
|
+
{ rx: /@fastify\/jwt|fastify\.jwt/i, reason: "Fastify JWT plugin signal" },
|
|
137
|
+
{ rx: /\b(isAdmin|adminOnly|permissions|rbac)\b/i, reason: "RBAC/permissions signal" },
|
|
138
|
+
];
|
|
139
|
+
|
|
140
|
+
const lines = content.split(/\r?\n/);
|
|
141
|
+
for (let i = 0; i < lines.length; i++) {
|
|
142
|
+
const line = lines[i];
|
|
143
|
+
for (const p of authLinePatterns) {
|
|
144
|
+
if (p.rx.test(line)) {
|
|
145
|
+
evidence.push(evidenceFromContent(content, fileRel, i + 1, p.reason));
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
if (evidence.length > 30) break;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const uniqueTypes = Array.from(new Set(signals.map(s => s.type)));
|
|
153
|
+
|
|
154
|
+
return {
|
|
155
|
+
signalTypes: uniqueTypes,
|
|
156
|
+
signals,
|
|
157
|
+
evidence
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function matcherCoversPath(matcherList, p) {
|
|
162
|
+
if (!Array.isArray(matcherList) || !matcherList.length) return false;
|
|
163
|
+
const pathStr = p.startsWith("/") ? p : `/${p}`;
|
|
164
|
+
|
|
165
|
+
return matcherList.some(m => {
|
|
166
|
+
if (!m) return false;
|
|
167
|
+
|
|
168
|
+
if (m.includes(":path*")) {
|
|
169
|
+
const prefix = m.split(":path*")[0].replace(/\/$/, "");
|
|
170
|
+
return pathStr.startsWith(prefix || "/");
|
|
171
|
+
}
|
|
172
|
+
if (m.includes("(.*)")) {
|
|
173
|
+
const prefix = m.split("(.*)")[0].replace(/\/$/, "");
|
|
174
|
+
return pathStr.startsWith(prefix || "/");
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (m === pathStr) return true;
|
|
178
|
+
if (pathStr.startsWith(m.endsWith("/") ? m : m + "/")) return true;
|
|
179
|
+
|
|
180
|
+
return false;
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Build auth truth using RepoIndex (optimized)
|
|
186
|
+
* @param {import('./repo-index').RepoIndex} index
|
|
187
|
+
* @param {Array} serverRoutes
|
|
188
|
+
* @returns {Object}
|
|
189
|
+
*/
|
|
190
|
+
function buildAuthTruthV2(index, serverRoutes) {
|
|
191
|
+
const middlewares = extractNextMiddleware(index);
|
|
192
|
+
const matchers = middlewares.flatMap(mw => mw.matcher || []);
|
|
193
|
+
const fastify = extractFastifyAuthSignals(index, serverRoutes);
|
|
194
|
+
|
|
195
|
+
return {
|
|
196
|
+
nextMiddleware: middlewares,
|
|
197
|
+
nextMatcherPatterns: matchers,
|
|
198
|
+
fastify,
|
|
199
|
+
helpers: {
|
|
200
|
+
matcherCoversPath: "runtime-only"
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
module.exports = {
|
|
206
|
+
buildAuthTruthV2,
|
|
207
|
+
extractNextMiddleware,
|
|
208
|
+
extractFastifyAuthSignals,
|
|
209
|
+
matcherCoversPath,
|
|
210
|
+
guessAuthSignalsFromCode,
|
|
211
|
+
};
|
|
@@ -1,112 +1,112 @@
|
|
|
1
|
-
// bin/runners/lib/engine/billing-extractor.js
|
|
2
|
-
// Optimized billing/Stripe extraction using RepoIndex
|
|
3
|
-
|
|
4
|
-
const path = require("path");
|
|
5
|
-
const crypto = require("crypto");
|
|
6
|
-
|
|
7
|
-
function sha256(text) {
|
|
8
|
-
return "sha256:" + crypto.createHash("sha256").update(text).digest("hex");
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
function evidenceFromContent(content, fileRel, lineNo, reason) {
|
|
12
|
-
if (!content) return null;
|
|
13
|
-
const lines = content.split(/\r?\n/);
|
|
14
|
-
const idx = Math.max(0, Math.min(lines.length - 1, lineNo - 1));
|
|
15
|
-
const snippet = lines[idx] || "";
|
|
16
|
-
return {
|
|
17
|
-
id: `ev_${crypto.randomBytes(4).toString("hex")}`,
|
|
18
|
-
file: fileRel,
|
|
19
|
-
lines: `${lineNo}-${lineNo}`,
|
|
20
|
-
snippetHash: sha256(snippet),
|
|
21
|
-
reason
|
|
22
|
-
};
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function findLineMatches(code, regex) {
|
|
26
|
-
const out = [];
|
|
27
|
-
const lines = code.split(/\r?\n/);
|
|
28
|
-
for (let i = 0; i < lines.length; i++) {
|
|
29
|
-
if (regex.test(lines[i])) out.push(i + 1);
|
|
30
|
-
}
|
|
31
|
-
return out;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function classifyStripeSignals(code) {
|
|
35
|
-
return {
|
|
36
|
-
usesStripeSdk: /\bstripe\b/i.test(code) && /from\s+['"]stripe['"]|require\(['"]stripe['"]\)/.test(code),
|
|
37
|
-
webhookConstructEvent: /\bconstructEvent(Async)?\b/.test(code) || /\bstripe\.webhooks\.constructEvent\b/.test(code),
|
|
38
|
-
readsStripeSignatureHeader: /stripe-signature/i.test(code) || /\bStripe-Signature\b/.test(code),
|
|
39
|
-
rawBodySignal:
|
|
40
|
-
/\bbodyParser\s*:\s*false\b/.test(code) ||
|
|
41
|
-
/\breq\.(text|arrayBuffer)\(\)/.test(code) ||
|
|
42
|
-
/\brawBody\b/.test(code) || /\brequest\.raw\b/.test(code) || /\bcontentTypeParser\b/i.test(code),
|
|
43
|
-
idempotencySignal:
|
|
44
|
-
/\bevent\.id\b/.test(code) && /\b(prisma|db|redis|cache|processed|dedupe|idempotent)\b/i.test(code) ||
|
|
45
|
-
/\bidempotenc(y|e)\b/i.test(code)
|
|
46
|
-
};
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Build billing truth using RepoIndex (optimized)
|
|
51
|
-
* @param {import('./repo-index').RepoIndex} index
|
|
52
|
-
* @param {Object} stats
|
|
53
|
-
* @returns {Object}
|
|
54
|
-
*/
|
|
55
|
-
function buildBillingTruthV2(index, stats) {
|
|
56
|
-
// Use token prefilter - only scan files that mention stripe
|
|
57
|
-
const candidateAbs = index.getByAnyToken(["stripe", "Stripe"]);
|
|
58
|
-
|
|
59
|
-
// Filter to JS/TS files
|
|
60
|
-
const jsExtensions = new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"]);
|
|
61
|
-
const files = candidateAbs.filter(abs => {
|
|
62
|
-
const ext = path.extname(abs).toLowerCase();
|
|
63
|
-
return jsExtensions.has(ext);
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
const webhookCandidates = [];
|
|
67
|
-
const stripeFiles = [];
|
|
68
|
-
|
|
69
|
-
for (const fileAbs of files) {
|
|
70
|
-
const fileRel = index.relPath(fileAbs);
|
|
71
|
-
const content = index.getContent(fileAbs);
|
|
72
|
-
if (!content) continue;
|
|
73
|
-
|
|
74
|
-
const signals = classifyStripeSignals(content);
|
|
75
|
-
|
|
76
|
-
if (signals.usesStripeSdk) stripeFiles.push(fileRel);
|
|
77
|
-
|
|
78
|
-
if (signals.webhookConstructEvent || signals.readsStripeSignatureHeader) {
|
|
79
|
-
const ev = [];
|
|
80
|
-
const lines1 = findLineMatches(content, /\bconstructEvent(Async)?\b|stripe\.webhooks\.constructEvent/);
|
|
81
|
-
const lines2 = findLineMatches(content, /stripe-signature|Stripe-Signature/i);
|
|
82
|
-
const lines3 = findLineMatches(content, /bodyParser\s*:\s*false|req\.(text|arrayBuffer)\(|rawBody|contentTypeParser/i);
|
|
83
|
-
const lines4 = findLineMatches(content, /event\.id|idempotenc(y|e)|dedupe|processed/i);
|
|
84
|
-
|
|
85
|
-
for (const ln of lines1.slice(0, 3)) ev.push(evidenceFromContent(content, fileRel, ln, "Stripe webhook signature constructEvent signal"));
|
|
86
|
-
for (const ln of lines2.slice(0, 3)) ev.push(evidenceFromContent(content, fileRel, ln, "Stripe-Signature header usage signal"));
|
|
87
|
-
for (const ln of lines3.slice(0, 3)) ev.push(evidenceFromContent(content, fileRel, ln, "Raw body handling signal (required for Stripe verification)"));
|
|
88
|
-
for (const ln of lines4.slice(0, 3)) ev.push(evidenceFromContent(content, fileRel, ln, "Idempotency/dedupe signal (event replay protection)"));
|
|
89
|
-
|
|
90
|
-
webhookCandidates.push({
|
|
91
|
-
file: fileRel,
|
|
92
|
-
signals,
|
|
93
|
-
evidence: ev
|
|
94
|
-
});
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
const hasStripe = stripeFiles.length > 0;
|
|
99
|
-
|
|
100
|
-
return {
|
|
101
|
-
hasStripe,
|
|
102
|
-
stripeFiles: stripeFiles.slice(0, 200),
|
|
103
|
-
webhookCandidates,
|
|
104
|
-
summary: {
|
|
105
|
-
webhookHandlersFound: webhookCandidates.length,
|
|
106
|
-
verifiedWebhookHandlers: webhookCandidates.filter(w => w.signals.webhookConstructEvent && w.signals.rawBodySignal).length,
|
|
107
|
-
idempotentWebhookHandlers: webhookCandidates.filter(w => w.signals.idempotencySignal).length
|
|
108
|
-
}
|
|
109
|
-
};
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
module.exports = { buildBillingTruthV2 };
|
|
1
|
+
// bin/runners/lib/engine/billing-extractor.js
|
|
2
|
+
// Optimized billing/Stripe extraction using RepoIndex
|
|
3
|
+
|
|
4
|
+
const path = require("path");
|
|
5
|
+
const crypto = require("crypto");
|
|
6
|
+
|
|
7
|
+
function sha256(text) {
|
|
8
|
+
return "sha256:" + crypto.createHash("sha256").update(text).digest("hex");
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function evidenceFromContent(content, fileRel, lineNo, reason) {
|
|
12
|
+
if (!content) return null;
|
|
13
|
+
const lines = content.split(/\r?\n/);
|
|
14
|
+
const idx = Math.max(0, Math.min(lines.length - 1, lineNo - 1));
|
|
15
|
+
const snippet = lines[idx] || "";
|
|
16
|
+
return {
|
|
17
|
+
id: `ev_${crypto.randomBytes(4).toString("hex")}`,
|
|
18
|
+
file: fileRel,
|
|
19
|
+
lines: `${lineNo}-${lineNo}`,
|
|
20
|
+
snippetHash: sha256(snippet),
|
|
21
|
+
reason
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function findLineMatches(code, regex) {
|
|
26
|
+
const out = [];
|
|
27
|
+
const lines = code.split(/\r?\n/);
|
|
28
|
+
for (let i = 0; i < lines.length; i++) {
|
|
29
|
+
if (regex.test(lines[i])) out.push(i + 1);
|
|
30
|
+
}
|
|
31
|
+
return out;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function classifyStripeSignals(code) {
|
|
35
|
+
return {
|
|
36
|
+
usesStripeSdk: /\bstripe\b/i.test(code) && /from\s+['"]stripe['"]|require\(['"]stripe['"]\)/.test(code),
|
|
37
|
+
webhookConstructEvent: /\bconstructEvent(Async)?\b/.test(code) || /\bstripe\.webhooks\.constructEvent\b/.test(code),
|
|
38
|
+
readsStripeSignatureHeader: /stripe-signature/i.test(code) || /\bStripe-Signature\b/.test(code),
|
|
39
|
+
rawBodySignal:
|
|
40
|
+
/\bbodyParser\s*:\s*false\b/.test(code) ||
|
|
41
|
+
/\breq\.(text|arrayBuffer)\(\)/.test(code) ||
|
|
42
|
+
/\brawBody\b/.test(code) || /\brequest\.raw\b/.test(code) || /\bcontentTypeParser\b/i.test(code),
|
|
43
|
+
idempotencySignal:
|
|
44
|
+
/\bevent\.id\b/.test(code) && /\b(prisma|db|redis|cache|processed|dedupe|idempotent)\b/i.test(code) ||
|
|
45
|
+
/\bidempotenc(y|e)\b/i.test(code)
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Build billing truth using RepoIndex (optimized)
|
|
51
|
+
* @param {import('./repo-index').RepoIndex} index
|
|
52
|
+
* @param {Object} stats
|
|
53
|
+
* @returns {Object}
|
|
54
|
+
*/
|
|
55
|
+
function buildBillingTruthV2(index, stats) {
|
|
56
|
+
// Use token prefilter - only scan files that mention stripe
|
|
57
|
+
const candidateAbs = index.getByAnyToken(["stripe", "Stripe"]);
|
|
58
|
+
|
|
59
|
+
// Filter to JS/TS files
|
|
60
|
+
const jsExtensions = new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"]);
|
|
61
|
+
const files = candidateAbs.filter(abs => {
|
|
62
|
+
const ext = path.extname(abs).toLowerCase();
|
|
63
|
+
return jsExtensions.has(ext);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
const webhookCandidates = [];
|
|
67
|
+
const stripeFiles = [];
|
|
68
|
+
|
|
69
|
+
for (const fileAbs of files) {
|
|
70
|
+
const fileRel = index.relPath(fileAbs);
|
|
71
|
+
const content = index.getContent(fileAbs);
|
|
72
|
+
if (!content) continue;
|
|
73
|
+
|
|
74
|
+
const signals = classifyStripeSignals(content);
|
|
75
|
+
|
|
76
|
+
if (signals.usesStripeSdk) stripeFiles.push(fileRel);
|
|
77
|
+
|
|
78
|
+
if (signals.webhookConstructEvent || signals.readsStripeSignatureHeader) {
|
|
79
|
+
const ev = [];
|
|
80
|
+
const lines1 = findLineMatches(content, /\bconstructEvent(Async)?\b|stripe\.webhooks\.constructEvent/);
|
|
81
|
+
const lines2 = findLineMatches(content, /stripe-signature|Stripe-Signature/i);
|
|
82
|
+
const lines3 = findLineMatches(content, /bodyParser\s*:\s*false|req\.(text|arrayBuffer)\(|rawBody|contentTypeParser/i);
|
|
83
|
+
const lines4 = findLineMatches(content, /event\.id|idempotenc(y|e)|dedupe|processed/i);
|
|
84
|
+
|
|
85
|
+
for (const ln of lines1.slice(0, 3)) ev.push(evidenceFromContent(content, fileRel, ln, "Stripe webhook signature constructEvent signal"));
|
|
86
|
+
for (const ln of lines2.slice(0, 3)) ev.push(evidenceFromContent(content, fileRel, ln, "Stripe-Signature header usage signal"));
|
|
87
|
+
for (const ln of lines3.slice(0, 3)) ev.push(evidenceFromContent(content, fileRel, ln, "Raw body handling signal (required for Stripe verification)"));
|
|
88
|
+
for (const ln of lines4.slice(0, 3)) ev.push(evidenceFromContent(content, fileRel, ln, "Idempotency/dedupe signal (event replay protection)"));
|
|
89
|
+
|
|
90
|
+
webhookCandidates.push({
|
|
91
|
+
file: fileRel,
|
|
92
|
+
signals,
|
|
93
|
+
evidence: ev
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const hasStripe = stripeFiles.length > 0;
|
|
99
|
+
|
|
100
|
+
return {
|
|
101
|
+
hasStripe,
|
|
102
|
+
stripeFiles: stripeFiles.slice(0, 200),
|
|
103
|
+
webhookCandidates,
|
|
104
|
+
summary: {
|
|
105
|
+
webhookHandlersFound: webhookCandidates.length,
|
|
106
|
+
verifiedWebhookHandlers: webhookCandidates.filter(w => w.signals.webhookConstructEvent && w.signals.rawBodySignal).length,
|
|
107
|
+
idempotentWebhookHandlers: webhookCandidates.filter(w => w.signals.idempotencySignal).length
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
module.exports = { buildBillingTruthV2 };
|