@pensar/apex 2.1.3-canary.fff17bfd → 2.2.0
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/build/agent-3cdfwxcd.js +19 -0
- package/build/agent-q5hwjdtb.js +27 -0
- package/build/{agent-c06jb5zv.js → agent-yayy5x2a.js} +10 -8
- package/build/{apps-mxhfapvz.js → apps-0es69e2s.js} +17 -16
- package/build/{auth-9ysskv2q.js → auth-q73k9bh9.js} +17 -16
- package/build/authentication-tqx2brcv.js +19 -0
- package/build/blackboxAgent-d0smgqt7.js +19 -0
- package/build/{blackboxPentest-pfs3d1nx.js → blackboxPentest-h28nykk5.js} +15 -14
- package/build/{cli-90dw3gm4.js → cli-1cf5vy5m.js} +9 -6
- package/build/{cli-f8tgnegf.js → cli-1f636vzd.js} +2 -2
- package/build/{cli-tg7mcs43.js → cli-1re2kv5v.js} +5 -3
- package/build/cli-2jn8104w.js +195 -0
- package/build/{cli-29yqhrjq.js → cli-3k1x5rn3.js} +1 -1
- package/build/{cli-k941a7ft.js → cli-4tc6888c.js} +1 -1
- package/build/{cli-qb3eddc7.js → cli-51dkmt5p.js} +1 -1
- package/build/{cli-v34r5t8t.js → cli-6kx46ck0.js} +6186 -513
- package/build/{cli-a1acedxf.js → cli-7e5ad5js.js} +15 -6
- package/build/{cli-8qb62k90.js → cli-7n43wz82.js} +1 -1
- package/build/{cli-fj04wady.js → cli-knqx15w6.js} +5 -2
- package/build/{cli-9wd7jy2s.js → cli-n114n8mk.js} +102 -21
- package/build/{cli-9h847y2w.js → cli-ptzy9ak5.js} +2 -2
- package/build/{cli-g6cjpbr9.js → cli-s43t5ay0.js} +1 -1
- package/build/{cli-nth9zebw.js → cli-tz9yg4ew.js} +1147 -336
- package/build/{cli-2cvp6x4p.js → cli-vgg6h2ax.js} +1 -1
- package/build/{cli-akhax3yn.js → cli-xbdn034b.js} +671 -105
- package/build/{cli-wkpsqyjc.js → cli-xpn9tsw0.js} +27 -5
- package/build/cli.js +48 -39
- package/build/{config-ky5za80x.js → config-xcf7rc6x.js} +3 -3
- package/build/{doctor-t94dqmcs.js → doctor-98zperf9.js} +7 -7
- package/build/fastStrike-9tm6bedh.js +156 -0
- package/build/{fixes-fv2zmk0m.js → fixes-1dry8j61.js} +17 -16
- package/build/{index-s2jr2ve2.js → index-0j880wqc.js} +70 -44
- package/build/{index-8mgv8hke.js → index-31rwrakh.js} +2 -2
- package/build/{index-b7zrdy9b.js → index-34h2nb6x.js} +3 -3
- package/build/{index-pmsx73c3.js → index-4hqbyzf5.js} +11 -9
- package/build/{index-qshyncep.js → index-6dbym5jy.js} +8 -7
- package/build/{index-pycryjc3.js → index-f6ttv7np.js} +4 -4
- package/build/{index-n1yh9e86.js → index-gq9h8j5j.js} +1 -1
- package/build/{index-rxskwf3k.js → index-pxwwcefn.js} +10 -10
- package/build/{issues-bwdnb233.js → issues-wjakj8yy.js} +28 -16
- package/build/{logs-2g3rsh9f.js → logs-ewyzcspy.js} +17 -16
- package/build/{offesecAgent-4ykghkvs.js → offesecAgent-7m9smy0t.js} +9 -9
- package/build/pentest-1cb5t0dt.js +29 -0
- package/build/{pentests-d9dt495f.js → pentests-1hcqjyk2.js} +17 -16
- package/build/targetedPentest-3x13kqvc.js +56 -0
- package/build/{targets-3n7wsm3g.js → targets-nqz8w37e.js} +17 -16
- package/build/threatModel-7f02av7y.js +27 -0
- package/build/{uninstall-jvjv6d2n.js → uninstall-p1q5pkx2.js} +1 -1
- package/build/{upload-x2b8s1xb.js → upload-x7etpfp4.js} +7 -7
- package/build/{utils-kx8jqmv0.js → utils-kw6yh5yn.js} +7 -7
- package/package.json +2 -2
- package/build/agent-7ab546zf.js +0 -19
- package/build/agent-dsqssedb.js +0 -25
- package/build/authentication-h1zxd74t.js +0 -19
- package/build/blackboxAgent-32zwxt6w.js +0 -19
- package/build/pentest-91bygv3h.js +0 -28
- package/build/targetedPentest-eavq5h7v.js +0 -43
- package/build/threatModel-2re9tykx.js +0 -26
|
@@ -1,13 +1,20 @@
|
|
|
1
1
|
import {
|
|
2
2
|
BlackboxAttackSurfaceAgent
|
|
3
|
-
} from "./cli-
|
|
3
|
+
} from "./cli-1re2kv5v.js";
|
|
4
|
+
import {
|
|
5
|
+
REPORT_FILENAME_JSON,
|
|
6
|
+
REPORT_FILENAME_MD,
|
|
7
|
+
buildPentestReport,
|
|
8
|
+
renderJson,
|
|
9
|
+
renderMarkdown
|
|
10
|
+
} from "./cli-2jn8104w.js";
|
|
4
11
|
import {
|
|
5
12
|
TargetedPentestAgent,
|
|
6
13
|
buildPentestSystemPrompt
|
|
7
|
-
} from "./cli-
|
|
14
|
+
} from "./cli-n114n8mk.js";
|
|
8
15
|
import {
|
|
9
16
|
CodeAgent
|
|
10
|
-
} from "./cli-
|
|
17
|
+
} from "./cli-knqx15w6.js";
|
|
11
18
|
import {
|
|
12
19
|
AppsDiscoveryResultSchema,
|
|
13
20
|
DiscoverySummarySchema,
|
|
@@ -15,20 +22,16 @@ import {
|
|
|
15
22
|
WHITEBOX_APPS_DISCOVERY_SYSTEM_PROMPT,
|
|
16
23
|
WHITEBOX_DISCOVERY_SYSTEM_PROMPT,
|
|
17
24
|
WHITEBOX_ENDPOINT_DOCUMENTATION_SYSTEM_PROMPT
|
|
18
|
-
} from "./cli-
|
|
19
|
-
import {
|
|
20
|
-
EvidenceFileEntrySchema
|
|
21
|
-
} from "./cli-9h847y2w.js";
|
|
25
|
+
} from "./cli-7e5ad5js.js";
|
|
22
26
|
import {
|
|
23
|
-
CweEntrySchema,
|
|
24
27
|
FindingsRegistry,
|
|
25
28
|
OffensiveSecurityAgent,
|
|
26
29
|
PLAN_MODE_TOOL_NAMES,
|
|
27
|
-
|
|
28
|
-
hasCanonicalName,
|
|
30
|
+
grpcAuthorityTarget,
|
|
29
31
|
pLimit,
|
|
30
|
-
runWithBoundedConcurrency
|
|
31
|
-
|
|
32
|
+
runWithBoundedConcurrency,
|
|
33
|
+
toGrpcPentestContext
|
|
34
|
+
} from "./cli-xbdn034b.js";
|
|
32
35
|
import {
|
|
33
36
|
createThreatModelPrompt
|
|
34
37
|
} from "./cli-fw5r7pfj.js";
|
|
@@ -36,20 +39,21 @@ import {
|
|
|
36
39
|
createLogger,
|
|
37
40
|
hasToolCall,
|
|
38
41
|
init_dist,
|
|
42
|
+
init_id,
|
|
39
43
|
init_lazyLogger,
|
|
40
44
|
init_session,
|
|
41
45
|
init_structured,
|
|
46
|
+
newSessionId,
|
|
42
47
|
scopedLogger
|
|
43
|
-
} from "./cli-
|
|
48
|
+
} from "./cli-6kx46ck0.js";
|
|
44
49
|
import {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
} from "./cli-29yqhrjq.js";
|
|
50
|
+
__require
|
|
51
|
+
} from "./cli-8rxa073f.js";
|
|
48
52
|
|
|
49
53
|
// src/core/workflows/pentest.ts
|
|
50
54
|
init_dist();
|
|
51
55
|
import { existsSync as existsSync13, readdirSync as readdirSync12, readFileSync as readFileSync11, writeFileSync as writeFileSync4 } from "node:fs";
|
|
52
|
-
import { join as
|
|
56
|
+
import { join as join18 } from "node:path";
|
|
53
57
|
|
|
54
58
|
// src/core/agents/specialized/pentest/planPrompt.ts
|
|
55
59
|
var PLAN_PHASE_SYSTEM_PROMPT = `You are an expert penetration tester performing reconnaissance and planning.
|
|
@@ -76,14 +80,23 @@ Guidelines:
|
|
|
76
80
|
- Rank attack vectors by likelihood of success and potential impact
|
|
77
81
|
- Each task should be atomic and testable independently
|
|
78
82
|
- Include authentication bypass, injection, access control, and business logic testing as appropriate`;
|
|
79
|
-
function buildPlanPrompt(target, objectives) {
|
|
83
|
+
function buildPlanPrompt(target, objectives, grpc) {
|
|
80
84
|
const objectiveList = objectives.map((o, i) => `${i + 1}. ${o}`).join(`
|
|
81
85
|
`);
|
|
86
|
+
const grpcSection = grpc ? `
|
|
87
|
+
## gRPC Target
|
|
88
|
+
This target is a **gRPC method**, not a plain HTTP endpoint — plan gRPC-specific attack vectors (reflection/schema recovery, per-method missing-auth, BOLA on request-message IDs, metadata trust, \`:path\` authz bypass, protobuf fuzzing, streaming abuse) rather than HTTP-centric ones.
|
|
89
|
+
- **Transport:** ${grpc.transport}
|
|
90
|
+
- **Service:** ${grpc.serviceFqn}
|
|
91
|
+
- **Method:** ${grpc.method}${grpc.streamingType ? `
|
|
92
|
+
- **Streaming:** ${grpc.streamingType}` : ""}${grpc.host ? `
|
|
93
|
+
- **Authority (host):** ${grpc.host}` : ""}
|
|
94
|
+
` : "";
|
|
82
95
|
return `# Planning Assignment
|
|
83
96
|
|
|
84
97
|
## Target
|
|
85
98
|
- **URL:** ${target}
|
|
86
|
-
|
|
99
|
+
${grpcSection}
|
|
87
100
|
## Objectives
|
|
88
101
|
${objectiveList}
|
|
89
102
|
|
|
@@ -97,185 +110,6 @@ ${objectiveList}
|
|
|
97
110
|
// src/core/workflows/pentest.ts
|
|
98
111
|
init_structured();
|
|
99
112
|
|
|
100
|
-
// src/core/report/schemas.ts
|
|
101
|
-
init_zod();
|
|
102
|
-
var PentestReportFindingSchema = exports_external.object({
|
|
103
|
-
title: exports_external.string(),
|
|
104
|
-
severity: exports_external.enum(["CRITICAL", "HIGH", "MEDIUM", "LOW"]),
|
|
105
|
-
description: exports_external.string(),
|
|
106
|
-
impact: exports_external.string(),
|
|
107
|
-
evidence: exports_external.string(),
|
|
108
|
-
endpoint: exports_external.string(),
|
|
109
|
-
pocPath: exports_external.string(),
|
|
110
|
-
remediation: exports_external.string(),
|
|
111
|
-
references: exports_external.string().optional(),
|
|
112
|
-
cwes: exports_external.array(ValidatedCweEntrySchema.or(CweEntrySchema)).optional(),
|
|
113
|
-
rootCauseGroup: exports_external.string().optional(),
|
|
114
|
-
relatedFindings: exports_external.array(exports_external.string()).optional(),
|
|
115
|
-
rootCauseLead: exports_external.boolean().optional(),
|
|
116
|
-
evidenceFiles: exports_external.array(EvidenceFileEntrySchema).optional()
|
|
117
|
-
});
|
|
118
|
-
var PentestReportSchema = exports_external.object({
|
|
119
|
-
version: exports_external.string().regex(/^1\.\d+$/),
|
|
120
|
-
metadata: exports_external.object({
|
|
121
|
-
target: exports_external.string(),
|
|
122
|
-
model: exports_external.string(),
|
|
123
|
-
timestamp: exports_external.string(),
|
|
124
|
-
sessionId: exports_external.string(),
|
|
125
|
-
mode: exports_external.enum(["blackbox", "whitebox", "targeted"])
|
|
126
|
-
}),
|
|
127
|
-
summary: exports_external.object({
|
|
128
|
-
totalFindings: exports_external.number(),
|
|
129
|
-
bySeverity: exports_external.object({
|
|
130
|
-
CRITICAL: exports_external.number(),
|
|
131
|
-
HIGH: exports_external.number(),
|
|
132
|
-
MEDIUM: exports_external.number(),
|
|
133
|
-
LOW: exports_external.number()
|
|
134
|
-
})
|
|
135
|
-
}),
|
|
136
|
-
findings: exports_external.array(PentestReportFindingSchema)
|
|
137
|
-
});
|
|
138
|
-
var REPORT_VERSION = "1.0";
|
|
139
|
-
|
|
140
|
-
// src/core/report/builder.ts
|
|
141
|
-
var SEVERITY_ORDER = ["CRITICAL", "HIGH", "MEDIUM", "LOW"];
|
|
142
|
-
function buildPentestReport(findings, context) {
|
|
143
|
-
const sorted = [...findings].sort((a, b) => SEVERITY_ORDER.indexOf(a.severity) - SEVERITY_ORDER.indexOf(b.severity));
|
|
144
|
-
const bySeverity = { CRITICAL: 0, HIGH: 0, MEDIUM: 0, LOW: 0 };
|
|
145
|
-
for (const f of findings) {
|
|
146
|
-
bySeverity[f.severity]++;
|
|
147
|
-
}
|
|
148
|
-
return {
|
|
149
|
-
version: REPORT_VERSION,
|
|
150
|
-
metadata: {
|
|
151
|
-
target: context.target,
|
|
152
|
-
model: context.model,
|
|
153
|
-
timestamp: new Date().toISOString(),
|
|
154
|
-
sessionId: context.sessionId,
|
|
155
|
-
mode: context.mode
|
|
156
|
-
},
|
|
157
|
-
summary: {
|
|
158
|
-
totalFindings: findings.length,
|
|
159
|
-
bySeverity
|
|
160
|
-
},
|
|
161
|
-
findings: sorted.map((f) => ({
|
|
162
|
-
title: f.title,
|
|
163
|
-
severity: f.severity,
|
|
164
|
-
description: f.description,
|
|
165
|
-
impact: f.impact,
|
|
166
|
-
evidence: f.evidence,
|
|
167
|
-
endpoint: f.endpoint,
|
|
168
|
-
pocPath: f.pocPath,
|
|
169
|
-
remediation: f.remediation,
|
|
170
|
-
references: f.references,
|
|
171
|
-
cwes: f.cwes,
|
|
172
|
-
rootCauseGroup: f.rootCauseGroup,
|
|
173
|
-
relatedFindings: f.relatedFindings,
|
|
174
|
-
rootCauseLead: f.rootCauseLead,
|
|
175
|
-
evidenceFiles: f.evidenceFiles
|
|
176
|
-
}))
|
|
177
|
-
};
|
|
178
|
-
}
|
|
179
|
-
// src/core/report/renderers/json.ts
|
|
180
|
-
function renderJson(report) {
|
|
181
|
-
return JSON.stringify(report, null, 2);
|
|
182
|
-
}
|
|
183
|
-
// src/core/report/renderers/markdown.ts
|
|
184
|
-
function renderMarkdown(report) {
|
|
185
|
-
const { metadata, findings, summary } = report;
|
|
186
|
-
const header = [
|
|
187
|
-
`# Pentest Report — ${metadata.target}`,
|
|
188
|
-
"",
|
|
189
|
-
`**Date:** ${metadata.timestamp} `,
|
|
190
|
-
`**Session:** ${metadata.sessionId} `,
|
|
191
|
-
`**Model:** ${metadata.model} `,
|
|
192
|
-
`**Mode:** ${metadata.mode}`,
|
|
193
|
-
"",
|
|
194
|
-
`**Findings:** ${summary.totalFindings}`,
|
|
195
|
-
""
|
|
196
|
-
];
|
|
197
|
-
if (findings.length === 0) {
|
|
198
|
-
return [
|
|
199
|
-
...header,
|
|
200
|
-
"No findings were identified during this assessment.",
|
|
201
|
-
""
|
|
202
|
-
].join(`
|
|
203
|
-
`);
|
|
204
|
-
}
|
|
205
|
-
const body = findings.map((finding) => renderFinding(finding, metadata)).join(`
|
|
206
|
-
`);
|
|
207
|
-
return [...header, body].join(`
|
|
208
|
-
`);
|
|
209
|
-
}
|
|
210
|
-
function renderFinding(finding, metadata) {
|
|
211
|
-
const lines = [
|
|
212
|
-
`# ${finding.title}`,
|
|
213
|
-
"",
|
|
214
|
-
`**Severity:** ${finding.severity} `,
|
|
215
|
-
`**Target:** ${metadata.target} `,
|
|
216
|
-
`**Endpoint:** ${finding.endpoint} `,
|
|
217
|
-
`**Date:** ${metadata.timestamp} `,
|
|
218
|
-
`**Session:** ${metadata.sessionId}`,
|
|
219
|
-
"",
|
|
220
|
-
"## Description",
|
|
221
|
-
"",
|
|
222
|
-
finding.description,
|
|
223
|
-
"",
|
|
224
|
-
"## Impact",
|
|
225
|
-
"",
|
|
226
|
-
finding.impact,
|
|
227
|
-
"",
|
|
228
|
-
"## Evidence",
|
|
229
|
-
"",
|
|
230
|
-
"```",
|
|
231
|
-
finding.evidence,
|
|
232
|
-
"```",
|
|
233
|
-
"",
|
|
234
|
-
...finding.evidenceFiles?.length ? [
|
|
235
|
-
"## Evidence Files",
|
|
236
|
-
"",
|
|
237
|
-
...finding.evidenceFiles.map((ef) => `- **[${ef.type}]** \`${ef.path}\` — ${ef.description}`),
|
|
238
|
-
""
|
|
239
|
-
] : [],
|
|
240
|
-
...finding.cwes?.length ? [
|
|
241
|
-
"## CWE Classification",
|
|
242
|
-
"",
|
|
243
|
-
...finding.cwes.map((cwe) => `- **${cwe.id}**${hasCanonicalName(cwe) ? `: ${cwe.name}` : ""} — ${cwe.reasoning}`),
|
|
244
|
-
""
|
|
245
|
-
] : [],
|
|
246
|
-
...finding.rootCauseGroup ? [
|
|
247
|
-
"## Root Cause Group",
|
|
248
|
-
"",
|
|
249
|
-
`**Group:** \`${finding.rootCauseGroup}\``,
|
|
250
|
-
...finding.relatedFindings?.length ? [
|
|
251
|
-
"",
|
|
252
|
-
"**Related Findings:**",
|
|
253
|
-
...finding.relatedFindings.map((rf) => `- ${rf}`)
|
|
254
|
-
] : [],
|
|
255
|
-
""
|
|
256
|
-
] : [],
|
|
257
|
-
"## POC",
|
|
258
|
-
"",
|
|
259
|
-
`Path: \`${finding.pocPath}\``,
|
|
260
|
-
"",
|
|
261
|
-
"## Remediation",
|
|
262
|
-
"",
|
|
263
|
-
finding.remediation,
|
|
264
|
-
...finding.references ? ["", `## References`, "", finding.references] : [],
|
|
265
|
-
"",
|
|
266
|
-
"---",
|
|
267
|
-
"",
|
|
268
|
-
"*This finding was automatically documented by the Pensar penetration testing agent.*",
|
|
269
|
-
""
|
|
270
|
-
];
|
|
271
|
-
return lines.join(`
|
|
272
|
-
`);
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
// src/core/report/index.ts
|
|
276
|
-
var REPORT_FILENAME_MD = "pentest-report.md";
|
|
277
|
-
var REPORT_FILENAME_JSON = "pentest-report.json";
|
|
278
|
-
|
|
279
113
|
// src/core/session/execution-metrics.ts
|
|
280
114
|
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
281
115
|
import { join } from "node:path";
|
|
@@ -363,6 +197,7 @@ init_lazyLogger();
|
|
|
363
197
|
init_structured();
|
|
364
198
|
init_lazyLogger();
|
|
365
199
|
init_session();
|
|
200
|
+
init_id();
|
|
366
201
|
import {
|
|
367
202
|
existsSync as existsSync2,
|
|
368
203
|
mkdirSync,
|
|
@@ -441,15 +276,34 @@ function readAgentManifest(session) {
|
|
|
441
276
|
}
|
|
442
277
|
}
|
|
443
278
|
function buildManifestEntries(targets) {
|
|
444
|
-
return targets.map((t, i) =>
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
279
|
+
return targets.map((t, i) => {
|
|
280
|
+
const sessionId = newSessionId();
|
|
281
|
+
return {
|
|
282
|
+
id: sessionId,
|
|
283
|
+
sessionId,
|
|
284
|
+
name: `Pentest Agent ${i + 1}`,
|
|
285
|
+
target: t.target,
|
|
286
|
+
vulnerabilityClass: t.objectives[0] || "general",
|
|
287
|
+
objective: t.objectives.join("; "),
|
|
288
|
+
status: "running",
|
|
289
|
+
spawnedAt: new Date().toISOString()
|
|
290
|
+
};
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
function reconcileManifestOnResume(existing, fresh) {
|
|
294
|
+
return fresh.map((f, i) => {
|
|
295
|
+
const prev = existing[i];
|
|
296
|
+
if (!prev || prev.target !== f.target)
|
|
297
|
+
return f;
|
|
298
|
+
if (prev.status === "completed")
|
|
299
|
+
return prev;
|
|
300
|
+
return {
|
|
301
|
+
...f,
|
|
302
|
+
id: prev.id,
|
|
303
|
+
sessionId: prev.sessionId ?? prev.id,
|
|
304
|
+
spawnedAt: prev.spawnedAt
|
|
305
|
+
};
|
|
306
|
+
});
|
|
453
307
|
}
|
|
454
308
|
function finalizeManifest(session, entries, results) {
|
|
455
309
|
const finalManifest = entries.map((entry, i) => {
|
|
@@ -470,7 +324,7 @@ function updateManifestEntryStatus(session, agentId, status) {
|
|
|
470
324
|
}
|
|
471
325
|
function getCompletedAgentIds(session) {
|
|
472
326
|
const manifest = readAgentManifest(session);
|
|
473
|
-
return new Set(manifest.filter((e) => e.
|
|
327
|
+
return new Set(manifest.filter((e) => e.status === "completed").map((e) => e.id));
|
|
474
328
|
}
|
|
475
329
|
function parseSubagentFilename(filename) {
|
|
476
330
|
if (filename.startsWith("attack-surface-agent")) {
|
|
@@ -611,6 +465,17 @@ function convertModelMessagesToUI(messages) {
|
|
|
611
465
|
}
|
|
612
466
|
function loadSubagents(rootPath) {
|
|
613
467
|
const subagentsPath = join2(rootPath, SUBAGENTS_DIR);
|
|
468
|
+
const manifestNameById = new Map;
|
|
469
|
+
{
|
|
470
|
+
const manifestPath2 = join2(rootPath, MANIFEST_FILE);
|
|
471
|
+
if (existsSync2(manifestPath2)) {
|
|
472
|
+
try {
|
|
473
|
+
const manifest = JSON.parse(readFileSync2(manifestPath2, "utf-8"));
|
|
474
|
+
for (const entry of manifest)
|
|
475
|
+
manifestNameById.set(entry.id, entry.name);
|
|
476
|
+
} catch {}
|
|
477
|
+
}
|
|
478
|
+
}
|
|
614
479
|
const subagents = [];
|
|
615
480
|
const agentNameIndex = new Map;
|
|
616
481
|
if (existsSync2(subagentsPath)) {
|
|
@@ -654,7 +519,7 @@ function loadSubagents(rootPath) {
|
|
|
654
519
|
agentNameIndex.set(data.agentName, subagents.length);
|
|
655
520
|
subagents.push({
|
|
656
521
|
id: data.agentName,
|
|
657
|
-
name: data.agentName === "attack-surface-agent" ? "Attack Surface Discovery" : name,
|
|
522
|
+
name: manifestNameById.get(data.agentName) ?? (data.agentName === "attack-surface-agent" ? "Attack Surface Discovery" : name),
|
|
658
523
|
type: agentType,
|
|
659
524
|
target: data.target || "Unknown",
|
|
660
525
|
messages,
|
|
@@ -685,7 +550,7 @@ function loadSubagents(rootPath) {
|
|
|
685
550
|
agentNameIndex.set(entry, subagents.length);
|
|
686
551
|
subagents.push({
|
|
687
552
|
id: entry,
|
|
688
|
-
name,
|
|
553
|
+
name: manifestNameById.get(entry) ?? name,
|
|
689
554
|
type: agentType,
|
|
690
555
|
target: "Unknown",
|
|
691
556
|
messages,
|
|
@@ -762,16 +627,14 @@ import {
|
|
|
762
627
|
statSync as statSync2,
|
|
763
628
|
writeFileSync as writeFileSync3
|
|
764
629
|
} from "node:fs";
|
|
765
|
-
import { join as
|
|
630
|
+
import { join as join16 } from "node:path";
|
|
766
631
|
|
|
767
632
|
// src/core/agents/specialized/whiteboxAttackSurface/endpointDocumentationAgent.ts
|
|
633
|
+
init_id();
|
|
768
634
|
init_structured();
|
|
769
635
|
init_lazyLogger();
|
|
770
636
|
var log3 = scopedLogger(() => createLogger("endpoint-documentation-agent"));
|
|
771
637
|
var ENDPOINT_DOCUMENTATION_CONCURRENCY = 10;
|
|
772
|
-
function slug(name) {
|
|
773
|
-
return name.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_|_$/g, "");
|
|
774
|
-
}
|
|
775
638
|
function getDocumentMethod(endpoint) {
|
|
776
639
|
return endpoint.kind === "page" ? ["PAGE"] : endpoint.method;
|
|
777
640
|
}
|
|
@@ -782,6 +645,18 @@ function buildEndpointDocumentationObjective(opts) {
|
|
|
782
645
|
const methodDisplay = documentMethod.join(",");
|
|
783
646
|
const methodValue = JSON.stringify(documentMethod.length === 1 ? documentMethod[0] : documentMethod);
|
|
784
647
|
const endpointType = endpoint.kind === "page" ? "web-endpoint" : "api-endpoint";
|
|
648
|
+
const isGrpc = !!endpoint.transport && endpoint.transport !== "http";
|
|
649
|
+
const grpcSection = isGrpc ? `
|
|
650
|
+
## gRPC (deterministically extracted by surface)
|
|
651
|
+
|
|
652
|
+
This endpoint is a **gRPC method**, not a plain HTTP route. \`routePath\` is the wire path \`/package.Service/Method\` — do NOT glue a host onto it.
|
|
653
|
+
|
|
654
|
+
- **transport**: ${endpoint.transport}
|
|
655
|
+
- **grpc.serviceFqn**: ${endpoint.grpc?.serviceFqn ?? "(derive from routePath)"}
|
|
656
|
+
- **grpc.method**: ${endpoint.grpc?.method ?? "(derive from routePath)"}
|
|
657
|
+
- **grpc.streamingType**: ${endpoint.grpc?.streamingType ?? "unary"}
|
|
658
|
+
` : "";
|
|
659
|
+
const grpcInstruction = isGrpc ? ` Because this is a gRPC method, ALSO pass \`transport: "${endpoint.transport}"\` and a \`grpc\` object built from the values above (\`serviceFqn\`, \`method\`, \`streamingType\`, plus \`schemaSource: "proto"\`); you MAY add \`reflectionAvailable\` or \`frontingGatewayOperation\` when the source makes them clear.` : "";
|
|
785
660
|
const auth = endpoint.auth.length > 0 ? endpoint.auth.join(", ") : "none";
|
|
786
661
|
const authPrefill = endpoint.auth.length > 0;
|
|
787
662
|
return `# Document Endpoint: ${methodDisplay} ${endpoint.path}
|
|
@@ -801,10 +676,10 @@ function buildEndpointDocumentationObjective(opts) {
|
|
|
801
676
|
- **endpointType**: ${endpointType}
|
|
802
677
|
- **auth signals**: ${auth}
|
|
803
678
|
- **prefilled authRequired**: ${authPrefill}
|
|
804
|
-
|
|
679
|
+
${grpcSection}
|
|
805
680
|
## Task
|
|
806
681
|
|
|
807
|
-
Document this **single endpoint** by calling \`document_endpoint\` exactly once. Use the deterministic fields above as-is for \`appName\` (\`"${app.name}"\`), \`routePath\`, \`method\` (\`${methodValue}\`), \`endpointType\` (\`"${endpointType}"\`), \`file\`, \`line\`, and \`handler
|
|
682
|
+
Document this **single endpoint** by calling \`document_endpoint\` exactly once. Use the deterministic fields above as-is for \`appName\` (\`"${app.name}"\`), \`routePath\`, \`method\` (\`${methodValue}\`), \`endpointType\` (\`"${endpointType}"\`), \`file\`, \`line\`, and \`handler\`.${grpcInstruction} The remaining fields you must produce yourself:
|
|
808
683
|
|
|
809
684
|
- **authRequired**: start from the prefilled value above. Override only after reading the middleware chain at the indicated file when the signals are genuinely ambiguous.
|
|
810
685
|
- **description**: 1–2 sentences explaining what this endpoint does in plain English. Read the handler at \`${endpoint.file}:${endpoint.line}\` to ground it.
|
|
@@ -840,11 +715,12 @@ async function runEndpointDocumentationAgent(opts) {
|
|
|
840
715
|
onCacheMetrics,
|
|
841
716
|
openAIReasoningEffort,
|
|
842
717
|
enableThinking,
|
|
718
|
+
thinkingEffort,
|
|
843
719
|
projectThreatModel,
|
|
844
720
|
parentSubagentId,
|
|
845
721
|
agentLimiter
|
|
846
722
|
} = opts;
|
|
847
|
-
const subagentId =
|
|
723
|
+
const subagentId = newSessionId();
|
|
848
724
|
const displayMethod = getDocumentMethod(endpoint);
|
|
849
725
|
const displayName = `${app.name}: ${displayMethod.join(",")} ${endpoint.path}`;
|
|
850
726
|
eventBus?.emit("subagent-spawn", {
|
|
@@ -875,10 +751,12 @@ async function runEndpointDocumentationAgent(opts) {
|
|
|
875
751
|
attackSurfaceRegistry,
|
|
876
752
|
eventBus,
|
|
877
753
|
subagentId,
|
|
754
|
+
subagentName: displayName,
|
|
878
755
|
onStepFinish: (event) => onStepFinish?.(event),
|
|
879
756
|
onCacheMetrics,
|
|
880
757
|
openAIReasoningEffort,
|
|
881
758
|
enableThinking,
|
|
759
|
+
thinkingEffort,
|
|
882
760
|
responseSchema: DiscoverySummarySchema,
|
|
883
761
|
excludeTools: ["document_app", "list_files", "grep"],
|
|
884
762
|
projectThreatModel
|
|
@@ -916,13 +794,16 @@ async function runAppEndpointDocumentation(opts) {
|
|
|
916
794
|
}
|
|
917
795
|
}
|
|
918
796
|
|
|
797
|
+
// src/core/workflows/whiteboxAttackSurface.ts
|
|
798
|
+
init_id();
|
|
799
|
+
|
|
919
800
|
// src/core/integrations/surface/index.ts
|
|
920
801
|
import { existsSync as existsSync10 } from "node:fs";
|
|
921
|
-
import { dirname as
|
|
802
|
+
import { dirname as dirname3, relative as relative3, resolve as resolve2, sep } from "node:path";
|
|
922
803
|
|
|
923
804
|
// node_modules/@pensar/surface/dist/index.js
|
|
924
805
|
import { existsSync as existsSync9, readdirSync as readdirSync9, readFileSync as readFileSync8 } from "fs";
|
|
925
|
-
import { join as
|
|
806
|
+
import { join as join15, resolve } from "path";
|
|
926
807
|
import { readdirSync as readdirSync2, readFileSync as readFileSync4, lstatSync, realpathSync } from "fs";
|
|
927
808
|
import { join as join4, relative, extname } from "path";
|
|
928
809
|
import { readdirSync as readdirSync22, existsSync as existsSync4, lstatSync as lstatSync2, realpathSync as realpathSync2 } from "fs";
|
|
@@ -3624,16 +3505,17 @@ var jsYaml = {
|
|
|
3624
3505
|
};
|
|
3625
3506
|
|
|
3626
3507
|
// node_modules/@pensar/surface/dist/index.js
|
|
3508
|
+
import { dirname as dirname2, join as join9 } from "path";
|
|
3627
3509
|
import { existsSync as existsSync5, readFileSync as readFileSync5, readdirSync as readdirSync5 } from "fs";
|
|
3628
|
-
import { join as
|
|
3510
|
+
import { join as join11, basename as basename2 } from "path";
|
|
3629
3511
|
import { readFileSync as readFileSync42 } from "fs";
|
|
3630
|
-
import { join as
|
|
3512
|
+
import { join as join10 } from "path";
|
|
3631
3513
|
import { existsSync as existsSync6, readFileSync as readFileSync6, readdirSync as readdirSync6 } from "fs";
|
|
3632
|
-
import { join as join11 } from "path";
|
|
3633
|
-
import { existsSync as existsSync7, readdirSync as readdirSync7 } from "fs";
|
|
3634
3514
|
import { join as join12 } from "path";
|
|
3515
|
+
import { existsSync as existsSync7, readdirSync as readdirSync7 } from "fs";
|
|
3516
|
+
import { join as join13 } from "path";
|
|
3635
3517
|
import { existsSync as existsSync8, readFileSync as readFileSync7, readdirSync as readdirSync8 } from "fs";
|
|
3636
|
-
import { join as
|
|
3518
|
+
import { join as join14, basename as basename3 } from "path";
|
|
3637
3519
|
var EndpointIndex = class {
|
|
3638
3520
|
_endpoints;
|
|
3639
3521
|
_byFramework = null;
|
|
@@ -3979,6 +3861,10 @@ function endpoint(e) {
|
|
|
3979
3861
|
auth: e.auth ?? [],
|
|
3980
3862
|
internal: false
|
|
3981
3863
|
};
|
|
3864
|
+
if (e.transport)
|
|
3865
|
+
ep.transport = e.transport;
|
|
3866
|
+
if (e.grpc)
|
|
3867
|
+
ep.grpc = e.grpc;
|
|
3982
3868
|
if (e.service)
|
|
3983
3869
|
ep.service = e.service;
|
|
3984
3870
|
if (e.handlerFile)
|
|
@@ -4014,6 +3900,7 @@ var flask = {
|
|
|
4014
3900
|
bpPrefixes[v] = p;
|
|
4015
3901
|
}
|
|
4016
3902
|
const routeRe = /@(\w+)\.(?:route|get|post|put|delete|patch)\s*\(\s*['"]([^'"]+)['"](?:\s*,\s*methods\s*=\s*\[([^\]]*)\])?\s*\)(.*?)(?:async\s+)?def\s+(\w+)\s*\(/gs;
|
|
3903
|
+
const bodyEndRe = /^(?:@\w|(?:async\s+)?def\s|class\s)/m;
|
|
4017
3904
|
for (const f of pyFiles) {
|
|
4018
3905
|
const content = ctx.readFile(f);
|
|
4019
3906
|
if (!content)
|
|
@@ -4038,9 +3925,15 @@ var flask = {
|
|
|
4038
3925
|
}
|
|
4039
3926
|
const auth = findAuthDecorators(between);
|
|
4040
3927
|
const params = extractPathParams(fullPath);
|
|
3928
|
+
const bodyStart = m.index + m[0].length;
|
|
3929
|
+
const rest = content.slice(bodyStart);
|
|
3930
|
+
const endMatch = bodyEndRe.exec(rest);
|
|
3931
|
+
const body = endMatch ? rest.slice(0, endMatch.index) : rest;
|
|
3932
|
+
const kind = /\brender_template(?:_string)?\s*\(/.test(body) ? "page" : "api";
|
|
4041
3933
|
for (const method of methods) {
|
|
4042
3934
|
endpoints.push(endpoint({
|
|
4043
3935
|
method,
|
|
3936
|
+
kind,
|
|
4044
3937
|
path: fullPath,
|
|
4045
3938
|
handler: funcName,
|
|
4046
3939
|
file: rel,
|
|
@@ -4081,7 +3974,7 @@ var fastapi = {
|
|
|
4081
3974
|
for (const [v, p] of Object.entries(includePrefixes)) {
|
|
4082
3975
|
routerPrefixes[v] = p;
|
|
4083
3976
|
}
|
|
4084
|
-
const routeRe = /@(\w+)\.(get|post|put|delete|patch|head|options|websocket)\s*\(\s*['"]([^'"]+)['"]\s*(?:,.*?)
|
|
3977
|
+
const routeRe = /@(\w+)\.(get|post|put|delete|patch|head|options|websocket)\s*\(\s*['"]([^'"]+)['"]\s*((?:,.*?)?)\)(.*?)(?:async\s+)?def\s+(\w+)\s*\(([^)]*)\)/gs;
|
|
4085
3978
|
for (const f of pyFiles) {
|
|
4086
3979
|
const content = ctx.readFile(f);
|
|
4087
3980
|
if (!content)
|
|
@@ -4092,13 +3985,21 @@ var fastapi = {
|
|
|
4092
3985
|
const varName = m[1];
|
|
4093
3986
|
let httpMethod = m[2].toUpperCase();
|
|
4094
3987
|
const routePath = m[3];
|
|
4095
|
-
const
|
|
4096
|
-
const
|
|
4097
|
-
const
|
|
3988
|
+
const decoratorArgs = m[4];
|
|
3989
|
+
const between = m[5];
|
|
3990
|
+
const funcName = m[6];
|
|
3991
|
+
const funcParams = m[7];
|
|
4098
3992
|
const line = lines.lineAt(m.index);
|
|
4099
3993
|
const isWebSocket = httpMethod === "WEBSOCKET";
|
|
4100
3994
|
if (isWebSocket)
|
|
4101
3995
|
httpMethod = "WS";
|
|
3996
|
+
const bodyStart = m.index + m[0].length;
|
|
3997
|
+
const after = content.slice(bodyStart, bodyStart + 4000);
|
|
3998
|
+
const nextRouteRe = /\n(?=@\w+\.|def\s|async\s+def\s)/;
|
|
3999
|
+
const nextMatch = after.match(nextRouteRe);
|
|
4000
|
+
const body = nextMatch ? after.slice(0, nextMatch.index) : after;
|
|
4001
|
+
const isPage = !isWebSocket && (/response_class\s*=\s*\w*HTMLResponse\b/.test(decoratorArgs) || /\bHTMLResponse\s*\(/.test(body) || /\.TemplateResponse\s*\(/.test(body));
|
|
4002
|
+
const kind = isWebSocket ? "websocket" : isPage ? "page" : "api";
|
|
4102
4003
|
const prefix = routerPrefixes[varName] ?? "";
|
|
4103
4004
|
const fullPath = normalizePath(prefix + routePath);
|
|
4104
4005
|
const auth = findAuthDecorators(between + funcParams);
|
|
@@ -4114,7 +4015,7 @@ var fastapi = {
|
|
|
4114
4015
|
}
|
|
4115
4016
|
endpoints.push(endpoint({
|
|
4116
4017
|
method: httpMethod,
|
|
4117
|
-
kind
|
|
4018
|
+
kind,
|
|
4118
4019
|
path: fullPath,
|
|
4119
4020
|
handler: funcName,
|
|
4120
4021
|
file: rel,
|
|
@@ -4129,12 +4030,105 @@ var fastapi = {
|
|
|
4129
4030
|
}
|
|
4130
4031
|
};
|
|
4131
4032
|
var PY_EXTS3 = [".py"];
|
|
4033
|
+
var PAGE_CBV_BASES = /* @__PURE__ */ new Set([
|
|
4034
|
+
"TemplateView",
|
|
4035
|
+
"ListView",
|
|
4036
|
+
"DetailView",
|
|
4037
|
+
"CreateView",
|
|
4038
|
+
"UpdateView",
|
|
4039
|
+
"DeleteView",
|
|
4040
|
+
"FormView"
|
|
4041
|
+
]);
|
|
4132
4042
|
var django = {
|
|
4133
4043
|
id: "django",
|
|
4134
4044
|
detect: { depKeywords: ["django"], markers: ["manage.py"], scope: "all" },
|
|
4135
4045
|
extract(ctx) {
|
|
4136
4046
|
const endpoints = [];
|
|
4137
4047
|
const pyFiles = ctx.iterFiles(PY_EXTS3);
|
|
4048
|
+
const dirOf2 = (f) => {
|
|
4049
|
+
const norm = f.replace(/\\/g, "/");
|
|
4050
|
+
const i2 = norm.lastIndexOf("/");
|
|
4051
|
+
return i2 < 0 ? "" : norm.slice(0, i2);
|
|
4052
|
+
};
|
|
4053
|
+
const classesByDir = /* @__PURE__ */ new Map;
|
|
4054
|
+
const funcsByDir = /* @__PURE__ */ new Map;
|
|
4055
|
+
const globalClasses = {};
|
|
4056
|
+
const globalFuncs = {};
|
|
4057
|
+
const classRe = /^class\s+(\w+)\s*\(([^)]*)\)\s*:/gm;
|
|
4058
|
+
const funcRe = /^(?:async\s+)?def\s+(\w+)\s*\([^)]*\)\s*(?:->[^:\n]+)?:[ \t]*\n?([\s\S]*?)(?=^(?:(?:async\s+)?def |class )|$(?![\s\S]))/gm;
|
|
4059
|
+
for (const f of pyFiles) {
|
|
4060
|
+
const content = ctx.readFile(f);
|
|
4061
|
+
if (!content)
|
|
4062
|
+
continue;
|
|
4063
|
+
const dir = dirOf2(f);
|
|
4064
|
+
const dirClasses = classesByDir.get(dir) ?? {};
|
|
4065
|
+
const dirFuncs = funcsByDir.get(dir) ?? {};
|
|
4066
|
+
for (const m of content.matchAll(classRe)) {
|
|
4067
|
+
const name = m[1];
|
|
4068
|
+
const bases = m[2].split(",").map((b) => b.trim()).filter(Boolean).map((b) => b.split(".").pop().replace(/\s+/g, ""));
|
|
4069
|
+
dirClasses[name] = { bases };
|
|
4070
|
+
globalClasses[name] = { bases };
|
|
4071
|
+
}
|
|
4072
|
+
for (const m of content.matchAll(funcRe)) {
|
|
4073
|
+
const name = m[1];
|
|
4074
|
+
const body = m[2] ?? "";
|
|
4075
|
+
dirFuncs[name] = { body };
|
|
4076
|
+
globalFuncs[name] = { body };
|
|
4077
|
+
}
|
|
4078
|
+
classesByDir.set(dir, dirClasses);
|
|
4079
|
+
funcsByDir.set(dir, dirFuncs);
|
|
4080
|
+
}
|
|
4081
|
+
const resolveHandlerName = (viewRef) => {
|
|
4082
|
+
const parts = viewRef.split(".");
|
|
4083
|
+
const last = parts[parts.length - 1];
|
|
4084
|
+
if ((last === "as_view" || last === "as_asgi") && parts.length >= 2) {
|
|
4085
|
+
return parts[parts.length - 2];
|
|
4086
|
+
}
|
|
4087
|
+
return last;
|
|
4088
|
+
};
|
|
4089
|
+
const lookupInApp = (byDir, global, name, dir) => {
|
|
4090
|
+
const exact = byDir.get(dir)?.[name];
|
|
4091
|
+
if (exact)
|
|
4092
|
+
return exact;
|
|
4093
|
+
let best;
|
|
4094
|
+
const prefix = dir + "/";
|
|
4095
|
+
for (const [d, rec] of byDir) {
|
|
4096
|
+
if (!d.startsWith(prefix))
|
|
4097
|
+
continue;
|
|
4098
|
+
const def = rec[name];
|
|
4099
|
+
if (def && (!best || d.length < best.depth)) {
|
|
4100
|
+
best = { def, depth: d.length };
|
|
4101
|
+
}
|
|
4102
|
+
}
|
|
4103
|
+
return best?.def ?? global[name];
|
|
4104
|
+
};
|
|
4105
|
+
const lookupClass = (name, dir) => lookupInApp(classesByDir, globalClasses, name, dir);
|
|
4106
|
+
const lookupFunc = (name, dir) => lookupInApp(funcsByDir, globalFuncs, name, dir);
|
|
4107
|
+
const isPageClass = (name, dir, seen = /* @__PURE__ */ new Set) => {
|
|
4108
|
+
if (seen.has(name))
|
|
4109
|
+
return false;
|
|
4110
|
+
seen.add(name);
|
|
4111
|
+
const cls = lookupClass(name, dir);
|
|
4112
|
+
if (!cls)
|
|
4113
|
+
return false;
|
|
4114
|
+
for (const b of cls.bases) {
|
|
4115
|
+
if (PAGE_CBV_BASES.has(b))
|
|
4116
|
+
return true;
|
|
4117
|
+
if (isPageClass(b, dir, seen))
|
|
4118
|
+
return true;
|
|
4119
|
+
}
|
|
4120
|
+
return false;
|
|
4121
|
+
};
|
|
4122
|
+
const classifyView = (viewRef, dir) => {
|
|
4123
|
+
const handler = resolveHandlerName(viewRef);
|
|
4124
|
+
if (lookupClass(handler, dir)) {
|
|
4125
|
+
return isPageClass(handler, dir) ? "page" : "api";
|
|
4126
|
+
}
|
|
4127
|
+
const fn = lookupFunc(handler, dir);
|
|
4128
|
+
if (fn && /\brender\s*\(/.test(fn.body))
|
|
4129
|
+
return "page";
|
|
4130
|
+
return "api";
|
|
4131
|
+
};
|
|
4138
4132
|
const urlFiles = pyFiles.filter((f) => {
|
|
4139
4133
|
const name = f.split("/").pop();
|
|
4140
4134
|
return name === "urls.py" || name === "routes.py";
|
|
@@ -4164,16 +4158,19 @@ var django = {
|
|
|
4164
4158
|
break;
|
|
4165
4159
|
}
|
|
4166
4160
|
}
|
|
4161
|
+
const urlDir = dirOf2(f);
|
|
4167
4162
|
const lines = buildLineIndex(content);
|
|
4168
4163
|
for (const m of content.matchAll(directPathRe)) {
|
|
4169
4164
|
const routePath = m[1];
|
|
4170
4165
|
const viewRef = m[2];
|
|
4171
4166
|
const line = lines.lineAt(m.index);
|
|
4172
4167
|
const fullPath = normalizePath(ownPrefix + routePath);
|
|
4168
|
+
const kind = classifyView(viewRef, urlDir);
|
|
4173
4169
|
endpoints.push(endpoint({
|
|
4174
4170
|
method: "ANY",
|
|
4171
|
+
kind,
|
|
4175
4172
|
path: fullPath,
|
|
4176
|
-
handler: viewRef
|
|
4173
|
+
handler: resolveHandlerName(viewRef),
|
|
4177
4174
|
file: rel,
|
|
4178
4175
|
line,
|
|
4179
4176
|
framework: "django",
|
|
@@ -4207,10 +4204,119 @@ var django = {
|
|
|
4207
4204
|
}
|
|
4208
4205
|
}
|
|
4209
4206
|
}
|
|
4207
|
+
const wsBlockRe = /websocket_urlpatterns\s*=\s*\[([\s\S]*?)\]/g;
|
|
4208
|
+
const wsEntryRe = /(?:re_path|path|url)\s*\(\s*r?['"]([^'"]*)['"]\s*,\s*(\w[\w.]*)/g;
|
|
4209
|
+
for (const f of pyFiles) {
|
|
4210
|
+
const content = ctx.readFile(f);
|
|
4211
|
+
if (!content || !content.includes("websocket_urlpatterns"))
|
|
4212
|
+
continue;
|
|
4213
|
+
const rel = ctx.rel(f);
|
|
4214
|
+
const lines = buildLineIndex(content);
|
|
4215
|
+
for (const block of content.matchAll(wsBlockRe)) {
|
|
4216
|
+
const blockBody = block[1];
|
|
4217
|
+
const blockOffset = block.index + block[0].indexOf(blockBody);
|
|
4218
|
+
for (const m of blockBody.matchAll(wsEntryRe)) {
|
|
4219
|
+
const routePath = m[1];
|
|
4220
|
+
const consumerRef = m[2];
|
|
4221
|
+
const absOffset = blockOffset + m.index;
|
|
4222
|
+
const line = lines.lineAt(absOffset);
|
|
4223
|
+
const fullPath = normalizePath(routePath);
|
|
4224
|
+
endpoints.push(endpoint({
|
|
4225
|
+
method: "WS",
|
|
4226
|
+
kind: "websocket",
|
|
4227
|
+
path: fullPath,
|
|
4228
|
+
handler: resolveHandlerName(consumerRef),
|
|
4229
|
+
file: rel,
|
|
4230
|
+
line,
|
|
4231
|
+
framework: "django",
|
|
4232
|
+
params: extractPathParams(fullPath)
|
|
4233
|
+
}));
|
|
4234
|
+
}
|
|
4235
|
+
}
|
|
4236
|
+
}
|
|
4210
4237
|
return endpoints;
|
|
4211
4238
|
}
|
|
4212
4239
|
};
|
|
4213
4240
|
var JS_EXTS = [".js", ".jsx", ".mjs", ".cjs", ".ts", ".tsx"];
|
|
4241
|
+
var PAGE_RENDER_RE = /\bres\s*\.\s*render\s*\(/;
|
|
4242
|
+
var PAGE_SENDFILE_RE = /\bres\s*\.\s*sendFile\s*\(/;
|
|
4243
|
+
function findMatchingParen(content, start) {
|
|
4244
|
+
let depth = 0;
|
|
4245
|
+
let i2 = start;
|
|
4246
|
+
const len = content.length;
|
|
4247
|
+
while (i2 < len) {
|
|
4248
|
+
const ch = content[i2];
|
|
4249
|
+
if (ch === "/" && content[i2 + 1] === "/") {
|
|
4250
|
+
const nl = content.indexOf(`
|
|
4251
|
+
`, i2);
|
|
4252
|
+
if (nl === -1)
|
|
4253
|
+
return -1;
|
|
4254
|
+
i2 = nl + 1;
|
|
4255
|
+
continue;
|
|
4256
|
+
}
|
|
4257
|
+
if (ch === "/" && content[i2 + 1] === "*") {
|
|
4258
|
+
const end = content.indexOf("*/", i2 + 2);
|
|
4259
|
+
if (end === -1)
|
|
4260
|
+
return -1;
|
|
4261
|
+
i2 = end + 2;
|
|
4262
|
+
continue;
|
|
4263
|
+
}
|
|
4264
|
+
if (ch === "'" || ch === '"') {
|
|
4265
|
+
i2++;
|
|
4266
|
+
while (i2 < len) {
|
|
4267
|
+
const c = content[i2];
|
|
4268
|
+
if (c === "\\") {
|
|
4269
|
+
i2 += 2;
|
|
4270
|
+
continue;
|
|
4271
|
+
}
|
|
4272
|
+
if (c === ch) {
|
|
4273
|
+
i2++;
|
|
4274
|
+
break;
|
|
4275
|
+
}
|
|
4276
|
+
i2++;
|
|
4277
|
+
}
|
|
4278
|
+
continue;
|
|
4279
|
+
}
|
|
4280
|
+
if (ch === "`") {
|
|
4281
|
+
i2++;
|
|
4282
|
+
while (i2 < len) {
|
|
4283
|
+
const c = content[i2];
|
|
4284
|
+
if (c === "\\") {
|
|
4285
|
+
i2 += 2;
|
|
4286
|
+
continue;
|
|
4287
|
+
}
|
|
4288
|
+
if (c === "`") {
|
|
4289
|
+
i2++;
|
|
4290
|
+
break;
|
|
4291
|
+
}
|
|
4292
|
+
if (c === "$" && content[i2 + 1] === "{") {
|
|
4293
|
+
i2 += 2;
|
|
4294
|
+
let d = 1;
|
|
4295
|
+
while (i2 < len && d > 0) {
|
|
4296
|
+
const cc = content[i2];
|
|
4297
|
+
if (cc === "{")
|
|
4298
|
+
d++;
|
|
4299
|
+
else if (cc === "}")
|
|
4300
|
+
d--;
|
|
4301
|
+
i2++;
|
|
4302
|
+
}
|
|
4303
|
+
continue;
|
|
4304
|
+
}
|
|
4305
|
+
i2++;
|
|
4306
|
+
}
|
|
4307
|
+
continue;
|
|
4308
|
+
}
|
|
4309
|
+
if (ch === "(" || ch === "[" || ch === "{")
|
|
4310
|
+
depth++;
|
|
4311
|
+
else if (ch === ")" || ch === "]" || ch === "}") {
|
|
4312
|
+
depth--;
|
|
4313
|
+
if (depth === 0 && ch === ")")
|
|
4314
|
+
return i2;
|
|
4315
|
+
}
|
|
4316
|
+
i2++;
|
|
4317
|
+
}
|
|
4318
|
+
return -1;
|
|
4319
|
+
}
|
|
4214
4320
|
var express = {
|
|
4215
4321
|
id: "express",
|
|
4216
4322
|
detect: {
|
|
@@ -4232,28 +4338,49 @@ var express = {
|
|
|
4232
4338
|
mountPrefixes[m[2]] = m[1];
|
|
4233
4339
|
}
|
|
4234
4340
|
}
|
|
4235
|
-
const
|
|
4341
|
+
const routeOpenerRe = /(\w+)\s*\.\s*(get|post|put|delete|patch|all|head|options|ws)\s*\(\s*(['"])([^'"]+)\3\s*,/g;
|
|
4236
4342
|
for (const f of jsFiles) {
|
|
4237
4343
|
const content = ctx.readFile(f);
|
|
4238
4344
|
if (!content)
|
|
4239
4345
|
continue;
|
|
4240
4346
|
const rel = ctx.rel(f);
|
|
4241
4347
|
const lines = buildLineIndex(content);
|
|
4242
|
-
for (const m of content.matchAll(
|
|
4348
|
+
for (const m of content.matchAll(routeOpenerRe)) {
|
|
4243
4349
|
const varName = m[1];
|
|
4244
|
-
|
|
4245
|
-
const routePath = m[
|
|
4246
|
-
const
|
|
4247
|
-
const line = lines.lineAt(
|
|
4350
|
+
const methodTok = m[2].toLowerCase();
|
|
4351
|
+
const routePath = m[4];
|
|
4352
|
+
const matchStart = m.index;
|
|
4353
|
+
const line = lines.lineAt(matchStart);
|
|
4354
|
+
const openParen = content.indexOf("(", matchStart);
|
|
4355
|
+
if (openParen === -1)
|
|
4356
|
+
continue;
|
|
4357
|
+
const closeParen = findMatchingParen(content, openParen);
|
|
4358
|
+
if (closeParen === -1)
|
|
4359
|
+
continue;
|
|
4360
|
+
const callBody = content.slice(openParen + 1, closeParen);
|
|
4361
|
+
const afterPathComma = callBody.indexOf(",");
|
|
4362
|
+
const handlerArgs = afterPathComma >= 0 ? callBody.slice(afterPathComma + 1) : callBody;
|
|
4363
|
+
const isWs = methodTok === "ws";
|
|
4364
|
+
let httpMethod = methodTok.toUpperCase();
|
|
4248
4365
|
if (httpMethod === "ALL")
|
|
4249
4366
|
httpMethod = "ANY";
|
|
4367
|
+
if (isWs)
|
|
4368
|
+
httpMethod = "WS";
|
|
4250
4369
|
const prefix = mountPrefixes[varName] ?? "";
|
|
4251
4370
|
const fullPath = normalizePath(prefix + routePath);
|
|
4252
|
-
const
|
|
4371
|
+
const handlerNameMatch = handlerArgs.match(/^\s*(\w+)\s*[,)]/);
|
|
4372
|
+
const handlerName = handlerNameMatch ? handlerNameMatch[1] : "<anonymous>";
|
|
4373
|
+
let kind = "api";
|
|
4374
|
+
if (isWs) {
|
|
4375
|
+
kind = "websocket";
|
|
4376
|
+
} else if (PAGE_RENDER_RE.test(handlerArgs) || PAGE_SENDFILE_RE.test(handlerArgs)) {
|
|
4377
|
+
kind = "page";
|
|
4378
|
+
}
|
|
4253
4379
|
endpoints.push(endpoint({
|
|
4254
4380
|
method: httpMethod,
|
|
4381
|
+
kind,
|
|
4255
4382
|
path: fullPath,
|
|
4256
|
-
handler:
|
|
4383
|
+
handler: handlerName,
|
|
4257
4384
|
file: rel,
|
|
4258
4385
|
line,
|
|
4259
4386
|
framework: "express",
|
|
@@ -4262,9 +4389,219 @@ var express = {
|
|
|
4262
4389
|
}));
|
|
4263
4390
|
}
|
|
4264
4391
|
}
|
|
4392
|
+
const ioOfRe = /\b(\w+)\s*\.\s*of\s*\(\s*(['"])([^'"]+)\2\s*\)/g;
|
|
4393
|
+
const ioOnConnRe = /\b(\w+)\s*\.\s*on\s*\(\s*(['"])connection\2\s*,\s*(.*?)\)/gs;
|
|
4394
|
+
const wsServerRe = /new\s+(?:WebSocket\s*\.\s*Server|WebSocketServer)\s*\(/g;
|
|
4395
|
+
for (const f of jsFiles) {
|
|
4396
|
+
const content = ctx.readFile(f);
|
|
4397
|
+
if (!content)
|
|
4398
|
+
continue;
|
|
4399
|
+
const rel = ctx.rel(f);
|
|
4400
|
+
const lines = buildLineIndex(content);
|
|
4401
|
+
const importsSocketIo = /(?:from\s+|require\(\s*)['"]socket\.io['"]/.test(content);
|
|
4402
|
+
const ioInstances = /* @__PURE__ */ new Set;
|
|
4403
|
+
if (importsSocketIo) {
|
|
4404
|
+
for (const m of content.matchAll(/\b(\w+)\s*=\s*new\s+(?:Server|SocketIOServer|IOServer)\s*\(/g))
|
|
4405
|
+
ioInstances.add(m[1]);
|
|
4406
|
+
for (const m of content.matchAll(/\b(\w+)\s*=\s*require\(\s*['"]socket\.io['"]\s*\)\s*\(/g))
|
|
4407
|
+
ioInstances.add(m[1]);
|
|
4408
|
+
const factories = /* @__PURE__ */ new Set;
|
|
4409
|
+
for (const m of content.matchAll(/\b(\w+)\s*=\s*require\(\s*['"]socket\.io['"]\s*\)(?!\s*\()/g))
|
|
4410
|
+
factories.add(m[1]);
|
|
4411
|
+
for (const m of content.matchAll(/import\s+(\w+)\s+from\s+['"]socket\.io['"]/g))
|
|
4412
|
+
factories.add(m[1]);
|
|
4413
|
+
for (const fac of factories) {
|
|
4414
|
+
for (const m of content.matchAll(new RegExp(`\\b(\\w+)\\s*=\\s*${fac}\\s*\\(`, "g")))
|
|
4415
|
+
ioInstances.add(m[1]);
|
|
4416
|
+
}
|
|
4417
|
+
}
|
|
4418
|
+
const namespacePaths = /* @__PURE__ */ new Set;
|
|
4419
|
+
let emittedRootIo = false;
|
|
4420
|
+
if (ioInstances.size > 0) {
|
|
4421
|
+
for (const m of content.matchAll(ioOfRe)) {
|
|
4422
|
+
if (!ioInstances.has(m[1]))
|
|
4423
|
+
continue;
|
|
4424
|
+
const ns = m[3];
|
|
4425
|
+
namespacePaths.add(ns);
|
|
4426
|
+
endpoints.push(endpoint({
|
|
4427
|
+
method: "WS",
|
|
4428
|
+
kind: "websocket",
|
|
4429
|
+
path: normalizePath(ns),
|
|
4430
|
+
handler: "<anonymous>",
|
|
4431
|
+
file: rel,
|
|
4432
|
+
line: lines.lineAt(m.index),
|
|
4433
|
+
framework: "express",
|
|
4434
|
+
params: extractPathParams(normalizePath(ns))
|
|
4435
|
+
}));
|
|
4436
|
+
}
|
|
4437
|
+
for (const m of content.matchAll(ioOnConnRe)) {
|
|
4438
|
+
if (!ioInstances.has(m[1]))
|
|
4439
|
+
continue;
|
|
4440
|
+
if (!namespacePaths.has("/")) {
|
|
4441
|
+
endpoints.push(endpoint({
|
|
4442
|
+
method: "WS",
|
|
4443
|
+
kind: "websocket",
|
|
4444
|
+
path: "/",
|
|
4445
|
+
handler: "<anonymous>",
|
|
4446
|
+
file: rel,
|
|
4447
|
+
line: lines.lineAt(m.index),
|
|
4448
|
+
framework: "express"
|
|
4449
|
+
}));
|
|
4450
|
+
emittedRootIo = true;
|
|
4451
|
+
}
|
|
4452
|
+
break;
|
|
4453
|
+
}
|
|
4454
|
+
}
|
|
4455
|
+
ioOnConnRe.lastIndex = 0;
|
|
4456
|
+
const hasRootIoEndpoint = namespacePaths.has("/") || emittedRootIo;
|
|
4457
|
+
if (!hasRootIoEndpoint) {
|
|
4458
|
+
for (const m of content.matchAll(wsServerRe)) {
|
|
4459
|
+
endpoints.push(endpoint({
|
|
4460
|
+
method: "WS",
|
|
4461
|
+
kind: "websocket",
|
|
4462
|
+
path: "/",
|
|
4463
|
+
handler: "<anonymous>",
|
|
4464
|
+
file: rel,
|
|
4465
|
+
line: lines.lineAt(m.index),
|
|
4466
|
+
framework: "express"
|
|
4467
|
+
}));
|
|
4468
|
+
break;
|
|
4469
|
+
}
|
|
4470
|
+
}
|
|
4471
|
+
}
|
|
4265
4472
|
return endpoints;
|
|
4266
4473
|
}
|
|
4267
4474
|
};
|
|
4475
|
+
function maskComments(src) {
|
|
4476
|
+
let out = "";
|
|
4477
|
+
let i2 = 0;
|
|
4478
|
+
const n = src.length;
|
|
4479
|
+
while (i2 < n) {
|
|
4480
|
+
const c = src[i2];
|
|
4481
|
+
if (c === '"' || c === "'" || c === "`") {
|
|
4482
|
+
out += c;
|
|
4483
|
+
i2++;
|
|
4484
|
+
while (i2 < n) {
|
|
4485
|
+
if (src[i2] === "\\" && i2 + 1 < n) {
|
|
4486
|
+
out += src[i2] + src[i2 + 1];
|
|
4487
|
+
i2 += 2;
|
|
4488
|
+
continue;
|
|
4489
|
+
}
|
|
4490
|
+
out += src[i2];
|
|
4491
|
+
const done = src[i2] === c;
|
|
4492
|
+
i2++;
|
|
4493
|
+
if (done)
|
|
4494
|
+
break;
|
|
4495
|
+
}
|
|
4496
|
+
} else if (c === "/" && src[i2 + 1] === "/") {
|
|
4497
|
+
while (i2 < n && src[i2] !== `
|
|
4498
|
+
`) {
|
|
4499
|
+
out += " ";
|
|
4500
|
+
i2++;
|
|
4501
|
+
}
|
|
4502
|
+
} else if (c === "/" && src[i2 + 1] === "*") {
|
|
4503
|
+
out += " ";
|
|
4504
|
+
i2 += 2;
|
|
4505
|
+
while (i2 < n && !(src[i2] === "*" && src[i2 + 1] === "/")) {
|
|
4506
|
+
out += src[i2] === `
|
|
4507
|
+
` ? `
|
|
4508
|
+
` : " ";
|
|
4509
|
+
i2++;
|
|
4510
|
+
}
|
|
4511
|
+
if (i2 < n) {
|
|
4512
|
+
out += " ";
|
|
4513
|
+
i2 += 2;
|
|
4514
|
+
}
|
|
4515
|
+
} else {
|
|
4516
|
+
out += c;
|
|
4517
|
+
i2++;
|
|
4518
|
+
}
|
|
4519
|
+
}
|
|
4520
|
+
return out;
|
|
4521
|
+
}
|
|
4522
|
+
function classDecoratorBlock(content, classStart) {
|
|
4523
|
+
const lineStart = (pos) => {
|
|
4524
|
+
let p = pos;
|
|
4525
|
+
while (p > 0 && content[p - 1] !== `
|
|
4526
|
+
`)
|
|
4527
|
+
p--;
|
|
4528
|
+
return p;
|
|
4529
|
+
};
|
|
4530
|
+
const isStackLine = (line) => line === "" || line.startsWith("@") || line.startsWith("//") || line.startsWith("/*") || line.startsWith("*") || line.endsWith(",") || line.endsWith("(") || line.endsWith(")");
|
|
4531
|
+
const endsPrevStatement = (line) => line.endsWith("}") || line.endsWith(";");
|
|
4532
|
+
let start = lineStart(classStart);
|
|
4533
|
+
while (start > 0) {
|
|
4534
|
+
const prev = lineStart(start - 1);
|
|
4535
|
+
const line = content.slice(prev, start - 1).trim();
|
|
4536
|
+
if (endsPrevStatement(line))
|
|
4537
|
+
break;
|
|
4538
|
+
if (!isStackLine(line))
|
|
4539
|
+
break;
|
|
4540
|
+
start = prev;
|
|
4541
|
+
}
|
|
4542
|
+
return content.slice(start, classStart);
|
|
4543
|
+
}
|
|
4544
|
+
function findClasses(content) {
|
|
4545
|
+
const classes = [];
|
|
4546
|
+
const classRe = /\bclass\s+(\w+)/g;
|
|
4547
|
+
const scan = maskComments(content);
|
|
4548
|
+
for (const m of scan.matchAll(classRe)) {
|
|
4549
|
+
const start = m.index;
|
|
4550
|
+
const name = m[1];
|
|
4551
|
+
const decorators = classDecoratorBlock(content, start);
|
|
4552
|
+
if (classes.length > 0)
|
|
4553
|
+
classes[classes.length - 1].end = start;
|
|
4554
|
+
classes.push({ start, end: content.length, name, decorators });
|
|
4555
|
+
}
|
|
4556
|
+
return classes;
|
|
4557
|
+
}
|
|
4558
|
+
function methodDecoratorBlock(content, offset) {
|
|
4559
|
+
const lineStart = (pos) => {
|
|
4560
|
+
let p = pos;
|
|
4561
|
+
while (p > 0 && content[p - 1] !== `
|
|
4562
|
+
`)
|
|
4563
|
+
p--;
|
|
4564
|
+
return p;
|
|
4565
|
+
};
|
|
4566
|
+
const isStackLine = (line) => line === "" || line.startsWith("@") || line.startsWith("//") || line.startsWith("/*") || line.startsWith("*") || line.endsWith(",") || line.endsWith("(") || line.endsWith(")");
|
|
4567
|
+
const endsPrevStatement = (line) => line.endsWith("}") || line.endsWith(";");
|
|
4568
|
+
let start = lineStart(offset);
|
|
4569
|
+
while (start > 0) {
|
|
4570
|
+
const prev = lineStart(start - 1);
|
|
4571
|
+
const line = content.slice(prev, start - 1).trim();
|
|
4572
|
+
if (endsPrevStatement(line))
|
|
4573
|
+
break;
|
|
4574
|
+
if (!isStackLine(line))
|
|
4575
|
+
break;
|
|
4576
|
+
start = prev;
|
|
4577
|
+
}
|
|
4578
|
+
let end = offset;
|
|
4579
|
+
while (end < content.length && content[end] !== `
|
|
4580
|
+
`)
|
|
4581
|
+
end++;
|
|
4582
|
+
while (end < content.length) {
|
|
4583
|
+
const nextStart = end + 1;
|
|
4584
|
+
let nextEnd = nextStart;
|
|
4585
|
+
while (nextEnd < content.length && content[nextEnd] !== `
|
|
4586
|
+
`)
|
|
4587
|
+
nextEnd++;
|
|
4588
|
+
const line = content.slice(nextStart, nextEnd).trim();
|
|
4589
|
+
if (line === "") {
|
|
4590
|
+
end = nextEnd;
|
|
4591
|
+
continue;
|
|
4592
|
+
}
|
|
4593
|
+
if (line.startsWith("@") || line.startsWith("//") || line.startsWith("*")) {
|
|
4594
|
+
end = nextEnd;
|
|
4595
|
+
continue;
|
|
4596
|
+
}
|
|
4597
|
+
if (line.endsWith(",") || line.endsWith(")")) {
|
|
4598
|
+
end = nextEnd;
|
|
4599
|
+
continue;
|
|
4600
|
+
}
|
|
4601
|
+
break;
|
|
4602
|
+
}
|
|
4603
|
+
return content.slice(start, end);
|
|
4604
|
+
}
|
|
4268
4605
|
var nestjs = {
|
|
4269
4606
|
id: "nestjs",
|
|
4270
4607
|
detect: { depKeywords: ["@nestjs/core"], markers: [], scope: "root" },
|
|
@@ -4272,31 +4609,77 @@ var nestjs = {
|
|
|
4272
4609
|
const endpoints = [];
|
|
4273
4610
|
const tsFiles = ctx.iterFiles([".ts"]);
|
|
4274
4611
|
const controllerRe = /@Controller\s*\(\s*['"]([^'"]*)['"]\s*\)/;
|
|
4612
|
+
const wsGatewayRe = /@WebSocketGateway\s*\(([^)]*)\)|@WebSocketGateway\b/;
|
|
4275
4613
|
const methodRe = /@(Get|Post|Put|Delete|Patch|Head|Options)\s*\(\s*['"]?([^'")]*?)['"]?\s*\)(.*?)(?:async\s+)?(\w+)\s*\(/gs;
|
|
4614
|
+
const subscribeRe = /@SubscribeMessage\s*\(\s*['"]([^'"]*)['"]\s*\)(.*?)(?:async\s+)?(\w+)\s*\(/gs;
|
|
4615
|
+
const grpcRe = /@(GrpcMethod|GrpcStreamMethod)\s*\(([^)]*)\)[\s\S]{0,160}?(?:async\s+)?(\w+)\s*\(/g;
|
|
4276
4616
|
const guardRe = /@UseGuards?\s*\(\s*(\w+)/g;
|
|
4277
4617
|
for (const f of tsFiles) {
|
|
4278
4618
|
const content = ctx.readFile(f);
|
|
4279
|
-
if (!content
|
|
4619
|
+
if (!content)
|
|
4620
|
+
continue;
|
|
4621
|
+
if (!content.includes("@Controller") && !content.includes("@WebSocketGateway") && !content.includes("@Grpc"))
|
|
4280
4622
|
continue;
|
|
4281
4623
|
const rel = ctx.rel(f);
|
|
4282
|
-
const
|
|
4283
|
-
const
|
|
4284
|
-
const
|
|
4285
|
-
const
|
|
4286
|
-
|
|
4287
|
-
|
|
4624
|
+
const lines = buildLineIndex(content);
|
|
4625
|
+
const classes = findClasses(content);
|
|
4626
|
+
const fileCtrlMatch = controllerRe.exec(content);
|
|
4627
|
+
const fileControllerPrefix = fileCtrlMatch?.[1] ?? "";
|
|
4628
|
+
const fileClassAuth = [];
|
|
4629
|
+
const firstClassIdx = content.indexOf("class ");
|
|
4630
|
+
if (firstClassIdx > 0) {
|
|
4631
|
+
const preClass = content.slice(0, firstClassIdx);
|
|
4288
4632
|
for (const gm of preClass.matchAll(guardRe)) {
|
|
4289
|
-
|
|
4633
|
+
fileClassAuth.push(`@UseGuards(${gm[1]})`);
|
|
4290
4634
|
}
|
|
4291
4635
|
}
|
|
4292
|
-
const
|
|
4636
|
+
const classAt = (offset) => {
|
|
4637
|
+
for (const c of classes) {
|
|
4638
|
+
if (offset >= c.start && offset < c.end)
|
|
4639
|
+
return c;
|
|
4640
|
+
}
|
|
4641
|
+
return null;
|
|
4642
|
+
};
|
|
4643
|
+
const classCtx = /* @__PURE__ */ new Map;
|
|
4644
|
+
for (const c of classes) {
|
|
4645
|
+
const ctrl = controllerRe.exec(c.decorators);
|
|
4646
|
+
const prefix = ctrl?.[1] ?? "";
|
|
4647
|
+
const auth = [];
|
|
4648
|
+
for (const gm of c.decorators.matchAll(guardRe)) {
|
|
4649
|
+
auth.push(`@UseGuards(${gm[1]})`);
|
|
4650
|
+
}
|
|
4651
|
+
const ws = wsGatewayRe.exec(c.decorators);
|
|
4652
|
+
const isGateway = !!ws;
|
|
4653
|
+
let wsNamespace = "";
|
|
4654
|
+
if (ws && ws[1]) {
|
|
4655
|
+
const nsMatch = /namespace\s*:\s*['"]([^'"]*)['"]/.exec(ws[1]);
|
|
4656
|
+
if (nsMatch)
|
|
4657
|
+
wsNamespace = nsMatch[1];
|
|
4658
|
+
}
|
|
4659
|
+
classCtx.set(c.name, { prefix, auth, isGateway, wsNamespace });
|
|
4660
|
+
}
|
|
4293
4661
|
for (const m of content.matchAll(methodRe)) {
|
|
4294
4662
|
const httpMethod = m[1].toUpperCase();
|
|
4295
4663
|
const routePath = m[2];
|
|
4296
4664
|
const between = m[3];
|
|
4297
|
-
|
|
4298
|
-
const
|
|
4299
|
-
const
|
|
4665
|
+
let handler = m[4];
|
|
4666
|
+
const offset = m.index;
|
|
4667
|
+
const line = lines.lineAt(offset);
|
|
4668
|
+
if (between.includes("@")) {
|
|
4669
|
+
const fwd = content.slice(offset, offset + 600);
|
|
4670
|
+
const after = fwd.indexOf(")");
|
|
4671
|
+
if (after >= 0) {
|
|
4672
|
+
const handlerRe = /(?:@\w+\s*\([^)]*\)\s*)*(?:async\s+)?(\w+)\s*\(/g;
|
|
4673
|
+
handlerRe.lastIndex = after + 1;
|
|
4674
|
+
const hm = handlerRe.exec(fwd);
|
|
4675
|
+
if (hm)
|
|
4676
|
+
handler = hm[1];
|
|
4677
|
+
}
|
|
4678
|
+
}
|
|
4679
|
+
const cls = classAt(offset);
|
|
4680
|
+
const prefix = cls ? classCtx.get(cls.name)?.prefix ?? "" : fileControllerPrefix;
|
|
4681
|
+
const classAuth = cls ? classCtx.get(cls.name)?.auth ?? [] : fileClassAuth;
|
|
4682
|
+
const fullPath = normalizePath(prefix + "/" + routePath);
|
|
4300
4683
|
const params = extractPathParams(fullPath);
|
|
4301
4684
|
const methodAuth = [...classAuth];
|
|
4302
4685
|
for (const gm of between.matchAll(/@UseGuards?\s*\(\s*(\w+)/g)) {
|
|
@@ -4309,6 +4692,9 @@ var nestjs = {
|
|
|
4309
4692
|
for (const pm of searchText.matchAll(/@Param\s*\(\s*['"]?(\w+)/g)) {
|
|
4310
4693
|
params.push({ name: pm[1], location: "path", required: true });
|
|
4311
4694
|
}
|
|
4695
|
+
const decoratorBlock = methodDecoratorBlock(content, offset);
|
|
4696
|
+
const hasRender = /@Render\s*\(/.test(decoratorBlock);
|
|
4697
|
+
const kind = hasRender ? "page" : "api";
|
|
4312
4698
|
endpoints.push(endpoint({
|
|
4313
4699
|
method: httpMethod,
|
|
4314
4700
|
path: fullPath,
|
|
@@ -4316,10 +4702,64 @@ var nestjs = {
|
|
|
4316
4702
|
file: rel,
|
|
4317
4703
|
line,
|
|
4318
4704
|
framework: "nestjs",
|
|
4705
|
+
kind,
|
|
4319
4706
|
params,
|
|
4320
4707
|
auth: methodAuth
|
|
4321
4708
|
}));
|
|
4322
4709
|
}
|
|
4710
|
+
for (const m of content.matchAll(subscribeRe)) {
|
|
4711
|
+
const eventName = m[1];
|
|
4712
|
+
const between = m[2];
|
|
4713
|
+
const handler = m[3];
|
|
4714
|
+
const offset = m.index;
|
|
4715
|
+
const line = lines.lineAt(offset);
|
|
4716
|
+
const cls = classAt(offset);
|
|
4717
|
+
if (!cls)
|
|
4718
|
+
continue;
|
|
4719
|
+
const info = classCtx.get(cls.name);
|
|
4720
|
+
if (!info || !info.isGateway)
|
|
4721
|
+
continue;
|
|
4722
|
+
const fullPath = normalizePath((info.wsNamespace || "") + "/" + eventName);
|
|
4723
|
+
const methodAuth = [...info.auth];
|
|
4724
|
+
for (const gm of between.matchAll(/@UseGuards?\s*\(\s*(\w+)/g)) {
|
|
4725
|
+
methodAuth.push(`@UseGuards(${gm[1]})`);
|
|
4726
|
+
}
|
|
4727
|
+
endpoints.push(endpoint({
|
|
4728
|
+
method: "WS",
|
|
4729
|
+
path: fullPath,
|
|
4730
|
+
handler,
|
|
4731
|
+
file: rel,
|
|
4732
|
+
line,
|
|
4733
|
+
framework: "nestjs",
|
|
4734
|
+
kind: "websocket",
|
|
4735
|
+
params: extractPathParams(fullPath),
|
|
4736
|
+
auth: methodAuth
|
|
4737
|
+
}));
|
|
4738
|
+
}
|
|
4739
|
+
for (const m of content.matchAll(grpcRe)) {
|
|
4740
|
+
const decorator = m[1];
|
|
4741
|
+
const args = [...m[2].matchAll(/['"]([^'"]+)['"]/g)].map((a) => a[1]);
|
|
4742
|
+
const handler = m[3];
|
|
4743
|
+
const offset = m.index;
|
|
4744
|
+
const line = lines.lineAt(offset);
|
|
4745
|
+
const cls = classAt(offset);
|
|
4746
|
+
const service = args[0] ?? (cls?.name ?? "").replace(/Controller$/, "");
|
|
4747
|
+
const method = args[1] ?? handler.charAt(0).toUpperCase() + handler.slice(1);
|
|
4748
|
+
endpoints.push(endpoint({
|
|
4749
|
+
method: "ANY",
|
|
4750
|
+
path: `/${service}/${method}`,
|
|
4751
|
+
handler,
|
|
4752
|
+
file: rel,
|
|
4753
|
+
line,
|
|
4754
|
+
framework: "nestjs",
|
|
4755
|
+
transport: "grpc",
|
|
4756
|
+
grpc: {
|
|
4757
|
+
serviceFqn: service,
|
|
4758
|
+
method,
|
|
4759
|
+
streamingType: decorator === "GrpcStreamMethod" ? "bidi" : "unary"
|
|
4760
|
+
}
|
|
4761
|
+
}));
|
|
4762
|
+
}
|
|
4323
4763
|
}
|
|
4324
4764
|
return endpoints;
|
|
4325
4765
|
}
|
|
@@ -4535,9 +4975,50 @@ var nextjs = {
|
|
|
4535
4975
|
};
|
|
4536
4976
|
var GO_EXTS = [".go"];
|
|
4537
4977
|
var GO_GROUP_RE = /(\w+)\s*[:=]+\s*(\w+)\.Group\s*\(\s*['"]([^'"]+)['"]/g;
|
|
4978
|
+
var dirOf = (f) => {
|
|
4979
|
+
const norm = f.replace(/\\/g, "/");
|
|
4980
|
+
const i2 = norm.lastIndexOf("/");
|
|
4981
|
+
return i2 < 0 ? "" : norm.slice(0, i2);
|
|
4982
|
+
};
|
|
4983
|
+
var GORILLA_WS_IMPORT_RE = /["']github\.com\/gorilla\/websocket["']/;
|
|
4984
|
+
var UPGRADE_CALL_RE = /\.\s*Upgrade\s*\(/;
|
|
4985
|
+
function findWebsocketHandlers(ctx, goFiles) {
|
|
4986
|
+
const byDir = /* @__PURE__ */ new Map;
|
|
4987
|
+
const funcRe = /\bfunc\s+(?:\([^)]*\)\s+)?(\w+)\s*\([^)]*\)[^{]*\{/g;
|
|
4988
|
+
for (const f of goFiles) {
|
|
4989
|
+
const content = ctx.readFile(f);
|
|
4990
|
+
if (!content)
|
|
4991
|
+
continue;
|
|
4992
|
+
if (!GORILLA_WS_IMPORT_RE.test(content))
|
|
4993
|
+
continue;
|
|
4994
|
+
const dir = dirOf(f);
|
|
4995
|
+
const set2 = byDir.get(dir) ?? /* @__PURE__ */ new Set;
|
|
4996
|
+
for (const m of content.matchAll(funcRe)) {
|
|
4997
|
+
const name = m[1];
|
|
4998
|
+
const bodyStart = m.index + m[0].length;
|
|
4999
|
+
let depth = 1;
|
|
5000
|
+
let i2 = bodyStart;
|
|
5001
|
+
const len = content.length;
|
|
5002
|
+
while (i2 < len && depth > 0) {
|
|
5003
|
+
const ch = content[i2];
|
|
5004
|
+
if (ch === "{")
|
|
5005
|
+
depth++;
|
|
5006
|
+
else if (ch === "}")
|
|
5007
|
+
depth--;
|
|
5008
|
+
i2++;
|
|
5009
|
+
}
|
|
5010
|
+
const body = content.slice(bodyStart, i2 - 1);
|
|
5011
|
+
if (UPGRADE_CALL_RE.test(body))
|
|
5012
|
+
set2.add(name);
|
|
5013
|
+
}
|
|
5014
|
+
byDir.set(dir, set2);
|
|
5015
|
+
}
|
|
5016
|
+
return byDir;
|
|
5017
|
+
}
|
|
4538
5018
|
function extractGoFramework(ctx, framework, routeRe) {
|
|
4539
5019
|
const endpoints = [];
|
|
4540
5020
|
const goFiles = ctx.iterFiles(GO_EXTS);
|
|
5021
|
+
const wsHandlersByDir = findWebsocketHandlers(ctx, goFiles);
|
|
4541
5022
|
const groupPrefixes = {};
|
|
4542
5023
|
const groupParents = {};
|
|
4543
5024
|
for (const f of goFiles) {
|
|
@@ -4567,6 +5048,8 @@ function extractGoFramework(ctx, framework, routeRe) {
|
|
|
4567
5048
|
continue;
|
|
4568
5049
|
const rel = ctx.rel(f);
|
|
4569
5050
|
const lines = buildLineIndex(content);
|
|
5051
|
+
const fileImportsGorilla = GORILLA_WS_IMPORT_RE.test(content);
|
|
5052
|
+
const wsHandlers = wsHandlersByDir.get(dirOf(f)) ?? /* @__PURE__ */ new Set;
|
|
4570
5053
|
for (const m of content.matchAll(routeRe)) {
|
|
4571
5054
|
const varName = m[1];
|
|
4572
5055
|
let httpMethod = m[2].toUpperCase();
|
|
@@ -4577,11 +5060,15 @@ function extractGoFramework(ctx, framework, routeRe) {
|
|
|
4577
5060
|
httpMethod = "ANY";
|
|
4578
5061
|
const prefix = resolved[varName] ?? "";
|
|
4579
5062
|
const fullPath = normalizePath(prefix + routePath);
|
|
4580
|
-
const
|
|
5063
|
+
const idMatches = [...handlerArgs.matchAll(/(\w+)/g)];
|
|
5064
|
+
const handlerName = idMatches.length > 0 ? idMatches[idMatches.length - 1][1] : "<anonymous>";
|
|
5065
|
+
const inlineUpgrade = fileImportsGorilla && UPGRADE_CALL_RE.test(handlerArgs);
|
|
5066
|
+
const isWebsocket = wsHandlers.has(handlerName) || inlineUpgrade;
|
|
4581
5067
|
endpoints.push(endpoint({
|
|
4582
|
-
method: httpMethod,
|
|
5068
|
+
method: isWebsocket ? "WS" : httpMethod,
|
|
5069
|
+
kind: isWebsocket ? "websocket" : "api",
|
|
4583
5070
|
path: fullPath,
|
|
4584
|
-
handler:
|
|
5071
|
+
handler: handlerName,
|
|
4585
5072
|
file: rel,
|
|
4586
5073
|
line,
|
|
4587
5074
|
framework,
|
|
@@ -4631,18 +5118,23 @@ var netHttp = {
|
|
|
4631
5118
|
const endpoints = [];
|
|
4632
5119
|
const goFiles = ctx.iterFiles(GO_EXTS);
|
|
4633
5120
|
const handleRe = /(?:http\.HandleFunc|mux\.HandleFunc|http\.Handle|mux\.Handle)\s*\(\s*['"]([^'"]+)['"]\s*,\s*(\w+)/g;
|
|
5121
|
+
const wsHandlersByDir = findWebsocketHandlers(ctx, goFiles);
|
|
4634
5122
|
for (const f of goFiles) {
|
|
4635
5123
|
const content = ctx.readFile(f);
|
|
4636
5124
|
if (!content)
|
|
4637
5125
|
continue;
|
|
4638
5126
|
const rel = ctx.rel(f);
|
|
5127
|
+
const wsHandlers = wsHandlersByDir.get(dirOf(f)) ?? /* @__PURE__ */ new Set;
|
|
4639
5128
|
const lines = buildLineIndex(content);
|
|
4640
5129
|
for (const m of content.matchAll(handleRe)) {
|
|
4641
5130
|
const fullPath = normalizePath(m[1]);
|
|
5131
|
+
const handlerName = m[2];
|
|
5132
|
+
const isWebsocket = wsHandlers.has(handlerName);
|
|
4642
5133
|
endpoints.push(endpoint({
|
|
4643
|
-
method: "ANY",
|
|
5134
|
+
method: isWebsocket ? "WS" : "ANY",
|
|
5135
|
+
kind: isWebsocket ? "websocket" : "api",
|
|
4644
5136
|
path: fullPath,
|
|
4645
|
-
handler:
|
|
5137
|
+
handler: handlerName,
|
|
4646
5138
|
file: rel,
|
|
4647
5139
|
line: lines.lineAt(m.index),
|
|
4648
5140
|
framework: "net_http",
|
|
@@ -4727,6 +5219,34 @@ var METHOD_MAP = {
|
|
|
4727
5219
|
DeleteMapping: "DELETE",
|
|
4728
5220
|
PatchMapping: "PATCH"
|
|
4729
5221
|
};
|
|
5222
|
+
function collectClassContexts(content) {
|
|
5223
|
+
const out = [];
|
|
5224
|
+
const classRe = /\bclass\s+\w+/g;
|
|
5225
|
+
for (const m of content.matchAll(classRe)) {
|
|
5226
|
+
const classStart = m.index;
|
|
5227
|
+
const windowStart = Math.max(0, classStart - 600);
|
|
5228
|
+
let preamble = content.slice(windowStart, classStart);
|
|
5229
|
+
const lastBrace = Math.max(preamble.lastIndexOf("}"), preamble.lastIndexOf(";"));
|
|
5230
|
+
if (lastBrace >= 0)
|
|
5231
|
+
preamble = preamble.slice(lastBrace + 1);
|
|
5232
|
+
const hasRest = /@RestController\b/.test(preamble);
|
|
5233
|
+
const hasController = /@Controller\b/.test(preamble);
|
|
5234
|
+
if (!hasRest && !hasController)
|
|
5235
|
+
continue;
|
|
5236
|
+
out.push({ start: classStart, kind: hasRest ? "api" : "page" });
|
|
5237
|
+
}
|
|
5238
|
+
return out;
|
|
5239
|
+
}
|
|
5240
|
+
function kindForOffset(classes, offset) {
|
|
5241
|
+
let current = "api";
|
|
5242
|
+
for (const c of classes) {
|
|
5243
|
+
if (c.start <= offset)
|
|
5244
|
+
current = c.kind;
|
|
5245
|
+
else
|
|
5246
|
+
break;
|
|
5247
|
+
}
|
|
5248
|
+
return current;
|
|
5249
|
+
}
|
|
4730
5250
|
var spring = {
|
|
4731
5251
|
id: "spring",
|
|
4732
5252
|
detect: { depKeywords: ["spring-boot"], markers: [], scope: "root" },
|
|
@@ -4736,6 +5256,7 @@ var spring = {
|
|
|
4736
5256
|
const classMappingRe = /@RequestMapping\s*\(\s*(?:value\s*=\s*)?['"]([^'"]+)['"]/;
|
|
4737
5257
|
const methodMappingRe = /@(GetMapping|PostMapping|PutMapping|DeleteMapping|PatchMapping)\s*\(\s*(?:value\s*=\s*)?['"]?([^'")]*?)['"]?\s*\)(.*?)(?:public|private|protected)?\s*\w[\w<>,\s]*\s+(\w+)\s*\(/gs;
|
|
4738
5258
|
const reqMappingMethodRe = /@RequestMapping\s*\([^)]*method\s*=\s*RequestMethod\.(\w+)[^)]*(?:value\s*=\s*)?['"]?([^'")]*?)['"]?[^)]*\)(.*?)(?:public|private|protected)?\s*\w[\w<>,\s]*\s+(\w+)\s*\(/gs;
|
|
5259
|
+
const wsMappingRe = /@(MessageMapping|SubscribeMapping)\s*\(\s*(?:value\s*=\s*)?['"]([^'"]+)['"]\s*\)(.*?)(?:public|private|protected)?\s*\w[\w<>,\s]*\s+(\w+)\s*\(/gs;
|
|
4739
5260
|
for (const f of javaFiles) {
|
|
4740
5261
|
const content = ctx.readFile(f);
|
|
4741
5262
|
if (!content)
|
|
@@ -4744,6 +5265,7 @@ var spring = {
|
|
|
4744
5265
|
const cm = classMappingRe.exec(content);
|
|
4745
5266
|
const classPrefix = cm?.[1] ?? "";
|
|
4746
5267
|
const lines = buildLineIndex(content);
|
|
5268
|
+
const classCtxs = collectClassContexts(content);
|
|
4747
5269
|
for (const m of content.matchAll(methodMappingRe)) {
|
|
4748
5270
|
const fullPath = normalizePath(classPrefix + "/" + m[2]);
|
|
4749
5271
|
endpoints.push(endpoint({
|
|
@@ -4753,6 +5275,7 @@ var spring = {
|
|
|
4753
5275
|
file: rel,
|
|
4754
5276
|
line: lines.lineAt(m.index),
|
|
4755
5277
|
framework: "spring",
|
|
5278
|
+
kind: kindForOffset(classCtxs, m.index),
|
|
4756
5279
|
params: extractPathParams(fullPath),
|
|
4757
5280
|
auth: findAuthDecorators(m[3])
|
|
4758
5281
|
}));
|
|
@@ -4766,6 +5289,21 @@ var spring = {
|
|
|
4766
5289
|
file: rel,
|
|
4767
5290
|
line: lines.lineAt(m.index),
|
|
4768
5291
|
framework: "spring",
|
|
5292
|
+
kind: kindForOffset(classCtxs, m.index),
|
|
5293
|
+
params: extractPathParams(fullPath),
|
|
5294
|
+
auth: findAuthDecorators(m[3])
|
|
5295
|
+
}));
|
|
5296
|
+
}
|
|
5297
|
+
for (const m of content.matchAll(wsMappingRe)) {
|
|
5298
|
+
const fullPath = normalizePath(m[2]);
|
|
5299
|
+
endpoints.push(endpoint({
|
|
5300
|
+
method: "WS",
|
|
5301
|
+
path: fullPath,
|
|
5302
|
+
handler: m[4],
|
|
5303
|
+
file: rel,
|
|
5304
|
+
line: lines.lineAt(m.index),
|
|
5305
|
+
framework: "spring",
|
|
5306
|
+
kind: "websocket",
|
|
4769
5307
|
params: extractPathParams(fullPath),
|
|
4770
5308
|
auth: findAuthDecorators(m[3])
|
|
4771
5309
|
}));
|
|
@@ -4782,6 +5320,53 @@ var REST_ACTIONS = [
|
|
|
4782
5320
|
["PATCH", "update", "/:id"],
|
|
4783
5321
|
["DELETE", "destroy", "/:id"]
|
|
4784
5322
|
];
|
|
5323
|
+
function createControllerResolver(ctx) {
|
|
5324
|
+
const kindCache = /* @__PURE__ */ new Map;
|
|
5325
|
+
function controllerPath(className) {
|
|
5326
|
+
const parts = className.replace(/Controller$/i, "").split("::").map((p) => p.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1_$2").toLowerCase()).filter(Boolean);
|
|
5327
|
+
return join32(ctx.repoPath, "app", "controllers", ...parts.slice(0, -1), `${parts[parts.length - 1]}_controller.rb`);
|
|
5328
|
+
}
|
|
5329
|
+
function classNameFromRef(ref) {
|
|
5330
|
+
const head = ref.split("#")[0];
|
|
5331
|
+
if (head.includes("::")) {
|
|
5332
|
+
return head.endsWith("Controller") ? head : `${head}Controller`;
|
|
5333
|
+
}
|
|
5334
|
+
const segs = head.split("/").filter(Boolean);
|
|
5335
|
+
const camel = segs.map((s) => s.split("_").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(""));
|
|
5336
|
+
return camel.join("::") + "Controller";
|
|
5337
|
+
}
|
|
5338
|
+
function classifyClass(className, seen) {
|
|
5339
|
+
const cached = kindCache.get(className);
|
|
5340
|
+
if (cached)
|
|
5341
|
+
return cached;
|
|
5342
|
+
if (seen.has(className))
|
|
5343
|
+
return "api";
|
|
5344
|
+
seen.add(className);
|
|
5345
|
+
const path = controllerPath(className);
|
|
5346
|
+
const src = ctx.readFile(path);
|
|
5347
|
+
if (!src)
|
|
5348
|
+
return "api";
|
|
5349
|
+
const m = /class\s+([A-Za-z0-9_:]+)\s*<\s*([A-Za-z0-9_:]+(?:::[A-Za-z0-9_]+)*)/.exec(src);
|
|
5350
|
+
if (!m)
|
|
5351
|
+
return "api";
|
|
5352
|
+
const parent = m[2];
|
|
5353
|
+
let kind;
|
|
5354
|
+
if (parent === "ActionController::Base")
|
|
5355
|
+
kind = "page";
|
|
5356
|
+
else if (parent === "ActionController::API")
|
|
5357
|
+
kind = "api";
|
|
5358
|
+
else
|
|
5359
|
+
kind = classifyClass(parent, seen);
|
|
5360
|
+
kindCache.set(className, kind);
|
|
5361
|
+
return kind;
|
|
5362
|
+
}
|
|
5363
|
+
return {
|
|
5364
|
+
resolve(controllerRef) {
|
|
5365
|
+
const className = classNameFromRef(controllerRef);
|
|
5366
|
+
return classifyClass(className, /* @__PURE__ */ new Set);
|
|
5367
|
+
}
|
|
5368
|
+
};
|
|
5369
|
+
}
|
|
4785
5370
|
var rails = {
|
|
4786
5371
|
id: "rails",
|
|
4787
5372
|
detect: {
|
|
@@ -4797,35 +5382,63 @@ var rails = {
|
|
|
4797
5382
|
return endpoints;
|
|
4798
5383
|
ctx.filesScanned++;
|
|
4799
5384
|
const rel = ctx.rel(routesFile);
|
|
4800
|
-
const
|
|
5385
|
+
const resolver = createControllerResolver(ctx);
|
|
5386
|
+
const routeRe = /(get|post|put|patch|delete)\s+['"]([^'"]+)['"](?:\s*(?:,\s*to:|=>)\s*['"]([^'"]+)['"])?/g;
|
|
4801
5387
|
const resourcesRe = /resources?\s+:(\w+)/g;
|
|
4802
|
-
const
|
|
4803
|
-
const namespaces = [];
|
|
4804
|
-
for (const line of content.split(`
|
|
4805
|
-
`)) {
|
|
4806
|
-
const nsM = namespaceRe.exec(line);
|
|
4807
|
-
if (nsM)
|
|
4808
|
-
namespaces.push(nsM[1]);
|
|
4809
|
-
namespaceRe.lastIndex = 0;
|
|
4810
|
-
}
|
|
4811
|
-
const prefix = namespaces.length ? "/" + namespaces.join("/") : "";
|
|
5388
|
+
const namespaceLineRe = /\bnamespace\s+:(\w+)\b/;
|
|
4812
5389
|
const lines = buildLineIndex(content);
|
|
5390
|
+
const sourceLines = content.split(`
|
|
5391
|
+
`);
|
|
5392
|
+
const nsStack = [];
|
|
5393
|
+
let depth = 0;
|
|
5394
|
+
const prefixAtLine = new Array(sourceLines.length + 1).fill("");
|
|
5395
|
+
for (let i2 = 0;i2 < sourceLines.length; i2++) {
|
|
5396
|
+
const line = sourceLines[i2];
|
|
5397
|
+
const code = line.replace(/#.*$/, "");
|
|
5398
|
+
const nsM = namespaceLineRe.exec(code);
|
|
5399
|
+
if (nsM) {
|
|
5400
|
+
nsStack.push({ name: nsM[1], depth });
|
|
5401
|
+
}
|
|
5402
|
+
const endMatches = /^\s*end\b/.test(code);
|
|
5403
|
+
if (endMatches) {
|
|
5404
|
+
depth = Math.max(0, depth - 1);
|
|
5405
|
+
while (nsStack.length > 0 && nsStack[nsStack.length - 1].depth >= depth) {
|
|
5406
|
+
nsStack.pop();
|
|
5407
|
+
}
|
|
5408
|
+
}
|
|
5409
|
+
const doMatches = code.match(/\bdo\b(?:\s*\|[^|]*\|)?\s*$/);
|
|
5410
|
+
if (doMatches)
|
|
5411
|
+
depth++;
|
|
5412
|
+
const prefix = nsStack.length ? "/" + nsStack.map((n) => n.name).join("/") : "";
|
|
5413
|
+
prefixAtLine[i2 + 1] = prefix;
|
|
5414
|
+
}
|
|
5415
|
+
const prefixAt = (offset) => {
|
|
5416
|
+
const ln = lines.lineAt(offset);
|
|
5417
|
+
return prefixAtLine[ln] ?? "";
|
|
5418
|
+
};
|
|
4813
5419
|
for (const m of content.matchAll(routeRe)) {
|
|
5420
|
+
const prefix = prefixAt(m.index);
|
|
4814
5421
|
const fullPath = normalizePath(prefix + "/" + m[2]);
|
|
5422
|
+
const handler = m[3] || m[2].replace(/\//g, "_");
|
|
5423
|
+
const kind = m[3] ? resolver.resolve(m[3]) : "api";
|
|
4815
5424
|
endpoints.push(endpoint({
|
|
4816
5425
|
method: m[1].toUpperCase(),
|
|
4817
5426
|
path: fullPath,
|
|
4818
|
-
handler
|
|
5427
|
+
handler,
|
|
4819
5428
|
file: rel,
|
|
4820
5429
|
line: lines.lineAt(m.index),
|
|
4821
5430
|
framework: "rails",
|
|
5431
|
+
kind,
|
|
4822
5432
|
params: extractPathParams(fullPath)
|
|
4823
5433
|
}));
|
|
4824
5434
|
}
|
|
4825
5435
|
for (const m of content.matchAll(resourcesRe)) {
|
|
4826
5436
|
const resource = m[1];
|
|
4827
5437
|
const line = lines.lineAt(m.index);
|
|
5438
|
+
const prefix = prefixAt(m.index);
|
|
4828
5439
|
const base = normalizePath(prefix + "/" + resource);
|
|
5440
|
+
const controllerRef = (prefix.replace(/^\//, "") + "/" + resource).replace(/^\/+/, "").replace(/\/+/g, "/");
|
|
5441
|
+
const kind = resolver.resolve(`${controllerRef}#index`);
|
|
4829
5442
|
for (const [method, action, suffix] of REST_ACTIONS) {
|
|
4830
5443
|
const fullPath = normalizePath(base + suffix);
|
|
4831
5444
|
endpoints.push(endpoint({
|
|
@@ -4835,6 +5448,7 @@ var rails = {
|
|
|
4835
5448
|
file: rel,
|
|
4836
5449
|
line,
|
|
4837
5450
|
framework: "rails",
|
|
5451
|
+
kind,
|
|
4838
5452
|
params: extractPathParams(fullPath)
|
|
4839
5453
|
}));
|
|
4840
5454
|
}
|
|
@@ -4864,7 +5478,9 @@ var laravel = {
|
|
|
4864
5478
|
continue;
|
|
4865
5479
|
ctx.filesScanned++;
|
|
4866
5480
|
const rel = ctx.rel(rf);
|
|
5481
|
+
const isWeb = rf.endsWith("web.php");
|
|
4867
5482
|
const filePrefix = rf.endsWith("api.php") ? "/api" : "";
|
|
5483
|
+
const kind = isWeb ? "page" : "api";
|
|
4868
5484
|
const groupPrefixes = [];
|
|
4869
5485
|
for (const pm of content.matchAll(prefixGroupRe)) {
|
|
4870
5486
|
groupPrefixes.push(pm[1]);
|
|
@@ -4891,6 +5507,7 @@ var laravel = {
|
|
|
4891
5507
|
file: rel,
|
|
4892
5508
|
line: lines.lineAt(m.index),
|
|
4893
5509
|
framework: "laravel",
|
|
5510
|
+
kind,
|
|
4894
5511
|
params: extractPathParams(fullPath),
|
|
4895
5512
|
auth: [...fileAuth]
|
|
4896
5513
|
}));
|
|
@@ -5279,6 +5896,160 @@ var openapi = {
|
|
|
5279
5896
|
return endpoints;
|
|
5280
5897
|
}
|
|
5281
5898
|
};
|
|
5899
|
+
var CONNECT_HINTS = [
|
|
5900
|
+
"connectrpc.com/connect",
|
|
5901
|
+
"@connectrpc/",
|
|
5902
|
+
"protoc-gen-connect",
|
|
5903
|
+
"connect-go",
|
|
5904
|
+
"connect-es",
|
|
5905
|
+
"connectrpc"
|
|
5906
|
+
];
|
|
5907
|
+
var CONNECT_DEP_FILES = [
|
|
5908
|
+
"buf.gen.yaml",
|
|
5909
|
+
"buf.yaml",
|
|
5910
|
+
"package.json",
|
|
5911
|
+
"go.mod"
|
|
5912
|
+
];
|
|
5913
|
+
function makeConnectResolver(ctx) {
|
|
5914
|
+
const memo = /* @__PURE__ */ new Map;
|
|
5915
|
+
const root = ctx.repoPath;
|
|
5916
|
+
const dirUsesConnect = (dir) => {
|
|
5917
|
+
const cached = memo.get(dir);
|
|
5918
|
+
if (cached !== undefined)
|
|
5919
|
+
return cached;
|
|
5920
|
+
let result = false;
|
|
5921
|
+
for (const name of CONNECT_DEP_FILES) {
|
|
5922
|
+
const c = ctx.readFile(join9(dir, name));
|
|
5923
|
+
if (c && CONNECT_HINTS.some((h) => c.toLowerCase().includes(h))) {
|
|
5924
|
+
result = true;
|
|
5925
|
+
break;
|
|
5926
|
+
}
|
|
5927
|
+
}
|
|
5928
|
+
if (!result && dir !== root && dir.startsWith(root)) {
|
|
5929
|
+
const parent = dirname2(dir);
|
|
5930
|
+
if (parent !== dir)
|
|
5931
|
+
result = dirUsesConnect(parent);
|
|
5932
|
+
}
|
|
5933
|
+
memo.set(dir, result);
|
|
5934
|
+
return result;
|
|
5935
|
+
};
|
|
5936
|
+
return (file) => dirUsesConnect(dirname2(file));
|
|
5937
|
+
}
|
|
5938
|
+
function stripComments(src) {
|
|
5939
|
+
let out = "";
|
|
5940
|
+
for (let i2 = 0;i2 < src.length; ) {
|
|
5941
|
+
const c = src[i2];
|
|
5942
|
+
if (c === '"' || c === "'") {
|
|
5943
|
+
out += c;
|
|
5944
|
+
i2++;
|
|
5945
|
+
while (i2 < src.length && src[i2] !== c) {
|
|
5946
|
+
if (src[i2] === "\\" && i2 + 1 < src.length) {
|
|
5947
|
+
out += src[i2] + src[i2 + 1];
|
|
5948
|
+
i2 += 2;
|
|
5949
|
+
} else {
|
|
5950
|
+
out += src[i2];
|
|
5951
|
+
i2++;
|
|
5952
|
+
}
|
|
5953
|
+
}
|
|
5954
|
+
if (i2 < src.length) {
|
|
5955
|
+
out += src[i2];
|
|
5956
|
+
i2++;
|
|
5957
|
+
}
|
|
5958
|
+
} else if (c === "/" && src[i2 + 1] === "/") {
|
|
5959
|
+
while (i2 < src.length && src[i2] !== `
|
|
5960
|
+
`) {
|
|
5961
|
+
out += " ";
|
|
5962
|
+
i2++;
|
|
5963
|
+
}
|
|
5964
|
+
} else if (c === "/" && src[i2 + 1] === "*") {
|
|
5965
|
+
out += " ";
|
|
5966
|
+
i2 += 2;
|
|
5967
|
+
while (i2 < src.length && !(src[i2] === "*" && src[i2 + 1] === "/")) {
|
|
5968
|
+
out += src[i2] === `
|
|
5969
|
+
` ? `
|
|
5970
|
+
` : " ";
|
|
5971
|
+
i2++;
|
|
5972
|
+
}
|
|
5973
|
+
if (i2 < src.length) {
|
|
5974
|
+
out += " ";
|
|
5975
|
+
i2 += 2;
|
|
5976
|
+
}
|
|
5977
|
+
} else {
|
|
5978
|
+
out += c;
|
|
5979
|
+
i2++;
|
|
5980
|
+
}
|
|
5981
|
+
}
|
|
5982
|
+
return out;
|
|
5983
|
+
}
|
|
5984
|
+
function streaming(client, server) {
|
|
5985
|
+
if (client && server)
|
|
5986
|
+
return "bidi";
|
|
5987
|
+
if (client)
|
|
5988
|
+
return "client_stream";
|
|
5989
|
+
if (server)
|
|
5990
|
+
return "server_stream";
|
|
5991
|
+
return "unary";
|
|
5992
|
+
}
|
|
5993
|
+
var PACKAGE_RE = /\bpackage\s+([A-Za-z_][\w.]*)\s*;/;
|
|
5994
|
+
var SERVICE_RE = /\bservice\s+([A-Za-z_]\w*)\s*\{/g;
|
|
5995
|
+
var RPC_RE = /\brpc\s+([A-Za-z_]\w*)\s*\(\s*(stream\s+)?[.\w]+\s*\)\s*returns\s*\(\s*(stream\s+)?[.\w]+\s*\)/g;
|
|
5996
|
+
var grpc = {
|
|
5997
|
+
id: "grpc",
|
|
5998
|
+
detect: (_repoPath, ctx) => ctx.iterFiles([".proto"]).length > 0,
|
|
5999
|
+
extract(ctx) {
|
|
6000
|
+
const endpoints = [];
|
|
6001
|
+
const connectForFile = makeConnectResolver(ctx);
|
|
6002
|
+
for (const file of ctx.iterFiles([".proto"])) {
|
|
6003
|
+
const raw = ctx.readFile(file);
|
|
6004
|
+
if (!raw)
|
|
6005
|
+
continue;
|
|
6006
|
+
const framework = connectForFile(file) ? "connect" : "grpc";
|
|
6007
|
+
const transport = framework === "connect" ? "connect" : "grpc";
|
|
6008
|
+
const src = stripComments(raw);
|
|
6009
|
+
const rel = ctx.rel(file);
|
|
6010
|
+
const lines = buildLineIndex(raw);
|
|
6011
|
+
const pkg = PACKAGE_RE.exec(src)?.[1] ?? "";
|
|
6012
|
+
const services = [];
|
|
6013
|
+
SERVICE_RE.lastIndex = 0;
|
|
6014
|
+
let s;
|
|
6015
|
+
while (s = SERVICE_RE.exec(src)) {
|
|
6016
|
+
services.push({ name: s[1], index: s.index });
|
|
6017
|
+
}
|
|
6018
|
+
if (!services.length)
|
|
6019
|
+
continue;
|
|
6020
|
+
RPC_RE.lastIndex = 0;
|
|
6021
|
+
let m;
|
|
6022
|
+
while (m = RPC_RE.exec(src)) {
|
|
6023
|
+
let svc = null;
|
|
6024
|
+
for (const cand of services) {
|
|
6025
|
+
if (cand.index < m.index)
|
|
6026
|
+
svc = cand;
|
|
6027
|
+
else
|
|
6028
|
+
break;
|
|
6029
|
+
}
|
|
6030
|
+
if (!svc)
|
|
6031
|
+
continue;
|
|
6032
|
+
const serviceFqn = pkg ? `${pkg}.${svc.name}` : svc.name;
|
|
6033
|
+
const method = m[1];
|
|
6034
|
+
endpoints.push(endpoint({
|
|
6035
|
+
method: "ANY",
|
|
6036
|
+
path: `/${serviceFqn}/${method}`,
|
|
6037
|
+
handler: method,
|
|
6038
|
+
file: rel,
|
|
6039
|
+
line: lines.lineAt(m.index),
|
|
6040
|
+
framework,
|
|
6041
|
+
transport,
|
|
6042
|
+
grpc: {
|
|
6043
|
+
serviceFqn,
|
|
6044
|
+
method,
|
|
6045
|
+
streamingType: streaming(!!m[2], !!m[3])
|
|
6046
|
+
}
|
|
6047
|
+
}));
|
|
6048
|
+
}
|
|
6049
|
+
}
|
|
6050
|
+
return endpoints;
|
|
6051
|
+
}
|
|
6052
|
+
};
|
|
5282
6053
|
var ALL_EXTRACTORS = [
|
|
5283
6054
|
flask,
|
|
5284
6055
|
fastapi,
|
|
@@ -5296,9 +6067,11 @@ var ALL_EXTRACTORS = [
|
|
|
5296
6067
|
laravel,
|
|
5297
6068
|
sst,
|
|
5298
6069
|
serverActions,
|
|
5299
|
-
openapi
|
|
6070
|
+
openapi,
|
|
6071
|
+
grpc
|
|
5300
6072
|
];
|
|
5301
6073
|
var EXTRACTOR_MAP = new Map(ALL_EXTRACTORS.map((e) => [e.id, e]));
|
|
6074
|
+
EXTRACTOR_MAP.set("connect", grpc);
|
|
5302
6075
|
function getExtractor(id) {
|
|
5303
6076
|
return EXTRACTOR_MAP.get(id);
|
|
5304
6077
|
}
|
|
@@ -5330,7 +6103,7 @@ var PY_DEP_TO_TYPE = [
|
|
|
5330
6103
|
];
|
|
5331
6104
|
function inferServiceType(absDir) {
|
|
5332
6105
|
try {
|
|
5333
|
-
const content = readFileSync42(
|
|
6106
|
+
const content = readFileSync42(join10(absDir, "package.json"), "utf-8");
|
|
5334
6107
|
const lower = content.toLowerCase();
|
|
5335
6108
|
for (const [dep, type2] of PKG_DEP_TO_TYPE) {
|
|
5336
6109
|
if (lower.includes(`"${dep}"`))
|
|
@@ -5339,7 +6112,7 @@ function inferServiceType(absDir) {
|
|
|
5339
6112
|
return "generic";
|
|
5340
6113
|
} catch {}
|
|
5341
6114
|
try {
|
|
5342
|
-
const content = readFileSync42(
|
|
6115
|
+
const content = readFileSync42(join10(absDir, "go.mod"), "utf-8");
|
|
5343
6116
|
for (const [dep, type2] of GO_DEP_TO_TYPE) {
|
|
5344
6117
|
if (content.includes(dep))
|
|
5345
6118
|
return type2;
|
|
@@ -5347,7 +6120,7 @@ function inferServiceType(absDir) {
|
|
|
5347
6120
|
return "generic";
|
|
5348
6121
|
} catch {}
|
|
5349
6122
|
try {
|
|
5350
|
-
const content = readFileSync42(
|
|
6123
|
+
const content = readFileSync42(join10(absDir, "Cargo.toml"), "utf-8");
|
|
5351
6124
|
for (const [dep, type2] of CARGO_DEP_TO_TYPE) {
|
|
5352
6125
|
if (content.includes(dep))
|
|
5353
6126
|
return type2;
|
|
@@ -5355,7 +6128,7 @@ function inferServiceType(absDir) {
|
|
|
5355
6128
|
return "generic";
|
|
5356
6129
|
} catch {}
|
|
5357
6130
|
try {
|
|
5358
|
-
const content = readFileSync42(
|
|
6131
|
+
const content = readFileSync42(join10(absDir, "requirements.txt"), "utf-8");
|
|
5359
6132
|
for (const [dep, type2] of PY_DEP_TO_TYPE) {
|
|
5360
6133
|
if (content.includes(dep))
|
|
5361
6134
|
return type2;
|
|
@@ -5376,7 +6149,7 @@ function expandWorkspaceGlobs(repoPath, patterns) {
|
|
|
5376
6149
|
const clean = pattern.replace(/\/+$/, "");
|
|
5377
6150
|
if (clean.endsWith("/*") || clean.endsWith("/**")) {
|
|
5378
6151
|
const parentDir = clean.replace(/\/\*\*?$/, "");
|
|
5379
|
-
const absParent =
|
|
6152
|
+
const absParent = join11(repoPath, parentDir);
|
|
5380
6153
|
try {
|
|
5381
6154
|
for (const entry of readdirSync5(absParent, { withFileTypes: true })) {
|
|
5382
6155
|
if (entry.isDirectory() && !entry.name.startsWith(".") && entry.name !== "node_modules") {
|
|
@@ -5385,7 +6158,7 @@ function expandWorkspaceGlobs(repoPath, patterns) {
|
|
|
5385
6158
|
}
|
|
5386
6159
|
} catch {}
|
|
5387
6160
|
} else {
|
|
5388
|
-
if (existsSync5(
|
|
6161
|
+
if (existsSync5(join11(repoPath, clean))) {
|
|
5389
6162
|
dirs.push(clean);
|
|
5390
6163
|
}
|
|
5391
6164
|
}
|
|
@@ -5393,7 +6166,7 @@ function expandWorkspaceGlobs(repoPath, patterns) {
|
|
|
5393
6166
|
return dirs;
|
|
5394
6167
|
}
|
|
5395
6168
|
function detectWorkspaces(repoPath, _ctx, _existing) {
|
|
5396
|
-
const rootPkgPath =
|
|
6169
|
+
const rootPkgPath = join11(repoPath, "package.json");
|
|
5397
6170
|
let rootPkg;
|
|
5398
6171
|
try {
|
|
5399
6172
|
rootPkg = readFileSync5(rootPkgPath, "utf-8");
|
|
@@ -5418,7 +6191,7 @@ function detectWorkspaces(repoPath, _ctx, _existing) {
|
|
|
5418
6191
|
return dirsToServices(repoPath, dirs);
|
|
5419
6192
|
}
|
|
5420
6193
|
function detectPnpmWorkspaces(repoPath) {
|
|
5421
|
-
const wsPath =
|
|
6194
|
+
const wsPath = join11(repoPath, "pnpm-workspace.yaml");
|
|
5422
6195
|
let content;
|
|
5423
6196
|
try {
|
|
5424
6197
|
content = readFileSync5(wsPath, "utf-8");
|
|
@@ -5448,7 +6221,7 @@ function detectPnpmWorkspaces(repoPath) {
|
|
|
5448
6221
|
function dirsToServices(repoPath, dirs) {
|
|
5449
6222
|
const services = [];
|
|
5450
6223
|
for (const dir of dirs) {
|
|
5451
|
-
const pkgPath =
|
|
6224
|
+
const pkgPath = join11(repoPath, dir, "package.json");
|
|
5452
6225
|
let pkgContent;
|
|
5453
6226
|
try {
|
|
5454
6227
|
pkgContent = readFileSync5(pkgPath, "utf-8");
|
|
@@ -5464,7 +6237,7 @@ function dirsToServices(repoPath, dirs) {
|
|
|
5464
6237
|
}
|
|
5465
6238
|
services.push({
|
|
5466
6239
|
name,
|
|
5467
|
-
type: inferServiceType(
|
|
6240
|
+
type: inferServiceType(join11(repoPath, dir)),
|
|
5468
6241
|
root: dir
|
|
5469
6242
|
});
|
|
5470
6243
|
}
|
|
@@ -5531,7 +6304,7 @@ function isInfraService(name) {
|
|
|
5531
6304
|
return INFRA_SUFFIXES.some((suffix) => lower.endsWith(suffix));
|
|
5532
6305
|
}
|
|
5533
6306
|
function hasSourceCode(absDir) {
|
|
5534
|
-
if (SOURCE_MARKERS.some((f) => existsSync6(
|
|
6307
|
+
if (SOURCE_MARKERS.some((f) => existsSync6(join12(absDir, f))))
|
|
5535
6308
|
return true;
|
|
5536
6309
|
try {
|
|
5537
6310
|
const entries = readdirSync6(absDir, { withFileTypes: true });
|
|
@@ -5541,7 +6314,7 @@ function hasSourceCode(absDir) {
|
|
|
5541
6314
|
if (!entry.isDirectory() || entry.name.startsWith("."))
|
|
5542
6315
|
continue;
|
|
5543
6316
|
try {
|
|
5544
|
-
const subEntries = readdirSync6(
|
|
6317
|
+
const subEntries = readdirSync6(join12(absDir, entry.name));
|
|
5545
6318
|
if (subEntries.some((e) => SOURCE_EXTENSIONS.some((ext) => e.endsWith(ext))))
|
|
5546
6319
|
return true;
|
|
5547
6320
|
} catch {}
|
|
@@ -5561,7 +6334,7 @@ function detectCompose(repoPath, _ctx, existing) {
|
|
|
5561
6334
|
let composeContent = null;
|
|
5562
6335
|
for (const name of composeNames) {
|
|
5563
6336
|
try {
|
|
5564
|
-
composeContent = readFileSync6(
|
|
6337
|
+
composeContent = readFileSync6(join12(repoPath, name), "utf-8");
|
|
5565
6338
|
break;
|
|
5566
6339
|
} catch {}
|
|
5567
6340
|
}
|
|
@@ -5586,26 +6359,26 @@ function detectCompose(repoPath, _ctx, existing) {
|
|
|
5586
6359
|
continue;
|
|
5587
6360
|
if (isInfraService(name))
|
|
5588
6361
|
continue;
|
|
5589
|
-
const absDir =
|
|
6362
|
+
const absDir = join12(repoPath, root);
|
|
5590
6363
|
if (!existsSync6(absDir) || !hasSourceCode(absDir))
|
|
5591
6364
|
continue;
|
|
5592
6365
|
services.push({
|
|
5593
6366
|
name,
|
|
5594
|
-
type: inferServiceType(
|
|
6367
|
+
type: inferServiceType(join12(repoPath, root)),
|
|
5595
6368
|
root
|
|
5596
6369
|
});
|
|
5597
6370
|
}
|
|
5598
6371
|
return services;
|
|
5599
6372
|
}
|
|
5600
6373
|
function detectSstServices(repoPath, ctx, existing) {
|
|
5601
|
-
if (!existsSync7(
|
|
6374
|
+
if (!existsSync7(join13(repoPath, "sst.config.ts")))
|
|
5602
6375
|
return [];
|
|
5603
|
-
const infraDir =
|
|
6376
|
+
const infraDir = join13(repoPath, "infra");
|
|
5604
6377
|
if (!existsSync7(infraDir))
|
|
5605
6378
|
return [];
|
|
5606
6379
|
let files;
|
|
5607
6380
|
try {
|
|
5608
|
-
files = readdirSync7(infraDir).filter((f) => f.endsWith(".ts")).map((f) =>
|
|
6381
|
+
files = readdirSync7(infraDir).filter((f) => f.endsWith(".ts")).map((f) => join13(infraDir, f));
|
|
5609
6382
|
} catch {
|
|
5610
6383
|
return [];
|
|
5611
6384
|
}
|
|
@@ -5651,7 +6424,7 @@ function detectDirectories(repoPath, _ctx, existing) {
|
|
|
5651
6424
|
if (existing.length > 0)
|
|
5652
6425
|
return [];
|
|
5653
6426
|
const services = [];
|
|
5654
|
-
const rootHasMarker = SERVICE_MARKERS.some((f) => existsSync8(
|
|
6427
|
+
const rootHasMarker = SERVICE_MARKERS.some((f) => existsSync8(join14(repoPath, f)));
|
|
5655
6428
|
if (rootHasMarker) {
|
|
5656
6429
|
const name = inferRootName(repoPath);
|
|
5657
6430
|
services.push({
|
|
@@ -5668,11 +6441,11 @@ function detectDirectories(repoPath, _ctx, existing) {
|
|
|
5668
6441
|
return [];
|
|
5669
6442
|
}
|
|
5670
6443
|
for (const child of children) {
|
|
5671
|
-
const hasMarker = SERVICE_MARKERS.some((f) => existsSync8(
|
|
6444
|
+
const hasMarker = SERVICE_MARKERS.some((f) => existsSync8(join14(repoPath, child, f)));
|
|
5672
6445
|
if (hasMarker) {
|
|
5673
6446
|
services.push({
|
|
5674
6447
|
name: child,
|
|
5675
|
-
type: inferServiceType(
|
|
6448
|
+
type: inferServiceType(join14(repoPath, child)),
|
|
5676
6449
|
root: child
|
|
5677
6450
|
});
|
|
5678
6451
|
}
|
|
@@ -5680,7 +6453,7 @@ function detectDirectories(repoPath, _ctx, existing) {
|
|
|
5680
6453
|
const claimedRoots = new Set(services.map((s) => s.root));
|
|
5681
6454
|
const serviceDirs = ["src/services", "services", "apps", "src"];
|
|
5682
6455
|
for (const svcDir of serviceDirs) {
|
|
5683
|
-
const absDir =
|
|
6456
|
+
const absDir = join14(repoPath, svcDir);
|
|
5684
6457
|
if (!existsSync8(absDir))
|
|
5685
6458
|
continue;
|
|
5686
6459
|
try {
|
|
@@ -5690,11 +6463,11 @@ function detectDirectories(repoPath, _ctx, existing) {
|
|
|
5690
6463
|
const childPath = svcDir + "/" + entry.name;
|
|
5691
6464
|
if (claimedRoots.has(childPath))
|
|
5692
6465
|
continue;
|
|
5693
|
-
const hasMarker = SERVICE_MARKERS.some((f) => existsSync8(
|
|
6466
|
+
const hasMarker = SERVICE_MARKERS.some((f) => existsSync8(join14(repoPath, childPath, f)));
|
|
5694
6467
|
if (hasMarker) {
|
|
5695
6468
|
services.push({
|
|
5696
6469
|
name: entry.name,
|
|
5697
|
-
type: inferServiceType(
|
|
6470
|
+
type: inferServiceType(join14(repoPath, childPath)),
|
|
5698
6471
|
root: childPath
|
|
5699
6472
|
});
|
|
5700
6473
|
}
|
|
@@ -5705,7 +6478,7 @@ function detectDirectories(repoPath, _ctx, existing) {
|
|
|
5705
6478
|
}
|
|
5706
6479
|
function inferRootName(repoPath) {
|
|
5707
6480
|
try {
|
|
5708
|
-
const pkg = JSON.parse(readFileSync7(
|
|
6481
|
+
const pkg = JSON.parse(readFileSync7(join14(repoPath, "package.json"), "utf-8"));
|
|
5709
6482
|
if (pkg.name)
|
|
5710
6483
|
return stripOrgScope(pkg.name);
|
|
5711
6484
|
} catch {}
|
|
@@ -5787,7 +6560,7 @@ function readDepFiles(repoPath) {
|
|
|
5787
6560
|
const rootParts = [];
|
|
5788
6561
|
for (const fname of DEP_FILENAMES) {
|
|
5789
6562
|
try {
|
|
5790
|
-
rootParts.push(readFileSync8(
|
|
6563
|
+
rootParts.push(readFileSync8(join15(repoPath, fname), "utf-8"));
|
|
5791
6564
|
} catch {}
|
|
5792
6565
|
}
|
|
5793
6566
|
const workspaceParts = [];
|
|
@@ -5796,7 +6569,7 @@ function readDepFiles(repoPath) {
|
|
|
5796
6569
|
if (!child.isDirectory() || SKIP_DIRS.has(child.name) || child.name.startsWith("."))
|
|
5797
6570
|
continue;
|
|
5798
6571
|
try {
|
|
5799
|
-
workspaceParts.push(readFileSync8(
|
|
6572
|
+
workspaceParts.push(readFileSync8(join15(repoPath, child.name, "package.json"), "utf-8"));
|
|
5800
6573
|
} catch {}
|
|
5801
6574
|
}
|
|
5802
6575
|
} catch {}
|
|
@@ -5815,10 +6588,10 @@ function getChildDirs(repoPath) {
|
|
|
5815
6588
|
}
|
|
5816
6589
|
function findMarkerFile(repoPath, markers, childDirs) {
|
|
5817
6590
|
for (const marker of markers) {
|
|
5818
|
-
if (existsSync9(
|
|
6591
|
+
if (existsSync9(join15(repoPath, marker)))
|
|
5819
6592
|
return true;
|
|
5820
6593
|
for (const child of childDirs) {
|
|
5821
|
-
if (existsSync9(
|
|
6594
|
+
if (existsSync9(join15(repoPath, child, marker)))
|
|
5822
6595
|
return true;
|
|
5823
6596
|
}
|
|
5824
6597
|
}
|
|
@@ -5854,7 +6627,7 @@ function detectFrameworks(repoPath, ctx) {
|
|
|
5854
6627
|
detected.push(extractor.id);
|
|
5855
6628
|
}
|
|
5856
6629
|
}
|
|
5857
|
-
if (!GO_FRAMEWORKS.some((gf) => detected.includes(gf)) && existsSync9(
|
|
6630
|
+
if (!GO_FRAMEWORKS.some((gf) => detected.includes(gf)) && existsSync9(join15(repoPath, "go.mod"))) {
|
|
5858
6631
|
for (const f of ctx.iterFiles([".go"])) {
|
|
5859
6632
|
const content = ctx.readFile(f);
|
|
5860
6633
|
if (content && /"net\/http"/.test(content)) {
|
|
@@ -5869,7 +6642,7 @@ function detectFrameworksInServices(repoPath, services, ctx, alreadyDetected) {
|
|
|
5869
6642
|
const additional = [];
|
|
5870
6643
|
const seen = new Set(alreadyDetected);
|
|
5871
6644
|
for (const svc of services) {
|
|
5872
|
-
const svcPath =
|
|
6645
|
+
const svcPath = join15(repoPath, svc.root);
|
|
5873
6646
|
if (!existsSync9(svcPath))
|
|
5874
6647
|
continue;
|
|
5875
6648
|
const deps = readDepFiles(svcPath);
|
|
@@ -5895,7 +6668,7 @@ function detectFrameworksInServices(repoPath, services, ctx, alreadyDetected) {
|
|
|
5895
6668
|
seen.add(extractor.id);
|
|
5896
6669
|
}
|
|
5897
6670
|
}
|
|
5898
|
-
if (!GO_FRAMEWORKS.some((gf) => seen.has(gf)) && !seen.has("net_http") && existsSync9(
|
|
6671
|
+
if (!GO_FRAMEWORKS.some((gf) => seen.has(gf)) && !seen.has("net_http") && existsSync9(join15(svcPath, "go.mod"))) {
|
|
5899
6672
|
for (const f of ctx.iterFiles([".go"])) {
|
|
5900
6673
|
const content = ctx.readFile(f);
|
|
5901
6674
|
if (content && /"net\/http"/.test(content)) {
|
|
@@ -5999,15 +6772,19 @@ function mapRaw(repoPath, options = {}) {
|
|
|
5999
6772
|
filesScanned: ctx.filesScanned
|
|
6000
6773
|
};
|
|
6001
6774
|
}
|
|
6775
|
+
var isProtoGrpc = (ep) => ep.framework === "grpc" || ep.framework === "connect";
|
|
6002
6776
|
function map2(repoPath, options = {}) {
|
|
6003
6777
|
const raw = mapRaw(repoPath, options);
|
|
6004
|
-
const
|
|
6778
|
+
const indexByKey = /* @__PURE__ */ new Map;
|
|
6005
6779
|
const unique = [];
|
|
6006
6780
|
for (const ep of raw.endpoints) {
|
|
6007
|
-
const key = `${ep.method}::${ep.path}`;
|
|
6008
|
-
|
|
6009
|
-
|
|
6781
|
+
const key = `${ep.transport ?? "http"}::${ep.method}::${ep.path}`;
|
|
6782
|
+
const idx = indexByKey.get(key);
|
|
6783
|
+
if (idx === undefined) {
|
|
6784
|
+
indexByKey.set(key, unique.length);
|
|
6010
6785
|
unique.push(ep);
|
|
6786
|
+
} else if (isProtoGrpc(ep) && !isProtoGrpc(unique[idx])) {
|
|
6787
|
+
unique[idx] = ep;
|
|
6011
6788
|
}
|
|
6012
6789
|
}
|
|
6013
6790
|
const filtered = options.includeInternal ? unique : unique.filter((e) => !e.internal);
|
|
@@ -6088,7 +6865,7 @@ function findDependencyRoot(appPath, repoRoot) {
|
|
|
6088
6865
|
}
|
|
6089
6866
|
if (current === absRoot)
|
|
6090
6867
|
break;
|
|
6091
|
-
const parent =
|
|
6868
|
+
const parent = dirname3(current);
|
|
6092
6869
|
if (parent === current)
|
|
6093
6870
|
break;
|
|
6094
6871
|
if (!parent.startsWith(absRoot))
|
|
@@ -6113,7 +6890,9 @@ function consolidateBySameRoute(endpoints) {
|
|
|
6113
6890
|
framework: ep.framework,
|
|
6114
6891
|
kind: ep.kind,
|
|
6115
6892
|
auth: [...ep.auth],
|
|
6116
|
-
internal: ep.internal
|
|
6893
|
+
internal: ep.internal,
|
|
6894
|
+
transport: ep.transport,
|
|
6895
|
+
grpc: ep.grpc
|
|
6117
6896
|
});
|
|
6118
6897
|
handlerSets.set(key, new Set(ep.handler ? [ep.handler] : []));
|
|
6119
6898
|
continue;
|
|
@@ -6234,6 +7013,7 @@ async function runWhiteboxAttackSurfaceWorkflow(input) {
|
|
|
6234
7013
|
onCacheMetrics,
|
|
6235
7014
|
openAIReasoningEffort,
|
|
6236
7015
|
enableThinking,
|
|
7016
|
+
thinkingEffort,
|
|
6237
7017
|
domains,
|
|
6238
7018
|
projectThreatModel,
|
|
6239
7019
|
environments,
|
|
@@ -6242,6 +7022,7 @@ async function runWhiteboxAttackSurfaceWorkflow(input) {
|
|
|
6242
7022
|
} = input;
|
|
6243
7023
|
const agentSlots = pLimit(Math.max(1, maxConcurrentAgents));
|
|
6244
7024
|
const agentLimiter = (fn) => agentSlots(fn);
|
|
7025
|
+
const WORKFLOW_UMBRELLA_ID = newSessionId();
|
|
6245
7026
|
const appsAgent = new CodeAgent({
|
|
6246
7027
|
codebasePath,
|
|
6247
7028
|
objective: buildAppsDiscoveryObjective(codebasePath, domains, environments),
|
|
@@ -6252,17 +7033,18 @@ async function runWhiteboxAttackSurfaceWorkflow(input) {
|
|
|
6252
7033
|
abortSignal,
|
|
6253
7034
|
attackSurfaceRegistry,
|
|
6254
7035
|
eventBus,
|
|
6255
|
-
subagentId:
|
|
7036
|
+
subagentId: WORKFLOW_UMBRELLA_ID,
|
|
7037
|
+
subagentName: "Whitebox Apps Discovery",
|
|
6256
7038
|
onStepFinish: (event) => onStepFinish?.(event),
|
|
6257
7039
|
onCacheMetrics,
|
|
6258
7040
|
openAIReasoningEffort,
|
|
6259
7041
|
enableThinking,
|
|
7042
|
+
thinkingEffort,
|
|
6260
7043
|
responseSchema: AppsDiscoveryResultSchema,
|
|
6261
7044
|
projectThreatModel,
|
|
6262
7045
|
excludeTools: ["document_endpoint"]
|
|
6263
7046
|
});
|
|
6264
7047
|
log4.info(`Phase 1: discovering apps in ${codebasePath}${domains?.length ? ` (${domains.length} known domains)` : ""}`);
|
|
6265
|
-
const WORKFLOW_UMBRELLA_ID = "whitebox-apps-discovery";
|
|
6266
7048
|
eventBus?.emit("subagent-spawn", {
|
|
6267
7049
|
subagentId: WORKFLOW_UMBRELLA_ID,
|
|
6268
7050
|
name: "Whitebox Apps Discovery",
|
|
@@ -6292,12 +7074,12 @@ async function runWhiteboxAttackSurfaceWorkflow(input) {
|
|
|
6292
7074
|
}
|
|
6293
7075
|
};
|
|
6294
7076
|
}
|
|
6295
|
-
const assetsPath =
|
|
7077
|
+
const assetsPath = join16(session.rootPath, "assets");
|
|
6296
7078
|
mkdirSync2(assetsPath, { recursive: true });
|
|
6297
7079
|
for (const app of appsResult.apps) {
|
|
6298
|
-
const appDir =
|
|
7080
|
+
const appDir = join16(assetsPath, sanitizeName(app.name));
|
|
6299
7081
|
mkdirSync2(appDir, { recursive: true });
|
|
6300
|
-
writeFileSync3(
|
|
7082
|
+
writeFileSync3(join16(appDir, "app.json"), JSON.stringify(toAppMetadata(app), null, 2), "utf-8");
|
|
6301
7083
|
}
|
|
6302
7084
|
log4.info(`Phase 2: surfaceIntegrationEnabled=${surfaceIntegrationEnabled}`);
|
|
6303
7085
|
const NON_SERVICE_TYPES = ["cloud_resource", "storage", "database"];
|
|
@@ -6323,7 +7105,7 @@ async function runWhiteboxAttackSurfaceWorkflow(input) {
|
|
|
6323
7105
|
});
|
|
6324
7106
|
}
|
|
6325
7107
|
const spawnDiscoveryAgent = async (app, type2, objective) => {
|
|
6326
|
-
const subagentId =
|
|
7108
|
+
const subagentId = newSessionId();
|
|
6327
7109
|
const appNodeId = appNodeIdFor(app.name);
|
|
6328
7110
|
log4.debug(`Phase 2: spawning agent id="${subagentId}" parent="${appNodeId}" (app="${app.name}", type=${type2}, appType=${app.type})`);
|
|
6329
7111
|
eventBus?.emit("subagent-spawn", {
|
|
@@ -6343,10 +7125,12 @@ async function runWhiteboxAttackSurfaceWorkflow(input) {
|
|
|
6343
7125
|
attackSurfaceRegistry,
|
|
6344
7126
|
eventBus,
|
|
6345
7127
|
subagentId,
|
|
7128
|
+
subagentName: TASK_TYPE_LABELS[type2],
|
|
6346
7129
|
onStepFinish: (event) => onStepFinish?.(event),
|
|
6347
7130
|
onCacheMetrics,
|
|
6348
7131
|
openAIReasoningEffort,
|
|
6349
7132
|
enableThinking,
|
|
7133
|
+
thinkingEffort,
|
|
6350
7134
|
responseSchema: DiscoverySummarySchema,
|
|
6351
7135
|
excludeTools: ["document_app"],
|
|
6352
7136
|
projectThreatModel
|
|
@@ -6384,7 +7168,7 @@ async function runWhiteboxAttackSurfaceWorkflow(input) {
|
|
|
6384
7168
|
spawnApiEndpointsAgent(app)
|
|
6385
7169
|
]);
|
|
6386
7170
|
} else {
|
|
6387
|
-
const surfaceResult = mapAppWithSurface(
|
|
7171
|
+
const surfaceResult = mapAppWithSurface(join16(codebasePath, app.location), codebasePath, { isSingleAppRepo: serviceApps.length === 1 });
|
|
6388
7172
|
if (surfaceResult.mode === "surface") {
|
|
6389
7173
|
log4.debug(`${app.name}: surface-driven (${surfaceResult.endpoints.length} endpoints, frameworks=${surfaceResult.frameworks.join(",")})`);
|
|
6390
7174
|
await runAppEndpointDocumentation({
|
|
@@ -6402,6 +7186,7 @@ async function runWhiteboxAttackSurfaceWorkflow(input) {
|
|
|
6402
7186
|
onCacheMetrics,
|
|
6403
7187
|
openAIReasoningEffort,
|
|
6404
7188
|
enableThinking,
|
|
7189
|
+
thinkingEffort,
|
|
6405
7190
|
projectThreatModel,
|
|
6406
7191
|
parentSubagentId: appNodeId,
|
|
6407
7192
|
agentLimiter
|
|
@@ -6471,12 +7256,12 @@ function readAppsFromAssetsDirectory(assetsPath, appsDiscovery) {
|
|
|
6471
7256
|
log4.debug(`readAssets: found ${entries.length} entries in ${assetsPath}: [${entries.join(", ")}]`);
|
|
6472
7257
|
const apps = [];
|
|
6473
7258
|
for (const entry of entries) {
|
|
6474
|
-
const entryPath =
|
|
7259
|
+
const entryPath = join16(assetsPath, entry);
|
|
6475
7260
|
if (!statSync2(entryPath).isDirectory()) {
|
|
6476
7261
|
log4.debug(`readAssets: skipping non-directory: ${entry}`);
|
|
6477
7262
|
continue;
|
|
6478
7263
|
}
|
|
6479
|
-
const appJsonPath =
|
|
7264
|
+
const appJsonPath = join16(entryPath, "app.json");
|
|
6480
7265
|
let metadata;
|
|
6481
7266
|
if (existsSync12(appJsonPath)) {
|
|
6482
7267
|
try {
|
|
@@ -6496,7 +7281,7 @@ function readAppsFromAssetsDirectory(assetsPath, appsDiscovery) {
|
|
|
6496
7281
|
let parseFailed = 0;
|
|
6497
7282
|
for (const file of assetFiles) {
|
|
6498
7283
|
try {
|
|
6499
|
-
const raw = readFileSync9(
|
|
7284
|
+
const raw = readFileSync9(join16(entryPath, file), "utf-8");
|
|
6500
7285
|
const data = JSON.parse(raw);
|
|
6501
7286
|
const endpoint2 = assetRecordToEndpoint(data);
|
|
6502
7287
|
if (!endpoint2) {
|
|
@@ -6542,7 +7327,9 @@ function assetRecordToEndpoint(record) {
|
|
|
6542
7327
|
description: record.description,
|
|
6543
7328
|
pentestObjectives: record.pentestObjectives ?? [],
|
|
6544
7329
|
riskScore: record.riskScore,
|
|
6545
|
-
threatModel: record.threatModel
|
|
7330
|
+
threatModel: record.threatModel,
|
|
7331
|
+
transport: record.transport,
|
|
7332
|
+
grpc: record.grpc
|
|
6546
7333
|
});
|
|
6547
7334
|
return parsed.success ? parsed.data : null;
|
|
6548
7335
|
}
|
|
@@ -6743,6 +7530,15 @@ Find ALL API endpoints **whose route definitions live in this application's sour
|
|
|
6743
7530
|
- **Rails**: routes.rb API namespaces, resources, controller actions
|
|
6744
7531
|
- **Spring**: @GetMapping, @PostMapping, @PutMapping, @DeleteMapping, @RequestMapping
|
|
6745
7532
|
- **Go**: http.HandleFunc, mux.Handle, gin router methods
|
|
7533
|
+
- **gRPC**: \`.proto\` service definitions (\`service X { rpc Method (Req) returns (Resp); }\`), \`buf.yaml\`/\`buf.gen.yaml\`, or generated gRPC stubs
|
|
7534
|
+
|
|
7535
|
+
### gRPC / Connect services — do NOT flatten into HTTP paths
|
|
7536
|
+
A gRPC method looks like a path (\`/package.Service/Method\`) but is NOT an HTTP route. When this app defines gRPC/Connect services, document **one \`document_endpoint\` per \`rpc\` method** with:
|
|
7537
|
+
- **endpointType**: \`"api-endpoint"\` (a gRPC method is still an API endpoint)
|
|
7538
|
+
- **transport**: \`"grpc"\` (or \`"grpc_web"\` / \`"connect"\` if the service is served that way)
|
|
7539
|
+
- **routePath**: the wire path \`/package.Service/Method\` (e.g. \`/account.v1.AccountService/GetAccount\`) — do NOT prepend a host
|
|
7540
|
+
- **grpc**: \`{ serviceFqn, method, streamingType, schemaSource: "proto" }\` where \`serviceFqn\` is the fully-qualified service (\`account.v1.AccountService\`), \`method\` is the rpc name, and \`streamingType\` is one of \`unary | server_stream | client_stream | bidi\` (based on the \`stream\` keywords in the rpc signature). Set \`reflectionAvailable: true\` if the server registers the gRPC reflection service.
|
|
7541
|
+
- If a REST/GraphQL gateway (e.g. grpc-gateway annotations, a GraphQL resolver, a Connect handler) maps to this rpc, set \`grpc.frontingGatewayOperation\` — prefer marking this whenever a gateway mapping exists, since it's what lets us later compute "shadow" methods (proto methods reachable directly but NOT exposed via the gateway, where auth checks usually live).
|
|
6746
7542
|
|
|
6747
7543
|
### How to document each endpoint
|
|
6748
7544
|
For each **unique route path**, call \`document_endpoint\` with:
|
|
@@ -6756,6 +7552,7 @@ For each **unique route path**, call \`document_endpoint\` with:
|
|
|
6756
7552
|
- **handler**: Handler function name (comma-separate if multiple handlers for different methods)
|
|
6757
7553
|
- **authRequired**: Whether the endpoint requires authentication (true if ANY method requires it)
|
|
6758
7554
|
- **riskLevel**: CRITICAL for auth/payment/admin, HIGH for user data mutations, MEDIUM for general, LOW for read-only public
|
|
7555
|
+
- **transport / grpc**: for gRPC / Connect / gRPC-Web methods, this checklist is not enough — ALSO pass the \`transport\` and \`grpc\` fields exactly as described in the "gRPC / Connect services" section above. Never document an \`rpc\` as a plain HTTP route.
|
|
6759
7556
|
|
|
6760
7557
|
**CRITICAL: ONE entry per route path.** If \`/api/products\` has GET (list) and POST (create), document it as ONE entry with \`method: ["GET", "POST"]\`. Do NOT create two separate entries.
|
|
6761
7558
|
|
|
@@ -6865,21 +7662,19 @@ async function runPentestSwarm(input) {
|
|
|
6865
7662
|
concurrency = DEFAULT_CONCURRENCY2,
|
|
6866
7663
|
onStepFinish,
|
|
6867
7664
|
enableThinking,
|
|
7665
|
+
thinkingEffort,
|
|
6868
7666
|
openAIReasoningEffort,
|
|
6869
7667
|
display
|
|
6870
7668
|
} = input;
|
|
6871
7669
|
const completedIds = getCompletedAgentIds(session);
|
|
6872
7670
|
const existingManifest = readAgentManifest(session);
|
|
6873
7671
|
const freshEntries = buildManifestEntries(targets);
|
|
6874
|
-
const manifestEntries = freshEntries
|
|
6875
|
-
const existing = existingManifest.find((e) => e.id === fresh.id);
|
|
6876
|
-
if (existing && existing.status === "completed")
|
|
6877
|
-
return existing;
|
|
6878
|
-
return fresh;
|
|
6879
|
-
});
|
|
7672
|
+
const manifestEntries = reconcileManifestOnResume(existingManifest, freshEntries);
|
|
6880
7673
|
writeAgentManifest(session, manifestEntries);
|
|
6881
7674
|
const results = await runWithBoundedConcurrency(targets, concurrency, async (target, index) => {
|
|
6882
|
-
const
|
|
7675
|
+
const entry = manifestEntries[index];
|
|
7676
|
+
const subagentId = entry.id;
|
|
7677
|
+
const subagentName = target.name ?? entry.name;
|
|
6883
7678
|
if (completedIds.has(subagentId))
|
|
6884
7679
|
return null;
|
|
6885
7680
|
const previousMessages = loadSubagentMessages(session, subagentId);
|
|
@@ -6892,18 +7687,19 @@ async function runPentestSwarm(input) {
|
|
|
6892
7687
|
};
|
|
6893
7688
|
eventBus?.emit("subagent-spawn", {
|
|
6894
7689
|
subagentId,
|
|
6895
|
-
name:
|
|
7690
|
+
name: subagentName,
|
|
6896
7691
|
input: { target: target.target, objectives: target.objectives }
|
|
6897
7692
|
});
|
|
6898
7693
|
if (session.config?.requirePlan && session.config?.taskDriven && previousMessages.length === 0) {
|
|
6899
7694
|
try {
|
|
6900
|
-
const executionTasksDir =
|
|
7695
|
+
const executionTasksDir = join18(session.rootPath, "subagents", `${subagentId}-tasks`);
|
|
6901
7696
|
const planAgent = new OffensiveSecurityAgent({
|
|
6902
7697
|
system: PLAN_PHASE_SYSTEM_PROMPT,
|
|
6903
|
-
prompt: buildPlanPrompt(target.target, target.objectives),
|
|
7698
|
+
prompt: buildPlanPrompt(target.target, target.objectives, target.grpc),
|
|
6904
7699
|
model,
|
|
6905
7700
|
session,
|
|
6906
7701
|
target: target.target,
|
|
7702
|
+
grpc: target.grpc,
|
|
6907
7703
|
mode: "plan",
|
|
6908
7704
|
activeTools: [...PLAN_MODE_TOOL_NAMES],
|
|
6909
7705
|
stopWhen: hasToolCall("submit_plan"),
|
|
@@ -6911,10 +7707,12 @@ async function runPentestSwarm(input) {
|
|
|
6911
7707
|
abortSignal,
|
|
6912
7708
|
findingsRegistry,
|
|
6913
7709
|
subagentId: `${subagentId}-plan`,
|
|
7710
|
+
subagentName: `${subagentName} (plan)`,
|
|
6914
7711
|
tasksDir: executionTasksDir,
|
|
6915
7712
|
planSubagentId: subagentId,
|
|
6916
7713
|
eventBus,
|
|
6917
7714
|
enableThinking,
|
|
7715
|
+
thinkingEffort,
|
|
6918
7716
|
openAIReasoningEffort,
|
|
6919
7717
|
display
|
|
6920
7718
|
});
|
|
@@ -6928,6 +7726,7 @@ async function runPentestSwarm(input) {
|
|
|
6928
7726
|
try {
|
|
6929
7727
|
agent = new TargetedPentestAgent({
|
|
6930
7728
|
target: target.target,
|
|
7729
|
+
grpc: target.grpc,
|
|
6931
7730
|
objectives: target.objectives,
|
|
6932
7731
|
model,
|
|
6933
7732
|
session,
|
|
@@ -6938,7 +7737,9 @@ async function runPentestSwarm(input) {
|
|
|
6938
7737
|
messages: previousMessages.length > 0 ? previousMessages : undefined,
|
|
6939
7738
|
eventBus,
|
|
6940
7739
|
subagentId,
|
|
7740
|
+
subagentName,
|
|
6941
7741
|
enableThinking,
|
|
7742
|
+
thinkingEffort,
|
|
6942
7743
|
openAIReasoningEffort,
|
|
6943
7744
|
display
|
|
6944
7745
|
});
|
|
@@ -6984,6 +7785,10 @@ async function runPentestSwarm(input) {
|
|
|
6984
7785
|
return results;
|
|
6985
7786
|
}
|
|
6986
7787
|
async function runPentestWorkflow(input) {
|
|
7788
|
+
if (input.fastStrike) {
|
|
7789
|
+
const { runFastStrike } = await import("./fastStrike-9tm6bedh.js");
|
|
7790
|
+
return runFastStrike(input);
|
|
7791
|
+
}
|
|
6987
7792
|
const {
|
|
6988
7793
|
target,
|
|
6989
7794
|
cwd,
|
|
@@ -6995,6 +7800,7 @@ async function runPentestWorkflow(input) {
|
|
|
6995
7800
|
onStepFinish,
|
|
6996
7801
|
onCacheMetrics,
|
|
6997
7802
|
enableThinking,
|
|
7803
|
+
thinkingEffort,
|
|
6998
7804
|
openAIReasoningEffort,
|
|
6999
7805
|
prompt,
|
|
7000
7806
|
threatModel,
|
|
@@ -7100,8 +7906,8 @@ ${combined}` : combined;
|
|
|
7100
7906
|
sessionId: session.id,
|
|
7101
7907
|
mode
|
|
7102
7908
|
});
|
|
7103
|
-
const mdPath2 =
|
|
7104
|
-
const jsonPath2 =
|
|
7909
|
+
const mdPath2 = join18(session.rootPath, REPORT_FILENAME_MD);
|
|
7910
|
+
const jsonPath2 = join18(session.rootPath, REPORT_FILENAME_JSON);
|
|
7105
7911
|
writeFileSync4(mdPath2, renderMarkdown(report2));
|
|
7106
7912
|
writeFileSync4(jsonPath2, renderJson(report2));
|
|
7107
7913
|
return {
|
|
@@ -7132,6 +7938,7 @@ ${combined}` : combined;
|
|
|
7132
7938
|
eventBus,
|
|
7133
7939
|
onStepFinish: wrappedOnStepFinish,
|
|
7134
7940
|
enableThinking,
|
|
7941
|
+
thinkingEffort,
|
|
7135
7942
|
openAIReasoningEffort
|
|
7136
7943
|
});
|
|
7137
7944
|
}
|
|
@@ -7150,8 +7957,8 @@ ${combined}` : combined;
|
|
|
7150
7957
|
sessionId: session.id,
|
|
7151
7958
|
mode
|
|
7152
7959
|
});
|
|
7153
|
-
const mdPath =
|
|
7154
|
-
const jsonPath =
|
|
7960
|
+
const mdPath = join18(session.rootPath, REPORT_FILENAME_MD);
|
|
7961
|
+
const jsonPath = join18(session.rootPath, REPORT_FILENAME_JSON);
|
|
7155
7962
|
writeFileSync4(mdPath, renderMarkdown(report));
|
|
7156
7963
|
writeFileSync4(jsonPath, renderJson(report));
|
|
7157
7964
|
const severityCounts = {};
|
|
@@ -7196,10 +8003,14 @@ async function runWhiteboxPhase(opts) {
|
|
|
7196
8003
|
surfaceIntegrationEnabled: opts.surfaceIntegrationEnabled
|
|
7197
8004
|
};
|
|
7198
8005
|
const result = await runWhiteboxAttackSurfaceWorkflow(workflowInput);
|
|
7199
|
-
return result.apps.flatMap((app) => [...app.pages, ...app.apiEndpoints].map((ep) =>
|
|
7200
|
-
|
|
7201
|
-
|
|
7202
|
-
|
|
8006
|
+
return result.apps.flatMap((app) => [...app.pages, ...app.apiEndpoints].map((ep) => {
|
|
8007
|
+
const grpc2 = toGrpcPentestContext(ep.transport, ep.grpc, ep.path);
|
|
8008
|
+
return {
|
|
8009
|
+
target: grpcAuthorityTarget(grpc2, ep.path) ?? (ep.path.startsWith("http") ? ep.path : `${opts.baseTarget}${ep.path}`),
|
|
8010
|
+
objectives: ep.pentestObjectives,
|
|
8011
|
+
grpc: grpc2
|
|
8012
|
+
};
|
|
8013
|
+
}));
|
|
7203
8014
|
}
|
|
7204
8015
|
async function runBlackboxPhase(opts) {
|
|
7205
8016
|
let lastMessages = [];
|
|
@@ -7257,4 +8068,4 @@ async function runBlackboxPhase(opts) {
|
|
|
7257
8068
|
throw e;
|
|
7258
8069
|
}
|
|
7259
8070
|
}
|
|
7260
|
-
export {
|
|
8071
|
+
export { readExecutionMetrics, writeExecutionMetrics, convertModelMessagesToUI, loadSubagents, DEFAULT_CONCURRENCY2 as DEFAULT_CONCURRENCY, runPentestSwarm, runPentestWorkflow };
|