@bbradar/mcp 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +156 -0
- package/dist/bbradarClient.d.ts +61 -0
- package/dist/bbradarClient.js +307 -0
- package/dist/bbradarClient.js.map +1 -0
- package/dist/cache.d.ts +26 -0
- package/dist/cache.js +60 -0
- package/dist/cache.js.map +1 -0
- package/dist/config.d.ts +12 -0
- package/dist/config.js +60 -0
- package/dist/config.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +21 -0
- package/dist/index.js.map +1 -0
- package/dist/json.d.ts +4 -0
- package/dist/json.js +21 -0
- package/dist/json.js.map +1 -0
- package/dist/rateLimit.d.ts +11 -0
- package/dist/rateLimit.js +29 -0
- package/dist/rateLimit.js.map +1 -0
- package/dist/sanitize.d.ts +11 -0
- package/dist/sanitize.js +202 -0
- package/dist/sanitize.js.map +1 -0
- package/dist/server.d.ts +4 -0
- package/dist/server.js +4418 -0
- package/dist/server.js.map +1 -0
- package/dist/url.d.ts +1 -0
- package/dist/url.js +21 -0
- package/dist/url.js.map +1 -0
- package/package.json +55 -0
package/dist/server.js
ADDED
|
@@ -0,0 +1,4418 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import { BBRadarApiError } from "./bbradarClient.js";
|
|
5
|
+
import { stripUndefined } from "./json.js";
|
|
6
|
+
import { FixedWindowRateLimiter } from "./rateLimit.js";
|
|
7
|
+
import { readArray, readBoolean, readNumber, readObject, readString, sanitizeJson, sanitizeChange, sanitizeProgram, sanitizeTarget, sanitizeTargetsForExport } from "./sanitize.js";
|
|
8
|
+
const SERVER_VERSION = "0.1.0";
|
|
9
|
+
const MAX_PROGRAMS_PER_SEARCH = 100;
|
|
10
|
+
const MAX_TARGET_EXPORT = 500;
|
|
11
|
+
const MAX_RECENT_CHANGES = 100;
|
|
12
|
+
const MAX_TARGETS_PER_PROGRAM = 500;
|
|
13
|
+
const MAX_WILDCARD_SEARCH_PAGES = 10;
|
|
14
|
+
const MAX_WILDCARD_RESULTS = 25;
|
|
15
|
+
const MAX_WILDCARD_TARGET_SAMPLE_PROGRAMS = 5;
|
|
16
|
+
const MAX_WILDCARD_TARGET_SAMPLES = 10;
|
|
17
|
+
const MAX_FIND_PROGRAM_PAGES = 10;
|
|
18
|
+
const MAX_FIND_PROGRAM_RESULTS = 50;
|
|
19
|
+
const MAX_FIND_TARGET_SAMPLE_PROGRAMS = 10;
|
|
20
|
+
const MAX_FIND_TARGET_SAMPLES = 20;
|
|
21
|
+
const MAX_FIND_UPSTREAM_REQUEST_BUDGET = 50;
|
|
22
|
+
const DEFAULT_FIND_UPSTREAM_REQUEST_BUDGET = 20;
|
|
23
|
+
const MAX_ACCEPTED_NUMERIC_LIMIT = 1_000;
|
|
24
|
+
const PROGRAM_COLLECTION_CONCURRENCY = 4;
|
|
25
|
+
const TARGET_SAMPLE_CONCURRENCY = 3;
|
|
26
|
+
const DEFAULT_FILTER_DISCOVERY_PAGES = 2;
|
|
27
|
+
const MAX_FILTER_DISCOVERY_PAGES = 5;
|
|
28
|
+
const DEFAULT_FILTER_TARGET_SAMPLE_PROGRAMS = 10;
|
|
29
|
+
const MAX_FILTER_TARGET_SAMPLE_PROGRAMS = 25;
|
|
30
|
+
const MAX_LOCAL_EXPORT_RESOURCES = 25;
|
|
31
|
+
const EXPORT_PREVIEW_LIMIT = 25;
|
|
32
|
+
const SDK_VERSION = "1.29.0";
|
|
33
|
+
const WEB3_TAGS = ["web3", "crypto", "blockchain", "smart-contract", "smart contract", "defi", "nft", "ethereum", "solana", "solidity"];
|
|
34
|
+
const WEB3_CONTEST_SIGNALS = ["contest", "audit", "competitive", "code4rena", "sherlock", "cantina", "codehawks", "hats", "spearbit", "warden"];
|
|
35
|
+
const VDP_SIGNALS = ["vdp", "disclosure", "responsible disclosure", "vulnerability disclosure", "no bounty", "non-bounty", "no reward"];
|
|
36
|
+
const targetListModeSchema = z.enum(["identifiers", "compact", "full"]);
|
|
37
|
+
const changeListModeSchema = z.enum(["compact", "full"]);
|
|
38
|
+
const programListModeSchema = z.enum(["compact", "full"]);
|
|
39
|
+
const rewardThresholdModeSchema = z.enum(["max_at_least", "min_at_least"]);
|
|
40
|
+
const vdpModeSchema = z.enum(["include", "exclude", "only_likely", "only_known_no_bounty"]);
|
|
41
|
+
const targetMatchModeSchema = z.enum(["contains", "exact", "suffix", "wildcard"]);
|
|
42
|
+
const programNameActionSchema = z.enum(["new_targets", "scope_delta", "brief", "target_breakdown"]);
|
|
43
|
+
const readOnlyAnnotations = {
|
|
44
|
+
readOnlyHint: true,
|
|
45
|
+
destructiveHint: false,
|
|
46
|
+
idempotentHint: true,
|
|
47
|
+
openWorldHint: true
|
|
48
|
+
};
|
|
49
|
+
const TOOL_METRICS = new Map();
|
|
50
|
+
const filterValueSchema = z
|
|
51
|
+
.string()
|
|
52
|
+
.trim()
|
|
53
|
+
.min(1)
|
|
54
|
+
.max(128)
|
|
55
|
+
.refine((value) => !/[,\u0000-\u001F\u007F]/.test(value), {
|
|
56
|
+
message: "Filter values cannot contain commas or control characters."
|
|
57
|
+
});
|
|
58
|
+
const stringListSchema = z
|
|
59
|
+
.array(filterValueSchema)
|
|
60
|
+
.max(50)
|
|
61
|
+
.default([])
|
|
62
|
+
.describe("Filter values.");
|
|
63
|
+
const searchTextSchema = z
|
|
64
|
+
.string()
|
|
65
|
+
.trim()
|
|
66
|
+
.min(2)
|
|
67
|
+
.max(128)
|
|
68
|
+
.refine((value) => !/[\u0000-\u001F\u007F]/.test(value), {
|
|
69
|
+
message: "Search text cannot contain control characters."
|
|
70
|
+
});
|
|
71
|
+
const isoDateTimeSchema = z.string().datetime({ offset: true });
|
|
72
|
+
const pageSchema = z.number().int().min(1).default(1);
|
|
73
|
+
const programsPageSizeSchema = z.number().int().min(1).max(MAX_PROGRAMS_PER_SEARCH).default(20);
|
|
74
|
+
const recentChangesPageSizeSchema = z.number().int().min(1).max(MAX_RECENT_CHANGES).default(50);
|
|
75
|
+
const opportunityLevelSchema = z.enum(["elite", "hot", "strong", "potential"]);
|
|
76
|
+
const programSortSchema = z
|
|
77
|
+
.enum([
|
|
78
|
+
"best_match",
|
|
79
|
+
"lowest_reports",
|
|
80
|
+
"highest_reward",
|
|
81
|
+
"most_wildcards",
|
|
82
|
+
"most_eligible_targets",
|
|
83
|
+
"freshest",
|
|
84
|
+
"recently_updated",
|
|
85
|
+
"highest_opportunity_score",
|
|
86
|
+
"most_new_targets",
|
|
87
|
+
"best_hunt_value"
|
|
88
|
+
])
|
|
89
|
+
.default("best_match");
|
|
90
|
+
const programIdSchema = z
|
|
91
|
+
.string()
|
|
92
|
+
.trim()
|
|
93
|
+
.min(3)
|
|
94
|
+
.max(256)
|
|
95
|
+
.regex(/^[A-Za-z0-9_-]+:[^?#]+$/, "Use platform:handle, for example HackerOne:example-handle.")
|
|
96
|
+
.refine((value) => !/[\u0000-\u001F\u007F]/.test(value), {
|
|
97
|
+
message: "program_id cannot contain control characters."
|
|
98
|
+
})
|
|
99
|
+
.describe("platform:handle id.");
|
|
100
|
+
const jsonRecordSchema = z.record(z.unknown());
|
|
101
|
+
const rateLimitOutputSchema = z.object({
|
|
102
|
+
remaining: z.number().int().min(0),
|
|
103
|
+
reset_at: z.string()
|
|
104
|
+
});
|
|
105
|
+
const toolTimingOutputSchema = z.object({
|
|
106
|
+
tool_name: z.string(),
|
|
107
|
+
duration_ms: z.number().int().min(0)
|
|
108
|
+
});
|
|
109
|
+
const cacheOutputSchema = z.object({
|
|
110
|
+
hit: z.boolean(),
|
|
111
|
+
coalesced_live_request: z.boolean().optional(),
|
|
112
|
+
expires_at: z.string().optional()
|
|
113
|
+
});
|
|
114
|
+
const errorOutputSchema = z.object({
|
|
115
|
+
message: z.string(),
|
|
116
|
+
status: z.number().int().optional(),
|
|
117
|
+
detail: z.unknown().optional(),
|
|
118
|
+
errors: z.unknown().optional()
|
|
119
|
+
});
|
|
120
|
+
const programOutputSchema = jsonRecordSchema;
|
|
121
|
+
const targetOutputSchema = jsonRecordSchema;
|
|
122
|
+
const apiEnvelopeOutputShape = {
|
|
123
|
+
request_id: z.string(),
|
|
124
|
+
upstream_request_id: z.string().optional(),
|
|
125
|
+
fetched_at: z.string().optional(),
|
|
126
|
+
cache: cacheOutputSchema.optional(),
|
|
127
|
+
mcp_rate_limit: rateLimitOutputSchema.optional(),
|
|
128
|
+
mcp_timing: toolTimingOutputSchema.optional(),
|
|
129
|
+
error: errorOutputSchema.optional()
|
|
130
|
+
};
|
|
131
|
+
const programListOutputShape = {
|
|
132
|
+
...apiEnvelopeOutputShape,
|
|
133
|
+
programs: z.array(programOutputSchema).optional(),
|
|
134
|
+
meta: jsonRecordSchema.optional()
|
|
135
|
+
};
|
|
136
|
+
const findProgramsOutputShape = {
|
|
137
|
+
...programListOutputShape,
|
|
138
|
+
source_requests: z.array(jsonRecordSchema).optional(),
|
|
139
|
+
filters: jsonRecordSchema.optional(),
|
|
140
|
+
warnings: z.array(z.string()).optional(),
|
|
141
|
+
errors: z.array(jsonRecordSchema).optional(),
|
|
142
|
+
upstream_budget: jsonRecordSchema.optional(),
|
|
143
|
+
ranking_scope: jsonRecordSchema.optional()
|
|
144
|
+
};
|
|
145
|
+
const decisionOutputShape = {
|
|
146
|
+
...findProgramsOutputShape,
|
|
147
|
+
partial_success: z.boolean().optional(),
|
|
148
|
+
program_ids_requested: z.array(z.string()).optional(),
|
|
149
|
+
failed_program_ids: z.array(z.string()).optional(),
|
|
150
|
+
compared_programs: z.array(programOutputSchema).optional(),
|
|
151
|
+
activity: jsonRecordSchema.optional(),
|
|
152
|
+
changes: z.array(jsonRecordSchema).optional()
|
|
153
|
+
};
|
|
154
|
+
const programCandidatePresetInputSchema = {
|
|
155
|
+
platforms: stringListSchema,
|
|
156
|
+
tags: stringListSchema,
|
|
157
|
+
scope_tags: stringListSchema,
|
|
158
|
+
target_types: stringListSchema,
|
|
159
|
+
language_tags: stringListSchema,
|
|
160
|
+
opportunity_levels: z.array(opportunityLevelSchema).max(4).default(["elite", "hot", "strong", "potential"]),
|
|
161
|
+
web3: z.boolean().default(false),
|
|
162
|
+
wildcard: z.boolean().default(false),
|
|
163
|
+
low_reports: z.boolean().default(true),
|
|
164
|
+
bounty: z.boolean().default(true),
|
|
165
|
+
max_public_report_count: z.number().int().min(0).default(15),
|
|
166
|
+
min_bounty_max: z.number().int().min(0).optional(),
|
|
167
|
+
min_eligible_targets: z.number().int().min(0).default(1),
|
|
168
|
+
fresh_launch_days: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).optional(),
|
|
169
|
+
has_api_scope: z.boolean().default(false),
|
|
170
|
+
exclude_ctf: z.boolean().default(true),
|
|
171
|
+
exclude_mobile_only: z.boolean().default(false),
|
|
172
|
+
exclude_non_web: z.boolean().default(false),
|
|
173
|
+
updated_since: isoDateTimeSchema.optional(),
|
|
174
|
+
max_pages: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(5),
|
|
175
|
+
max_results: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(10),
|
|
176
|
+
include_target_samples: z.boolean().default(true),
|
|
177
|
+
target_sample_programs: z.number().int().min(0).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(5),
|
|
178
|
+
target_sample_size: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(5),
|
|
179
|
+
output_mode: programListModeSchema.default("compact"),
|
|
180
|
+
upstream_request_budget: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(30)
|
|
181
|
+
};
|
|
182
|
+
export function createBbradarServer(client, config) {
|
|
183
|
+
const server = new McpServer({
|
|
184
|
+
name: "bbradar.io",
|
|
185
|
+
version: SERVER_VERSION
|
|
186
|
+
}, {
|
|
187
|
+
instructions: [
|
|
188
|
+
"Use BBRadar only for passive bug bounty intelligence; never scan or contact targets.",
|
|
189
|
+
"For BBRadar asks, use these tools first and avoid external skills unless methodology is explicitly requested.",
|
|
190
|
+
"Name + action: run_program_name_action. Name only: resolve_program.",
|
|
191
|
+
"Freshness: get_latest_added_targets, check_program_new_targets, check_watchlist_new_targets, find_recent_by_type, get_program_scope_delta.",
|
|
192
|
+
"Discovery: find_program_candidates, find_stack_matches, find_language_programs, find_target_type_programs, find_reward_programs, find_paid_programs, find_vdp_programs, find_web3_contests, find_low_noise_programs.",
|
|
193
|
+
"Details: get_program_brief, get_program_scope_summary, get_program_target_breakdown, find_programs_by_target, compare_programs_compact.",
|
|
194
|
+
"Prefer compact/identifier outputs and disclose sampled ranking scope when present."
|
|
195
|
+
].join("\n")
|
|
196
|
+
});
|
|
197
|
+
const rateLimiter = new FixedWindowRateLimiter();
|
|
198
|
+
const exportStore = new Map();
|
|
199
|
+
server.registerTool("search_programs", {
|
|
200
|
+
title: "Search BBRadar Programs",
|
|
201
|
+
description: `List active programs. page_size max ${MAX_PROGRAMS_PER_SEARCH}.`,
|
|
202
|
+
inputSchema: {
|
|
203
|
+
platforms: stringListSchema,
|
|
204
|
+
tags: stringListSchema,
|
|
205
|
+
updated_since: isoDateTimeSchema.optional().describe("ISO datetime."),
|
|
206
|
+
page: pageSchema,
|
|
207
|
+
page_size: programsPageSizeSchema
|
|
208
|
+
},
|
|
209
|
+
outputSchema: programListOutputShape,
|
|
210
|
+
annotations: readOnlyAnnotations
|
|
211
|
+
}, (args) => runTool("search_programs", config, rateLimiter, async () => {
|
|
212
|
+
const api = await client.listPrograms(toQuery(args));
|
|
213
|
+
const data = readObject(api.data);
|
|
214
|
+
const programs = readArray(data?.programs).map((program) => addProgramResourceLinks(sanitizeProgram(program, config.webBaseUrl)));
|
|
215
|
+
return withApiMetadata(api, {
|
|
216
|
+
programs,
|
|
217
|
+
meta: sanitizeJson(data?.meta)
|
|
218
|
+
});
|
|
219
|
+
}));
|
|
220
|
+
server.registerTool("get_program", {
|
|
221
|
+
title: "Get BBRadar Program",
|
|
222
|
+
description: "Get one program by id.",
|
|
223
|
+
inputSchema: {
|
|
224
|
+
program_id: programIdSchema
|
|
225
|
+
},
|
|
226
|
+
outputSchema: {
|
|
227
|
+
...apiEnvelopeOutputShape,
|
|
228
|
+
program: programOutputSchema.optional()
|
|
229
|
+
},
|
|
230
|
+
annotations: readOnlyAnnotations
|
|
231
|
+
}, (args) => runTool("get_program", config, rateLimiter, async () => {
|
|
232
|
+
const api = await client.getProgram(args.program_id);
|
|
233
|
+
return withApiMetadata(api, {
|
|
234
|
+
program: addProgramResourceLinks(sanitizeProgram(api.data, config.webBaseUrl))
|
|
235
|
+
});
|
|
236
|
+
}));
|
|
237
|
+
server.registerTool("resolve_program", {
|
|
238
|
+
title: "Resolve Program",
|
|
239
|
+
description: "Resolve name/handle text to likely program_ids.",
|
|
240
|
+
inputSchema: {
|
|
241
|
+
query: searchTextSchema,
|
|
242
|
+
platforms: stringListSchema,
|
|
243
|
+
tags: stringListSchema,
|
|
244
|
+
opportunity_levels: z.array(opportunityLevelSchema).max(4).default([]),
|
|
245
|
+
max_pages: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(3),
|
|
246
|
+
max_results: z.number().int().min(1).max(25).default(10),
|
|
247
|
+
output_mode: programListModeSchema.default("compact"),
|
|
248
|
+
upstream_request_budget: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(DEFAULT_FIND_UPSTREAM_REQUEST_BUDGET)
|
|
249
|
+
},
|
|
250
|
+
outputSchema: {
|
|
251
|
+
...programListOutputShape,
|
|
252
|
+
source_requests: z.array(jsonRecordSchema).optional(),
|
|
253
|
+
query: jsonRecordSchema.optional(),
|
|
254
|
+
matches: z.array(jsonRecordSchema).optional(),
|
|
255
|
+
warnings: z.array(z.string()).optional(),
|
|
256
|
+
upstream_budget: jsonRecordSchema.optional(),
|
|
257
|
+
ranking_scope: jsonRecordSchema.optional()
|
|
258
|
+
},
|
|
259
|
+
annotations: readOnlyAnnotations
|
|
260
|
+
}, (args) => runTool("resolve_program", config, rateLimiter, async () => resolveProgram(client, config, args)));
|
|
261
|
+
server.registerTool("run_program_name_action", {
|
|
262
|
+
title: "Run Program Name Action",
|
|
263
|
+
description: "Resolve a name, then run new_targets, scope_delta, brief, or target_breakdown.",
|
|
264
|
+
inputSchema: {
|
|
265
|
+
query: searchTextSchema,
|
|
266
|
+
action: programNameActionSchema.default("new_targets"),
|
|
267
|
+
platforms: stringListSchema,
|
|
268
|
+
tags: stringListSchema,
|
|
269
|
+
opportunity_levels: z.array(opportunityLevelSchema).max(4).default([]),
|
|
270
|
+
since: isoDateTimeSchema.optional(),
|
|
271
|
+
target_type: filterValueSchema.optional(),
|
|
272
|
+
language_tags: stringListSchema,
|
|
273
|
+
include_out_of_scope: z.boolean().default(false),
|
|
274
|
+
include_ineligible: z.boolean().default(false),
|
|
275
|
+
page_size: recentChangesPageSizeSchema.default(MAX_RECENT_CHANGES),
|
|
276
|
+
max_targets: z.number().int().min(1).max(MAX_RECENT_CHANGES).default(25),
|
|
277
|
+
group_limit: z.number().int().min(1).max(MAX_TARGETS_PER_PROGRAM).default(100),
|
|
278
|
+
target_list_mode: targetListModeSchema.default("identifiers"),
|
|
279
|
+
max_resolve_pages: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(3),
|
|
280
|
+
output_mode: programListModeSchema.default("compact"),
|
|
281
|
+
upstream_request_budget: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(DEFAULT_FIND_UPSTREAM_REQUEST_BUDGET)
|
|
282
|
+
},
|
|
283
|
+
outputSchema: {
|
|
284
|
+
...apiEnvelopeOutputShape,
|
|
285
|
+
source_requests: z.array(jsonRecordSchema).optional(),
|
|
286
|
+
resolved_program: jsonRecordSchema.optional(),
|
|
287
|
+
action: z.string().optional(),
|
|
288
|
+
result: jsonRecordSchema.optional(),
|
|
289
|
+
warnings: z.array(z.string()).optional(),
|
|
290
|
+
upstream_budget: jsonRecordSchema.optional(),
|
|
291
|
+
ranking_scope: jsonRecordSchema.optional()
|
|
292
|
+
},
|
|
293
|
+
annotations: readOnlyAnnotations
|
|
294
|
+
}, (args) => runTool("run_program_name_action", config, rateLimiter, async () => runProgramNameAction(client, config, args)));
|
|
295
|
+
server.registerTool("get_program_targets", {
|
|
296
|
+
title: "Get Program Targets",
|
|
297
|
+
description: `Get active targets for one program. limit max ${MAX_TARGETS_PER_PROGRAM}.`,
|
|
298
|
+
inputSchema: {
|
|
299
|
+
program_id: programIdSchema,
|
|
300
|
+
include_out_of_scope: z.boolean().default(false),
|
|
301
|
+
include_ineligible: z.boolean().default(false),
|
|
302
|
+
strict_scope_filter: z.boolean().default(true),
|
|
303
|
+
offset: z.number().int().min(0).default(0),
|
|
304
|
+
limit: z.number().int().min(1).max(MAX_TARGETS_PER_PROGRAM).default(100),
|
|
305
|
+
output_mode: targetListModeSchema.default("compact")
|
|
306
|
+
},
|
|
307
|
+
outputSchema: {
|
|
308
|
+
...apiEnvelopeOutputShape,
|
|
309
|
+
program_id: z.string().optional(),
|
|
310
|
+
targets: z.array(z.unknown()).optional(),
|
|
311
|
+
meta: jsonRecordSchema.optional()
|
|
312
|
+
},
|
|
313
|
+
annotations: readOnlyAnnotations
|
|
314
|
+
}, (args) => runTool("get_program_targets", config, rateLimiter, async () => {
|
|
315
|
+
const api = await client.getProgramTargets(args.program_id);
|
|
316
|
+
const data = readObject(api.data);
|
|
317
|
+
const rawTargets = readArray(data?.targets);
|
|
318
|
+
const sanitizedTargets = rawTargets.map(sanitizeTarget).filter((target) => targetHasAllowedScope(target, args));
|
|
319
|
+
const targets = sanitizedTargets.slice(args.offset, args.offset + args.limit);
|
|
320
|
+
return withApiMetadata(api, {
|
|
321
|
+
program_id: args.program_id,
|
|
322
|
+
targets: formatTargetList(targets, args.output_mode),
|
|
323
|
+
meta: {
|
|
324
|
+
offset: args.offset,
|
|
325
|
+
limit: args.limit,
|
|
326
|
+
output_mode: args.output_mode,
|
|
327
|
+
total_active_targets: rawTargets.length,
|
|
328
|
+
total_after_filters: sanitizedTargets.length,
|
|
329
|
+
returned: targets.length,
|
|
330
|
+
has_more: args.offset + args.limit < sanitizedTargets.length
|
|
331
|
+
}
|
|
332
|
+
});
|
|
333
|
+
}));
|
|
334
|
+
server.registerTool("get_recent_changes", {
|
|
335
|
+
title: "Get Recent Target Changes",
|
|
336
|
+
description: `List recent target changes. page_size max ${MAX_RECENT_CHANGES}.`,
|
|
337
|
+
inputSchema: {
|
|
338
|
+
change_type: z.enum(["added", "updated", "removed"]).optional(),
|
|
339
|
+
include_removed: z.boolean().default(false),
|
|
340
|
+
include_ineligible: z.boolean().default(false),
|
|
341
|
+
include_out_of_scope: z.boolean().default(false),
|
|
342
|
+
search: searchTextSchema.optional(),
|
|
343
|
+
platforms: stringListSchema,
|
|
344
|
+
tags: stringListSchema,
|
|
345
|
+
page: pageSchema,
|
|
346
|
+
page_size: recentChangesPageSizeSchema,
|
|
347
|
+
output_mode: changeListModeSchema.default("compact")
|
|
348
|
+
},
|
|
349
|
+
outputSchema: {
|
|
350
|
+
...apiEnvelopeOutputShape,
|
|
351
|
+
changes: z.array(jsonRecordSchema).optional(),
|
|
352
|
+
meta: jsonRecordSchema.optional(),
|
|
353
|
+
facets: jsonRecordSchema.optional()
|
|
354
|
+
},
|
|
355
|
+
annotations: readOnlyAnnotations
|
|
356
|
+
}, (args) => runTool("get_recent_changes", config, rateLimiter, async () => {
|
|
357
|
+
const { output_mode, ...filters } = args;
|
|
358
|
+
const api = await client.getRecentChanges(toQuery(filters));
|
|
359
|
+
const data = readObject(api.data);
|
|
360
|
+
const sanitizedMeta = readObject(sanitizeJson(data?.meta));
|
|
361
|
+
const changes = readArray(data?.results)
|
|
362
|
+
.map((change) => sanitizeChange(change, config.webBaseUrl))
|
|
363
|
+
.map((change) => formatChange(change, output_mode));
|
|
364
|
+
return withApiMetadata(api, {
|
|
365
|
+
changes,
|
|
366
|
+
meta: stripUndefined({
|
|
367
|
+
...(sanitizedMeta ?? {}),
|
|
368
|
+
output_mode
|
|
369
|
+
}),
|
|
370
|
+
facets: sanitizeJson(data?.facets)
|
|
371
|
+
});
|
|
372
|
+
}));
|
|
373
|
+
server.registerTool("get_latest_added_targets", {
|
|
374
|
+
title: "Get Latest Added Targets",
|
|
375
|
+
description: "Latest added targets by type, optionally with that program's target list.",
|
|
376
|
+
inputSchema: {
|
|
377
|
+
target_type: filterValueSchema.default("domain"),
|
|
378
|
+
include_out_of_scope: z.boolean().default(false).describe("Include out-of-scope."),
|
|
379
|
+
include_ineligible: z.boolean().default(false).describe("Include ineligible."),
|
|
380
|
+
recent_changes_page_size: recentChangesPageSizeSchema.default(MAX_RECENT_CHANGES),
|
|
381
|
+
include_full_target_list: z.boolean().default(true),
|
|
382
|
+
full_target_list_mode: targetListModeSchema.default("identifiers"),
|
|
383
|
+
full_target_limit: z.number().int().min(1).max(MAX_TARGETS_PER_PROGRAM).default(MAX_TARGETS_PER_PROGRAM),
|
|
384
|
+
full_list_include_out_of_scope: z.boolean().default(true),
|
|
385
|
+
full_list_include_ineligible: z.boolean().default(true)
|
|
386
|
+
},
|
|
387
|
+
outputSchema: {
|
|
388
|
+
...apiEnvelopeOutputShape,
|
|
389
|
+
source_requests: z.array(jsonRecordSchema).optional(),
|
|
390
|
+
query: jsonRecordSchema.optional(),
|
|
391
|
+
latest_added_at: z.string().optional(),
|
|
392
|
+
program: programOutputSchema.optional(),
|
|
393
|
+
new_targets: z.array(z.unknown()).optional(),
|
|
394
|
+
full_targets: z.array(z.unknown()).optional(),
|
|
395
|
+
meta: jsonRecordSchema.optional()
|
|
396
|
+
},
|
|
397
|
+
annotations: readOnlyAnnotations
|
|
398
|
+
}, (args) => runTool("get_latest_added_targets", config, rateLimiter, async () => getLatestAddedTargets(client, config, args)));
|
|
399
|
+
server.registerTool("get_program_scope_summary", {
|
|
400
|
+
title: "Get Program Scope Summary",
|
|
401
|
+
description: "Grouped compact scope for one program.",
|
|
402
|
+
inputSchema: {
|
|
403
|
+
program_id: programIdSchema,
|
|
404
|
+
target_list_mode: targetListModeSchema.default("identifiers"),
|
|
405
|
+
group_limit: z.number().int().min(1).max(MAX_TARGETS_PER_PROGRAM).default(MAX_TARGETS_PER_PROGRAM),
|
|
406
|
+
include_out_of_scope: z.boolean().default(true),
|
|
407
|
+
include_ineligible: z.boolean().default(true)
|
|
408
|
+
},
|
|
409
|
+
outputSchema: {
|
|
410
|
+
...apiEnvelopeOutputShape,
|
|
411
|
+
source_requests: z.array(jsonRecordSchema).optional(),
|
|
412
|
+
program: programOutputSchema.optional(),
|
|
413
|
+
scope: jsonRecordSchema.optional(),
|
|
414
|
+
meta: jsonRecordSchema.optional()
|
|
415
|
+
},
|
|
416
|
+
annotations: readOnlyAnnotations
|
|
417
|
+
}, (args) => runTool("get_program_scope_summary", config, rateLimiter, async () => getProgramScopeSummary(client, config, args)));
|
|
418
|
+
server.registerTool("get_program_target_breakdown", {
|
|
419
|
+
title: "Get Program Target Breakdown",
|
|
420
|
+
description: "Target type, scope tag, language tag, and bucket counts for one program.",
|
|
421
|
+
inputSchema: {
|
|
422
|
+
program_id: programIdSchema,
|
|
423
|
+
target_list_mode: targetListModeSchema.default("identifiers"),
|
|
424
|
+
group_limit: z.number().int().min(1).max(MAX_TARGETS_PER_PROGRAM).default(100),
|
|
425
|
+
include_out_of_scope: z.boolean().default(true),
|
|
426
|
+
include_ineligible: z.boolean().default(true)
|
|
427
|
+
},
|
|
428
|
+
outputSchema: {
|
|
429
|
+
...apiEnvelopeOutputShape,
|
|
430
|
+
source_requests: z.array(jsonRecordSchema).optional(),
|
|
431
|
+
program: programOutputSchema.optional(),
|
|
432
|
+
breakdown: jsonRecordSchema.optional(),
|
|
433
|
+
meta: jsonRecordSchema.optional()
|
|
434
|
+
},
|
|
435
|
+
annotations: readOnlyAnnotations
|
|
436
|
+
}, (args) => runTool("get_program_target_breakdown", config, rateLimiter, async () => getProgramTargetBreakdown(client, config, args)));
|
|
437
|
+
server.registerTool("get_program_scope_delta", {
|
|
438
|
+
title: "Get Program Scope Delta",
|
|
439
|
+
description: "Compact recent scope changes for one program.",
|
|
440
|
+
inputSchema: {
|
|
441
|
+
program_id: programIdSchema,
|
|
442
|
+
since: isoDateTimeSchema.optional(),
|
|
443
|
+
change_type: z.enum(["added", "updated", "removed"]).optional(),
|
|
444
|
+
target_type: filterValueSchema.optional(),
|
|
445
|
+
language_tags: stringListSchema,
|
|
446
|
+
include_removed: z.boolean().default(true),
|
|
447
|
+
include_out_of_scope: z.boolean().default(false),
|
|
448
|
+
include_ineligible: z.boolean().default(false),
|
|
449
|
+
page_size: recentChangesPageSizeSchema.default(MAX_RECENT_CHANGES),
|
|
450
|
+
max_targets: z.number().int().min(1).max(MAX_RECENT_CHANGES).default(50),
|
|
451
|
+
target_list_mode: targetListModeSchema.default("identifiers")
|
|
452
|
+
},
|
|
453
|
+
outputSchema: {
|
|
454
|
+
...apiEnvelopeOutputShape,
|
|
455
|
+
source_requests: z.array(jsonRecordSchema).optional(),
|
|
456
|
+
program: programOutputSchema.optional(),
|
|
457
|
+
delta: jsonRecordSchema.optional(),
|
|
458
|
+
changes: z.array(jsonRecordSchema).optional(),
|
|
459
|
+
meta: jsonRecordSchema.optional()
|
|
460
|
+
},
|
|
461
|
+
annotations: readOnlyAnnotations
|
|
462
|
+
}, (args) => runTool("get_program_scope_delta", config, rateLimiter, async () => getProgramScopeDelta(client, config, args)));
|
|
463
|
+
server.registerTool("get_recent_target_activity", {
|
|
464
|
+
title: "Get Recent Target Activity",
|
|
465
|
+
description: "Recent target changes grouped by program.",
|
|
466
|
+
inputSchema: {
|
|
467
|
+
change_type: z.enum(["added", "updated", "removed"]).optional(),
|
|
468
|
+
target_type: filterValueSchema.optional(),
|
|
469
|
+
include_removed: z.boolean().default(true),
|
|
470
|
+
include_out_of_scope: z.boolean().default(false),
|
|
471
|
+
include_ineligible: z.boolean().default(false),
|
|
472
|
+
search: searchTextSchema.optional(),
|
|
473
|
+
platforms: stringListSchema,
|
|
474
|
+
tags: stringListSchema,
|
|
475
|
+
language_tags: stringListSchema,
|
|
476
|
+
page_size: recentChangesPageSizeSchema.default(MAX_RECENT_CHANGES),
|
|
477
|
+
max_programs: z.number().int().min(1).max(50).default(10),
|
|
478
|
+
sample_size: z.number().int().min(1).max(20).default(5),
|
|
479
|
+
target_list_mode: targetListModeSchema.default("identifiers")
|
|
480
|
+
},
|
|
481
|
+
outputSchema: {
|
|
482
|
+
...apiEnvelopeOutputShape,
|
|
483
|
+
source_requests: z.array(jsonRecordSchema).optional(),
|
|
484
|
+
activity: z.array(jsonRecordSchema).optional(),
|
|
485
|
+
meta: jsonRecordSchema.optional()
|
|
486
|
+
},
|
|
487
|
+
annotations: readOnlyAnnotations
|
|
488
|
+
}, (args) => runTool("get_recent_target_activity", config, rateLimiter, async () => getRecentTargetActivity(client, config, args)));
|
|
489
|
+
server.registerTool("check_program_new_targets", {
|
|
490
|
+
title: "Check Program New Targets",
|
|
491
|
+
description: "Yes/no new in-scope targets for one program.",
|
|
492
|
+
inputSchema: {
|
|
493
|
+
program_id: programIdSchema,
|
|
494
|
+
since: isoDateTimeSchema.optional().describe("ISO datetime."),
|
|
495
|
+
target_type: filterValueSchema.optional(),
|
|
496
|
+
include_ineligible: z.boolean().default(false).describe("Include ineligible."),
|
|
497
|
+
page_size: recentChangesPageSizeSchema.default(MAX_RECENT_CHANGES),
|
|
498
|
+
max_targets: z.number().int().min(1).max(MAX_RECENT_CHANGES).default(25),
|
|
499
|
+
target_list_mode: targetListModeSchema.default("identifiers")
|
|
500
|
+
},
|
|
501
|
+
outputSchema: {
|
|
502
|
+
...apiEnvelopeOutputShape,
|
|
503
|
+
source_requests: z.array(jsonRecordSchema).optional(),
|
|
504
|
+
program_id: z.string().optional(),
|
|
505
|
+
program: programOutputSchema.optional(),
|
|
506
|
+
has_new_targets: z.boolean().optional(),
|
|
507
|
+
new_target_count: z.number().int().min(0).optional(),
|
|
508
|
+
latest_added_at: z.string().optional(),
|
|
509
|
+
new_targets: z.array(z.unknown()).optional(),
|
|
510
|
+
meta: jsonRecordSchema.optional()
|
|
511
|
+
},
|
|
512
|
+
annotations: readOnlyAnnotations
|
|
513
|
+
}, (args) => runTool("check_program_new_targets", config, rateLimiter, async () => checkProgramNewTargets(client, config, args)));
|
|
514
|
+
server.registerTool("check_watchlist_new_targets", {
|
|
515
|
+
title: "Check Watchlist New Targets",
|
|
516
|
+
description: "New in-scope targets across many known program_ids.",
|
|
517
|
+
inputSchema: {
|
|
518
|
+
program_ids: z.array(programIdSchema).min(1).max(50),
|
|
519
|
+
since: isoDateTimeSchema.optional(),
|
|
520
|
+
target_type: filterValueSchema.optional(),
|
|
521
|
+
include_ineligible: z.boolean().default(false),
|
|
522
|
+
page_size: recentChangesPageSizeSchema.default(MAX_RECENT_CHANGES),
|
|
523
|
+
max_targets_per_program: z.number().int().min(1).max(50).default(10),
|
|
524
|
+
target_list_mode: targetListModeSchema.default("identifiers")
|
|
525
|
+
},
|
|
526
|
+
outputSchema: {
|
|
527
|
+
...apiEnvelopeOutputShape,
|
|
528
|
+
source_requests: z.array(jsonRecordSchema).optional(),
|
|
529
|
+
programs: z.array(jsonRecordSchema).optional(),
|
|
530
|
+
any_new_targets: z.boolean().optional(),
|
|
531
|
+
meta: jsonRecordSchema.optional()
|
|
532
|
+
},
|
|
533
|
+
annotations: readOnlyAnnotations
|
|
534
|
+
}, (args) => runTool("check_watchlist_new_targets", config, rateLimiter, async () => checkWatchlistNewTargets(client, config, args)));
|
|
535
|
+
server.registerTool("get_opportunities", {
|
|
536
|
+
title: "Get BBRadar Opportunities",
|
|
537
|
+
description: `List programs in one opportunity tier. page_size max ${MAX_PROGRAMS_PER_SEARCH}.`,
|
|
538
|
+
inputSchema: {
|
|
539
|
+
level: opportunityLevelSchema.default("elite"),
|
|
540
|
+
platforms: stringListSchema,
|
|
541
|
+
tags: stringListSchema,
|
|
542
|
+
updated_since: isoDateTimeSchema.optional(),
|
|
543
|
+
page: pageSchema,
|
|
544
|
+
page_size: programsPageSizeSchema
|
|
545
|
+
},
|
|
546
|
+
outputSchema: {
|
|
547
|
+
...programListOutputShape,
|
|
548
|
+
level: opportunityLevelSchema.optional()
|
|
549
|
+
},
|
|
550
|
+
annotations: readOnlyAnnotations
|
|
551
|
+
}, (args) => runTool("get_opportunities", config, rateLimiter, async () => {
|
|
552
|
+
const { level, ...filters } = args;
|
|
553
|
+
const api = await client.getOpportunities(level, toQuery(filters));
|
|
554
|
+
const data = readObject(api.data);
|
|
555
|
+
const programs = readArray(data?.programs).map((program) => addProgramResourceLinks(sanitizeProgram(program, config.webBaseUrl)));
|
|
556
|
+
return withApiMetadata(api, {
|
|
557
|
+
level,
|
|
558
|
+
programs,
|
|
559
|
+
meta: sanitizeJson(data?.meta)
|
|
560
|
+
});
|
|
561
|
+
}));
|
|
562
|
+
server.registerTool("find_programs", {
|
|
563
|
+
title: "Find BBRadar Programs",
|
|
564
|
+
description: "Ranked program discovery with filters and compact output.",
|
|
565
|
+
inputSchema: {
|
|
566
|
+
platforms: stringListSchema,
|
|
567
|
+
tags: stringListSchema,
|
|
568
|
+
scope_tags: stringListSchema.describe("Scope tags."),
|
|
569
|
+
target_types: stringListSchema.describe("Target-type tags."),
|
|
570
|
+
language_tags: stringListSchema.describe("Language tags."),
|
|
571
|
+
web3: z.boolean().default(false).describe("Require Web3 signals."),
|
|
572
|
+
opportunity_levels: z.array(opportunityLevelSchema).max(4).default([]),
|
|
573
|
+
updated_since: isoDateTimeSchema.optional(),
|
|
574
|
+
min_bounty_min: z.number().int().min(0).optional(),
|
|
575
|
+
min_bounty_max: z.number().int().min(0).optional(),
|
|
576
|
+
max_bounty_max: z.number().int().min(0).optional(),
|
|
577
|
+
require_bounty: z.boolean().default(false),
|
|
578
|
+
max_public_report_count: z.number().int().min(0).optional(),
|
|
579
|
+
require_known_report_count: z.boolean().default(false),
|
|
580
|
+
include_unknown_report_count: z.boolean().default(false),
|
|
581
|
+
exclude_ctf: z.boolean().default(false),
|
|
582
|
+
exclude_mobile_only: z.boolean().default(false),
|
|
583
|
+
exclude_non_web: z.boolean().default(false),
|
|
584
|
+
has_api_scope: z.boolean().default(false),
|
|
585
|
+
contest_like: z.boolean().default(false).describe("Require contest/audit signals."),
|
|
586
|
+
vdp_mode: vdpModeSchema.default("include").describe("VDP/no-bounty filter mode."),
|
|
587
|
+
fresh_launch_days: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).optional(),
|
|
588
|
+
min_wildcards: z.number().int().min(0).max(1000).default(0),
|
|
589
|
+
min_eligible_targets: z.number().int().min(0).optional(),
|
|
590
|
+
min_total_targets: z.number().int().min(0).optional(),
|
|
591
|
+
min_added_24h: z.number().int().min(0).optional(),
|
|
592
|
+
min_added_7d: z.number().int().min(0).optional(),
|
|
593
|
+
max_pages: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(3),
|
|
594
|
+
max_results: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(10),
|
|
595
|
+
sort_by: programSortSchema,
|
|
596
|
+
include_target_samples: z.boolean().default(false),
|
|
597
|
+
target_sample_programs: z.number().int().min(0).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(3),
|
|
598
|
+
target_sample_size: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(5),
|
|
599
|
+
only_in_scope_targets: z.boolean().default(true),
|
|
600
|
+
only_bounty_eligible_targets: z.boolean().default(true),
|
|
601
|
+
output_mode: programListModeSchema.default("compact"),
|
|
602
|
+
upstream_request_budget: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(DEFAULT_FIND_UPSTREAM_REQUEST_BUDGET)
|
|
603
|
+
},
|
|
604
|
+
outputSchema: findProgramsOutputShape,
|
|
605
|
+
annotations: readOnlyAnnotations
|
|
606
|
+
}, (args) => runTool("find_programs", config, rateLimiter, async () => findPrograms(client, config, {
|
|
607
|
+
...args,
|
|
608
|
+
target_samples_only_wildcards: args.min_wildcards > 0
|
|
609
|
+
})));
|
|
610
|
+
server.registerTool("find_wildcard_programs", {
|
|
611
|
+
title: "Find Wildcard Programs",
|
|
612
|
+
description: "Wildcard program shortlist.",
|
|
613
|
+
inputSchema: {
|
|
614
|
+
platforms: stringListSchema,
|
|
615
|
+
tags: stringListSchema,
|
|
616
|
+
updated_since: isoDateTimeSchema.optional(),
|
|
617
|
+
max_public_report_count: z.number().int().min(0).default(25),
|
|
618
|
+
require_known_report_count: z.boolean().default(true),
|
|
619
|
+
include_unknown_report_count: z.boolean().default(false),
|
|
620
|
+
exclude_ctf: z.boolean().default(true),
|
|
621
|
+
require_bounty: z.boolean().default(true),
|
|
622
|
+
min_wildcards: z.number().int().min(1).max(100).default(1),
|
|
623
|
+
max_pages: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(3),
|
|
624
|
+
max_results: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(10),
|
|
625
|
+
include_target_samples: z.boolean().default(false),
|
|
626
|
+
target_sample_programs: z.number().int().min(0).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(3),
|
|
627
|
+
target_sample_size: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(5),
|
|
628
|
+
upstream_request_budget: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(DEFAULT_FIND_UPSTREAM_REQUEST_BUDGET)
|
|
629
|
+
},
|
|
630
|
+
outputSchema: findProgramsOutputShape,
|
|
631
|
+
annotations: readOnlyAnnotations
|
|
632
|
+
}, (args) => runTool("find_wildcard_programs", config, rateLimiter, async () => findPrograms(client, config, {
|
|
633
|
+
platforms: args.platforms,
|
|
634
|
+
tags: args.tags,
|
|
635
|
+
scope_tags: [],
|
|
636
|
+
target_types: [],
|
|
637
|
+
language_tags: [],
|
|
638
|
+
web3: false,
|
|
639
|
+
opportunity_levels: [],
|
|
640
|
+
updated_since: args.updated_since,
|
|
641
|
+
min_bounty_min: undefined,
|
|
642
|
+
min_bounty_max: undefined,
|
|
643
|
+
max_bounty_max: undefined,
|
|
644
|
+
require_bounty: args.require_bounty,
|
|
645
|
+
max_public_report_count: args.max_public_report_count,
|
|
646
|
+
require_known_report_count: args.require_known_report_count,
|
|
647
|
+
include_unknown_report_count: args.include_unknown_report_count,
|
|
648
|
+
exclude_ctf: args.exclude_ctf,
|
|
649
|
+
exclude_mobile_only: false,
|
|
650
|
+
exclude_non_web: false,
|
|
651
|
+
has_api_scope: false,
|
|
652
|
+
contest_like: false,
|
|
653
|
+
fresh_launch_days: undefined,
|
|
654
|
+
min_wildcards: args.min_wildcards,
|
|
655
|
+
min_eligible_targets: undefined,
|
|
656
|
+
min_total_targets: undefined,
|
|
657
|
+
min_added_24h: undefined,
|
|
658
|
+
min_added_7d: undefined,
|
|
659
|
+
max_pages: args.max_pages,
|
|
660
|
+
max_results: args.max_results,
|
|
661
|
+
sort_by: "best_hunt_value",
|
|
662
|
+
include_target_samples: args.include_target_samples,
|
|
663
|
+
target_sample_programs: args.target_sample_programs,
|
|
664
|
+
target_sample_size: args.target_sample_size,
|
|
665
|
+
only_in_scope_targets: true,
|
|
666
|
+
only_bounty_eligible_targets: true,
|
|
667
|
+
target_samples_only_wildcards: true,
|
|
668
|
+
upstream_request_budget: args.upstream_request_budget
|
|
669
|
+
})));
|
|
670
|
+
server.registerTool("find_web3_programs", {
|
|
671
|
+
title: "Find Web3 Programs",
|
|
672
|
+
description: "Web3/blockchain/smart-contract program shortlist.",
|
|
673
|
+
inputSchema: {
|
|
674
|
+
platforms: stringListSchema,
|
|
675
|
+
tags: stringListSchema,
|
|
676
|
+
updated_since: isoDateTimeSchema.optional(),
|
|
677
|
+
max_public_report_count: z.number().int().min(0).optional(),
|
|
678
|
+
require_known_report_count: z.boolean().default(false),
|
|
679
|
+
include_unknown_report_count: z.boolean().default(true),
|
|
680
|
+
require_bounty: z.boolean().default(false),
|
|
681
|
+
min_bounty_max: z.number().int().min(0).optional(),
|
|
682
|
+
min_wildcards: z.number().int().min(0).max(1000).default(0),
|
|
683
|
+
exclude_ctf: z.boolean().default(true),
|
|
684
|
+
max_pages: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(5),
|
|
685
|
+
max_results: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(10),
|
|
686
|
+
include_target_samples: z.boolean().default(false),
|
|
687
|
+
target_sample_programs: z.number().int().min(0).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(3),
|
|
688
|
+
target_sample_size: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(5),
|
|
689
|
+
upstream_request_budget: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(DEFAULT_FIND_UPSTREAM_REQUEST_BUDGET)
|
|
690
|
+
},
|
|
691
|
+
outputSchema: findProgramsOutputShape,
|
|
692
|
+
annotations: readOnlyAnnotations
|
|
693
|
+
}, (args) => runTool("find_web3_programs", config, rateLimiter, async () => findPrograms(client, config, {
|
|
694
|
+
platforms: args.platforms,
|
|
695
|
+
tags: args.tags,
|
|
696
|
+
scope_tags: [],
|
|
697
|
+
target_types: [],
|
|
698
|
+
language_tags: [],
|
|
699
|
+
web3: true,
|
|
700
|
+
opportunity_levels: [],
|
|
701
|
+
updated_since: args.updated_since,
|
|
702
|
+
min_bounty_min: undefined,
|
|
703
|
+
min_bounty_max: args.min_bounty_max,
|
|
704
|
+
max_bounty_max: undefined,
|
|
705
|
+
require_bounty: args.require_bounty,
|
|
706
|
+
max_public_report_count: args.max_public_report_count,
|
|
707
|
+
require_known_report_count: args.require_known_report_count,
|
|
708
|
+
include_unknown_report_count: args.include_unknown_report_count,
|
|
709
|
+
exclude_ctf: args.exclude_ctf,
|
|
710
|
+
exclude_mobile_only: false,
|
|
711
|
+
exclude_non_web: false,
|
|
712
|
+
has_api_scope: false,
|
|
713
|
+
contest_like: false,
|
|
714
|
+
fresh_launch_days: undefined,
|
|
715
|
+
min_wildcards: args.min_wildcards,
|
|
716
|
+
min_eligible_targets: undefined,
|
|
717
|
+
min_total_targets: undefined,
|
|
718
|
+
min_added_24h: undefined,
|
|
719
|
+
min_added_7d: undefined,
|
|
720
|
+
max_pages: args.max_pages,
|
|
721
|
+
max_results: args.max_results,
|
|
722
|
+
sort_by: "best_hunt_value",
|
|
723
|
+
include_target_samples: args.include_target_samples,
|
|
724
|
+
target_sample_programs: args.target_sample_programs,
|
|
725
|
+
target_sample_size: args.target_sample_size,
|
|
726
|
+
only_in_scope_targets: true,
|
|
727
|
+
only_bounty_eligible_targets: true,
|
|
728
|
+
target_samples_only_wildcards: args.min_wildcards > 0,
|
|
729
|
+
upstream_request_budget: args.upstream_request_budget
|
|
730
|
+
})));
|
|
731
|
+
server.registerTool("find_reward_programs", {
|
|
732
|
+
title: "Find Reward Programs",
|
|
733
|
+
description: "Programs matching reward thresholds.",
|
|
734
|
+
inputSchema: {
|
|
735
|
+
platforms: stringListSchema,
|
|
736
|
+
tags: stringListSchema,
|
|
737
|
+
target_types: stringListSchema,
|
|
738
|
+
language_tags: stringListSchema,
|
|
739
|
+
min_reward: z.number().int().min(0).default(5_000),
|
|
740
|
+
reward_threshold_mode: rewardThresholdModeSchema.default("max_at_least"),
|
|
741
|
+
web3: z.boolean().default(false),
|
|
742
|
+
max_public_report_count: z.number().int().min(0).optional(),
|
|
743
|
+
require_known_report_count: z.boolean().default(false),
|
|
744
|
+
include_unknown_report_count: z.boolean().default(true),
|
|
745
|
+
min_eligible_targets: z.number().int().min(0).default(1),
|
|
746
|
+
exclude_ctf: z.boolean().default(true),
|
|
747
|
+
exclude_mobile_only: z.boolean().default(false),
|
|
748
|
+
exclude_non_web: z.boolean().default(false),
|
|
749
|
+
max_pages: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(3),
|
|
750
|
+
max_results: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(10),
|
|
751
|
+
include_target_samples: z.boolean().default(false),
|
|
752
|
+
target_sample_programs: z.number().int().min(0).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(3),
|
|
753
|
+
target_sample_size: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(3),
|
|
754
|
+
output_mode: programListModeSchema.default("compact"),
|
|
755
|
+
upstream_request_budget: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(DEFAULT_FIND_UPSTREAM_REQUEST_BUDGET)
|
|
756
|
+
},
|
|
757
|
+
outputSchema: findProgramsOutputShape,
|
|
758
|
+
annotations: readOnlyAnnotations
|
|
759
|
+
}, (args) => runTool("find_reward_programs", config, rateLimiter, async () => findRewardPrograms(client, config, args)));
|
|
760
|
+
server.registerTool("find_web3_contests", {
|
|
761
|
+
title: "Find Web3 Contests",
|
|
762
|
+
description: "Web3 contest or competitive audit shortlist.",
|
|
763
|
+
inputSchema: {
|
|
764
|
+
platforms: stringListSchema,
|
|
765
|
+
tags: stringListSchema,
|
|
766
|
+
min_reward: z.number().int().min(0).optional(),
|
|
767
|
+
reward_threshold_mode: rewardThresholdModeSchema.default("max_at_least"),
|
|
768
|
+
require_contest_signal: z.boolean().default(true),
|
|
769
|
+
require_bounty: z.boolean().default(false),
|
|
770
|
+
max_public_report_count: z.number().int().min(0).optional(),
|
|
771
|
+
include_unknown_report_count: z.boolean().default(true),
|
|
772
|
+
max_pages: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(5),
|
|
773
|
+
max_results: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(10),
|
|
774
|
+
include_target_samples: z.boolean().default(false),
|
|
775
|
+
target_sample_programs: z.number().int().min(0).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(3),
|
|
776
|
+
target_sample_size: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(3),
|
|
777
|
+
output_mode: programListModeSchema.default("compact"),
|
|
778
|
+
upstream_request_budget: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(30)
|
|
779
|
+
},
|
|
780
|
+
outputSchema: findProgramsOutputShape,
|
|
781
|
+
annotations: readOnlyAnnotations
|
|
782
|
+
}, (args) => runTool("find_web3_contests", config, rateLimiter, async () => findWeb3Contests(client, config, args)));
|
|
783
|
+
server.registerTool("find_language_programs", {
|
|
784
|
+
title: "Find Language Programs",
|
|
785
|
+
description: "Language-specific program shortlist.",
|
|
786
|
+
inputSchema: {
|
|
787
|
+
platforms: stringListSchema,
|
|
788
|
+
tags: stringListSchema,
|
|
789
|
+
language_tags: z.array(filterValueSchema).min(1).max(50),
|
|
790
|
+
target_types: stringListSchema,
|
|
791
|
+
min_reward: z.number().int().min(0).optional(),
|
|
792
|
+
reward_threshold_mode: rewardThresholdModeSchema.default("max_at_least"),
|
|
793
|
+
require_bounty: z.boolean().default(false),
|
|
794
|
+
exclude_vdp: z.boolean().default(false),
|
|
795
|
+
max_public_report_count: z.number().int().min(0).optional(),
|
|
796
|
+
require_known_report_count: z.boolean().default(false),
|
|
797
|
+
include_unknown_report_count: z.boolean().default(true),
|
|
798
|
+
min_eligible_targets: z.number().int().min(0).optional(),
|
|
799
|
+
max_pages: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(3),
|
|
800
|
+
max_results: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(10),
|
|
801
|
+
include_target_samples: z.boolean().default(false),
|
|
802
|
+
target_sample_programs: z.number().int().min(0).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(5),
|
|
803
|
+
target_sample_size: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(3),
|
|
804
|
+
output_mode: programListModeSchema.default("compact"),
|
|
805
|
+
upstream_request_budget: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(DEFAULT_FIND_UPSTREAM_REQUEST_BUDGET)
|
|
806
|
+
},
|
|
807
|
+
outputSchema: findProgramsOutputShape,
|
|
808
|
+
annotations: readOnlyAnnotations
|
|
809
|
+
}, (args) => runTool("find_language_programs", config, rateLimiter, async () => findLanguagePrograms(client, config, args)));
|
|
810
|
+
server.registerTool("find_target_type_programs", {
|
|
811
|
+
title: "Find Target Type Programs",
|
|
812
|
+
description: "Target-type program shortlist.",
|
|
813
|
+
inputSchema: {
|
|
814
|
+
platforms: stringListSchema,
|
|
815
|
+
tags: stringListSchema,
|
|
816
|
+
target_types: z.array(filterValueSchema).min(1).max(50),
|
|
817
|
+
scope_tags: stringListSchema,
|
|
818
|
+
language_tags: stringListSchema,
|
|
819
|
+
min_reward: z.number().int().min(0).optional(),
|
|
820
|
+
reward_threshold_mode: rewardThresholdModeSchema.default("max_at_least"),
|
|
821
|
+
require_bounty: z.boolean().default(false),
|
|
822
|
+
fresh_only: z.boolean().default(false),
|
|
823
|
+
min_added_7d: z.number().int().min(0).optional(),
|
|
824
|
+
exclude_vdp: z.boolean().default(false),
|
|
825
|
+
max_pages: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(3),
|
|
826
|
+
max_results: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(10),
|
|
827
|
+
include_target_samples: z.boolean().default(false),
|
|
828
|
+
target_sample_programs: z.number().int().min(0).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(5),
|
|
829
|
+
target_sample_size: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(3),
|
|
830
|
+
output_mode: programListModeSchema.default("compact"),
|
|
831
|
+
upstream_request_budget: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(DEFAULT_FIND_UPSTREAM_REQUEST_BUDGET)
|
|
832
|
+
},
|
|
833
|
+
outputSchema: findProgramsOutputShape,
|
|
834
|
+
annotations: readOnlyAnnotations
|
|
835
|
+
}, (args) => runTool("find_target_type_programs", config, rateLimiter, async () => findTargetTypePrograms(client, config, args)));
|
|
836
|
+
server.registerTool("find_vdp_programs", {
|
|
837
|
+
title: "Find VDP Programs",
|
|
838
|
+
description: "VDP/disclosure-only/no-bounty program shortlist.",
|
|
839
|
+
inputSchema: {
|
|
840
|
+
platforms: stringListSchema,
|
|
841
|
+
tags: stringListSchema,
|
|
842
|
+
target_types: stringListSchema,
|
|
843
|
+
language_tags: stringListSchema,
|
|
844
|
+
vdp_mode: z.enum(["only_likely", "only_known_no_bounty"]).default("only_likely"),
|
|
845
|
+
max_pages: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(3),
|
|
846
|
+
max_results: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(10),
|
|
847
|
+
include_target_samples: z.boolean().default(false),
|
|
848
|
+
target_sample_programs: z.number().int().min(0).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(3),
|
|
849
|
+
target_sample_size: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(3),
|
|
850
|
+
output_mode: programListModeSchema.default("compact"),
|
|
851
|
+
upstream_request_budget: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(DEFAULT_FIND_UPSTREAM_REQUEST_BUDGET)
|
|
852
|
+
},
|
|
853
|
+
outputSchema: findProgramsOutputShape,
|
|
854
|
+
annotations: readOnlyAnnotations
|
|
855
|
+
}, (args) => runTool("find_vdp_programs", config, rateLimiter, async () => findVdpPrograms(client, config, args)));
|
|
856
|
+
server.registerTool("find_paid_programs", {
|
|
857
|
+
title: "Find Paid Programs",
|
|
858
|
+
description: "Paid bounty program shortlist.",
|
|
859
|
+
inputSchema: {
|
|
860
|
+
platforms: stringListSchema,
|
|
861
|
+
tags: stringListSchema,
|
|
862
|
+
target_types: stringListSchema,
|
|
863
|
+
language_tags: stringListSchema,
|
|
864
|
+
min_reward: z.number().int().min(0).optional(),
|
|
865
|
+
reward_threshold_mode: rewardThresholdModeSchema.default("max_at_least"),
|
|
866
|
+
min_eligible_targets: z.number().int().min(0).default(1),
|
|
867
|
+
max_public_report_count: z.number().int().min(0).optional(),
|
|
868
|
+
max_pages: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(3),
|
|
869
|
+
max_results: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(10),
|
|
870
|
+
include_target_samples: z.boolean().default(false),
|
|
871
|
+
target_sample_programs: z.number().int().min(0).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(3),
|
|
872
|
+
target_sample_size: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(3),
|
|
873
|
+
output_mode: programListModeSchema.default("compact"),
|
|
874
|
+
upstream_request_budget: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(DEFAULT_FIND_UPSTREAM_REQUEST_BUDGET)
|
|
875
|
+
},
|
|
876
|
+
outputSchema: findProgramsOutputShape,
|
|
877
|
+
annotations: readOnlyAnnotations
|
|
878
|
+
}, (args) => runTool("find_paid_programs", config, rateLimiter, async () => findPaidPrograms(client, config, args)));
|
|
879
|
+
server.registerTool("find_stack_matches", {
|
|
880
|
+
title: "Find Stack Matches",
|
|
881
|
+
description: "Combined stack/language/type/reward shortlist.",
|
|
882
|
+
inputSchema: {
|
|
883
|
+
platforms: stringListSchema,
|
|
884
|
+
tags: stringListSchema,
|
|
885
|
+
language_tags: stringListSchema,
|
|
886
|
+
target_types: stringListSchema,
|
|
887
|
+
scope_tags: stringListSchema,
|
|
888
|
+
min_reward: z.number().int().min(0).optional(),
|
|
889
|
+
reward_threshold_mode: rewardThresholdModeSchema.default("max_at_least"),
|
|
890
|
+
web3: z.boolean().default(false),
|
|
891
|
+
contest_like: z.boolean().default(false),
|
|
892
|
+
require_bounty: z.boolean().default(false),
|
|
893
|
+
exclude_vdp: z.boolean().default(false),
|
|
894
|
+
max_public_report_count: z.number().int().min(0).optional(),
|
|
895
|
+
min_eligible_targets: z.number().int().min(0).optional(),
|
|
896
|
+
fresh_only: z.boolean().default(false),
|
|
897
|
+
min_added_7d: z.number().int().min(0).optional(),
|
|
898
|
+
max_pages: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(3),
|
|
899
|
+
max_results: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(10),
|
|
900
|
+
include_target_samples: z.boolean().default(false),
|
|
901
|
+
target_sample_programs: z.number().int().min(0).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(5),
|
|
902
|
+
target_sample_size: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(3),
|
|
903
|
+
output_mode: programListModeSchema.default("compact"),
|
|
904
|
+
upstream_request_budget: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(30)
|
|
905
|
+
},
|
|
906
|
+
outputSchema: findProgramsOutputShape,
|
|
907
|
+
annotations: readOnlyAnnotations
|
|
908
|
+
}, (args) => runTool("find_stack_matches", config, rateLimiter, async () => findStackMatches(client, config, args)));
|
|
909
|
+
server.registerTool("find_low_noise_programs", {
|
|
910
|
+
title: "Find Low Noise Programs",
|
|
911
|
+
description: "Low-public-report program shortlist.",
|
|
912
|
+
inputSchema: {
|
|
913
|
+
platforms: stringListSchema,
|
|
914
|
+
tags: stringListSchema,
|
|
915
|
+
target_types: stringListSchema,
|
|
916
|
+
language_tags: stringListSchema,
|
|
917
|
+
min_reward: z.number().int().min(0).optional(),
|
|
918
|
+
reward_threshold_mode: rewardThresholdModeSchema.default("max_at_least"),
|
|
919
|
+
max_public_report_count: z.number().int().min(0).default(5),
|
|
920
|
+
require_known_report_count: z.boolean().default(true),
|
|
921
|
+
include_unknown_report_count: z.boolean().default(false),
|
|
922
|
+
require_bounty: z.boolean().default(true),
|
|
923
|
+
min_eligible_targets: z.number().int().min(0).default(1),
|
|
924
|
+
max_pages: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(3),
|
|
925
|
+
max_results: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(10),
|
|
926
|
+
include_target_samples: z.boolean().default(false),
|
|
927
|
+
target_sample_programs: z.number().int().min(0).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(3),
|
|
928
|
+
target_sample_size: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(3),
|
|
929
|
+
output_mode: programListModeSchema.default("compact"),
|
|
930
|
+
upstream_request_budget: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(DEFAULT_FIND_UPSTREAM_REQUEST_BUDGET)
|
|
931
|
+
},
|
|
932
|
+
outputSchema: findProgramsOutputShape,
|
|
933
|
+
annotations: readOnlyAnnotations
|
|
934
|
+
}, (args) => runTool("find_low_noise_programs", config, rateLimiter, async () => findLowNoisePrograms(client, config, args)));
|
|
935
|
+
server.registerTool("find_recent_by_type", {
|
|
936
|
+
title: "Find Recent By Type",
|
|
937
|
+
description: "Recent changes by target type grouped by program.",
|
|
938
|
+
inputSchema: {
|
|
939
|
+
target_type: filterValueSchema,
|
|
940
|
+
change_type: z.enum(["added", "updated", "removed"]).default("added"),
|
|
941
|
+
include_removed: z.boolean().default(false),
|
|
942
|
+
include_out_of_scope: z.boolean().default(false),
|
|
943
|
+
include_ineligible: z.boolean().default(false),
|
|
944
|
+
search: searchTextSchema.optional(),
|
|
945
|
+
platforms: stringListSchema,
|
|
946
|
+
tags: stringListSchema,
|
|
947
|
+
language_tags: stringListSchema,
|
|
948
|
+
page_size: recentChangesPageSizeSchema.default(MAX_RECENT_CHANGES),
|
|
949
|
+
max_programs: z.number().int().min(1).max(50).default(10),
|
|
950
|
+
sample_size: z.number().int().min(1).max(20).default(5),
|
|
951
|
+
target_list_mode: targetListModeSchema.default("identifiers")
|
|
952
|
+
},
|
|
953
|
+
outputSchema: {
|
|
954
|
+
...apiEnvelopeOutputShape,
|
|
955
|
+
source_requests: z.array(jsonRecordSchema).optional(),
|
|
956
|
+
activity: z.array(jsonRecordSchema).optional(),
|
|
957
|
+
meta: jsonRecordSchema.optional()
|
|
958
|
+
},
|
|
959
|
+
annotations: readOnlyAnnotations
|
|
960
|
+
}, (args) => runTool("find_recent_by_type", config, rateLimiter, async () => findRecentByType(client, config, args)));
|
|
961
|
+
server.registerTool("find_programs_by_target", {
|
|
962
|
+
title: "Find Programs By Target",
|
|
963
|
+
description: "Reverse lookup target/domain ownership.",
|
|
964
|
+
inputSchema: {
|
|
965
|
+
target_query: searchTextSchema,
|
|
966
|
+
match_mode: targetMatchModeSchema.default("contains"),
|
|
967
|
+
platforms: stringListSchema,
|
|
968
|
+
tags: stringListSchema,
|
|
969
|
+
use_api_search: z.boolean().default(true),
|
|
970
|
+
max_pages: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(2),
|
|
971
|
+
max_results: z.number().int().min(1).max(25).default(10),
|
|
972
|
+
include_out_of_scope: z.boolean().default(true),
|
|
973
|
+
include_ineligible: z.boolean().default(true),
|
|
974
|
+
max_targets_per_program: z.number().int().min(1).max(50).default(10),
|
|
975
|
+
target_list_mode: targetListModeSchema.default("identifiers"),
|
|
976
|
+
upstream_request_budget: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(30)
|
|
977
|
+
},
|
|
978
|
+
outputSchema: {
|
|
979
|
+
...findProgramsOutputShape,
|
|
980
|
+
matches: z.array(jsonRecordSchema).optional()
|
|
981
|
+
},
|
|
982
|
+
annotations: readOnlyAnnotations
|
|
983
|
+
}, (args) => runTool("find_programs_by_target", config, rateLimiter, async () => findProgramsByTarget(client, config, args)));
|
|
984
|
+
server.registerTool("find_program_candidates", {
|
|
985
|
+
title: "Find BBRadar Program Candidates",
|
|
986
|
+
description: "Default ranked BBRadar discovery preset.",
|
|
987
|
+
inputSchema: programCandidatePresetInputSchema,
|
|
988
|
+
outputSchema: findProgramsOutputShape,
|
|
989
|
+
annotations: readOnlyAnnotations
|
|
990
|
+
}, (args) => runTool("find_program_candidates", config, rateLimiter, async () => findProgramCandidates(client, config, args)));
|
|
991
|
+
server.registerTool("find_hunt_candidates", {
|
|
992
|
+
title: "Find BBRadar Hunt Candidates",
|
|
993
|
+
description: "Legacy alias for find_program_candidates.",
|
|
994
|
+
inputSchema: programCandidatePresetInputSchema,
|
|
995
|
+
outputSchema: findProgramsOutputShape,
|
|
996
|
+
annotations: readOnlyAnnotations
|
|
997
|
+
}, (args) => runTool("find_hunt_candidates", config, rateLimiter, async () => findProgramCandidates(client, config, args)));
|
|
998
|
+
server.registerTool("find_fresh_hunt_candidates", {
|
|
999
|
+
title: "Find Fresh Hunt Candidates",
|
|
1000
|
+
description: "Freshness-first hunt shortlist.",
|
|
1001
|
+
inputSchema: {
|
|
1002
|
+
platforms: stringListSchema,
|
|
1003
|
+
tags: stringListSchema,
|
|
1004
|
+
target_types: z.array(filterValueSchema).max(50).default(["domain"]),
|
|
1005
|
+
language_tags: stringListSchema,
|
|
1006
|
+
opportunity_levels: z.array(opportunityLevelSchema).max(4).default(["elite", "hot", "strong", "potential"]),
|
|
1007
|
+
min_added_7d: z.number().int().min(0).default(1),
|
|
1008
|
+
max_public_report_count: z.number().int().min(0).default(25),
|
|
1009
|
+
require_known_report_count: z.boolean().default(false),
|
|
1010
|
+
include_unknown_report_count: z.boolean().default(false),
|
|
1011
|
+
require_bounty: z.boolean().default(true),
|
|
1012
|
+
min_bounty_max: z.number().int().min(0).optional(),
|
|
1013
|
+
min_eligible_targets: z.number().int().min(0).default(1),
|
|
1014
|
+
has_api_scope: z.boolean().default(false),
|
|
1015
|
+
wildcard: z.boolean().default(false),
|
|
1016
|
+
exclude_ctf: z.boolean().default(true),
|
|
1017
|
+
exclude_mobile_only: z.boolean().default(false),
|
|
1018
|
+
exclude_non_web: z.boolean().default(false),
|
|
1019
|
+
max_pages: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(3),
|
|
1020
|
+
max_results: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(10),
|
|
1021
|
+
include_target_samples: z.boolean().default(true),
|
|
1022
|
+
target_sample_programs: z.number().int().min(0).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(5),
|
|
1023
|
+
target_sample_size: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(3),
|
|
1024
|
+
output_mode: programListModeSchema.default("compact"),
|
|
1025
|
+
upstream_request_budget: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(30)
|
|
1026
|
+
},
|
|
1027
|
+
outputSchema: findProgramsOutputShape,
|
|
1028
|
+
annotations: readOnlyAnnotations
|
|
1029
|
+
}, (args) => runTool("find_fresh_hunt_candidates", config, rateLimiter, async () => findPrograms(client, config, {
|
|
1030
|
+
platforms: args.platforms,
|
|
1031
|
+
tags: args.tags,
|
|
1032
|
+
scope_tags: [],
|
|
1033
|
+
target_types: args.target_types,
|
|
1034
|
+
language_tags: args.language_tags,
|
|
1035
|
+
web3: false,
|
|
1036
|
+
opportunity_levels: args.opportunity_levels,
|
|
1037
|
+
updated_since: undefined,
|
|
1038
|
+
min_bounty_min: undefined,
|
|
1039
|
+
min_bounty_max: args.min_bounty_max,
|
|
1040
|
+
max_bounty_max: undefined,
|
|
1041
|
+
require_bounty: args.require_bounty,
|
|
1042
|
+
max_public_report_count: args.max_public_report_count,
|
|
1043
|
+
require_known_report_count: args.require_known_report_count,
|
|
1044
|
+
include_unknown_report_count: args.include_unknown_report_count,
|
|
1045
|
+
exclude_ctf: args.exclude_ctf,
|
|
1046
|
+
exclude_mobile_only: args.exclude_mobile_only,
|
|
1047
|
+
exclude_non_web: args.exclude_non_web,
|
|
1048
|
+
has_api_scope: args.has_api_scope,
|
|
1049
|
+
fresh_launch_days: undefined,
|
|
1050
|
+
min_wildcards: args.wildcard ? 1 : 0,
|
|
1051
|
+
min_eligible_targets: args.min_eligible_targets,
|
|
1052
|
+
min_total_targets: undefined,
|
|
1053
|
+
min_added_24h: undefined,
|
|
1054
|
+
min_added_7d: args.min_added_7d,
|
|
1055
|
+
max_pages: args.max_pages,
|
|
1056
|
+
max_results: args.max_results,
|
|
1057
|
+
sort_by: "most_new_targets",
|
|
1058
|
+
include_target_samples: args.include_target_samples,
|
|
1059
|
+
target_sample_programs: args.target_sample_programs,
|
|
1060
|
+
target_sample_size: args.target_sample_size,
|
|
1061
|
+
only_in_scope_targets: true,
|
|
1062
|
+
only_bounty_eligible_targets: true,
|
|
1063
|
+
target_samples_only_wildcards: args.wildcard,
|
|
1064
|
+
output_mode: args.output_mode,
|
|
1065
|
+
upstream_request_budget: args.upstream_request_budget
|
|
1066
|
+
})));
|
|
1067
|
+
server.registerTool("list_filters", {
|
|
1068
|
+
title: "List BBRadar Filters",
|
|
1069
|
+
description: "Discover observed filter values.",
|
|
1070
|
+
inputSchema: {
|
|
1071
|
+
platforms: stringListSchema,
|
|
1072
|
+
tags: stringListSchema,
|
|
1073
|
+
max_pages: z.number().int().min(1).max(MAX_FILTER_DISCOVERY_PAGES).default(DEFAULT_FILTER_DISCOVERY_PAGES),
|
|
1074
|
+
include_target_facets: z.boolean().default(true),
|
|
1075
|
+
target_sample_programs: z.number().int().min(0).max(MAX_FILTER_TARGET_SAMPLE_PROGRAMS).default(DEFAULT_FILTER_TARGET_SAMPLE_PROGRAMS),
|
|
1076
|
+
upstream_request_budget: z.number().int().min(1).max(MAX_FIND_UPSTREAM_REQUEST_BUDGET).default(DEFAULT_FIND_UPSTREAM_REQUEST_BUDGET)
|
|
1077
|
+
},
|
|
1078
|
+
outputSchema: {
|
|
1079
|
+
...apiEnvelopeOutputShape,
|
|
1080
|
+
filters: jsonRecordSchema.optional(),
|
|
1081
|
+
observed_from: jsonRecordSchema.optional(),
|
|
1082
|
+
upstream_budget: jsonRecordSchema.optional()
|
|
1083
|
+
},
|
|
1084
|
+
annotations: readOnlyAnnotations
|
|
1085
|
+
}, (args) => runTool("list_filters", config, rateLimiter, async () => listFilters(client, args)));
|
|
1086
|
+
server.registerTool("get_mcp_status", {
|
|
1087
|
+
title: "Get BBRadar MCP Status",
|
|
1088
|
+
description: "Local MCP status and timing metrics.",
|
|
1089
|
+
inputSchema: {},
|
|
1090
|
+
outputSchema: {
|
|
1091
|
+
...apiEnvelopeOutputShape,
|
|
1092
|
+
status: jsonRecordSchema.optional()
|
|
1093
|
+
},
|
|
1094
|
+
annotations: readOnlyAnnotations
|
|
1095
|
+
}, () => runTool("get_mcp_status", config, rateLimiter, async () => ({
|
|
1096
|
+
request_id: randomUUID(),
|
|
1097
|
+
status: {
|
|
1098
|
+
server_name: "bbradar.io",
|
|
1099
|
+
server_version: SERVER_VERSION,
|
|
1100
|
+
sdk_version: SDK_VERSION,
|
|
1101
|
+
api_base_url: config.apiBaseUrl,
|
|
1102
|
+
web_base_url: config.webBaseUrl,
|
|
1103
|
+
response_cache_enabled: config.cacheTtlMs > 0 && config.cacheMaxEntries > 0,
|
|
1104
|
+
cache_ttl_ms: config.cacheTtlMs,
|
|
1105
|
+
cache_max_entries: config.cacheMaxEntries,
|
|
1106
|
+
local_program_index_enabled: false,
|
|
1107
|
+
default_rate_limit_per_minute: config.defaultRateLimitPerMinute,
|
|
1108
|
+
export_rate_limit_per_minute: config.exportRateLimitPerMinute,
|
|
1109
|
+
request_timeout_ms: config.requestTimeoutMs,
|
|
1110
|
+
validate_on_startup: config.validateOnStartup,
|
|
1111
|
+
transport: "stdio",
|
|
1112
|
+
remote_streamable_http: "not_enabled_for_local_setup",
|
|
1113
|
+
tool_metrics: toolMetricsSnapshot(),
|
|
1114
|
+
client: client.diagnostics()
|
|
1115
|
+
}
|
|
1116
|
+
})));
|
|
1117
|
+
server.registerTool("compare_programs", {
|
|
1118
|
+
title: "Compare BBRadar Programs",
|
|
1119
|
+
description: "Compare programs.",
|
|
1120
|
+
inputSchema: {
|
|
1121
|
+
program_ids: z.array(programIdSchema).min(2).max(10),
|
|
1122
|
+
include_target_samples: z.boolean().default(false),
|
|
1123
|
+
target_sample_size: z.number().int().min(1).max(MAX_FIND_TARGET_SAMPLES).default(5),
|
|
1124
|
+
only_in_scope_targets: z.boolean().default(true),
|
|
1125
|
+
only_bounty_eligible_targets: z.boolean().default(true),
|
|
1126
|
+
output_mode: programListModeSchema.default("compact")
|
|
1127
|
+
},
|
|
1128
|
+
outputSchema: decisionOutputShape,
|
|
1129
|
+
annotations: readOnlyAnnotations
|
|
1130
|
+
}, (args) => runTool("compare_programs", config, rateLimiter, async () => comparePrograms(client, config, args)));
|
|
1131
|
+
server.registerTool("compare_programs_compact", {
|
|
1132
|
+
title: "Compare Programs Compact",
|
|
1133
|
+
description: "Compact program comparison.",
|
|
1134
|
+
inputSchema: {
|
|
1135
|
+
program_ids: z.array(programIdSchema).min(2).max(10),
|
|
1136
|
+
include_target_samples: z.boolean().default(false),
|
|
1137
|
+
target_sample_size: z.number().int().min(1).max(5).default(3),
|
|
1138
|
+
only_in_scope_targets: z.boolean().default(true),
|
|
1139
|
+
only_bounty_eligible_targets: z.boolean().default(true)
|
|
1140
|
+
},
|
|
1141
|
+
outputSchema: decisionOutputShape,
|
|
1142
|
+
annotations: readOnlyAnnotations
|
|
1143
|
+
}, (args) => runTool("compare_programs_compact", config, rateLimiter, async () => comparePrograms(client, config, { ...args, output_mode: "compact" })));
|
|
1144
|
+
server.registerTool("summarize_program_activity", {
|
|
1145
|
+
title: "Summarize Program Activity",
|
|
1146
|
+
description: "Summarize one program's activity.",
|
|
1147
|
+
inputSchema: {
|
|
1148
|
+
program_id: programIdSchema,
|
|
1149
|
+
recent_changes_limit: z.number().int().min(0).max(MAX_RECENT_CHANGES).default(25)
|
|
1150
|
+
},
|
|
1151
|
+
outputSchema: {
|
|
1152
|
+
...apiEnvelopeOutputShape,
|
|
1153
|
+
program: programOutputSchema.optional(),
|
|
1154
|
+
activity: jsonRecordSchema.optional(),
|
|
1155
|
+
changes: z.array(jsonRecordSchema).optional()
|
|
1156
|
+
},
|
|
1157
|
+
annotations: readOnlyAnnotations
|
|
1158
|
+
}, (args) => runTool("summarize_program_activity", config, rateLimiter, async () => summarizeProgramActivity(client, config, args.program_id, args.recent_changes_limit)));
|
|
1159
|
+
server.registerTool("get_program_brief", {
|
|
1160
|
+
title: "Get Program Brief",
|
|
1161
|
+
description: "Compact worth-hunting brief for one program.",
|
|
1162
|
+
inputSchema: {
|
|
1163
|
+
program_id: programIdSchema,
|
|
1164
|
+
target_sample_size: z.number().int().min(1).max(50).default(10),
|
|
1165
|
+
target_list_mode: targetListModeSchema.default("compact"),
|
|
1166
|
+
include_recent_changes: z.boolean().default(true),
|
|
1167
|
+
recent_changes_limit: z.number().int().min(0).max(MAX_RECENT_CHANGES).default(10),
|
|
1168
|
+
include_out_of_scope: z.boolean().default(false),
|
|
1169
|
+
include_ineligible: z.boolean().default(false)
|
|
1170
|
+
},
|
|
1171
|
+
outputSchema: {
|
|
1172
|
+
...apiEnvelopeOutputShape,
|
|
1173
|
+
source_requests: z.array(jsonRecordSchema).optional(),
|
|
1174
|
+
program: programOutputSchema.optional(),
|
|
1175
|
+
brief: jsonRecordSchema.optional(),
|
|
1176
|
+
target_samples: jsonRecordSchema.optional(),
|
|
1177
|
+
recent_changes: z.array(jsonRecordSchema).optional(),
|
|
1178
|
+
meta: jsonRecordSchema.optional()
|
|
1179
|
+
},
|
|
1180
|
+
annotations: readOnlyAnnotations
|
|
1181
|
+
}, (args) => runTool("get_program_brief", config, rateLimiter, async () => getProgramBrief(client, config, args)));
|
|
1182
|
+
server.registerTool("find_recently_added_wildcards", {
|
|
1183
|
+
title: "Find Recently Added Wildcards",
|
|
1184
|
+
description: "Recently added wildcard programs.",
|
|
1185
|
+
inputSchema: {
|
|
1186
|
+
platforms: stringListSchema,
|
|
1187
|
+
tags: stringListSchema,
|
|
1188
|
+
min_added_7d: z.number().int().min(1).default(1),
|
|
1189
|
+
max_public_report_count: z.number().int().min(0).optional(),
|
|
1190
|
+
require_bounty: z.boolean().default(true),
|
|
1191
|
+
max_pages: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(3),
|
|
1192
|
+
max_results: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(10),
|
|
1193
|
+
upstream_request_budget: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(DEFAULT_FIND_UPSTREAM_REQUEST_BUDGET)
|
|
1194
|
+
},
|
|
1195
|
+
outputSchema: findProgramsOutputShape,
|
|
1196
|
+
annotations: readOnlyAnnotations
|
|
1197
|
+
}, (args) => runTool("find_recently_added_wildcards", config, rateLimiter, async () => findPrograms(client, config, {
|
|
1198
|
+
platforms: args.platforms,
|
|
1199
|
+
tags: args.tags,
|
|
1200
|
+
scope_tags: [],
|
|
1201
|
+
target_types: [],
|
|
1202
|
+
language_tags: [],
|
|
1203
|
+
web3: false,
|
|
1204
|
+
opportunity_levels: [],
|
|
1205
|
+
updated_since: undefined,
|
|
1206
|
+
min_bounty_min: undefined,
|
|
1207
|
+
min_bounty_max: undefined,
|
|
1208
|
+
max_bounty_max: undefined,
|
|
1209
|
+
require_bounty: args.require_bounty,
|
|
1210
|
+
max_public_report_count: args.max_public_report_count,
|
|
1211
|
+
require_known_report_count: false,
|
|
1212
|
+
include_unknown_report_count: false,
|
|
1213
|
+
exclude_ctf: true,
|
|
1214
|
+
exclude_mobile_only: false,
|
|
1215
|
+
exclude_non_web: false,
|
|
1216
|
+
has_api_scope: false,
|
|
1217
|
+
fresh_launch_days: undefined,
|
|
1218
|
+
min_wildcards: 1,
|
|
1219
|
+
min_eligible_targets: undefined,
|
|
1220
|
+
min_total_targets: undefined,
|
|
1221
|
+
min_added_24h: undefined,
|
|
1222
|
+
min_added_7d: args.min_added_7d,
|
|
1223
|
+
max_pages: args.max_pages,
|
|
1224
|
+
max_results: args.max_results,
|
|
1225
|
+
sort_by: "best_hunt_value",
|
|
1226
|
+
include_target_samples: true,
|
|
1227
|
+
target_sample_programs: Math.min(args.max_results, MAX_FIND_TARGET_SAMPLE_PROGRAMS),
|
|
1228
|
+
target_sample_size: 5,
|
|
1229
|
+
only_in_scope_targets: true,
|
|
1230
|
+
only_bounty_eligible_targets: true,
|
|
1231
|
+
target_samples_only_wildcards: true,
|
|
1232
|
+
upstream_request_budget: args.upstream_request_budget
|
|
1233
|
+
})));
|
|
1234
|
+
server.registerTool("find_low_competition_high_reward", {
|
|
1235
|
+
title: "Find Low Competition High Reward",
|
|
1236
|
+
description: "Legacy low-report high-reward shortlist.",
|
|
1237
|
+
inputSchema: {
|
|
1238
|
+
platforms: stringListSchema,
|
|
1239
|
+
tags: stringListSchema,
|
|
1240
|
+
target_types: stringListSchema,
|
|
1241
|
+
language_tags: stringListSchema,
|
|
1242
|
+
max_public_report_count: z.number().int().min(0).default(10),
|
|
1243
|
+
min_bounty_max: z.number().int().min(0).default(5_000),
|
|
1244
|
+
min_eligible_targets: z.number().int().min(0).default(1),
|
|
1245
|
+
exclude_ctf: z.boolean().default(true),
|
|
1246
|
+
exclude_mobile_only: z.boolean().default(false),
|
|
1247
|
+
exclude_non_web: z.boolean().default(false),
|
|
1248
|
+
has_api_scope: z.boolean().default(false),
|
|
1249
|
+
fresh_launch_days: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).optional(),
|
|
1250
|
+
max_pages: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(3),
|
|
1251
|
+
max_results: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(10),
|
|
1252
|
+
upstream_request_budget: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(DEFAULT_FIND_UPSTREAM_REQUEST_BUDGET)
|
|
1253
|
+
},
|
|
1254
|
+
outputSchema: findProgramsOutputShape,
|
|
1255
|
+
annotations: readOnlyAnnotations
|
|
1256
|
+
}, (args) => runTool("find_low_competition_high_reward", config, rateLimiter, async () => findPrograms(client, config, {
|
|
1257
|
+
platforms: args.platforms,
|
|
1258
|
+
tags: args.tags,
|
|
1259
|
+
scope_tags: [],
|
|
1260
|
+
target_types: args.target_types,
|
|
1261
|
+
language_tags: args.language_tags,
|
|
1262
|
+
web3: false,
|
|
1263
|
+
opportunity_levels: [],
|
|
1264
|
+
updated_since: undefined,
|
|
1265
|
+
min_bounty_min: undefined,
|
|
1266
|
+
min_bounty_max: args.min_bounty_max,
|
|
1267
|
+
max_bounty_max: undefined,
|
|
1268
|
+
require_bounty: true,
|
|
1269
|
+
max_public_report_count: args.max_public_report_count,
|
|
1270
|
+
require_known_report_count: true,
|
|
1271
|
+
include_unknown_report_count: false,
|
|
1272
|
+
exclude_ctf: args.exclude_ctf,
|
|
1273
|
+
exclude_mobile_only: args.exclude_mobile_only,
|
|
1274
|
+
exclude_non_web: args.exclude_non_web,
|
|
1275
|
+
has_api_scope: args.has_api_scope,
|
|
1276
|
+
fresh_launch_days: args.fresh_launch_days,
|
|
1277
|
+
min_wildcards: 0,
|
|
1278
|
+
min_eligible_targets: args.min_eligible_targets,
|
|
1279
|
+
min_total_targets: undefined,
|
|
1280
|
+
min_added_24h: undefined,
|
|
1281
|
+
min_added_7d: undefined,
|
|
1282
|
+
max_pages: args.max_pages,
|
|
1283
|
+
max_results: args.max_results,
|
|
1284
|
+
sort_by: "best_hunt_value",
|
|
1285
|
+
include_target_samples: true,
|
|
1286
|
+
target_sample_programs: Math.min(args.max_results, MAX_FIND_TARGET_SAMPLE_PROGRAMS),
|
|
1287
|
+
target_sample_size: 5,
|
|
1288
|
+
only_in_scope_targets: true,
|
|
1289
|
+
only_bounty_eligible_targets: true,
|
|
1290
|
+
target_samples_only_wildcards: false,
|
|
1291
|
+
upstream_request_budget: args.upstream_request_budget
|
|
1292
|
+
})));
|
|
1293
|
+
server.registerTool("get_program_delta", {
|
|
1294
|
+
title: "Get Program Delta",
|
|
1295
|
+
description: "Recent target changes for one program.",
|
|
1296
|
+
inputSchema: {
|
|
1297
|
+
program_id: programIdSchema,
|
|
1298
|
+
include_removed: z.boolean().default(true),
|
|
1299
|
+
include_ineligible: z.boolean().default(false),
|
|
1300
|
+
include_out_of_scope: z.boolean().default(false),
|
|
1301
|
+
page_size: recentChangesPageSizeSchema.default(50)
|
|
1302
|
+
},
|
|
1303
|
+
outputSchema: {
|
|
1304
|
+
...apiEnvelopeOutputShape,
|
|
1305
|
+
program: programOutputSchema.optional(),
|
|
1306
|
+
changes: z.array(jsonRecordSchema).optional(),
|
|
1307
|
+
meta: jsonRecordSchema.optional()
|
|
1308
|
+
},
|
|
1309
|
+
annotations: readOnlyAnnotations
|
|
1310
|
+
}, (args) => runTool("get_program_delta", config, rateLimiter, async () => getProgramDelta(client, config, args)));
|
|
1311
|
+
server.registerTool("export_targets", {
|
|
1312
|
+
title: "Export BBRadar Targets",
|
|
1313
|
+
description: `Read-only target export. limit max ${MAX_TARGET_EXPORT}.`,
|
|
1314
|
+
inputSchema: {
|
|
1315
|
+
program_ids: z.array(programIdSchema).max(50).default([]),
|
|
1316
|
+
platforms: stringListSchema,
|
|
1317
|
+
tags: stringListSchema,
|
|
1318
|
+
opportunity_levels: z.array(z.enum(["elite", "hot", "strong", "potential"])).max(4).default([]),
|
|
1319
|
+
include_out_of_scope: z.boolean().default(false),
|
|
1320
|
+
include_ineligible: z.boolean().default(false),
|
|
1321
|
+
format: z.enum(["json", "csv"]).default("json"),
|
|
1322
|
+
limit: z.number().int().min(1).max(MAX_TARGET_EXPORT).default(MAX_TARGET_EXPORT)
|
|
1323
|
+
},
|
|
1324
|
+
outputSchema: {
|
|
1325
|
+
...apiEnvelopeOutputShape,
|
|
1326
|
+
export: z.unknown().optional()
|
|
1327
|
+
},
|
|
1328
|
+
annotations: readOnlyAnnotations
|
|
1329
|
+
}, (args) => runTool("export_targets", config, rateLimiter, async () => {
|
|
1330
|
+
const api = await client.exportTargets(compactRecord(args));
|
|
1331
|
+
const exportId = randomUUID();
|
|
1332
|
+
const sanitizedExport = sanitizeTargetsForExport(api.data, args.limit);
|
|
1333
|
+
const resourceUri = exportResourceUri(exportId);
|
|
1334
|
+
rememberExport(exportStore, exportId, sanitizedExport);
|
|
1335
|
+
return withApiMetadata(api, {
|
|
1336
|
+
export: {
|
|
1337
|
+
export_id: exportId,
|
|
1338
|
+
resource_uri: resourceUri,
|
|
1339
|
+
preview: previewExportPayload(sanitizedExport),
|
|
1340
|
+
returned_preview_count: previewExportCount(sanitizedExport),
|
|
1341
|
+
limit: args.limit
|
|
1342
|
+
}
|
|
1343
|
+
});
|
|
1344
|
+
}));
|
|
1345
|
+
registerPrompts(server);
|
|
1346
|
+
registerResources(server, client, config, exportStore);
|
|
1347
|
+
return server;
|
|
1348
|
+
}
|
|
1349
|
+
function registerResources(server, client, config, exportStore) {
|
|
1350
|
+
server.registerResource("target-export", new ResourceTemplate("bbradar://exports{?export_id}", { list: undefined }), {
|
|
1351
|
+
title: "BBRadar Target Export",
|
|
1352
|
+
description: "In-memory local target export payload by export_id query parameter.",
|
|
1353
|
+
mimeType: "application/json"
|
|
1354
|
+
}, async (uri) => {
|
|
1355
|
+
const exportId = uri.searchParams.get("export_id") ?? "";
|
|
1356
|
+
const payload = exportStore.get(exportId);
|
|
1357
|
+
if (!payload) {
|
|
1358
|
+
return jsonResource(uri.toString(), {
|
|
1359
|
+
request_id: randomUUID(),
|
|
1360
|
+
error: {
|
|
1361
|
+
message: "Export resource not found or expired from local memory."
|
|
1362
|
+
}
|
|
1363
|
+
});
|
|
1364
|
+
}
|
|
1365
|
+
return jsonResource(uri.toString(), {
|
|
1366
|
+
request_id: randomUUID(),
|
|
1367
|
+
export_id: exportId,
|
|
1368
|
+
export: payload
|
|
1369
|
+
});
|
|
1370
|
+
});
|
|
1371
|
+
server.registerResource("program", new ResourceTemplate("bbradar://program{?program_id}", { list: undefined }), {
|
|
1372
|
+
title: "BBRadar Program",
|
|
1373
|
+
description: "Sanitized BBRadar program details by program_id query parameter.",
|
|
1374
|
+
mimeType: "application/json"
|
|
1375
|
+
}, async (uri) => {
|
|
1376
|
+
const programId = parseProgramIdFromResourceUri(uri);
|
|
1377
|
+
const api = await client.getProgram(programId);
|
|
1378
|
+
const payload = withApiMetadata(api, {
|
|
1379
|
+
program: addProgramResourceLinks(sanitizeProgram(api.data, config.webBaseUrl))
|
|
1380
|
+
});
|
|
1381
|
+
return jsonResource(uri.toString(), payload);
|
|
1382
|
+
});
|
|
1383
|
+
server.registerResource("program-targets", new ResourceTemplate("bbradar://program/targets{?program_id}", { list: undefined }), {
|
|
1384
|
+
title: "BBRadar Program Targets",
|
|
1385
|
+
description: "Sanitized active target list by program_id query parameter.",
|
|
1386
|
+
mimeType: "application/json"
|
|
1387
|
+
}, async (uri) => {
|
|
1388
|
+
const programId = parseProgramIdFromResourceUri(uri);
|
|
1389
|
+
const api = await client.getProgramTargets(programId);
|
|
1390
|
+
const data = readObject(api.data);
|
|
1391
|
+
const targets = readArray(data?.targets).map(sanitizeTarget);
|
|
1392
|
+
const payload = withApiMetadata(api, {
|
|
1393
|
+
program_id: programId,
|
|
1394
|
+
targets,
|
|
1395
|
+
meta: {
|
|
1396
|
+
total_active_targets: targets.length
|
|
1397
|
+
}
|
|
1398
|
+
});
|
|
1399
|
+
return jsonResource(uri.toString(), payload);
|
|
1400
|
+
});
|
|
1401
|
+
server.registerResource("opportunities", new ResourceTemplate("bbradar://opportunities{?level}", { list: undefined }), {
|
|
1402
|
+
title: "BBRadar Opportunities",
|
|
1403
|
+
description: "Sanitized opportunity programs by level query parameter.",
|
|
1404
|
+
mimeType: "application/json"
|
|
1405
|
+
}, async (uri) => {
|
|
1406
|
+
const level = opportunityLevelSchema.parse(uri.searchParams.get("level") ?? "elite");
|
|
1407
|
+
const api = await client.getOpportunities(level, { page: 1, page_size: MAX_PROGRAMS_PER_SEARCH });
|
|
1408
|
+
const data = readObject(api.data);
|
|
1409
|
+
const programs = readArray(data?.programs).map((program) => addProgramResourceLinks(sanitizeProgram(program, config.webBaseUrl)));
|
|
1410
|
+
return jsonResource(uri.toString(), withApiMetadata(api, {
|
|
1411
|
+
level,
|
|
1412
|
+
programs,
|
|
1413
|
+
meta: sanitizeJson(data?.meta)
|
|
1414
|
+
}));
|
|
1415
|
+
});
|
|
1416
|
+
server.registerResource("recent-changes", new ResourceTemplate("bbradar://recent-changes{?change_type}", { list: undefined }), {
|
|
1417
|
+
title: "BBRadar Recent Changes",
|
|
1418
|
+
description: "Sanitized recent target changes with optional change_type query parameter.",
|
|
1419
|
+
mimeType: "application/json"
|
|
1420
|
+
}, async (uri) => {
|
|
1421
|
+
const changeType = uri.searchParams.get("change_type") ?? undefined;
|
|
1422
|
+
const api = await client.getRecentChanges(toQuery({
|
|
1423
|
+
change_type: changeType,
|
|
1424
|
+
page: 1,
|
|
1425
|
+
page_size: MAX_RECENT_CHANGES
|
|
1426
|
+
}));
|
|
1427
|
+
const data = readObject(api.data);
|
|
1428
|
+
const changes = readArray(data?.results).map((change) => sanitizeChange(change, config.webBaseUrl));
|
|
1429
|
+
return jsonResource(uri.toString(), withApiMetadata(api, {
|
|
1430
|
+
changes,
|
|
1431
|
+
meta: sanitizeJson(data?.meta),
|
|
1432
|
+
facets: sanitizeJson(data?.facets)
|
|
1433
|
+
}));
|
|
1434
|
+
});
|
|
1435
|
+
}
|
|
1436
|
+
async function resolveProgram(client, config, input) {
|
|
1437
|
+
const warnings = [];
|
|
1438
|
+
const budget = {
|
|
1439
|
+
initial: input.upstream_request_budget,
|
|
1440
|
+
remaining: input.upstream_request_budget
|
|
1441
|
+
};
|
|
1442
|
+
const maxPages = clampLimit("max_pages", input.max_pages, MAX_FIND_PROGRAM_PAGES, warnings);
|
|
1443
|
+
const collected = await collectPrograms(client, {
|
|
1444
|
+
platforms: input.platforms,
|
|
1445
|
+
tags: input.tags,
|
|
1446
|
+
updated_since: undefined,
|
|
1447
|
+
opportunity_levels: input.opportunity_levels,
|
|
1448
|
+
max_pages: maxPages,
|
|
1449
|
+
budget,
|
|
1450
|
+
warnings
|
|
1451
|
+
});
|
|
1452
|
+
const matches = collected.programs
|
|
1453
|
+
.map((program) => {
|
|
1454
|
+
const candidate = toProgramCandidate(program, config.webBaseUrl);
|
|
1455
|
+
const match = programResolutionMatch(candidate, input.query);
|
|
1456
|
+
return { candidate, match };
|
|
1457
|
+
})
|
|
1458
|
+
.filter((entry) => entry.match.score > 0)
|
|
1459
|
+
.sort((left, right) => right.match.score - left.match.score || compareProgramCandidates(left.candidate, right.candidate, "best_hunt_value"))
|
|
1460
|
+
.slice(0, input.max_results);
|
|
1461
|
+
const programs = matches.map((entry) => formatProgram(entry.candidate.program, input.output_mode));
|
|
1462
|
+
return stripUndefined({
|
|
1463
|
+
request_id: randomUUID(),
|
|
1464
|
+
source_requests: collected.sourceRequests,
|
|
1465
|
+
warnings: warnings.length > 0 ? warnings : undefined,
|
|
1466
|
+
query: {
|
|
1467
|
+
text: input.query,
|
|
1468
|
+
platforms: input.platforms.length > 0 ? input.platforms : undefined,
|
|
1469
|
+
tags: input.tags.length > 0 ? input.tags : undefined
|
|
1470
|
+
},
|
|
1471
|
+
programs,
|
|
1472
|
+
matches: matches.map((entry) => stripUndefined({
|
|
1473
|
+
program_id: stringField(entry.candidate.program, "id"),
|
|
1474
|
+
score: entry.match.score,
|
|
1475
|
+
matched_fields: entry.match.fields,
|
|
1476
|
+
reasons: entry.match.reasons,
|
|
1477
|
+
program: formatProgram(entry.candidate.program, input.output_mode)
|
|
1478
|
+
})),
|
|
1479
|
+
upstream_budget: {
|
|
1480
|
+
used: budget.initial - budget.remaining,
|
|
1481
|
+
remaining: budget.remaining
|
|
1482
|
+
},
|
|
1483
|
+
ranking_scope: {
|
|
1484
|
+
mode: "sampled",
|
|
1485
|
+
max_pages_scanned_per_source: maxPages,
|
|
1486
|
+
upstream_total_pages: collected.totalPagesBySource,
|
|
1487
|
+
possibly_incomplete: isRankingPossiblyIncomplete(collected.totalPagesBySource, maxPages)
|
|
1488
|
+
},
|
|
1489
|
+
meta: {
|
|
1490
|
+
returned: programs.length,
|
|
1491
|
+
programs_scanned: collected.programsScanned,
|
|
1492
|
+
upstream_requests_scanned: collected.requestsScanned
|
|
1493
|
+
}
|
|
1494
|
+
});
|
|
1495
|
+
}
|
|
1496
|
+
async function runProgramNameAction(client, config, input) {
|
|
1497
|
+
const resolution = await resolveProgram(client, config, {
|
|
1498
|
+
query: input.query,
|
|
1499
|
+
platforms: input.platforms,
|
|
1500
|
+
tags: input.tags,
|
|
1501
|
+
opportunity_levels: input.opportunity_levels,
|
|
1502
|
+
max_pages: input.max_resolve_pages,
|
|
1503
|
+
max_results: 1,
|
|
1504
|
+
output_mode: input.output_mode,
|
|
1505
|
+
upstream_request_budget: input.upstream_request_budget
|
|
1506
|
+
});
|
|
1507
|
+
const bestMatch = readObject(readArray(resolution.matches)[0]);
|
|
1508
|
+
const programId = stringField(bestMatch, "program_id");
|
|
1509
|
+
const resolvedProgram = readObject(bestMatch?.program);
|
|
1510
|
+
const warnings = readArray(resolution.warnings).filter((warning) => typeof warning === "string");
|
|
1511
|
+
if (!programId) {
|
|
1512
|
+
return stripUndefined({
|
|
1513
|
+
request_id: randomUUID(),
|
|
1514
|
+
source_requests: readArray(resolution.source_requests).filter((request) => readObject(request) !== undefined),
|
|
1515
|
+
warnings: [...warnings, "No matching program_id was resolved for the requested program name."],
|
|
1516
|
+
resolved_program: undefined,
|
|
1517
|
+
action: input.action,
|
|
1518
|
+
result: {
|
|
1519
|
+
found: false
|
|
1520
|
+
},
|
|
1521
|
+
upstream_budget: readObject(resolution.upstream_budget),
|
|
1522
|
+
ranking_scope: readObject(resolution.ranking_scope)
|
|
1523
|
+
});
|
|
1524
|
+
}
|
|
1525
|
+
const actionPayload = await runResolvedProgramAction(client, config, programId, input);
|
|
1526
|
+
const resolutionSourceRequests = readArray(resolution.source_requests).filter((request) => readObject(request) !== undefined);
|
|
1527
|
+
const actionSourceRequests = readArray(actionPayload.source_requests).filter((request) => readObject(request) !== undefined);
|
|
1528
|
+
return stripUndefined({
|
|
1529
|
+
request_id: randomUUID(),
|
|
1530
|
+
source_requests: [...resolutionSourceRequests, ...actionSourceRequests],
|
|
1531
|
+
warnings: warnings.length > 0 ? warnings : undefined,
|
|
1532
|
+
resolved_program: resolvedProgram,
|
|
1533
|
+
action: input.action,
|
|
1534
|
+
result: compactNestedActionResult(actionPayload),
|
|
1535
|
+
upstream_budget: readObject(resolution.upstream_budget),
|
|
1536
|
+
ranking_scope: readObject(resolution.ranking_scope)
|
|
1537
|
+
});
|
|
1538
|
+
}
|
|
1539
|
+
async function runResolvedProgramAction(client, config, programId, input) {
|
|
1540
|
+
if (input.action === "scope_delta") {
|
|
1541
|
+
return getProgramScopeDelta(client, config, {
|
|
1542
|
+
program_id: programId,
|
|
1543
|
+
since: input.since,
|
|
1544
|
+
change_type: undefined,
|
|
1545
|
+
target_type: input.target_type,
|
|
1546
|
+
language_tags: input.language_tags,
|
|
1547
|
+
include_removed: true,
|
|
1548
|
+
include_out_of_scope: input.include_out_of_scope,
|
|
1549
|
+
include_ineligible: input.include_ineligible,
|
|
1550
|
+
page_size: input.page_size,
|
|
1551
|
+
max_targets: input.max_targets,
|
|
1552
|
+
target_list_mode: input.target_list_mode
|
|
1553
|
+
});
|
|
1554
|
+
}
|
|
1555
|
+
if (input.action === "brief") {
|
|
1556
|
+
return getProgramBrief(client, config, {
|
|
1557
|
+
program_id: programId,
|
|
1558
|
+
target_sample_size: Math.min(input.max_targets, 50),
|
|
1559
|
+
target_list_mode: input.target_list_mode,
|
|
1560
|
+
include_recent_changes: true,
|
|
1561
|
+
recent_changes_limit: Math.min(input.page_size, MAX_RECENT_CHANGES),
|
|
1562
|
+
include_out_of_scope: input.include_out_of_scope,
|
|
1563
|
+
include_ineligible: input.include_ineligible
|
|
1564
|
+
});
|
|
1565
|
+
}
|
|
1566
|
+
if (input.action === "target_breakdown") {
|
|
1567
|
+
return getProgramTargetBreakdown(client, config, {
|
|
1568
|
+
program_id: programId,
|
|
1569
|
+
target_list_mode: input.target_list_mode,
|
|
1570
|
+
group_limit: input.group_limit,
|
|
1571
|
+
include_out_of_scope: input.include_out_of_scope,
|
|
1572
|
+
include_ineligible: input.include_ineligible
|
|
1573
|
+
});
|
|
1574
|
+
}
|
|
1575
|
+
return checkProgramNewTargets(client, config, {
|
|
1576
|
+
program_id: programId,
|
|
1577
|
+
since: input.since,
|
|
1578
|
+
target_type: input.target_type,
|
|
1579
|
+
include_ineligible: input.include_ineligible,
|
|
1580
|
+
page_size: input.page_size,
|
|
1581
|
+
max_targets: input.max_targets,
|
|
1582
|
+
target_list_mode: input.target_list_mode
|
|
1583
|
+
});
|
|
1584
|
+
}
|
|
1585
|
+
function compactNestedActionResult(payload) {
|
|
1586
|
+
const { source_requests: _sourceRequests, request_id: requestId, ...rest } = payload;
|
|
1587
|
+
return stripUndefined({
|
|
1588
|
+
action_request_id: typeof requestId === "string" ? requestId : undefined,
|
|
1589
|
+
...rest
|
|
1590
|
+
});
|
|
1591
|
+
}
|
|
1592
|
+
function findProgramCandidates(client, config, input) {
|
|
1593
|
+
return findPrograms(client, config, {
|
|
1594
|
+
platforms: input.platforms,
|
|
1595
|
+
tags: input.tags,
|
|
1596
|
+
scope_tags: input.scope_tags,
|
|
1597
|
+
target_types: input.target_types,
|
|
1598
|
+
language_tags: input.language_tags,
|
|
1599
|
+
web3: input.web3,
|
|
1600
|
+
opportunity_levels: input.opportunity_levels,
|
|
1601
|
+
updated_since: input.updated_since,
|
|
1602
|
+
min_bounty_min: undefined,
|
|
1603
|
+
min_bounty_max: input.min_bounty_max,
|
|
1604
|
+
max_bounty_max: undefined,
|
|
1605
|
+
require_bounty: input.bounty,
|
|
1606
|
+
max_public_report_count: input.low_reports ? input.max_public_report_count : undefined,
|
|
1607
|
+
require_known_report_count: input.low_reports,
|
|
1608
|
+
include_unknown_report_count: false,
|
|
1609
|
+
exclude_ctf: input.exclude_ctf,
|
|
1610
|
+
exclude_mobile_only: input.exclude_mobile_only,
|
|
1611
|
+
exclude_non_web: input.exclude_non_web,
|
|
1612
|
+
has_api_scope: input.has_api_scope,
|
|
1613
|
+
fresh_launch_days: input.fresh_launch_days,
|
|
1614
|
+
min_wildcards: input.wildcard ? 1 : 0,
|
|
1615
|
+
min_eligible_targets: input.min_eligible_targets,
|
|
1616
|
+
min_total_targets: undefined,
|
|
1617
|
+
min_added_24h: undefined,
|
|
1618
|
+
min_added_7d: undefined,
|
|
1619
|
+
max_pages: input.max_pages,
|
|
1620
|
+
max_results: input.max_results,
|
|
1621
|
+
sort_by: "best_hunt_value",
|
|
1622
|
+
include_target_samples: input.include_target_samples,
|
|
1623
|
+
target_sample_programs: input.target_sample_programs,
|
|
1624
|
+
target_sample_size: input.target_sample_size,
|
|
1625
|
+
only_in_scope_targets: true,
|
|
1626
|
+
only_bounty_eligible_targets: true,
|
|
1627
|
+
target_samples_only_wildcards: input.wildcard,
|
|
1628
|
+
output_mode: input.output_mode ?? "compact",
|
|
1629
|
+
upstream_request_budget: input.upstream_request_budget
|
|
1630
|
+
});
|
|
1631
|
+
}
|
|
1632
|
+
function findRewardPrograms(client, config, input) {
|
|
1633
|
+
return findPrograms(client, config, {
|
|
1634
|
+
platforms: input.platforms,
|
|
1635
|
+
tags: input.tags,
|
|
1636
|
+
scope_tags: [],
|
|
1637
|
+
target_types: input.target_types,
|
|
1638
|
+
language_tags: input.language_tags,
|
|
1639
|
+
web3: input.web3,
|
|
1640
|
+
opportunity_levels: [],
|
|
1641
|
+
updated_since: undefined,
|
|
1642
|
+
min_bounty_min: input.reward_threshold_mode === "min_at_least" ? input.min_reward : undefined,
|
|
1643
|
+
min_bounty_max: input.reward_threshold_mode === "max_at_least" ? input.min_reward : undefined,
|
|
1644
|
+
max_bounty_max: undefined,
|
|
1645
|
+
require_bounty: true,
|
|
1646
|
+
max_public_report_count: input.max_public_report_count,
|
|
1647
|
+
require_known_report_count: input.require_known_report_count,
|
|
1648
|
+
include_unknown_report_count: input.include_unknown_report_count,
|
|
1649
|
+
exclude_ctf: input.exclude_ctf,
|
|
1650
|
+
exclude_mobile_only: input.exclude_mobile_only,
|
|
1651
|
+
exclude_non_web: input.exclude_non_web,
|
|
1652
|
+
has_api_scope: false,
|
|
1653
|
+
fresh_launch_days: undefined,
|
|
1654
|
+
min_wildcards: 0,
|
|
1655
|
+
min_eligible_targets: input.min_eligible_targets,
|
|
1656
|
+
min_total_targets: undefined,
|
|
1657
|
+
min_added_24h: undefined,
|
|
1658
|
+
min_added_7d: undefined,
|
|
1659
|
+
max_pages: input.max_pages,
|
|
1660
|
+
max_results: input.max_results,
|
|
1661
|
+
sort_by: "highest_reward",
|
|
1662
|
+
include_target_samples: input.include_target_samples,
|
|
1663
|
+
target_sample_programs: input.target_sample_programs,
|
|
1664
|
+
target_sample_size: input.target_sample_size,
|
|
1665
|
+
only_in_scope_targets: true,
|
|
1666
|
+
only_bounty_eligible_targets: true,
|
|
1667
|
+
target_samples_only_wildcards: false,
|
|
1668
|
+
output_mode: input.output_mode,
|
|
1669
|
+
upstream_request_budget: input.upstream_request_budget
|
|
1670
|
+
});
|
|
1671
|
+
}
|
|
1672
|
+
function findWeb3Contests(client, config, input) {
|
|
1673
|
+
return findPrograms(client, config, {
|
|
1674
|
+
platforms: input.platforms,
|
|
1675
|
+
tags: input.tags,
|
|
1676
|
+
scope_tags: [],
|
|
1677
|
+
target_types: [],
|
|
1678
|
+
language_tags: [],
|
|
1679
|
+
web3: true,
|
|
1680
|
+
opportunity_levels: [],
|
|
1681
|
+
updated_since: undefined,
|
|
1682
|
+
min_bounty_min: input.min_reward !== undefined && input.reward_threshold_mode === "min_at_least" ? input.min_reward : undefined,
|
|
1683
|
+
min_bounty_max: input.min_reward !== undefined && input.reward_threshold_mode === "max_at_least" ? input.min_reward : undefined,
|
|
1684
|
+
max_bounty_max: undefined,
|
|
1685
|
+
require_bounty: input.require_bounty,
|
|
1686
|
+
max_public_report_count: input.max_public_report_count,
|
|
1687
|
+
require_known_report_count: false,
|
|
1688
|
+
include_unknown_report_count: input.include_unknown_report_count,
|
|
1689
|
+
exclude_ctf: false,
|
|
1690
|
+
exclude_mobile_only: false,
|
|
1691
|
+
exclude_non_web: false,
|
|
1692
|
+
has_api_scope: false,
|
|
1693
|
+
contest_like: input.require_contest_signal,
|
|
1694
|
+
fresh_launch_days: undefined,
|
|
1695
|
+
min_wildcards: 0,
|
|
1696
|
+
min_eligible_targets: undefined,
|
|
1697
|
+
min_total_targets: undefined,
|
|
1698
|
+
min_added_24h: undefined,
|
|
1699
|
+
min_added_7d: undefined,
|
|
1700
|
+
max_pages: input.max_pages,
|
|
1701
|
+
max_results: input.max_results,
|
|
1702
|
+
sort_by: input.min_reward !== undefined ? "highest_reward" : "best_hunt_value",
|
|
1703
|
+
include_target_samples: input.include_target_samples,
|
|
1704
|
+
target_sample_programs: input.target_sample_programs,
|
|
1705
|
+
target_sample_size: input.target_sample_size,
|
|
1706
|
+
only_in_scope_targets: true,
|
|
1707
|
+
only_bounty_eligible_targets: true,
|
|
1708
|
+
target_samples_only_wildcards: false,
|
|
1709
|
+
output_mode: input.output_mode,
|
|
1710
|
+
upstream_request_budget: input.upstream_request_budget
|
|
1711
|
+
});
|
|
1712
|
+
}
|
|
1713
|
+
function findLanguagePrograms(client, config, input) {
|
|
1714
|
+
const rewardFilters = rewardThresholdFilters(input.min_reward, input.reward_threshold_mode);
|
|
1715
|
+
return findPrograms(client, config, {
|
|
1716
|
+
platforms: input.platforms,
|
|
1717
|
+
tags: input.tags,
|
|
1718
|
+
scope_tags: [],
|
|
1719
|
+
target_types: input.target_types,
|
|
1720
|
+
language_tags: input.language_tags,
|
|
1721
|
+
web3: false,
|
|
1722
|
+
opportunity_levels: [],
|
|
1723
|
+
updated_since: undefined,
|
|
1724
|
+
...rewardFilters,
|
|
1725
|
+
max_bounty_max: undefined,
|
|
1726
|
+
require_bounty: input.require_bounty,
|
|
1727
|
+
max_public_report_count: input.max_public_report_count,
|
|
1728
|
+
require_known_report_count: input.require_known_report_count,
|
|
1729
|
+
include_unknown_report_count: input.include_unknown_report_count,
|
|
1730
|
+
exclude_ctf: true,
|
|
1731
|
+
exclude_mobile_only: false,
|
|
1732
|
+
exclude_non_web: false,
|
|
1733
|
+
has_api_scope: false,
|
|
1734
|
+
vdp_mode: input.exclude_vdp ? "exclude" : "include",
|
|
1735
|
+
fresh_launch_days: undefined,
|
|
1736
|
+
min_wildcards: 0,
|
|
1737
|
+
min_eligible_targets: input.min_eligible_targets,
|
|
1738
|
+
min_total_targets: undefined,
|
|
1739
|
+
min_added_24h: undefined,
|
|
1740
|
+
min_added_7d: undefined,
|
|
1741
|
+
max_pages: input.max_pages,
|
|
1742
|
+
max_results: input.max_results,
|
|
1743
|
+
sort_by: input.min_reward !== undefined ? "highest_reward" : "best_hunt_value",
|
|
1744
|
+
include_target_samples: input.include_target_samples,
|
|
1745
|
+
target_sample_programs: input.target_sample_programs,
|
|
1746
|
+
target_sample_size: input.target_sample_size,
|
|
1747
|
+
only_in_scope_targets: true,
|
|
1748
|
+
only_bounty_eligible_targets: true,
|
|
1749
|
+
target_samples_only_wildcards: false,
|
|
1750
|
+
output_mode: input.output_mode,
|
|
1751
|
+
upstream_request_budget: input.upstream_request_budget
|
|
1752
|
+
});
|
|
1753
|
+
}
|
|
1754
|
+
function findTargetTypePrograms(client, config, input) {
|
|
1755
|
+
const rewardFilters = rewardThresholdFilters(input.min_reward, input.reward_threshold_mode);
|
|
1756
|
+
return findPrograms(client, config, {
|
|
1757
|
+
platforms: input.platforms,
|
|
1758
|
+
tags: input.tags,
|
|
1759
|
+
scope_tags: input.scope_tags,
|
|
1760
|
+
target_types: input.target_types,
|
|
1761
|
+
language_tags: input.language_tags,
|
|
1762
|
+
web3: false,
|
|
1763
|
+
opportunity_levels: [],
|
|
1764
|
+
updated_since: undefined,
|
|
1765
|
+
...rewardFilters,
|
|
1766
|
+
max_bounty_max: undefined,
|
|
1767
|
+
require_bounty: input.require_bounty,
|
|
1768
|
+
max_public_report_count: undefined,
|
|
1769
|
+
require_known_report_count: false,
|
|
1770
|
+
include_unknown_report_count: true,
|
|
1771
|
+
exclude_ctf: true,
|
|
1772
|
+
exclude_mobile_only: false,
|
|
1773
|
+
exclude_non_web: false,
|
|
1774
|
+
has_api_scope: input.target_types.some((targetType) => normalizeTag(targetType).includes("api")),
|
|
1775
|
+
vdp_mode: input.exclude_vdp ? "exclude" : "include",
|
|
1776
|
+
fresh_launch_days: undefined,
|
|
1777
|
+
min_wildcards: input.target_types.some((targetType) => normalizeTag(targetType).includes("wildcard")) ? 1 : 0,
|
|
1778
|
+
min_eligible_targets: undefined,
|
|
1779
|
+
min_total_targets: undefined,
|
|
1780
|
+
min_added_24h: undefined,
|
|
1781
|
+
min_added_7d: input.fresh_only ? input.min_added_7d ?? 1 : input.min_added_7d,
|
|
1782
|
+
max_pages: input.max_pages,
|
|
1783
|
+
max_results: input.max_results,
|
|
1784
|
+
sort_by: input.fresh_only ? "most_new_targets" : input.min_reward !== undefined ? "highest_reward" : "best_hunt_value",
|
|
1785
|
+
include_target_samples: input.include_target_samples,
|
|
1786
|
+
target_sample_programs: input.target_sample_programs,
|
|
1787
|
+
target_sample_size: input.target_sample_size,
|
|
1788
|
+
only_in_scope_targets: true,
|
|
1789
|
+
only_bounty_eligible_targets: true,
|
|
1790
|
+
target_samples_only_wildcards: input.target_types.some((targetType) => normalizeTag(targetType).includes("wildcard")),
|
|
1791
|
+
output_mode: input.output_mode,
|
|
1792
|
+
upstream_request_budget: input.upstream_request_budget
|
|
1793
|
+
});
|
|
1794
|
+
}
|
|
1795
|
+
function findVdpPrograms(client, config, input) {
|
|
1796
|
+
return findPrograms(client, config, {
|
|
1797
|
+
platforms: input.platforms,
|
|
1798
|
+
tags: input.tags,
|
|
1799
|
+
scope_tags: [],
|
|
1800
|
+
target_types: input.target_types,
|
|
1801
|
+
language_tags: input.language_tags,
|
|
1802
|
+
web3: false,
|
|
1803
|
+
opportunity_levels: [],
|
|
1804
|
+
updated_since: undefined,
|
|
1805
|
+
min_bounty_min: undefined,
|
|
1806
|
+
min_bounty_max: undefined,
|
|
1807
|
+
max_bounty_max: undefined,
|
|
1808
|
+
require_bounty: false,
|
|
1809
|
+
max_public_report_count: undefined,
|
|
1810
|
+
require_known_report_count: false,
|
|
1811
|
+
include_unknown_report_count: true,
|
|
1812
|
+
exclude_ctf: true,
|
|
1813
|
+
exclude_mobile_only: false,
|
|
1814
|
+
exclude_non_web: false,
|
|
1815
|
+
has_api_scope: false,
|
|
1816
|
+
vdp_mode: input.vdp_mode,
|
|
1817
|
+
fresh_launch_days: undefined,
|
|
1818
|
+
min_wildcards: 0,
|
|
1819
|
+
min_eligible_targets: undefined,
|
|
1820
|
+
min_total_targets: undefined,
|
|
1821
|
+
min_added_24h: undefined,
|
|
1822
|
+
min_added_7d: undefined,
|
|
1823
|
+
max_pages: input.max_pages,
|
|
1824
|
+
max_results: input.max_results,
|
|
1825
|
+
sort_by: "best_hunt_value",
|
|
1826
|
+
include_target_samples: input.include_target_samples,
|
|
1827
|
+
target_sample_programs: input.target_sample_programs,
|
|
1828
|
+
target_sample_size: input.target_sample_size,
|
|
1829
|
+
only_in_scope_targets: true,
|
|
1830
|
+
only_bounty_eligible_targets: false,
|
|
1831
|
+
target_samples_only_wildcards: false,
|
|
1832
|
+
output_mode: input.output_mode,
|
|
1833
|
+
upstream_request_budget: input.upstream_request_budget
|
|
1834
|
+
});
|
|
1835
|
+
}
|
|
1836
|
+
function findPaidPrograms(client, config, input) {
|
|
1837
|
+
const rewardFilters = rewardThresholdFilters(input.min_reward, input.reward_threshold_mode);
|
|
1838
|
+
return findPrograms(client, config, {
|
|
1839
|
+
platforms: input.platforms,
|
|
1840
|
+
tags: input.tags,
|
|
1841
|
+
scope_tags: [],
|
|
1842
|
+
target_types: input.target_types,
|
|
1843
|
+
language_tags: input.language_tags,
|
|
1844
|
+
web3: false,
|
|
1845
|
+
opportunity_levels: [],
|
|
1846
|
+
updated_since: undefined,
|
|
1847
|
+
...rewardFilters,
|
|
1848
|
+
max_bounty_max: undefined,
|
|
1849
|
+
require_bounty: true,
|
|
1850
|
+
max_public_report_count: input.max_public_report_count,
|
|
1851
|
+
require_known_report_count: false,
|
|
1852
|
+
include_unknown_report_count: true,
|
|
1853
|
+
exclude_ctf: true,
|
|
1854
|
+
exclude_mobile_only: false,
|
|
1855
|
+
exclude_non_web: false,
|
|
1856
|
+
has_api_scope: false,
|
|
1857
|
+
vdp_mode: "exclude",
|
|
1858
|
+
fresh_launch_days: undefined,
|
|
1859
|
+
min_wildcards: 0,
|
|
1860
|
+
min_eligible_targets: input.min_eligible_targets,
|
|
1861
|
+
min_total_targets: undefined,
|
|
1862
|
+
min_added_24h: undefined,
|
|
1863
|
+
min_added_7d: undefined,
|
|
1864
|
+
max_pages: input.max_pages,
|
|
1865
|
+
max_results: input.max_results,
|
|
1866
|
+
sort_by: input.min_reward !== undefined ? "highest_reward" : "best_hunt_value",
|
|
1867
|
+
include_target_samples: input.include_target_samples,
|
|
1868
|
+
target_sample_programs: input.target_sample_programs,
|
|
1869
|
+
target_sample_size: input.target_sample_size,
|
|
1870
|
+
only_in_scope_targets: true,
|
|
1871
|
+
only_bounty_eligible_targets: true,
|
|
1872
|
+
target_samples_only_wildcards: false,
|
|
1873
|
+
output_mode: input.output_mode,
|
|
1874
|
+
upstream_request_budget: input.upstream_request_budget
|
|
1875
|
+
});
|
|
1876
|
+
}
|
|
1877
|
+
function findStackMatches(client, config, input) {
|
|
1878
|
+
const rewardFilters = rewardThresholdFilters(input.min_reward, input.reward_threshold_mode);
|
|
1879
|
+
return findPrograms(client, config, {
|
|
1880
|
+
platforms: input.platforms,
|
|
1881
|
+
tags: input.tags,
|
|
1882
|
+
scope_tags: input.scope_tags,
|
|
1883
|
+
target_types: input.target_types,
|
|
1884
|
+
language_tags: input.language_tags,
|
|
1885
|
+
web3: input.web3,
|
|
1886
|
+
opportunity_levels: [],
|
|
1887
|
+
updated_since: undefined,
|
|
1888
|
+
...rewardFilters,
|
|
1889
|
+
max_bounty_max: undefined,
|
|
1890
|
+
require_bounty: input.require_bounty,
|
|
1891
|
+
max_public_report_count: input.max_public_report_count,
|
|
1892
|
+
require_known_report_count: false,
|
|
1893
|
+
include_unknown_report_count: true,
|
|
1894
|
+
exclude_ctf: true,
|
|
1895
|
+
exclude_mobile_only: false,
|
|
1896
|
+
exclude_non_web: false,
|
|
1897
|
+
has_api_scope: input.target_types.some((targetType) => normalizeTag(targetType).includes("api")),
|
|
1898
|
+
contest_like: input.contest_like,
|
|
1899
|
+
vdp_mode: input.exclude_vdp ? "exclude" : "include",
|
|
1900
|
+
fresh_launch_days: undefined,
|
|
1901
|
+
min_wildcards: input.target_types.some((targetType) => normalizeTag(targetType).includes("wildcard")) ? 1 : 0,
|
|
1902
|
+
min_eligible_targets: input.min_eligible_targets,
|
|
1903
|
+
min_total_targets: undefined,
|
|
1904
|
+
min_added_24h: undefined,
|
|
1905
|
+
min_added_7d: input.fresh_only ? input.min_added_7d ?? 1 : input.min_added_7d,
|
|
1906
|
+
max_pages: input.max_pages,
|
|
1907
|
+
max_results: input.max_results,
|
|
1908
|
+
sort_by: input.fresh_only ? "most_new_targets" : input.min_reward !== undefined ? "highest_reward" : "best_hunt_value",
|
|
1909
|
+
include_target_samples: input.include_target_samples,
|
|
1910
|
+
target_sample_programs: input.target_sample_programs,
|
|
1911
|
+
target_sample_size: input.target_sample_size,
|
|
1912
|
+
only_in_scope_targets: true,
|
|
1913
|
+
only_bounty_eligible_targets: true,
|
|
1914
|
+
target_samples_only_wildcards: input.target_types.some((targetType) => normalizeTag(targetType).includes("wildcard")),
|
|
1915
|
+
output_mode: input.output_mode,
|
|
1916
|
+
upstream_request_budget: input.upstream_request_budget
|
|
1917
|
+
});
|
|
1918
|
+
}
|
|
1919
|
+
function findLowNoisePrograms(client, config, input) {
|
|
1920
|
+
const rewardFilters = rewardThresholdFilters(input.min_reward, input.reward_threshold_mode);
|
|
1921
|
+
return findPrograms(client, config, {
|
|
1922
|
+
platforms: input.platforms,
|
|
1923
|
+
tags: input.tags,
|
|
1924
|
+
scope_tags: [],
|
|
1925
|
+
target_types: input.target_types,
|
|
1926
|
+
language_tags: input.language_tags,
|
|
1927
|
+
web3: false,
|
|
1928
|
+
opportunity_levels: [],
|
|
1929
|
+
updated_since: undefined,
|
|
1930
|
+
...rewardFilters,
|
|
1931
|
+
max_bounty_max: undefined,
|
|
1932
|
+
require_bounty: input.require_bounty,
|
|
1933
|
+
max_public_report_count: input.max_public_report_count,
|
|
1934
|
+
require_known_report_count: input.require_known_report_count,
|
|
1935
|
+
include_unknown_report_count: input.include_unknown_report_count,
|
|
1936
|
+
exclude_ctf: true,
|
|
1937
|
+
exclude_mobile_only: false,
|
|
1938
|
+
exclude_non_web: false,
|
|
1939
|
+
has_api_scope: false,
|
|
1940
|
+
vdp_mode: input.require_bounty ? "exclude" : "include",
|
|
1941
|
+
fresh_launch_days: undefined,
|
|
1942
|
+
min_wildcards: 0,
|
|
1943
|
+
min_eligible_targets: input.min_eligible_targets,
|
|
1944
|
+
min_total_targets: undefined,
|
|
1945
|
+
min_added_24h: undefined,
|
|
1946
|
+
min_added_7d: undefined,
|
|
1947
|
+
max_pages: input.max_pages,
|
|
1948
|
+
max_results: input.max_results,
|
|
1949
|
+
sort_by: input.min_reward !== undefined ? "highest_reward" : "best_hunt_value",
|
|
1950
|
+
include_target_samples: input.include_target_samples,
|
|
1951
|
+
target_sample_programs: input.target_sample_programs,
|
|
1952
|
+
target_sample_size: input.target_sample_size,
|
|
1953
|
+
only_in_scope_targets: true,
|
|
1954
|
+
only_bounty_eligible_targets: true,
|
|
1955
|
+
target_samples_only_wildcards: false,
|
|
1956
|
+
output_mode: input.output_mode,
|
|
1957
|
+
upstream_request_budget: input.upstream_request_budget
|
|
1958
|
+
});
|
|
1959
|
+
}
|
|
1960
|
+
function findRecentByType(client, config, input) {
|
|
1961
|
+
return getRecentTargetActivity(client, config, {
|
|
1962
|
+
change_type: input.change_type,
|
|
1963
|
+
target_type: input.target_type,
|
|
1964
|
+
include_removed: input.include_removed || input.change_type === "removed",
|
|
1965
|
+
include_out_of_scope: input.include_out_of_scope,
|
|
1966
|
+
include_ineligible: input.include_ineligible,
|
|
1967
|
+
search: input.search,
|
|
1968
|
+
platforms: input.platforms,
|
|
1969
|
+
tags: input.tags,
|
|
1970
|
+
language_tags: input.language_tags,
|
|
1971
|
+
page_size: input.page_size,
|
|
1972
|
+
max_programs: input.max_programs,
|
|
1973
|
+
sample_size: input.sample_size,
|
|
1974
|
+
target_list_mode: input.target_list_mode
|
|
1975
|
+
});
|
|
1976
|
+
}
|
|
1977
|
+
async function findProgramsByTarget(client, config, input) {
|
|
1978
|
+
const warnings = [];
|
|
1979
|
+
if (input.use_api_search) {
|
|
1980
|
+
const directResult = await searchProgramsByTargetApi(client, config, input, warnings);
|
|
1981
|
+
if (directResult) {
|
|
1982
|
+
return directResult;
|
|
1983
|
+
}
|
|
1984
|
+
}
|
|
1985
|
+
const budget = {
|
|
1986
|
+
initial: input.upstream_request_budget,
|
|
1987
|
+
remaining: input.upstream_request_budget
|
|
1988
|
+
};
|
|
1989
|
+
const maxPages = clampLimit("max_pages", input.max_pages, MAX_FIND_PROGRAM_PAGES, warnings);
|
|
1990
|
+
const collected = await collectPrograms(client, {
|
|
1991
|
+
platforms: input.platforms,
|
|
1992
|
+
tags: input.tags,
|
|
1993
|
+
updated_since: undefined,
|
|
1994
|
+
opportunity_levels: [],
|
|
1995
|
+
max_pages: maxPages,
|
|
1996
|
+
budget,
|
|
1997
|
+
warnings
|
|
1998
|
+
});
|
|
1999
|
+
const sourceRequests = [...collected.sourceRequests];
|
|
2000
|
+
const matches = [];
|
|
2001
|
+
const candidates = collected.programs
|
|
2002
|
+
.map((program) => toProgramCandidate(program, config.webBaseUrl))
|
|
2003
|
+
.sort((left, right) => compareProgramCandidates(left, right, "best_hunt_value"));
|
|
2004
|
+
for (const candidate of candidates) {
|
|
2005
|
+
if (matches.length >= input.max_results) {
|
|
2006
|
+
break;
|
|
2007
|
+
}
|
|
2008
|
+
const programId = stringField(candidate.program, "id");
|
|
2009
|
+
if (!programId) {
|
|
2010
|
+
continue;
|
|
2011
|
+
}
|
|
2012
|
+
if (!tryConsumeBudget(budget)) {
|
|
2013
|
+
addUniqueWarning(warnings, "Upstream request budget was exhausted before all candidate target lists could be checked.");
|
|
2014
|
+
break;
|
|
2015
|
+
}
|
|
2016
|
+
const targetsApi = await client.getProgramTargets(programId);
|
|
2017
|
+
refundBudgetIfNoUpstreamRequest(budget, targetsApi);
|
|
2018
|
+
sourceRequests.push({
|
|
2019
|
+
source: "program_targets",
|
|
2020
|
+
program_id: programId,
|
|
2021
|
+
request_id: targetsApi.requestId,
|
|
2022
|
+
upstream_request_id: targetsApi.upstreamRequestId,
|
|
2023
|
+
...apiSourceMetadata(targetsApi)
|
|
2024
|
+
});
|
|
2025
|
+
const targetsData = readObject(targetsApi.data);
|
|
2026
|
+
const targets = readArray(targetsData?.targets)
|
|
2027
|
+
.map(sanitizeTarget)
|
|
2028
|
+
.filter((target) => targetHasAllowedScope(target, {
|
|
2029
|
+
include_out_of_scope: input.include_out_of_scope,
|
|
2030
|
+
include_ineligible: input.include_ineligible,
|
|
2031
|
+
strict_scope_filter: false
|
|
2032
|
+
}));
|
|
2033
|
+
const matchingTargets = targets.filter((target) => targetMatchesQuery(target, input.target_query, input.match_mode));
|
|
2034
|
+
if (matchingTargets.length === 0) {
|
|
2035
|
+
continue;
|
|
2036
|
+
}
|
|
2037
|
+
matches.push(stripUndefined({
|
|
2038
|
+
program: formatProgram(candidate.program, "compact"),
|
|
2039
|
+
match_count: matchingTargets.length,
|
|
2040
|
+
matching_targets: formatTargetList(matchingTargets.slice(0, input.max_targets_per_program), input.target_list_mode),
|
|
2041
|
+
matching_targets_has_more: matchingTargets.length > input.max_targets_per_program,
|
|
2042
|
+
total_targets_checked: targets.length
|
|
2043
|
+
}));
|
|
2044
|
+
}
|
|
2045
|
+
return stripUndefined({
|
|
2046
|
+
request_id: randomUUID(),
|
|
2047
|
+
source_requests: sourceRequests,
|
|
2048
|
+
warnings: warnings.length > 0 ? warnings : undefined,
|
|
2049
|
+
filters: {
|
|
2050
|
+
target_query: input.target_query,
|
|
2051
|
+
match_mode: input.match_mode,
|
|
2052
|
+
platforms: input.platforms.length > 0 ? input.platforms : undefined,
|
|
2053
|
+
tags: input.tags.length > 0 ? input.tags : undefined,
|
|
2054
|
+
source: "sampled_program_targets",
|
|
2055
|
+
include_out_of_scope: input.include_out_of_scope,
|
|
2056
|
+
include_ineligible: input.include_ineligible,
|
|
2057
|
+
target_list_mode: input.target_list_mode
|
|
2058
|
+
},
|
|
2059
|
+
matches,
|
|
2060
|
+
programs: matches.map((match) => readObject(match.program)).filter((program) => program !== undefined),
|
|
2061
|
+
upstream_budget: {
|
|
2062
|
+
used: budget.initial - budget.remaining,
|
|
2063
|
+
remaining: budget.remaining
|
|
2064
|
+
},
|
|
2065
|
+
ranking_scope: {
|
|
2066
|
+
mode: "sampled",
|
|
2067
|
+
max_pages_scanned_per_source: maxPages,
|
|
2068
|
+
upstream_total_pages: collected.totalPagesBySource,
|
|
2069
|
+
possibly_incomplete: isRankingPossiblyIncomplete(collected.totalPagesBySource, maxPages)
|
|
2070
|
+
},
|
|
2071
|
+
meta: {
|
|
2072
|
+
returned: matches.length,
|
|
2073
|
+
programs_scanned: collected.programsScanned,
|
|
2074
|
+
target_lists_checked: sourceRequests.filter((request) => request.source === "program_targets").length
|
|
2075
|
+
}
|
|
2076
|
+
});
|
|
2077
|
+
}
|
|
2078
|
+
async function searchProgramsByTargetApi(client, config, input, warnings) {
|
|
2079
|
+
let api;
|
|
2080
|
+
try {
|
|
2081
|
+
api = await client.searchTargets({
|
|
2082
|
+
q: input.target_query,
|
|
2083
|
+
match_mode: input.match_mode,
|
|
2084
|
+
platforms: input.platforms,
|
|
2085
|
+
tags: input.tags,
|
|
2086
|
+
include_out_of_scope: input.include_out_of_scope,
|
|
2087
|
+
include_ineligible: input.include_ineligible,
|
|
2088
|
+
page: 1,
|
|
2089
|
+
page_size: Math.max(input.max_results * input.max_targets_per_program, input.max_results)
|
|
2090
|
+
});
|
|
2091
|
+
}
|
|
2092
|
+
catch (error) {
|
|
2093
|
+
if (error instanceof BBRadarApiError && (error.status === 404 || error.status === 405)) {
|
|
2094
|
+
warnings.push("Direct target search API is unavailable; fell back to sampled program target lists.");
|
|
2095
|
+
return undefined;
|
|
2096
|
+
}
|
|
2097
|
+
throw error;
|
|
2098
|
+
}
|
|
2099
|
+
const data = readObject(api.data);
|
|
2100
|
+
const rawMatches = readTargetSearchRows(data);
|
|
2101
|
+
const grouped = new Map();
|
|
2102
|
+
for (const row of rawMatches) {
|
|
2103
|
+
const program = addProgramResourceLinks(sanitizeProgram(readObject(row.program) ?? readObject(readObject(row.target)?.program) ?? row, config.webBaseUrl));
|
|
2104
|
+
const programId = stringField(program, "id") ?? stringField(row, "program_id");
|
|
2105
|
+
const target = sanitizeTarget(readObject(row.target) ?? row);
|
|
2106
|
+
if (!programId || !targetIdentifier(target)) {
|
|
2107
|
+
continue;
|
|
2108
|
+
}
|
|
2109
|
+
if (!targetHasAllowedScope(target, {
|
|
2110
|
+
include_out_of_scope: input.include_out_of_scope,
|
|
2111
|
+
include_ineligible: input.include_ineligible,
|
|
2112
|
+
strict_scope_filter: false
|
|
2113
|
+
})) {
|
|
2114
|
+
continue;
|
|
2115
|
+
}
|
|
2116
|
+
if (!targetMatchesQuery(target, input.target_query, input.match_mode)) {
|
|
2117
|
+
continue;
|
|
2118
|
+
}
|
|
2119
|
+
const existing = grouped.get(programId) ?? { program: { ...program, id: programId }, targets: [] };
|
|
2120
|
+
existing.targets.push(target);
|
|
2121
|
+
grouped.set(programId, existing);
|
|
2122
|
+
}
|
|
2123
|
+
const matches = [...grouped.values()].slice(0, input.max_results).map((entry) => stripUndefined({
|
|
2124
|
+
program: formatProgram(entry.program, "compact"),
|
|
2125
|
+
match_count: entry.targets.length,
|
|
2126
|
+
matching_targets: formatTargetList(entry.targets.slice(0, input.max_targets_per_program), input.target_list_mode),
|
|
2127
|
+
matching_targets_has_more: entry.targets.length > input.max_targets_per_program
|
|
2128
|
+
}));
|
|
2129
|
+
const sanitizedMeta = readObject(sanitizeJson(data?.meta));
|
|
2130
|
+
return stripUndefined({
|
|
2131
|
+
request_id: randomUUID(),
|
|
2132
|
+
source_requests: [
|
|
2133
|
+
{
|
|
2134
|
+
source: "target_search",
|
|
2135
|
+
request_id: api.requestId,
|
|
2136
|
+
upstream_request_id: api.upstreamRequestId,
|
|
2137
|
+
...apiSourceMetadata(api)
|
|
2138
|
+
}
|
|
2139
|
+
],
|
|
2140
|
+
warnings: warnings.length > 0 ? warnings : undefined,
|
|
2141
|
+
filters: {
|
|
2142
|
+
target_query: input.target_query,
|
|
2143
|
+
match_mode: input.match_mode,
|
|
2144
|
+
platforms: input.platforms.length > 0 ? input.platforms : undefined,
|
|
2145
|
+
tags: input.tags.length > 0 ? input.tags : undefined,
|
|
2146
|
+
source: "target_search_api",
|
|
2147
|
+
include_out_of_scope: input.include_out_of_scope,
|
|
2148
|
+
include_ineligible: input.include_ineligible,
|
|
2149
|
+
target_list_mode: input.target_list_mode
|
|
2150
|
+
},
|
|
2151
|
+
matches,
|
|
2152
|
+
programs: matches.map((match) => readObject(match.program)).filter((program) => program !== undefined),
|
|
2153
|
+
meta: stripUndefined({
|
|
2154
|
+
...(sanitizedMeta ?? {}),
|
|
2155
|
+
returned: matches.length,
|
|
2156
|
+
target_search_rows_scanned: rawMatches.length
|
|
2157
|
+
})
|
|
2158
|
+
});
|
|
2159
|
+
}
|
|
2160
|
+
function readTargetSearchRows(data) {
|
|
2161
|
+
return [data?.results, data?.matches, data?.targets]
|
|
2162
|
+
.map(readArray)
|
|
2163
|
+
.find((rows) => rows.length > 0)
|
|
2164
|
+
?.map((row) => readObject(row))
|
|
2165
|
+
.filter((row) => row !== undefined) ?? [];
|
|
2166
|
+
}
|
|
2167
|
+
function rewardThresholdFilters(minReward, mode) {
|
|
2168
|
+
return {
|
|
2169
|
+
min_bounty_min: minReward !== undefined && mode === "min_at_least" ? minReward : undefined,
|
|
2170
|
+
min_bounty_max: minReward !== undefined && mode === "max_at_least" ? minReward : undefined
|
|
2171
|
+
};
|
|
2172
|
+
}
|
|
2173
|
+
async function listFilters(client, input) {
|
|
2174
|
+
const budget = {
|
|
2175
|
+
initial: input.upstream_request_budget,
|
|
2176
|
+
remaining: input.upstream_request_budget
|
|
2177
|
+
};
|
|
2178
|
+
const collected = await collectPrograms(client, {
|
|
2179
|
+
platforms: input.platforms,
|
|
2180
|
+
tags: input.tags,
|
|
2181
|
+
updated_since: undefined,
|
|
2182
|
+
opportunity_levels: [],
|
|
2183
|
+
max_pages: input.max_pages,
|
|
2184
|
+
budget
|
|
2185
|
+
});
|
|
2186
|
+
const platforms = new Set();
|
|
2187
|
+
const scopeTags = new Set();
|
|
2188
|
+
const targetTypes = new Set();
|
|
2189
|
+
const languageTags = new Set();
|
|
2190
|
+
const sourceRequests = [...collected.sourceRequests];
|
|
2191
|
+
for (const rawProgram of collected.programs) {
|
|
2192
|
+
const program = sanitizeProgram(rawProgram, "");
|
|
2193
|
+
addSetValue(platforms, stringField(program, "platform"));
|
|
2194
|
+
addSetValues(scopeTags, readStringArrayField(readObject(program.scope_summary) ?? {}, "tags"));
|
|
2195
|
+
}
|
|
2196
|
+
try {
|
|
2197
|
+
if (tryConsumeBudget(budget)) {
|
|
2198
|
+
const changesApi = await client.getRecentChanges({ page: 1, page_size: MAX_RECENT_CHANGES });
|
|
2199
|
+
refundBudgetIfNoUpstreamRequest(budget, changesApi);
|
|
2200
|
+
sourceRequests.push({
|
|
2201
|
+
source: "recent_changes_facets",
|
|
2202
|
+
request_id: changesApi.requestId,
|
|
2203
|
+
upstream_request_id: changesApi.upstreamRequestId,
|
|
2204
|
+
...apiSourceMetadata(changesApi)
|
|
2205
|
+
});
|
|
2206
|
+
const changesData = readObject(changesApi.data);
|
|
2207
|
+
const facets = readObject(changesData?.facets);
|
|
2208
|
+
for (const platform of readFacetValues(facets?.platforms)) {
|
|
2209
|
+
addSetValue(platforms, platform);
|
|
2210
|
+
}
|
|
2211
|
+
for (const tag of readFacetValues(facets?.tags)) {
|
|
2212
|
+
addSetValue(scopeTags, tag);
|
|
2213
|
+
}
|
|
2214
|
+
}
|
|
2215
|
+
}
|
|
2216
|
+
catch {
|
|
2217
|
+
// Facets are best-effort; list_filters should still return observed program filters.
|
|
2218
|
+
}
|
|
2219
|
+
if (input.include_target_facets && input.target_sample_programs > 0) {
|
|
2220
|
+
const programIds = collected.programs
|
|
2221
|
+
.map((program) => readString(readObject(program)?.id))
|
|
2222
|
+
.filter((id) => id !== undefined)
|
|
2223
|
+
.slice(0, input.target_sample_programs);
|
|
2224
|
+
await mapWithConcurrency(programIds, TARGET_SAMPLE_CONCURRENCY, async (programId) => {
|
|
2225
|
+
if (!tryConsumeBudget(budget)) {
|
|
2226
|
+
return;
|
|
2227
|
+
}
|
|
2228
|
+
const targetsApi = await client.getProgramTargets(programId);
|
|
2229
|
+
refundBudgetIfNoUpstreamRequest(budget, targetsApi);
|
|
2230
|
+
const targetsData = readObject(targetsApi.data);
|
|
2231
|
+
sourceRequests.push({
|
|
2232
|
+
source: "target_facets",
|
|
2233
|
+
program_id: programId,
|
|
2234
|
+
request_id: targetsApi.requestId,
|
|
2235
|
+
upstream_request_id: targetsApi.upstreamRequestId,
|
|
2236
|
+
...apiSourceMetadata(targetsApi)
|
|
2237
|
+
});
|
|
2238
|
+
for (const target of readArray(targetsData?.targets).map(sanitizeTarget)) {
|
|
2239
|
+
addSetValue(targetTypes, stringField(target, "target_type"));
|
|
2240
|
+
addSetValues(scopeTags, readStringArrayField(target, "scope_tags"));
|
|
2241
|
+
addSetValues(languageTags, readStringArrayField(target, "language_tags"));
|
|
2242
|
+
}
|
|
2243
|
+
});
|
|
2244
|
+
}
|
|
2245
|
+
return {
|
|
2246
|
+
request_id: randomUUID(),
|
|
2247
|
+
filters: {
|
|
2248
|
+
platforms: sortedValues(platforms),
|
|
2249
|
+
tags: sortedValues(scopeTags),
|
|
2250
|
+
scope_tags: sortedValues(scopeTags),
|
|
2251
|
+
target_types: sortedValues(targetTypes),
|
|
2252
|
+
language_tags: sortedValues(languageTags),
|
|
2253
|
+
opportunity_levels: opportunityLevelSchema.options
|
|
2254
|
+
},
|
|
2255
|
+
observed_from: {
|
|
2256
|
+
programs_scanned: collected.programsScanned,
|
|
2257
|
+
max_pages_scanned: input.max_pages,
|
|
2258
|
+
target_facets_enabled: input.include_target_facets,
|
|
2259
|
+
source_requests: sourceRequests
|
|
2260
|
+
},
|
|
2261
|
+
upstream_budget: {
|
|
2262
|
+
used: budget.initial - budget.remaining,
|
|
2263
|
+
remaining: budget.remaining
|
|
2264
|
+
}
|
|
2265
|
+
};
|
|
2266
|
+
}
|
|
2267
|
+
async function comparePrograms(client, config, input) {
|
|
2268
|
+
const candidates = [];
|
|
2269
|
+
const sourceRequests = [];
|
|
2270
|
+
const errors = [];
|
|
2271
|
+
await mapWithConcurrency(input.program_ids, TARGET_SAMPLE_CONCURRENCY, async (programId) => {
|
|
2272
|
+
try {
|
|
2273
|
+
const api = await client.getProgram(programId);
|
|
2274
|
+
candidates.push(toProgramCandidate(api.data, config.webBaseUrl));
|
|
2275
|
+
sourceRequests.push({
|
|
2276
|
+
source: "program",
|
|
2277
|
+
program_id: programId,
|
|
2278
|
+
request_id: api.requestId,
|
|
2279
|
+
upstream_request_id: api.upstreamRequestId,
|
|
2280
|
+
...apiSourceMetadata(api)
|
|
2281
|
+
});
|
|
2282
|
+
}
|
|
2283
|
+
catch (error) {
|
|
2284
|
+
errors.push(programFetchError(programId, error));
|
|
2285
|
+
}
|
|
2286
|
+
});
|
|
2287
|
+
candidates.sort((left, right) => compareProgramCandidates(left, right, "best_hunt_value"));
|
|
2288
|
+
const sampleInput = {
|
|
2289
|
+
platforms: [],
|
|
2290
|
+
tags: [],
|
|
2291
|
+
scope_tags: [],
|
|
2292
|
+
target_types: [],
|
|
2293
|
+
language_tags: [],
|
|
2294
|
+
web3: false,
|
|
2295
|
+
opportunity_levels: [],
|
|
2296
|
+
updated_since: undefined,
|
|
2297
|
+
min_bounty_min: undefined,
|
|
2298
|
+
min_bounty_max: undefined,
|
|
2299
|
+
max_bounty_max: undefined,
|
|
2300
|
+
require_bounty: false,
|
|
2301
|
+
max_public_report_count: undefined,
|
|
2302
|
+
require_known_report_count: false,
|
|
2303
|
+
include_unknown_report_count: true,
|
|
2304
|
+
exclude_ctf: false,
|
|
2305
|
+
exclude_mobile_only: false,
|
|
2306
|
+
exclude_non_web: false,
|
|
2307
|
+
has_api_scope: false,
|
|
2308
|
+
fresh_launch_days: undefined,
|
|
2309
|
+
min_wildcards: 0,
|
|
2310
|
+
min_eligible_targets: undefined,
|
|
2311
|
+
min_total_targets: undefined,
|
|
2312
|
+
min_added_24h: undefined,
|
|
2313
|
+
min_added_7d: undefined,
|
|
2314
|
+
max_pages: 1,
|
|
2315
|
+
max_results: candidates.length,
|
|
2316
|
+
sort_by: "best_match",
|
|
2317
|
+
include_target_samples: input.include_target_samples,
|
|
2318
|
+
target_sample_programs: input.include_target_samples ? candidates.length : 0,
|
|
2319
|
+
target_sample_size: input.target_sample_size,
|
|
2320
|
+
only_in_scope_targets: input.only_in_scope_targets,
|
|
2321
|
+
only_bounty_eligible_targets: input.only_bounty_eligible_targets,
|
|
2322
|
+
target_samples_only_wildcards: false,
|
|
2323
|
+
output_mode: input.output_mode ?? "compact",
|
|
2324
|
+
upstream_request_budget: Math.max(candidates.length + 1, DEFAULT_FIND_UPSTREAM_REQUEST_BUDGET)
|
|
2325
|
+
};
|
|
2326
|
+
const budget = {
|
|
2327
|
+
initial: sampleInput.upstream_request_budget,
|
|
2328
|
+
remaining: sampleInput.upstream_request_budget
|
|
2329
|
+
};
|
|
2330
|
+
const targetSamples = input.include_target_samples ? await collectTargetSamples(client, candidates, sampleInput, budget) : {};
|
|
2331
|
+
return stripUndefined({
|
|
2332
|
+
request_id: randomUUID(),
|
|
2333
|
+
partial_success: errors.length > 0,
|
|
2334
|
+
program_ids_requested: input.program_ids,
|
|
2335
|
+
failed_program_ids: errors.map((error) => error.program_id).filter((id) => typeof id === "string"),
|
|
2336
|
+
errors: errors.length > 0 ? errors : undefined,
|
|
2337
|
+
source_requests: sourceRequests,
|
|
2338
|
+
compared_programs: candidates.map((candidate, index) => candidateOutput(candidate, index, targetSamples, input.output_mode ?? "compact")),
|
|
2339
|
+
upstream_budget: {
|
|
2340
|
+
used: budget.initial - budget.remaining,
|
|
2341
|
+
remaining: budget.remaining
|
|
2342
|
+
}
|
|
2343
|
+
});
|
|
2344
|
+
}
|
|
2345
|
+
async function summarizeProgramActivity(client, config, programId, recentChangesLimit) {
|
|
2346
|
+
const programApi = await client.getProgram(programId);
|
|
2347
|
+
const program = addProgramResourceLinks(sanitizeProgram(programApi.data, config.webBaseUrl));
|
|
2348
|
+
const scopeSummary = readObject(program.scope_summary);
|
|
2349
|
+
const targetActivity = readObject(scopeSummary?.target_activity);
|
|
2350
|
+
const changes = recentChangesLimit > 0 ? await fetchProgramChanges(client, config, programId, recentChangesLimit, true, true, true) : undefined;
|
|
2351
|
+
return {
|
|
2352
|
+
request_id: randomUUID(),
|
|
2353
|
+
source_requests: [
|
|
2354
|
+
{
|
|
2355
|
+
source: "program",
|
|
2356
|
+
request_id: programApi.requestId,
|
|
2357
|
+
upstream_request_id: programApi.upstreamRequestId,
|
|
2358
|
+
...apiSourceMetadata(programApi)
|
|
2359
|
+
},
|
|
2360
|
+
...(changes?.sourceRequests ?? [])
|
|
2361
|
+
],
|
|
2362
|
+
program,
|
|
2363
|
+
activity: stripUndefined({
|
|
2364
|
+
target_counts: readObject(scopeSummary?.target_counts),
|
|
2365
|
+
target_activity: targetActivity,
|
|
2366
|
+
recent_change_count: changes?.changes.length
|
|
2367
|
+
}),
|
|
2368
|
+
changes: changes?.changes ?? []
|
|
2369
|
+
};
|
|
2370
|
+
}
|
|
2371
|
+
async function getLatestAddedTargets(client, config, input) {
|
|
2372
|
+
const recentApi = await client.getRecentChanges({
|
|
2373
|
+
change_type: "added",
|
|
2374
|
+
include_removed: false,
|
|
2375
|
+
include_ineligible: input.include_ineligible,
|
|
2376
|
+
include_out_of_scope: input.include_out_of_scope,
|
|
2377
|
+
tags: [input.target_type],
|
|
2378
|
+
page: 1,
|
|
2379
|
+
page_size: input.recent_changes_page_size
|
|
2380
|
+
});
|
|
2381
|
+
const recentData = readObject(recentApi.data);
|
|
2382
|
+
const changes = readArray(recentData?.results).map((change) => sanitizeChange(change, config.webBaseUrl));
|
|
2383
|
+
const matchingChanges = changes
|
|
2384
|
+
.filter((change) => {
|
|
2385
|
+
const target = readObject(change.target);
|
|
2386
|
+
const programId = stringField(readObject(change.program), "id");
|
|
2387
|
+
return (stringField(change, "change_type") === "added" &&
|
|
2388
|
+
programId !== undefined &&
|
|
2389
|
+
target !== undefined &&
|
|
2390
|
+
targetMatchesRequestedType(target, input.target_type) &&
|
|
2391
|
+
targetHasAllowedScope(target, {
|
|
2392
|
+
include_out_of_scope: input.include_out_of_scope,
|
|
2393
|
+
include_ineligible: input.include_ineligible,
|
|
2394
|
+
strict_scope_filter: true
|
|
2395
|
+
}));
|
|
2396
|
+
})
|
|
2397
|
+
.sort((left, right) => timestampField(right, "changed_at") - timestampField(left, "changed_at"));
|
|
2398
|
+
const latest = matchingChanges[0];
|
|
2399
|
+
const sourceRequests = [
|
|
2400
|
+
{
|
|
2401
|
+
source: "recent_changes",
|
|
2402
|
+
request_id: recentApi.requestId,
|
|
2403
|
+
upstream_request_id: recentApi.upstreamRequestId,
|
|
2404
|
+
...apiSourceMetadata(recentApi)
|
|
2405
|
+
}
|
|
2406
|
+
];
|
|
2407
|
+
if (!latest) {
|
|
2408
|
+
return {
|
|
2409
|
+
request_id: randomUUID(),
|
|
2410
|
+
source_requests: sourceRequests,
|
|
2411
|
+
query: latestAddedTargetsQuery(input),
|
|
2412
|
+
new_targets: [],
|
|
2413
|
+
meta: {
|
|
2414
|
+
recent_changes_scanned: changes.length,
|
|
2415
|
+
no_match: true
|
|
2416
|
+
}
|
|
2417
|
+
};
|
|
2418
|
+
}
|
|
2419
|
+
const latestAddedAt = stringField(latest, "changed_at");
|
|
2420
|
+
const programId = stringField(readObject(latest.program), "id");
|
|
2421
|
+
const program = addProgramResourceLinks(readObject(latest.program) ?? {});
|
|
2422
|
+
const latestNewTargets = matchingChanges
|
|
2423
|
+
.filter((change) => stringField(change, "changed_at") === latestAddedAt && stringField(readObject(change.program), "id") === programId)
|
|
2424
|
+
.map((change) => readObject(change.target))
|
|
2425
|
+
.filter((target) => target !== undefined);
|
|
2426
|
+
let fullTargets = [];
|
|
2427
|
+
let totalActiveTargets;
|
|
2428
|
+
let totalAfterFilters;
|
|
2429
|
+
if (input.include_full_target_list && programId) {
|
|
2430
|
+
const targetsApi = await client.getProgramTargets(programId);
|
|
2431
|
+
const targetsData = readObject(targetsApi.data);
|
|
2432
|
+
const rawTargets = readArray(targetsData?.targets);
|
|
2433
|
+
fullTargets = rawTargets
|
|
2434
|
+
.map(sanitizeTarget)
|
|
2435
|
+
.filter((target) => targetHasAllowedScope(target, {
|
|
2436
|
+
include_out_of_scope: input.full_list_include_out_of_scope,
|
|
2437
|
+
include_ineligible: input.full_list_include_ineligible,
|
|
2438
|
+
strict_scope_filter: false
|
|
2439
|
+
}));
|
|
2440
|
+
totalActiveTargets = rawTargets.length;
|
|
2441
|
+
totalAfterFilters = fullTargets.length;
|
|
2442
|
+
sourceRequests.push({
|
|
2443
|
+
source: "program_targets",
|
|
2444
|
+
program_id: programId,
|
|
2445
|
+
request_id: targetsApi.requestId,
|
|
2446
|
+
upstream_request_id: targetsApi.upstreamRequestId,
|
|
2447
|
+
...apiSourceMetadata(targetsApi)
|
|
2448
|
+
});
|
|
2449
|
+
}
|
|
2450
|
+
const limitedFullTargets = fullTargets.slice(0, input.full_target_limit);
|
|
2451
|
+
return stripUndefined({
|
|
2452
|
+
request_id: randomUUID(),
|
|
2453
|
+
source_requests: sourceRequests,
|
|
2454
|
+
query: latestAddedTargetsQuery(input),
|
|
2455
|
+
latest_added_at: latestAddedAt,
|
|
2456
|
+
program,
|
|
2457
|
+
new_targets: formatTargetList(latestNewTargets, input.full_target_list_mode),
|
|
2458
|
+
full_targets: input.include_full_target_list ? formatTargetList(limitedFullTargets, input.full_target_list_mode) : undefined,
|
|
2459
|
+
meta: stripUndefined({
|
|
2460
|
+
recent_changes_scanned: changes.length,
|
|
2461
|
+
matching_changes_scanned: matchingChanges.length,
|
|
2462
|
+
new_targets_returned: formatTargetList(latestNewTargets, input.full_target_list_mode).length,
|
|
2463
|
+
full_targets_total_active: totalActiveTargets,
|
|
2464
|
+
full_targets_total_after_filters: totalAfterFilters,
|
|
2465
|
+
full_targets_returned: input.include_full_target_list ? formatTargetList(limitedFullTargets, input.full_target_list_mode).length : undefined,
|
|
2466
|
+
full_targets_has_more: input.include_full_target_list ? limitedFullTargets.length < fullTargets.length : undefined
|
|
2467
|
+
})
|
|
2468
|
+
});
|
|
2469
|
+
}
|
|
2470
|
+
async function getProgramScopeSummary(client, config, input) {
|
|
2471
|
+
const [programApi, targetsApi] = await Promise.all([client.getProgram(input.program_id), client.getProgramTargets(input.program_id)]);
|
|
2472
|
+
const targetsData = readObject(targetsApi.data);
|
|
2473
|
+
const rawTargets = readArray(targetsData?.targets);
|
|
2474
|
+
const targets = rawTargets.map(sanitizeTarget);
|
|
2475
|
+
return {
|
|
2476
|
+
request_id: randomUUID(),
|
|
2477
|
+
source_requests: [
|
|
2478
|
+
{
|
|
2479
|
+
source: "program",
|
|
2480
|
+
request_id: programApi.requestId,
|
|
2481
|
+
upstream_request_id: programApi.upstreamRequestId,
|
|
2482
|
+
...apiSourceMetadata(programApi)
|
|
2483
|
+
},
|
|
2484
|
+
{
|
|
2485
|
+
source: "program_targets",
|
|
2486
|
+
request_id: targetsApi.requestId,
|
|
2487
|
+
upstream_request_id: targetsApi.upstreamRequestId,
|
|
2488
|
+
...apiSourceMetadata(targetsApi)
|
|
2489
|
+
}
|
|
2490
|
+
],
|
|
2491
|
+
program: formatProgram(addProgramResourceLinks(sanitizeProgram(programApi.data, config.webBaseUrl)), "compact"),
|
|
2492
|
+
scope: buildTargetScopeSummary(targets, input.target_list_mode, input.group_limit, input.include_out_of_scope, input.include_ineligible),
|
|
2493
|
+
meta: {
|
|
2494
|
+
total_active_targets: rawTargets.length,
|
|
2495
|
+
target_list_mode: input.target_list_mode,
|
|
2496
|
+
group_limit: input.group_limit
|
|
2497
|
+
}
|
|
2498
|
+
};
|
|
2499
|
+
}
|
|
2500
|
+
async function getProgramTargetBreakdown(client, config, input) {
|
|
2501
|
+
const [programApi, targetsApi] = await Promise.all([client.getProgram(input.program_id), client.getProgramTargets(input.program_id)]);
|
|
2502
|
+
const targetsData = readObject(targetsApi.data);
|
|
2503
|
+
const rawTargets = readArray(targetsData?.targets);
|
|
2504
|
+
const targets = rawTargets
|
|
2505
|
+
.map(sanitizeTarget)
|
|
2506
|
+
.filter((target) => targetHasAllowedScope(target, {
|
|
2507
|
+
include_out_of_scope: input.include_out_of_scope,
|
|
2508
|
+
include_ineligible: input.include_ineligible,
|
|
2509
|
+
strict_scope_filter: false
|
|
2510
|
+
}));
|
|
2511
|
+
const scope = buildTargetScopeSummary(targets, input.target_list_mode, input.group_limit, input.include_out_of_scope, input.include_ineligible);
|
|
2512
|
+
return {
|
|
2513
|
+
request_id: randomUUID(),
|
|
2514
|
+
source_requests: [
|
|
2515
|
+
{
|
|
2516
|
+
source: "program",
|
|
2517
|
+
request_id: programApi.requestId,
|
|
2518
|
+
upstream_request_id: programApi.upstreamRequestId,
|
|
2519
|
+
...apiSourceMetadata(programApi)
|
|
2520
|
+
},
|
|
2521
|
+
{
|
|
2522
|
+
source: "program_targets",
|
|
2523
|
+
request_id: targetsApi.requestId,
|
|
2524
|
+
upstream_request_id: targetsApi.upstreamRequestId,
|
|
2525
|
+
...apiSourceMetadata(targetsApi)
|
|
2526
|
+
}
|
|
2527
|
+
],
|
|
2528
|
+
program: formatProgram(addProgramResourceLinks(sanitizeProgram(programApi.data, config.webBaseUrl)), "compact"),
|
|
2529
|
+
breakdown: {
|
|
2530
|
+
target_counts: readObject(scope.target_counts),
|
|
2531
|
+
by_target_type: countTargetValues(targets, (target) => stringField(target, "target_type")),
|
|
2532
|
+
by_scope_tag: countTargetValues(targets, (target) => readStringArrayField(target, "scope_tags")),
|
|
2533
|
+
by_language_tag: countTargetValues(targets, (target) => readStringArrayField(target, "language_tags")),
|
|
2534
|
+
group_counts: readObject(scope.group_counts),
|
|
2535
|
+
group_has_more: readObject(scope.group_has_more),
|
|
2536
|
+
groups: readObject(scope.groups)
|
|
2537
|
+
},
|
|
2538
|
+
meta: {
|
|
2539
|
+
total_active_targets: rawTargets.length,
|
|
2540
|
+
total_after_filters: targets.length,
|
|
2541
|
+
target_list_mode: input.target_list_mode,
|
|
2542
|
+
group_limit: input.group_limit
|
|
2543
|
+
}
|
|
2544
|
+
};
|
|
2545
|
+
}
|
|
2546
|
+
async function getProgramScopeDelta(client, config, input) {
|
|
2547
|
+
const programApi = await client.getProgram(input.program_id);
|
|
2548
|
+
const program = addProgramResourceLinks(sanitizeProgram(programApi.data, config.webBaseUrl));
|
|
2549
|
+
const changesPayload = await fetchProgramChanges(client, config, input.program_id, input.page_size, input.include_removed || input.change_type === "removed", input.include_ineligible, input.include_out_of_scope);
|
|
2550
|
+
const sinceTimestamp = input.since ? Date.parse(input.since) : undefined;
|
|
2551
|
+
const filteredChanges = changesPayload.changes
|
|
2552
|
+
.filter((change) => !input.change_type || stringField(change, "change_type") === input.change_type)
|
|
2553
|
+
.filter((change) => sinceTimestamp === undefined || timestampField(change, "changed_at") >= sinceTimestamp)
|
|
2554
|
+
.filter((change) => {
|
|
2555
|
+
const target = readObject(change.target);
|
|
2556
|
+
return !input.target_type || (target !== undefined && targetMatchesRequestedType(target, input.target_type));
|
|
2557
|
+
})
|
|
2558
|
+
.filter((change) => {
|
|
2559
|
+
const target = readObject(change.target);
|
|
2560
|
+
return input.language_tags.length === 0 || (target !== undefined && targetMatchesAnySignal(target, input.language_tags));
|
|
2561
|
+
});
|
|
2562
|
+
const limitedChanges = filteredChanges.slice(0, input.max_targets);
|
|
2563
|
+
return stripUndefined({
|
|
2564
|
+
request_id: randomUUID(),
|
|
2565
|
+
source_requests: [
|
|
2566
|
+
{
|
|
2567
|
+
source: "program",
|
|
2568
|
+
request_id: programApi.requestId,
|
|
2569
|
+
upstream_request_id: programApi.upstreamRequestId,
|
|
2570
|
+
...apiSourceMetadata(programApi)
|
|
2571
|
+
},
|
|
2572
|
+
...changesPayload.sourceRequests
|
|
2573
|
+
],
|
|
2574
|
+
program: formatProgram(program, "compact"),
|
|
2575
|
+
delta: buildScopeDelta(limitedChanges, input.target_list_mode),
|
|
2576
|
+
changes: limitedChanges.map((change) => formatChange(change, "compact")),
|
|
2577
|
+
meta: stripUndefined({
|
|
2578
|
+
recent_changes_scanned: changesPayload.changes.length,
|
|
2579
|
+
changes_after_filters: filteredChanges.length,
|
|
2580
|
+
returned: limitedChanges.length,
|
|
2581
|
+
has_more: filteredChanges.length > limitedChanges.length,
|
|
2582
|
+
since: input.since,
|
|
2583
|
+
change_type: input.change_type,
|
|
2584
|
+
target_type: input.target_type,
|
|
2585
|
+
language_tags: input.language_tags.length > 0 ? input.language_tags : undefined,
|
|
2586
|
+
target_list_mode: input.target_list_mode
|
|
2587
|
+
})
|
|
2588
|
+
});
|
|
2589
|
+
}
|
|
2590
|
+
async function getRecentTargetActivity(client, config, input) {
|
|
2591
|
+
const query = toQuery({
|
|
2592
|
+
change_type: input.change_type,
|
|
2593
|
+
include_removed: input.include_removed,
|
|
2594
|
+
include_ineligible: input.include_ineligible,
|
|
2595
|
+
include_out_of_scope: input.include_out_of_scope,
|
|
2596
|
+
search: input.search,
|
|
2597
|
+
platforms: input.platforms,
|
|
2598
|
+
tags: uniqueStrings([...input.tags, ...(input.target_type ? [input.target_type] : []), ...input.language_tags]),
|
|
2599
|
+
page: 1,
|
|
2600
|
+
page_size: input.page_size
|
|
2601
|
+
});
|
|
2602
|
+
const api = await client.getRecentChanges(query);
|
|
2603
|
+
const data = readObject(api.data);
|
|
2604
|
+
const changes = readArray(data?.results)
|
|
2605
|
+
.map((change) => sanitizeChange(change, config.webBaseUrl))
|
|
2606
|
+
.filter((change) => {
|
|
2607
|
+
const target = readObject(change.target);
|
|
2608
|
+
return !input.target_type || (target !== undefined && targetMatchesRequestedType(target, input.target_type));
|
|
2609
|
+
})
|
|
2610
|
+
.filter((change) => {
|
|
2611
|
+
const target = readObject(change.target);
|
|
2612
|
+
return input.language_tags.length === 0 || (target !== undefined && targetMatchesAnySignal(target, input.language_tags));
|
|
2613
|
+
});
|
|
2614
|
+
const grouped = new Map();
|
|
2615
|
+
for (const change of changes) {
|
|
2616
|
+
const program = readObject(change.program);
|
|
2617
|
+
const programId = stringField(program, "id");
|
|
2618
|
+
const target = readObject(change.target);
|
|
2619
|
+
if (!program || !programId || !target) {
|
|
2620
|
+
continue;
|
|
2621
|
+
}
|
|
2622
|
+
const existing = grouped.get(programId) ??
|
|
2623
|
+
{
|
|
2624
|
+
program: addProgramResourceLinks(program),
|
|
2625
|
+
latestChangedAt: undefined,
|
|
2626
|
+
latestTimestamp: 0,
|
|
2627
|
+
changes: [],
|
|
2628
|
+
targets: [],
|
|
2629
|
+
targetTypes: new Set(),
|
|
2630
|
+
changeCounts: {}
|
|
2631
|
+
};
|
|
2632
|
+
const changedAt = stringField(change, "changed_at");
|
|
2633
|
+
const timestamp = timestampField(change, "changed_at");
|
|
2634
|
+
const changeType = stringField(change, "change_type") ?? "unknown";
|
|
2635
|
+
existing.changes.push(change);
|
|
2636
|
+
existing.targets.push(target);
|
|
2637
|
+
existing.targetTypes.add(stringField(target, "target_type") ?? "unknown");
|
|
2638
|
+
existing.changeCounts[changeType] = (existing.changeCounts[changeType] ?? 0) + 1;
|
|
2639
|
+
if (timestamp >= existing.latestTimestamp) {
|
|
2640
|
+
existing.latestTimestamp = timestamp;
|
|
2641
|
+
existing.latestChangedAt = changedAt;
|
|
2642
|
+
}
|
|
2643
|
+
grouped.set(programId, existing);
|
|
2644
|
+
}
|
|
2645
|
+
const activity = [...grouped.values()]
|
|
2646
|
+
.sort((left, right) => right.latestTimestamp - left.latestTimestamp)
|
|
2647
|
+
.slice(0, input.max_programs)
|
|
2648
|
+
.map((entry) => stripUndefined({
|
|
2649
|
+
program: formatProgram(entry.program, "compact"),
|
|
2650
|
+
latest_changed_at: entry.latestChangedAt,
|
|
2651
|
+
change_counts: entry.changeCounts,
|
|
2652
|
+
target_types: sortedValues(entry.targetTypes),
|
|
2653
|
+
sample_targets: formatTargetList(entry.targets.slice(0, input.sample_size), input.target_list_mode),
|
|
2654
|
+
sample_changes: entry.changes.slice(0, input.sample_size).map((change) => formatChange(change, "compact"))
|
|
2655
|
+
}));
|
|
2656
|
+
return {
|
|
2657
|
+
request_id: randomUUID(),
|
|
2658
|
+
source_requests: [
|
|
2659
|
+
{
|
|
2660
|
+
source: "recent_changes",
|
|
2661
|
+
request_id: api.requestId,
|
|
2662
|
+
upstream_request_id: api.upstreamRequestId,
|
|
2663
|
+
...apiSourceMetadata(api)
|
|
2664
|
+
}
|
|
2665
|
+
],
|
|
2666
|
+
activity,
|
|
2667
|
+
meta: {
|
|
2668
|
+
recent_changes_scanned: changes.length,
|
|
2669
|
+
programs_matched: grouped.size,
|
|
2670
|
+
returned: activity.length,
|
|
2671
|
+
target_list_mode: input.target_list_mode
|
|
2672
|
+
}
|
|
2673
|
+
};
|
|
2674
|
+
}
|
|
2675
|
+
async function checkProgramNewTargets(client, config, input) {
|
|
2676
|
+
const handle = programSearchText(input.program_id);
|
|
2677
|
+
const api = await client.getRecentChanges({
|
|
2678
|
+
change_type: "added",
|
|
2679
|
+
include_removed: false,
|
|
2680
|
+
include_ineligible: input.include_ineligible,
|
|
2681
|
+
include_out_of_scope: false,
|
|
2682
|
+
search: handle.length >= 2 ? handle : input.program_id,
|
|
2683
|
+
tags: input.target_type ? [input.target_type] : [],
|
|
2684
|
+
page: 1,
|
|
2685
|
+
page_size: input.page_size
|
|
2686
|
+
});
|
|
2687
|
+
const data = readObject(api.data);
|
|
2688
|
+
const upstreamMeta = readObject(sanitizeJson(data?.meta));
|
|
2689
|
+
const sinceTimestamp = input.since ? Date.parse(input.since) : undefined;
|
|
2690
|
+
const changes = readArray(data?.results).map((change) => sanitizeChange(change, config.webBaseUrl));
|
|
2691
|
+
const matchingChanges = changes
|
|
2692
|
+
.filter((change) => {
|
|
2693
|
+
const target = readObject(change.target);
|
|
2694
|
+
const changedAt = timestampField(change, "changed_at");
|
|
2695
|
+
return (stringField(change, "change_type") === "added" &&
|
|
2696
|
+
stringField(readObject(change.program), "id") === input.program_id &&
|
|
2697
|
+
target !== undefined &&
|
|
2698
|
+
(sinceTimestamp === undefined || changedAt >= sinceTimestamp) &&
|
|
2699
|
+
(!input.target_type || targetMatchesRequestedType(target, input.target_type)) &&
|
|
2700
|
+
targetHasAllowedScope(target, {
|
|
2701
|
+
include_out_of_scope: false,
|
|
2702
|
+
include_ineligible: input.include_ineligible,
|
|
2703
|
+
strict_scope_filter: true
|
|
2704
|
+
}));
|
|
2705
|
+
})
|
|
2706
|
+
.sort((left, right) => timestampField(right, "changed_at") - timestampField(left, "changed_at"));
|
|
2707
|
+
const limitedChanges = matchingChanges.slice(0, input.max_targets);
|
|
2708
|
+
const targets = limitedChanges
|
|
2709
|
+
.map((change) => readObject(change.target))
|
|
2710
|
+
.filter((target) => target !== undefined);
|
|
2711
|
+
const latest = matchingChanges[0];
|
|
2712
|
+
return stripUndefined({
|
|
2713
|
+
request_id: randomUUID(),
|
|
2714
|
+
source_requests: [
|
|
2715
|
+
{
|
|
2716
|
+
source: "recent_changes",
|
|
2717
|
+
request_id: api.requestId,
|
|
2718
|
+
upstream_request_id: api.upstreamRequestId,
|
|
2719
|
+
...apiSourceMetadata(api)
|
|
2720
|
+
}
|
|
2721
|
+
],
|
|
2722
|
+
program_id: input.program_id,
|
|
2723
|
+
program: latest ? formatProgram(addProgramResourceLinks(readObject(latest.program) ?? {}), "compact") : undefined,
|
|
2724
|
+
has_new_targets: matchingChanges.length > 0,
|
|
2725
|
+
new_target_count: matchingChanges.length,
|
|
2726
|
+
latest_added_at: latest ? stringField(latest, "changed_at") : undefined,
|
|
2727
|
+
new_targets: formatTargetList(targets, input.target_list_mode),
|
|
2728
|
+
meta: stripUndefined({
|
|
2729
|
+
recent_changes_scanned: changes.length,
|
|
2730
|
+
returned: targets.length,
|
|
2731
|
+
has_more: matchingChanges.length > targets.length,
|
|
2732
|
+
since: input.since,
|
|
2733
|
+
target_type: input.target_type,
|
|
2734
|
+
in_scope_only: true,
|
|
2735
|
+
include_ineligible: input.include_ineligible,
|
|
2736
|
+
target_list_mode: input.target_list_mode,
|
|
2737
|
+
upstream_total_pages: readNumber(upstreamMeta?.total_pages),
|
|
2738
|
+
scan_may_be_incomplete: (readNumber(upstreamMeta?.total_pages) ?? 1) > 1
|
|
2739
|
+
})
|
|
2740
|
+
});
|
|
2741
|
+
}
|
|
2742
|
+
async function checkWatchlistNewTargets(client, config, input) {
|
|
2743
|
+
const watchlist = new Set(input.program_ids);
|
|
2744
|
+
const api = await client.getRecentChanges({
|
|
2745
|
+
change_type: "added",
|
|
2746
|
+
include_removed: false,
|
|
2747
|
+
include_ineligible: input.include_ineligible,
|
|
2748
|
+
include_out_of_scope: false,
|
|
2749
|
+
tags: input.target_type ? [input.target_type] : [],
|
|
2750
|
+
page: 1,
|
|
2751
|
+
page_size: input.page_size
|
|
2752
|
+
});
|
|
2753
|
+
const data = readObject(api.data);
|
|
2754
|
+
const upstreamMeta = readObject(sanitizeJson(data?.meta));
|
|
2755
|
+
const sinceTimestamp = input.since ? Date.parse(input.since) : undefined;
|
|
2756
|
+
const changes = readArray(data?.results).map((change) => sanitizeChange(change, config.webBaseUrl));
|
|
2757
|
+
const grouped = new Map();
|
|
2758
|
+
for (const programId of input.program_ids) {
|
|
2759
|
+
grouped.set(programId, {
|
|
2760
|
+
program: undefined,
|
|
2761
|
+
changes: [],
|
|
2762
|
+
targets: [],
|
|
2763
|
+
latestTimestamp: 0,
|
|
2764
|
+
latestAddedAt: undefined
|
|
2765
|
+
});
|
|
2766
|
+
}
|
|
2767
|
+
for (const change of changes) {
|
|
2768
|
+
const program = readObject(change.program);
|
|
2769
|
+
const programId = stringField(program, "id");
|
|
2770
|
+
const target = readObject(change.target);
|
|
2771
|
+
const changedAt = timestampField(change, "changed_at");
|
|
2772
|
+
if (!programId ||
|
|
2773
|
+
!watchlist.has(programId) ||
|
|
2774
|
+
!target ||
|
|
2775
|
+
stringField(change, "change_type") !== "added" ||
|
|
2776
|
+
(sinceTimestamp !== undefined && changedAt < sinceTimestamp) ||
|
|
2777
|
+
(input.target_type && !targetMatchesRequestedType(target, input.target_type)) ||
|
|
2778
|
+
!targetHasAllowedScope(target, {
|
|
2779
|
+
include_out_of_scope: false,
|
|
2780
|
+
include_ineligible: input.include_ineligible,
|
|
2781
|
+
strict_scope_filter: true
|
|
2782
|
+
})) {
|
|
2783
|
+
continue;
|
|
2784
|
+
}
|
|
2785
|
+
const existing = grouped.get(programId);
|
|
2786
|
+
if (!existing) {
|
|
2787
|
+
continue;
|
|
2788
|
+
}
|
|
2789
|
+
existing.program = program ? addProgramResourceLinks(program) : undefined;
|
|
2790
|
+
existing.changes.push(change);
|
|
2791
|
+
existing.targets.push(target);
|
|
2792
|
+
if (changedAt >= existing.latestTimestamp) {
|
|
2793
|
+
existing.latestTimestamp = changedAt;
|
|
2794
|
+
existing.latestAddedAt = stringField(change, "changed_at");
|
|
2795
|
+
}
|
|
2796
|
+
}
|
|
2797
|
+
const programs = input.program_ids.map((programId) => {
|
|
2798
|
+
const entry = grouped.get(programId);
|
|
2799
|
+
const targets = entry?.targets ?? [];
|
|
2800
|
+
const limitedTargets = targets.slice(0, input.max_targets_per_program);
|
|
2801
|
+
return stripUndefined({
|
|
2802
|
+
program_id: programId,
|
|
2803
|
+
program: entry?.program ? formatProgram(entry.program, "compact") : undefined,
|
|
2804
|
+
has_new_targets: targets.length > 0,
|
|
2805
|
+
new_target_count: targets.length,
|
|
2806
|
+
latest_added_at: entry?.latestAddedAt,
|
|
2807
|
+
new_targets: formatTargetList(limitedTargets, input.target_list_mode),
|
|
2808
|
+
has_more: targets.length > limitedTargets.length
|
|
2809
|
+
});
|
|
2810
|
+
});
|
|
2811
|
+
return stripUndefined({
|
|
2812
|
+
request_id: randomUUID(),
|
|
2813
|
+
source_requests: [
|
|
2814
|
+
{
|
|
2815
|
+
source: "recent_changes",
|
|
2816
|
+
request_id: api.requestId,
|
|
2817
|
+
upstream_request_id: api.upstreamRequestId,
|
|
2818
|
+
...apiSourceMetadata(api)
|
|
2819
|
+
}
|
|
2820
|
+
],
|
|
2821
|
+
any_new_targets: programs.some((program) => program.has_new_targets === true),
|
|
2822
|
+
programs,
|
|
2823
|
+
meta: stripUndefined({
|
|
2824
|
+
recent_changes_scanned: changes.length,
|
|
2825
|
+
watchlist_size: input.program_ids.length,
|
|
2826
|
+
programs_with_new_targets: programs.filter((program) => program.has_new_targets === true).length,
|
|
2827
|
+
since: input.since,
|
|
2828
|
+
target_type: input.target_type,
|
|
2829
|
+
in_scope_only: true,
|
|
2830
|
+
include_ineligible: input.include_ineligible,
|
|
2831
|
+
target_list_mode: input.target_list_mode,
|
|
2832
|
+
upstream_total_pages: readNumber(upstreamMeta?.total_pages),
|
|
2833
|
+
scan_may_be_incomplete: (readNumber(upstreamMeta?.total_pages) ?? 1) > 1
|
|
2834
|
+
})
|
|
2835
|
+
});
|
|
2836
|
+
}
|
|
2837
|
+
async function getProgramBrief(client, config, input) {
|
|
2838
|
+
const programPromise = client.getProgram(input.program_id);
|
|
2839
|
+
const targetsPromise = client.getProgramTargets(input.program_id);
|
|
2840
|
+
const changesPromise = input.include_recent_changes && input.recent_changes_limit > 0
|
|
2841
|
+
? fetchProgramChanges(client, config, input.program_id, input.recent_changes_limit, true, input.include_ineligible, input.include_out_of_scope)
|
|
2842
|
+
: Promise.resolve(undefined);
|
|
2843
|
+
const [programApi, targetsApi, changes] = await Promise.all([programPromise, targetsPromise, changesPromise]);
|
|
2844
|
+
const candidate = toProgramCandidate(programApi.data, config.webBaseUrl);
|
|
2845
|
+
const targetsData = readObject(targetsApi.data);
|
|
2846
|
+
const rawTargets = readArray(targetsData?.targets);
|
|
2847
|
+
const targets = rawTargets
|
|
2848
|
+
.map(sanitizeTarget)
|
|
2849
|
+
.filter((target) => targetHasAllowedScope(target, {
|
|
2850
|
+
include_out_of_scope: input.include_out_of_scope,
|
|
2851
|
+
include_ineligible: input.include_ineligible,
|
|
2852
|
+
strict_scope_filter: false
|
|
2853
|
+
}));
|
|
2854
|
+
const scope = buildTargetScopeSummary(targets, input.target_list_mode, input.target_sample_size, input.include_out_of_scope, input.include_ineligible);
|
|
2855
|
+
return stripUndefined({
|
|
2856
|
+
request_id: randomUUID(),
|
|
2857
|
+
source_requests: [
|
|
2858
|
+
{
|
|
2859
|
+
source: "program",
|
|
2860
|
+
request_id: programApi.requestId,
|
|
2861
|
+
upstream_request_id: programApi.upstreamRequestId,
|
|
2862
|
+
...apiSourceMetadata(programApi)
|
|
2863
|
+
},
|
|
2864
|
+
{
|
|
2865
|
+
source: "program_targets",
|
|
2866
|
+
request_id: targetsApi.requestId,
|
|
2867
|
+
upstream_request_id: targetsApi.upstreamRequestId,
|
|
2868
|
+
...apiSourceMetadata(targetsApi)
|
|
2869
|
+
},
|
|
2870
|
+
...(changes?.sourceRequests ?? [])
|
|
2871
|
+
],
|
|
2872
|
+
program: formatProgram(candidate.program, "compact"),
|
|
2873
|
+
brief: {
|
|
2874
|
+
rank_factors: rankFactors(candidate),
|
|
2875
|
+
target_surface_score: targetSurfaceScore(candidate),
|
|
2876
|
+
hunt_value_score: huntValueScore(candidate),
|
|
2877
|
+
why_ranked_here: candidateWhy(candidate),
|
|
2878
|
+
tradeoffs: candidateTradeoffs(candidate),
|
|
2879
|
+
next_action: candidateNextAction(candidate)
|
|
2880
|
+
},
|
|
2881
|
+
target_samples: scope,
|
|
2882
|
+
recent_changes: changes?.changes.map((change) => formatChange(change, "compact")),
|
|
2883
|
+
meta: {
|
|
2884
|
+
total_active_targets: rawTargets.length,
|
|
2885
|
+
total_targets_after_filters: targets.length,
|
|
2886
|
+
recent_changes_returned: changes?.changes.length ?? 0,
|
|
2887
|
+
target_list_mode: input.target_list_mode
|
|
2888
|
+
}
|
|
2889
|
+
});
|
|
2890
|
+
}
|
|
2891
|
+
async function getProgramDelta(client, config, input) {
|
|
2892
|
+
const programApi = await client.getProgram(input.program_id);
|
|
2893
|
+
const program = addProgramResourceLinks(sanitizeProgram(programApi.data, config.webBaseUrl));
|
|
2894
|
+
const changes = await fetchProgramChanges(client, config, input.program_id, input.page_size, input.include_removed, input.include_ineligible, input.include_out_of_scope);
|
|
2895
|
+
return {
|
|
2896
|
+
request_id: randomUUID(),
|
|
2897
|
+
source_requests: [
|
|
2898
|
+
{
|
|
2899
|
+
source: "program",
|
|
2900
|
+
request_id: programApi.requestId,
|
|
2901
|
+
upstream_request_id: programApi.upstreamRequestId,
|
|
2902
|
+
...apiSourceMetadata(programApi)
|
|
2903
|
+
},
|
|
2904
|
+
...changes.sourceRequests
|
|
2905
|
+
],
|
|
2906
|
+
program,
|
|
2907
|
+
changes: changes.changes,
|
|
2908
|
+
meta: changes.meta
|
|
2909
|
+
};
|
|
2910
|
+
}
|
|
2911
|
+
async function fetchProgramChanges(client, config, programId, pageSize, includeRemoved, includeIneligible, includeOutOfScope) {
|
|
2912
|
+
const handle = programSearchText(programId);
|
|
2913
|
+
const api = await client.getRecentChanges({
|
|
2914
|
+
search: handle.length >= 2 ? handle : programId,
|
|
2915
|
+
include_removed: includeRemoved,
|
|
2916
|
+
include_ineligible: includeIneligible,
|
|
2917
|
+
include_out_of_scope: includeOutOfScope,
|
|
2918
|
+
page: 1,
|
|
2919
|
+
page_size: pageSize
|
|
2920
|
+
});
|
|
2921
|
+
const data = readObject(api.data);
|
|
2922
|
+
const changes = readArray(data?.results)
|
|
2923
|
+
.map((change) => sanitizeChange(change, config.webBaseUrl))
|
|
2924
|
+
.filter((change) => stringField(readObject(change.program), "id") === programId);
|
|
2925
|
+
return {
|
|
2926
|
+
changes,
|
|
2927
|
+
meta: sanitizeJson(data?.meta),
|
|
2928
|
+
sourceRequests: [
|
|
2929
|
+
{
|
|
2930
|
+
source: "recent_changes",
|
|
2931
|
+
request_id: api.requestId,
|
|
2932
|
+
upstream_request_id: api.upstreamRequestId,
|
|
2933
|
+
...apiSourceMetadata(api)
|
|
2934
|
+
}
|
|
2935
|
+
]
|
|
2936
|
+
};
|
|
2937
|
+
}
|
|
2938
|
+
function addProgramResourceLinks(program) {
|
|
2939
|
+
const id = stringField(program, "id");
|
|
2940
|
+
if (!id) {
|
|
2941
|
+
return program;
|
|
2942
|
+
}
|
|
2943
|
+
return {
|
|
2944
|
+
...program,
|
|
2945
|
+
resource_links: {
|
|
2946
|
+
program: programResourceUri(id),
|
|
2947
|
+
targets: programTargetsResourceUri(id)
|
|
2948
|
+
}
|
|
2949
|
+
};
|
|
2950
|
+
}
|
|
2951
|
+
function parseProgramIdFromResourceUri(uri) {
|
|
2952
|
+
return programIdSchema.parse(uri.searchParams.get("program_id") ?? "");
|
|
2953
|
+
}
|
|
2954
|
+
function programResourceUri(programId) {
|
|
2955
|
+
return `bbradar://program?program_id=${encodeURIComponent(programId)}`;
|
|
2956
|
+
}
|
|
2957
|
+
function programTargetsResourceUri(programId) {
|
|
2958
|
+
return `bbradar://program/targets?program_id=${encodeURIComponent(programId)}`;
|
|
2959
|
+
}
|
|
2960
|
+
function exportResourceUri(exportId) {
|
|
2961
|
+
return `bbradar://exports?export_id=${encodeURIComponent(exportId)}`;
|
|
2962
|
+
}
|
|
2963
|
+
function jsonResource(uri, payload) {
|
|
2964
|
+
return {
|
|
2965
|
+
contents: [
|
|
2966
|
+
{
|
|
2967
|
+
uri,
|
|
2968
|
+
mimeType: "application/json",
|
|
2969
|
+
text: JSON.stringify(payload)
|
|
2970
|
+
}
|
|
2971
|
+
]
|
|
2972
|
+
};
|
|
2973
|
+
}
|
|
2974
|
+
function rememberExport(exportStore, exportId, payload) {
|
|
2975
|
+
if (exportStore.has(exportId)) {
|
|
2976
|
+
exportStore.delete(exportId);
|
|
2977
|
+
}
|
|
2978
|
+
while (exportStore.size >= MAX_LOCAL_EXPORT_RESOURCES) {
|
|
2979
|
+
const oldestKey = exportStore.keys().next().value;
|
|
2980
|
+
if (oldestKey === undefined) {
|
|
2981
|
+
break;
|
|
2982
|
+
}
|
|
2983
|
+
exportStore.delete(oldestKey);
|
|
2984
|
+
}
|
|
2985
|
+
exportStore.set(exportId, payload);
|
|
2986
|
+
}
|
|
2987
|
+
function previewExportPayload(payload) {
|
|
2988
|
+
if (Array.isArray(payload)) {
|
|
2989
|
+
return payload.slice(0, EXPORT_PREVIEW_LIMIT);
|
|
2990
|
+
}
|
|
2991
|
+
if (readObject(payload) && Array.isArray(payload.targets)) {
|
|
2992
|
+
return {
|
|
2993
|
+
...payload,
|
|
2994
|
+
targets: payload.targets.slice(0, EXPORT_PREVIEW_LIMIT)
|
|
2995
|
+
};
|
|
2996
|
+
}
|
|
2997
|
+
return payload;
|
|
2998
|
+
}
|
|
2999
|
+
function previewExportCount(payload) {
|
|
3000
|
+
if (Array.isArray(payload)) {
|
|
3001
|
+
return Math.min(payload.length, EXPORT_PREVIEW_LIMIT);
|
|
3002
|
+
}
|
|
3003
|
+
const object = readObject(payload);
|
|
3004
|
+
const targets = object?.targets;
|
|
3005
|
+
if (Array.isArray(targets)) {
|
|
3006
|
+
return Math.min(targets.length, EXPORT_PREVIEW_LIMIT);
|
|
3007
|
+
}
|
|
3008
|
+
return payload === undefined || payload === null ? 0 : 1;
|
|
3009
|
+
}
|
|
3010
|
+
function collectResourceLinks(payload) {
|
|
3011
|
+
const links = new Map();
|
|
3012
|
+
for (const program of extractProgramsFromPayload(payload)) {
|
|
3013
|
+
const id = stringField(program, "id");
|
|
3014
|
+
const resourceLinks = readObject(program.resource_links);
|
|
3015
|
+
const programUri = stringField(resourceLinks, "program");
|
|
3016
|
+
const targetsUri = stringField(resourceLinks, "targets");
|
|
3017
|
+
if (id && programUri) {
|
|
3018
|
+
links.set(programUri, {
|
|
3019
|
+
type: "resource_link",
|
|
3020
|
+
uri: programUri,
|
|
3021
|
+
name: `${id} program`,
|
|
3022
|
+
title: `${stringField(program, "name") ?? id} program`,
|
|
3023
|
+
description: "Read sanitized BBRadar program details.",
|
|
3024
|
+
mimeType: "application/json"
|
|
3025
|
+
});
|
|
3026
|
+
}
|
|
3027
|
+
if (id && targetsUri) {
|
|
3028
|
+
links.set(targetsUri, {
|
|
3029
|
+
type: "resource_link",
|
|
3030
|
+
uri: targetsUri,
|
|
3031
|
+
name: `${id} targets`,
|
|
3032
|
+
title: `${stringField(program, "name") ?? id} targets`,
|
|
3033
|
+
description: "Read sanitized BBRadar program targets.",
|
|
3034
|
+
mimeType: "application/json"
|
|
3035
|
+
});
|
|
3036
|
+
}
|
|
3037
|
+
}
|
|
3038
|
+
const exportObject = readObject(payload.export);
|
|
3039
|
+
const exportUri = stringField(exportObject, "resource_uri");
|
|
3040
|
+
const exportId = stringField(exportObject, "export_id");
|
|
3041
|
+
if (exportUri && exportId) {
|
|
3042
|
+
links.set(exportUri, {
|
|
3043
|
+
type: "resource_link",
|
|
3044
|
+
uri: exportUri,
|
|
3045
|
+
name: `${exportId} export`,
|
|
3046
|
+
title: "Target export",
|
|
3047
|
+
description: "Read the full local target export payload.",
|
|
3048
|
+
mimeType: "application/json"
|
|
3049
|
+
});
|
|
3050
|
+
}
|
|
3051
|
+
return [...links.values()];
|
|
3052
|
+
}
|
|
3053
|
+
function extractProgramsFromPayload(payload) {
|
|
3054
|
+
const programs = [];
|
|
3055
|
+
const program = readObject(payload.program);
|
|
3056
|
+
if (program) {
|
|
3057
|
+
programs.push(program);
|
|
3058
|
+
}
|
|
3059
|
+
for (const entry of readArray(payload.programs)) {
|
|
3060
|
+
const object = readObject(entry);
|
|
3061
|
+
if (object) {
|
|
3062
|
+
programs.push(object);
|
|
3063
|
+
}
|
|
3064
|
+
}
|
|
3065
|
+
for (const entry of readArray(payload.compared_programs)) {
|
|
3066
|
+
const object = readObject(entry);
|
|
3067
|
+
if (object) {
|
|
3068
|
+
programs.push(object);
|
|
3069
|
+
}
|
|
3070
|
+
}
|
|
3071
|
+
return programs;
|
|
3072
|
+
}
|
|
3073
|
+
function readFacetValues(value) {
|
|
3074
|
+
return readArray(value)
|
|
3075
|
+
.map((entry) => readString(readObject(entry)?.value) ?? readString(entry))
|
|
3076
|
+
.filter((entry) => entry !== undefined);
|
|
3077
|
+
}
|
|
3078
|
+
function addSetValue(values, value) {
|
|
3079
|
+
if (value) {
|
|
3080
|
+
values.add(value);
|
|
3081
|
+
}
|
|
3082
|
+
}
|
|
3083
|
+
function addSetValues(values, entries) {
|
|
3084
|
+
for (const entry of entries) {
|
|
3085
|
+
addSetValue(values, entry);
|
|
3086
|
+
}
|
|
3087
|
+
}
|
|
3088
|
+
function sortedValues(values) {
|
|
3089
|
+
return [...values].sort((left, right) => left.localeCompare(right));
|
|
3090
|
+
}
|
|
3091
|
+
function formatProgram(program, mode) {
|
|
3092
|
+
return mode === "full" ? program : compactProgram(program);
|
|
3093
|
+
}
|
|
3094
|
+
function compactProgram(program) {
|
|
3095
|
+
return stripUndefined({
|
|
3096
|
+
id: stringField(program, "id"),
|
|
3097
|
+
handle: stringField(program, "handle"),
|
|
3098
|
+
name: stringField(program, "name"),
|
|
3099
|
+
platform: stringField(program, "platform"),
|
|
3100
|
+
platform_url: stringField(program, "platform_url"),
|
|
3101
|
+
bbradar_url: stringField(program, "bbradar_url"),
|
|
3102
|
+
opportunity_tag: readObject(program.opportunity_tag),
|
|
3103
|
+
reward_range: readObject(program.reward_range),
|
|
3104
|
+
public_report_count: readNumber(program.public_report_count),
|
|
3105
|
+
public_report_count_label: stringField(program, "public_report_count_label"),
|
|
3106
|
+
first_seen_date: stringField(program, "first_seen_date"),
|
|
3107
|
+
last_updated: stringField(program, "last_updated"),
|
|
3108
|
+
targets_updated_at: stringField(program, "targets_updated_at"),
|
|
3109
|
+
scope_summary: readObject(program.scope_summary),
|
|
3110
|
+
resource_links: readObject(program.resource_links)
|
|
3111
|
+
});
|
|
3112
|
+
}
|
|
3113
|
+
function candidateOutput(candidate, index, targetSamples, mode) {
|
|
3114
|
+
const id = stringField(candidate.program, "id");
|
|
3115
|
+
const samples = id ? targetSamples[id] : undefined;
|
|
3116
|
+
const formattedSamples = samples ? formatTargetList(samples, mode === "full" ? "full" : "identifiers") : undefined;
|
|
3117
|
+
if (mode === "full") {
|
|
3118
|
+
return stripUndefined({
|
|
3119
|
+
rank: index + 1,
|
|
3120
|
+
...candidate.program,
|
|
3121
|
+
rank_factors: rankFactors(candidate),
|
|
3122
|
+
target_surface_score: targetSurfaceScore(candidate),
|
|
3123
|
+
why_ranked_here: candidateWhy(candidate),
|
|
3124
|
+
tradeoffs: candidateTradeoffs(candidate),
|
|
3125
|
+
next_action: candidateNextAction(candidate),
|
|
3126
|
+
target_samples: formattedSamples
|
|
3127
|
+
});
|
|
3128
|
+
}
|
|
3129
|
+
return stripUndefined({
|
|
3130
|
+
rank: index + 1,
|
|
3131
|
+
...compactProgram(candidate.program),
|
|
3132
|
+
rank_factors: rankFactors(candidate),
|
|
3133
|
+
why_ranked_here: candidateWhy(candidate),
|
|
3134
|
+
tradeoffs: candidateTradeoffs(candidate),
|
|
3135
|
+
next_action: candidateNextAction(candidate),
|
|
3136
|
+
target_samples: formattedSamples
|
|
3137
|
+
});
|
|
3138
|
+
}
|
|
3139
|
+
function formatChange(change, mode) {
|
|
3140
|
+
if (mode === "full") {
|
|
3141
|
+
return change;
|
|
3142
|
+
}
|
|
3143
|
+
const target = readObject(change.target);
|
|
3144
|
+
return stripUndefined({
|
|
3145
|
+
id: change.id,
|
|
3146
|
+
change_type: stringField(change, "change_type"),
|
|
3147
|
+
changed_at: stringField(change, "changed_at"),
|
|
3148
|
+
program: formatProgram(readObject(change.program) ?? {}, "compact"),
|
|
3149
|
+
target: target ? compactTarget(target) : undefined
|
|
3150
|
+
});
|
|
3151
|
+
}
|
|
3152
|
+
function buildTargetScopeSummary(targets, mode, groupLimit, includeOutOfScope, includeIneligible) {
|
|
3153
|
+
const bountyTargets = targets.filter((target) => target.in_scope === true && target.eligible_for_bounty === true);
|
|
3154
|
+
const groups = {
|
|
3155
|
+
in_scope_bounty_domains: bountyTargets.filter(targetIsDomain),
|
|
3156
|
+
wildcards: bountyTargets.filter(targetIsWildcard),
|
|
3157
|
+
apis: bountyTargets.filter(targetIsApi),
|
|
3158
|
+
mobile: bountyTargets.filter(targetIsMobile),
|
|
3159
|
+
source_code: bountyTargets.filter(targetIsSourceCode)
|
|
3160
|
+
};
|
|
3161
|
+
const classified = new Set(Object.values(groups).flat());
|
|
3162
|
+
groups.other_in_scope_bounty = bountyTargets.filter((target) => !classified.has(target));
|
|
3163
|
+
if (includeOutOfScope) {
|
|
3164
|
+
groups.out_of_scope = targets.filter((target) => target.in_scope === false);
|
|
3165
|
+
}
|
|
3166
|
+
if (includeIneligible) {
|
|
3167
|
+
groups.ineligible = targets.filter((target) => target.eligible_for_bounty === false);
|
|
3168
|
+
}
|
|
3169
|
+
const groupCounts = Object.fromEntries(Object.entries(groups).map(([name, entries]) => [name, entries.length]));
|
|
3170
|
+
const formattedGroups = Object.fromEntries(Object.entries(groups).map(([name, entries]) => [name, formatTargetList(entries.slice(0, groupLimit), mode)]));
|
|
3171
|
+
const groupHasMore = Object.fromEntries(Object.entries(groups).map(([name, entries]) => [name, entries.length > groupLimit]));
|
|
3172
|
+
return {
|
|
3173
|
+
target_counts: {
|
|
3174
|
+
total: targets.length,
|
|
3175
|
+
in_scope: targets.filter((target) => target.in_scope === true).length,
|
|
3176
|
+
out_of_scope: targets.filter((target) => target.in_scope === false).length,
|
|
3177
|
+
eligible_for_bounty: targets.filter((target) => target.eligible_for_bounty === true).length,
|
|
3178
|
+
in_scope_bounty: bountyTargets.length,
|
|
3179
|
+
wildcard: targets.filter(targetIsWildcard).length,
|
|
3180
|
+
api: targets.filter(targetIsApi).length,
|
|
3181
|
+
mobile: targets.filter(targetIsMobile).length,
|
|
3182
|
+
source_code: targets.filter(targetIsSourceCode).length
|
|
3183
|
+
},
|
|
3184
|
+
group_counts: groupCounts,
|
|
3185
|
+
group_has_more: groupHasMore,
|
|
3186
|
+
groups: formattedGroups
|
|
3187
|
+
};
|
|
3188
|
+
}
|
|
3189
|
+
function countTargetValues(targets, readValues) {
|
|
3190
|
+
const counts = new Map();
|
|
3191
|
+
for (const target of targets) {
|
|
3192
|
+
const rawValues = readValues(target);
|
|
3193
|
+
const values = Array.isArray(rawValues) ? rawValues : rawValues === undefined ? [] : [rawValues];
|
|
3194
|
+
for (const value of values) {
|
|
3195
|
+
const normalized = value.trim();
|
|
3196
|
+
if (normalized.length > 0) {
|
|
3197
|
+
counts.set(normalized, (counts.get(normalized) ?? 0) + 1);
|
|
3198
|
+
}
|
|
3199
|
+
}
|
|
3200
|
+
}
|
|
3201
|
+
return Object.fromEntries([...counts.entries()].sort((left, right) => right[1] - left[1] || left[0].localeCompare(right[0])));
|
|
3202
|
+
}
|
|
3203
|
+
function buildScopeDelta(changes, targetListMode) {
|
|
3204
|
+
const byChangeType = new Map();
|
|
3205
|
+
let latestTimestamp = 0;
|
|
3206
|
+
let latestChangedAt;
|
|
3207
|
+
for (const change of changes) {
|
|
3208
|
+
const changeType = stringField(change, "change_type") ?? "unknown";
|
|
3209
|
+
const target = readObject(change.target);
|
|
3210
|
+
const timestamp = timestampField(change, "changed_at");
|
|
3211
|
+
const existing = byChangeType.get(changeType) ??
|
|
3212
|
+
{
|
|
3213
|
+
changes: [],
|
|
3214
|
+
targets: [],
|
|
3215
|
+
latestTimestamp: 0,
|
|
3216
|
+
latestChangedAt: undefined
|
|
3217
|
+
};
|
|
3218
|
+
existing.changes.push(change);
|
|
3219
|
+
if (target) {
|
|
3220
|
+
existing.targets.push(target);
|
|
3221
|
+
}
|
|
3222
|
+
if (timestamp >= existing.latestTimestamp) {
|
|
3223
|
+
existing.latestTimestamp = timestamp;
|
|
3224
|
+
existing.latestChangedAt = stringField(change, "changed_at");
|
|
3225
|
+
}
|
|
3226
|
+
if (timestamp >= latestTimestamp) {
|
|
3227
|
+
latestTimestamp = timestamp;
|
|
3228
|
+
latestChangedAt = stringField(change, "changed_at");
|
|
3229
|
+
}
|
|
3230
|
+
byChangeType.set(changeType, existing);
|
|
3231
|
+
}
|
|
3232
|
+
return stripUndefined({
|
|
3233
|
+
total_changes: changes.length,
|
|
3234
|
+
latest_changed_at: latestChangedAt,
|
|
3235
|
+
change_counts: Object.fromEntries([...byChangeType.entries()].map(([changeType, entry]) => [changeType, entry.changes.length])),
|
|
3236
|
+
targets_by_change_type: Object.fromEntries([...byChangeType.entries()].map(([changeType, entry]) => [changeType, formatTargetList(entry.targets, targetListMode)])),
|
|
3237
|
+
latest_by_change_type: Object.fromEntries([...byChangeType.entries()].map(([changeType, entry]) => [changeType, entry.latestChangedAt]))
|
|
3238
|
+
});
|
|
3239
|
+
}
|
|
3240
|
+
function rankFactors(candidate) {
|
|
3241
|
+
return stripUndefined({
|
|
3242
|
+
public_report_count: candidate.publicReportCount,
|
|
3243
|
+
public_report_count_label: stringField(candidate.program, "public_report_count_label"),
|
|
3244
|
+
bounty_min: candidate.bountyMin,
|
|
3245
|
+
bounty_max: candidate.bountyMax,
|
|
3246
|
+
total_targets: candidate.totalTargetCount,
|
|
3247
|
+
in_scope_targets: candidate.inScopeTargetCount,
|
|
3248
|
+
eligible_targets: candidate.eligibleTargetCount,
|
|
3249
|
+
wildcard_targets: candidate.wildcardCount,
|
|
3250
|
+
added_24h: candidate.added24h,
|
|
3251
|
+
added_7d: candidate.added7d,
|
|
3252
|
+
opportunity_tier: candidate.opportunityTier,
|
|
3253
|
+
opportunity_score: candidate.opportunityScore,
|
|
3254
|
+
target_surface_score: targetSurfaceScore(candidate),
|
|
3255
|
+
hunt_value_score: huntValueScore(candidate)
|
|
3256
|
+
});
|
|
3257
|
+
}
|
|
3258
|
+
function candidateWhy(candidate) {
|
|
3259
|
+
const reasons = [];
|
|
3260
|
+
if (candidate.publicReportCount !== undefined) {
|
|
3261
|
+
const label = stringField(candidate.program, "public_report_count_label");
|
|
3262
|
+
reasons.push(label ? `${candidate.publicReportCount} ${label}` : `${candidate.publicReportCount} public reports`);
|
|
3263
|
+
}
|
|
3264
|
+
if (candidate.bountyMax !== undefined) {
|
|
3265
|
+
reasons.push(`max bounty ${candidate.bountyMax}`);
|
|
3266
|
+
}
|
|
3267
|
+
if (candidate.eligibleTargetCount !== undefined) {
|
|
3268
|
+
reasons.push(`${candidate.eligibleTargetCount} eligible targets`);
|
|
3269
|
+
}
|
|
3270
|
+
if (candidate.wildcardCount > 0) {
|
|
3271
|
+
reasons.push(`${candidate.wildcardCount} wildcard targets`);
|
|
3272
|
+
}
|
|
3273
|
+
if (candidateHasWeb3Surface(candidate)) {
|
|
3274
|
+
reasons.push("Web3/blockchain scope signals");
|
|
3275
|
+
}
|
|
3276
|
+
if (candidate.added7d !== undefined && candidate.added7d > 0) {
|
|
3277
|
+
reasons.push(`${candidate.added7d} targets added in 7d`);
|
|
3278
|
+
}
|
|
3279
|
+
if (candidate.opportunityTier) {
|
|
3280
|
+
reasons.push(`${candidate.opportunityTier} opportunity tier`);
|
|
3281
|
+
}
|
|
3282
|
+
return reasons;
|
|
3283
|
+
}
|
|
3284
|
+
function candidateTradeoffs(candidate) {
|
|
3285
|
+
const tradeoffs = [];
|
|
3286
|
+
if (candidate.publicReportCount === undefined) {
|
|
3287
|
+
tradeoffs.push("public report count is unknown");
|
|
3288
|
+
}
|
|
3289
|
+
if ((candidate.bountyMax ?? candidate.bountyMin ?? 0) <= 0) {
|
|
3290
|
+
tradeoffs.push("no bounty range exposed");
|
|
3291
|
+
}
|
|
3292
|
+
else if ((candidate.bountyMax ?? 0) < 1_000) {
|
|
3293
|
+
tradeoffs.push("lower reward ceiling");
|
|
3294
|
+
}
|
|
3295
|
+
if (candidate.wildcardCount === 0) {
|
|
3296
|
+
tradeoffs.push("no wildcard targets in BBRadar counts");
|
|
3297
|
+
}
|
|
3298
|
+
else if (candidate.wildcardCount === 1) {
|
|
3299
|
+
tradeoffs.push("only one wildcard target in BBRadar counts");
|
|
3300
|
+
}
|
|
3301
|
+
if ((candidate.eligibleTargetCount ?? 0) <= 0) {
|
|
3302
|
+
tradeoffs.push("no bounty-eligible target count exposed");
|
|
3303
|
+
}
|
|
3304
|
+
if (candidateLooksLikeCtf(candidate)) {
|
|
3305
|
+
tradeoffs.push("program appears CTF-like");
|
|
3306
|
+
}
|
|
3307
|
+
if (candidateLooksMobileOnly(candidate)) {
|
|
3308
|
+
tradeoffs.push("appears mobile-only from available tags");
|
|
3309
|
+
}
|
|
3310
|
+
return tradeoffs;
|
|
3311
|
+
}
|
|
3312
|
+
function candidateNextAction(candidate) {
|
|
3313
|
+
if (candidate.wildcardCount > 0) {
|
|
3314
|
+
return "Review in-scope wildcard targets with get_program_targets and keep follow-up analysis passive and BBRadar-data-backed.";
|
|
3315
|
+
}
|
|
3316
|
+
if (candidateHasApiSurface(candidate)) {
|
|
3317
|
+
return "Review API targets with get_program_targets and summarize the allowed API scope from BBRadar data.";
|
|
3318
|
+
}
|
|
3319
|
+
if (candidateHasWebSurface(candidate)) {
|
|
3320
|
+
return "Review web/domain targets with get_program_targets and summarize freshness, reward, and report-count signals.";
|
|
3321
|
+
}
|
|
3322
|
+
return "Fetch exact scope with get_program_targets before making recommendations.";
|
|
3323
|
+
}
|
|
3324
|
+
function targetSurfaceScore(candidate) {
|
|
3325
|
+
return candidate.targetSurfaceScore;
|
|
3326
|
+
}
|
|
3327
|
+
function calculateTargetSurfaceScore(candidate) {
|
|
3328
|
+
let score = 0;
|
|
3329
|
+
score += Math.min(candidate.wildcardCount, 10) * 10;
|
|
3330
|
+
score += Math.min(candidate.eligibleTargetCount ?? 0, 25) * 2;
|
|
3331
|
+
if (candidateHasApiSurface(candidate)) {
|
|
3332
|
+
score += 20;
|
|
3333
|
+
}
|
|
3334
|
+
if (candidateHasWebSurface(candidate)) {
|
|
3335
|
+
score += 15;
|
|
3336
|
+
}
|
|
3337
|
+
if (candidateHasWeb3Surface(candidate)) {
|
|
3338
|
+
score += 15;
|
|
3339
|
+
}
|
|
3340
|
+
if (candidateHasMobileSurface(candidate)) {
|
|
3341
|
+
score += 8;
|
|
3342
|
+
}
|
|
3343
|
+
return score;
|
|
3344
|
+
}
|
|
3345
|
+
function huntValueScore(candidate) {
|
|
3346
|
+
return candidate.huntValueScore;
|
|
3347
|
+
}
|
|
3348
|
+
function calculateHuntValueScore(candidate) {
|
|
3349
|
+
const reports = candidate.publicReportCount;
|
|
3350
|
+
const competitionScore = reports === undefined ? 20 : Math.max(0, 100 - Math.min(reports, 100));
|
|
3351
|
+
const rewardScore = Math.min(candidate.bountyMax ?? candidate.bountyMin ?? 0, 50_000) / 500;
|
|
3352
|
+
const freshnessScore = freshnessScoreFor(candidate);
|
|
3353
|
+
const opportunityScore = candidate.opportunityScore ?? 0;
|
|
3354
|
+
const surfaceScore = Math.min(targetSurfaceScore(candidate), 100);
|
|
3355
|
+
const platformScore = platformWeight(candidate);
|
|
3356
|
+
const ctfPenalty = candidateLooksLikeCtf(candidate) ? 50 : 0;
|
|
3357
|
+
return Math.round((competitionScore * 0.3 + rewardScore * 0.2 + surfaceScore * 0.2 + freshnessScore * 0.15 + opportunityScore * 0.1 + platformScore * 0.05 - ctfPenalty) * 100) / 100;
|
|
3358
|
+
}
|
|
3359
|
+
function freshnessScoreFor(candidate) {
|
|
3360
|
+
const timestamps = [candidate.firstSeenTime, candidate.lastUpdatedTime, candidate.latestChangeTime].filter((value) => value > 0);
|
|
3361
|
+
if (timestamps.length === 0) {
|
|
3362
|
+
return 0;
|
|
3363
|
+
}
|
|
3364
|
+
const newest = Math.max(...timestamps);
|
|
3365
|
+
const ageDays = Math.max(0, (Date.now() - newest) / (24 * 60 * 60 * 1000));
|
|
3366
|
+
return Math.max(0, 100 - ageDays);
|
|
3367
|
+
}
|
|
3368
|
+
function platformWeight(candidate) {
|
|
3369
|
+
const platform = normalizeTag(stringField(candidate.program, "platform") ?? "");
|
|
3370
|
+
const weights = {
|
|
3371
|
+
hackerone: 100,
|
|
3372
|
+
bugcrowd: 95,
|
|
3373
|
+
intigriti: 90,
|
|
3374
|
+
yeswehack: 85,
|
|
3375
|
+
hackenproof: 80,
|
|
3376
|
+
issuehunt: 70,
|
|
3377
|
+
bugrap: 60
|
|
3378
|
+
};
|
|
3379
|
+
return weights[platform] ?? 50;
|
|
3380
|
+
}
|
|
3381
|
+
function candidateHasApiSurface(candidate) {
|
|
3382
|
+
return candidate.hasApiSurface;
|
|
3383
|
+
}
|
|
3384
|
+
function candidateHasWebSurface(candidate) {
|
|
3385
|
+
return candidate.hasWebSurface;
|
|
3386
|
+
}
|
|
3387
|
+
function candidateHasWeb3Surface(candidate) {
|
|
3388
|
+
return candidate.hasWeb3Surface;
|
|
3389
|
+
}
|
|
3390
|
+
function candidateHasMobileSurface(candidate) {
|
|
3391
|
+
return candidate.hasMobileSurface;
|
|
3392
|
+
}
|
|
3393
|
+
function candidateLooksMobileOnly(candidate) {
|
|
3394
|
+
return candidate.looksMobileOnly;
|
|
3395
|
+
}
|
|
3396
|
+
function candidateLooksLikeCtf(candidate) {
|
|
3397
|
+
return candidate.looksLikeCtf;
|
|
3398
|
+
}
|
|
3399
|
+
function candidateLooksContestLike(candidate) {
|
|
3400
|
+
return tagIncludesAnyNormalized(candidate.normalizedSearchSignals, WEB3_CONTEST_SIGNALS);
|
|
3401
|
+
}
|
|
3402
|
+
function candidateHasKnownNoBounty(candidate) {
|
|
3403
|
+
if (candidate.bountyMin === undefined && candidate.bountyMax === undefined) {
|
|
3404
|
+
return false;
|
|
3405
|
+
}
|
|
3406
|
+
return (candidate.bountyMin ?? 0) <= 0 && (candidate.bountyMax ?? 0) <= 0;
|
|
3407
|
+
}
|
|
3408
|
+
function candidateLooksLikeVdp(candidate) {
|
|
3409
|
+
return candidateHasKnownNoBounty(candidate) || tagIncludesAnyNormalized(candidate.normalizedSearchSignals, VDP_SIGNALS);
|
|
3410
|
+
}
|
|
3411
|
+
function programResolutionMatch(candidate, query) {
|
|
3412
|
+
const normalizedQuery = normalizeTag(query);
|
|
3413
|
+
const queryTokens = tokenSet(normalizedQuery);
|
|
3414
|
+
const fields = [
|
|
3415
|
+
{ name: "id", value: stringField(candidate.program, "id") },
|
|
3416
|
+
{ name: "handle", value: stringField(candidate.program, "handle") },
|
|
3417
|
+
{ name: "name", value: stringField(candidate.program, "name") },
|
|
3418
|
+
{ name: "platform", value: stringField(candidate.program, "platform") },
|
|
3419
|
+
{ name: "platform_url", value: stringField(candidate.program, "platform_url") },
|
|
3420
|
+
{ name: "bbradar_url", value: stringField(candidate.program, "bbradar_url") }
|
|
3421
|
+
];
|
|
3422
|
+
let score = 0;
|
|
3423
|
+
const matchedFields = new Set();
|
|
3424
|
+
const reasons = new Set();
|
|
3425
|
+
for (const field of fields) {
|
|
3426
|
+
if (!field.value) {
|
|
3427
|
+
continue;
|
|
3428
|
+
}
|
|
3429
|
+
const normalizedValue = normalizeTag(field.value);
|
|
3430
|
+
const fieldTokens = tokenSet(normalizedValue);
|
|
3431
|
+
if (normalizedValue === normalizedQuery) {
|
|
3432
|
+
score = Math.max(score, field.name === "id" || field.name === "handle" ? 100 : 95);
|
|
3433
|
+
matchedFields.add(field.name);
|
|
3434
|
+
reasons.add(`${field.name} exact match`);
|
|
3435
|
+
continue;
|
|
3436
|
+
}
|
|
3437
|
+
if (normalizedValue.includes(normalizedQuery)) {
|
|
3438
|
+
score = Math.max(score, field.name === "name" || field.name === "handle" ? 80 : 65);
|
|
3439
|
+
matchedFields.add(field.name);
|
|
3440
|
+
reasons.add(`${field.name} contains query`);
|
|
3441
|
+
}
|
|
3442
|
+
const tokenOverlap = [...queryTokens].filter((token) => fieldTokens.has(token)).length;
|
|
3443
|
+
if (tokenOverlap > 0) {
|
|
3444
|
+
const tokenScore = Math.round((tokenOverlap / Math.max(queryTokens.size, 1)) * 70);
|
|
3445
|
+
score = Math.max(score, tokenScore);
|
|
3446
|
+
matchedFields.add(field.name);
|
|
3447
|
+
reasons.add(`${field.name} token match`);
|
|
3448
|
+
}
|
|
3449
|
+
}
|
|
3450
|
+
if (candidate.normalizedSearchSignals.some((signal) => signal.includes(normalizedQuery))) {
|
|
3451
|
+
score = Math.max(score, 55);
|
|
3452
|
+
matchedFields.add("search_signals");
|
|
3453
|
+
reasons.add("program search signals contain query");
|
|
3454
|
+
}
|
|
3455
|
+
return {
|
|
3456
|
+
score,
|
|
3457
|
+
fields: [...matchedFields],
|
|
3458
|
+
reasons: [...reasons]
|
|
3459
|
+
};
|
|
3460
|
+
}
|
|
3461
|
+
function normalizedCandidateValues(program, normalizedScopeTags) {
|
|
3462
|
+
return [
|
|
3463
|
+
stringField(program, "id"),
|
|
3464
|
+
stringField(program, "handle"),
|
|
3465
|
+
stringField(program, "name"),
|
|
3466
|
+
stringField(program, "platform"),
|
|
3467
|
+
stringField(program, "platform_url"),
|
|
3468
|
+
stringField(program, "bbradar_url")
|
|
3469
|
+
]
|
|
3470
|
+
.filter((value) => value !== undefined)
|
|
3471
|
+
.map(normalizeTag)
|
|
3472
|
+
.concat(normalizedScopeTags);
|
|
3473
|
+
}
|
|
3474
|
+
function tagIncludesAnyNormalized(normalizedTags, needles) {
|
|
3475
|
+
return needles.some((needle) => normalizedTags.some((tag) => tag === needle || tag.includes(needle)));
|
|
3476
|
+
}
|
|
3477
|
+
function candidateMatchesAnyTag(candidate, values) {
|
|
3478
|
+
const normalizedValues = values.map(normalizeTag);
|
|
3479
|
+
return normalizedValues.some((value) => candidate.normalizedScopeTags.some((tag) => tag === value || tag.includes(value) || value.includes(tag)) ||
|
|
3480
|
+
candidate.normalizedSearchSignals.some((signal) => signal === value || signal.includes(value)));
|
|
3481
|
+
}
|
|
3482
|
+
function programFetchError(programId, error) {
|
|
3483
|
+
if (error instanceof BBRadarApiError) {
|
|
3484
|
+
return stripUndefined({
|
|
3485
|
+
program_id: programId,
|
|
3486
|
+
request_id: error.requestId,
|
|
3487
|
+
upstream_request_id: error.upstreamRequestId,
|
|
3488
|
+
status: error.status,
|
|
3489
|
+
message: error.message,
|
|
3490
|
+
detail: sanitizeJson(error.detail),
|
|
3491
|
+
errors: sanitizeJson(error.errors),
|
|
3492
|
+
suggested_fix: "Check that the program_id uses BBRadar platform:handle format. Spaces and slashes are supported and are encoded by the MCP server."
|
|
3493
|
+
});
|
|
3494
|
+
}
|
|
3495
|
+
return {
|
|
3496
|
+
program_id: programId,
|
|
3497
|
+
message: error instanceof Error ? error.message : String(error),
|
|
3498
|
+
suggested_fix: "Retry the program lookup or verify the BBRadar program id."
|
|
3499
|
+
};
|
|
3500
|
+
}
|
|
3501
|
+
function programSearchText(programId) {
|
|
3502
|
+
const colonIndex = programId.indexOf(":");
|
|
3503
|
+
const handle = colonIndex >= 0 ? programId.slice(colonIndex + 1) : programId;
|
|
3504
|
+
const lastPathSegment = handle.split("/").filter(Boolean).at(-1) ?? handle;
|
|
3505
|
+
return lastPathSegment.length >= 2 ? lastPathSegment : handle;
|
|
3506
|
+
}
|
|
3507
|
+
function normalizeFindProgramsInput(input) {
|
|
3508
|
+
const warnings = [];
|
|
3509
|
+
const normalized = { ...input };
|
|
3510
|
+
normalized.max_pages = clampLimit("max_pages", normalized.max_pages, MAX_FIND_PROGRAM_PAGES, warnings);
|
|
3511
|
+
normalized.max_results = clampLimit("max_results", normalized.max_results, MAX_FIND_PROGRAM_RESULTS, warnings);
|
|
3512
|
+
normalized.target_sample_programs = clampLimit("target_sample_programs", normalized.target_sample_programs, MAX_FIND_TARGET_SAMPLE_PROGRAMS, warnings);
|
|
3513
|
+
normalized.target_sample_size = clampLimit("target_sample_size", normalized.target_sample_size, MAX_FIND_TARGET_SAMPLES, warnings);
|
|
3514
|
+
normalized.upstream_request_budget = clampLimit("upstream_request_budget", normalized.upstream_request_budget, MAX_FIND_UPSTREAM_REQUEST_BUDGET, warnings);
|
|
3515
|
+
if (normalized.target_samples_only_wildcards) {
|
|
3516
|
+
normalized.target_sample_programs = clampLimit("target_sample_programs", normalized.target_sample_programs, MAX_WILDCARD_TARGET_SAMPLE_PROGRAMS, warnings);
|
|
3517
|
+
normalized.target_sample_size = clampLimit("target_sample_size", normalized.target_sample_size, MAX_WILDCARD_TARGET_SAMPLES, warnings);
|
|
3518
|
+
}
|
|
3519
|
+
return { input: normalized, warnings };
|
|
3520
|
+
}
|
|
3521
|
+
function clampLimit(name, value, max, warnings) {
|
|
3522
|
+
if (value <= max) {
|
|
3523
|
+
return value;
|
|
3524
|
+
}
|
|
3525
|
+
warnings.push(`${name} was clamped from ${value} to ${max}.`);
|
|
3526
|
+
return max;
|
|
3527
|
+
}
|
|
3528
|
+
async function findPrograms(client, config, input) {
|
|
3529
|
+
const normalized = normalizeFindProgramsInput(input);
|
|
3530
|
+
const warnings = normalized.warnings;
|
|
3531
|
+
input = normalized.input;
|
|
3532
|
+
const budget = {
|
|
3533
|
+
initial: input.upstream_request_budget,
|
|
3534
|
+
remaining: input.upstream_request_budget
|
|
3535
|
+
};
|
|
3536
|
+
const upstreamTags = uniqueStrings([...input.tags, ...input.scope_tags, ...input.target_types, ...input.language_tags]);
|
|
3537
|
+
const collected = await collectPrograms(client, {
|
|
3538
|
+
platforms: input.platforms,
|
|
3539
|
+
tags: upstreamTags,
|
|
3540
|
+
updated_since: input.updated_since,
|
|
3541
|
+
opportunity_levels: input.opportunity_levels,
|
|
3542
|
+
max_pages: input.max_pages,
|
|
3543
|
+
budget,
|
|
3544
|
+
warnings
|
|
3545
|
+
});
|
|
3546
|
+
const filteredCandidates = collected.programs
|
|
3547
|
+
.map((program) => toProgramCandidate(program, config.webBaseUrl))
|
|
3548
|
+
.filter((program) => programCandidateMatches(program, input))
|
|
3549
|
+
.sort((left, right) => compareProgramCandidates(left, right, input.sort_by));
|
|
3550
|
+
const candidates = filteredCandidates.slice(0, input.max_results);
|
|
3551
|
+
const targetSamplePrograms = Math.min(input.target_sample_programs, candidates.length, Math.max(0, budget.remaining));
|
|
3552
|
+
if (input.include_target_samples && input.target_sample_programs > targetSamplePrograms) {
|
|
3553
|
+
warnings.push(`target_sample_programs was reduced from ${input.target_sample_programs} to ${targetSamplePrograms} to stay within the upstream request budget.`);
|
|
3554
|
+
}
|
|
3555
|
+
const targetSamples = input.include_target_samples && targetSamplePrograms > 0
|
|
3556
|
+
? await collectTargetSamples(client, candidates, { ...input, target_sample_programs: targetSamplePrograms }, budget)
|
|
3557
|
+
: {};
|
|
3558
|
+
const outputMode = input.output_mode ?? "compact";
|
|
3559
|
+
const programs = candidates.map((candidate, index) => candidateOutput(candidate, index, targetSamples, outputMode));
|
|
3560
|
+
return stripUndefined({
|
|
3561
|
+
request_id: randomUUID(),
|
|
3562
|
+
source_requests: collected.sourceRequests,
|
|
3563
|
+
fetched_at: new Date().toISOString(),
|
|
3564
|
+
warnings: warnings.length > 0 ? warnings : undefined,
|
|
3565
|
+
filters: stripUndefined({
|
|
3566
|
+
platforms: input.platforms.length > 0 ? input.platforms : undefined,
|
|
3567
|
+
tags: upstreamTags.length > 0 ? upstreamTags : undefined,
|
|
3568
|
+
web3: input.web3 ? true : undefined,
|
|
3569
|
+
opportunity_levels: input.opportunity_levels.length > 0 ? input.opportunity_levels : undefined,
|
|
3570
|
+
updated_since: input.updated_since,
|
|
3571
|
+
min_bounty_min: input.min_bounty_min,
|
|
3572
|
+
min_bounty_max: input.min_bounty_max,
|
|
3573
|
+
max_bounty_max: input.max_bounty_max,
|
|
3574
|
+
require_bounty: input.require_bounty,
|
|
3575
|
+
max_public_report_count: input.max_public_report_count,
|
|
3576
|
+
require_known_report_count: input.require_known_report_count,
|
|
3577
|
+
include_unknown_report_count: input.include_unknown_report_count,
|
|
3578
|
+
exclude_ctf: input.exclude_ctf,
|
|
3579
|
+
exclude_mobile_only: input.exclude_mobile_only,
|
|
3580
|
+
exclude_non_web: input.exclude_non_web,
|
|
3581
|
+
has_api_scope: input.has_api_scope,
|
|
3582
|
+
contest_like: input.contest_like ? true : undefined,
|
|
3583
|
+
vdp_mode: input.vdp_mode && input.vdp_mode !== "include" ? input.vdp_mode : undefined,
|
|
3584
|
+
fresh_launch_days: input.fresh_launch_days,
|
|
3585
|
+
min_wildcards: input.min_wildcards,
|
|
3586
|
+
min_eligible_targets: input.min_eligible_targets,
|
|
3587
|
+
min_total_targets: input.min_total_targets,
|
|
3588
|
+
min_added_24h: input.min_added_24h,
|
|
3589
|
+
min_added_7d: input.min_added_7d,
|
|
3590
|
+
sort_by: input.sort_by,
|
|
3591
|
+
output_mode: outputMode
|
|
3592
|
+
}),
|
|
3593
|
+
upstream_budget: {
|
|
3594
|
+
used: budget.initial - budget.remaining,
|
|
3595
|
+
remaining: budget.remaining
|
|
3596
|
+
},
|
|
3597
|
+
ranking_scope: {
|
|
3598
|
+
mode: "sampled",
|
|
3599
|
+
max_pages_scanned_per_source: input.max_pages,
|
|
3600
|
+
upstream_total_pages: collected.totalPagesBySource,
|
|
3601
|
+
possibly_incomplete: isRankingPossiblyIncomplete(collected.totalPagesBySource, input.max_pages)
|
|
3602
|
+
},
|
|
3603
|
+
programs,
|
|
3604
|
+
meta: {
|
|
3605
|
+
returned: programs.length,
|
|
3606
|
+
total_candidates_after_filters: filteredCandidates.length,
|
|
3607
|
+
upstream_requests_scanned: collected.requestsScanned,
|
|
3608
|
+
programs_scanned: collected.programsScanned,
|
|
3609
|
+
upstream_total_pages: collected.totalPagesBySource
|
|
3610
|
+
}
|
|
3611
|
+
});
|
|
3612
|
+
}
|
|
3613
|
+
async function collectPrograms(client, options) {
|
|
3614
|
+
const sources = options.opportunity_levels.length > 0
|
|
3615
|
+
? options.opportunity_levels.map((level) => ({ kind: "opportunity", level }))
|
|
3616
|
+
: [{ kind: "programs" }];
|
|
3617
|
+
const collectionOptions = {
|
|
3618
|
+
...options,
|
|
3619
|
+
runWithProgramPageSlot: createAsyncLimiter(PROGRAM_COLLECTION_CONCURRENCY)
|
|
3620
|
+
};
|
|
3621
|
+
const pagesBySource = await collectProgramSources(client, sources, collectionOptions);
|
|
3622
|
+
const programs = new Map();
|
|
3623
|
+
const sourceRequests = [];
|
|
3624
|
+
const totalPagesBySource = {};
|
|
3625
|
+
for (const page of pagesBySource.sort(compareProgramPageResults)) {
|
|
3626
|
+
for (const program of page.programs) {
|
|
3627
|
+
programs.set(programKey(program, programs.size), program);
|
|
3628
|
+
}
|
|
3629
|
+
totalPagesBySource[page.sourceName] = page.totalPages;
|
|
3630
|
+
sourceRequests.push(page.sourceRequest);
|
|
3631
|
+
}
|
|
3632
|
+
return {
|
|
3633
|
+
programs: [...programs.values()],
|
|
3634
|
+
sourceRequests,
|
|
3635
|
+
requestsScanned: pagesBySource.length,
|
|
3636
|
+
programsScanned: programs.size,
|
|
3637
|
+
totalPagesBySource
|
|
3638
|
+
};
|
|
3639
|
+
}
|
|
3640
|
+
async function collectProgramSources(client, sources, options) {
|
|
3641
|
+
const pages = [];
|
|
3642
|
+
await mapWithConcurrency(sources.map((source, sourceIndex) => ({ source, sourceIndex })), PROGRAM_COLLECTION_CONCURRENCY, async ({ source, sourceIndex }) => {
|
|
3643
|
+
pages.push(...(await collectProgramSource(client, source, sourceIndex, options)));
|
|
3644
|
+
});
|
|
3645
|
+
return pages;
|
|
3646
|
+
}
|
|
3647
|
+
async function collectProgramSource(client, source, sourceIndex, options) {
|
|
3648
|
+
const firstPage = await fetchProgramPage(client, source, sourceIndex, 1, options);
|
|
3649
|
+
if (!firstPage) {
|
|
3650
|
+
return [];
|
|
3651
|
+
}
|
|
3652
|
+
if (firstPage.programs.length === 0 || firstPage.totalPages === 1 || options.max_pages === 1) {
|
|
3653
|
+
return [firstPage];
|
|
3654
|
+
}
|
|
3655
|
+
const maxKnownPage = firstPage.totalPages === undefined ? undefined : Math.min(firstPage.totalPages, options.max_pages);
|
|
3656
|
+
if (maxKnownPage === undefined) {
|
|
3657
|
+
const pages = [firstPage];
|
|
3658
|
+
for (let page = 2; page <= options.max_pages; page += 1) {
|
|
3659
|
+
const nextPage = await fetchProgramPage(client, source, sourceIndex, page, options);
|
|
3660
|
+
if (!nextPage) {
|
|
3661
|
+
break;
|
|
3662
|
+
}
|
|
3663
|
+
pages.push(nextPage);
|
|
3664
|
+
if (nextPage.programs.length === 0 || (nextPage.totalPages !== undefined && page >= nextPage.totalPages)) {
|
|
3665
|
+
break;
|
|
3666
|
+
}
|
|
3667
|
+
}
|
|
3668
|
+
return pages;
|
|
3669
|
+
}
|
|
3670
|
+
const remainingPageNumbers = Array.from({ length: Math.max(0, maxKnownPage - 1) }, (_, index) => index + 2);
|
|
3671
|
+
const remainingPages = [];
|
|
3672
|
+
await mapWithConcurrency(remainingPageNumbers, PROGRAM_COLLECTION_CONCURRENCY, async (page) => {
|
|
3673
|
+
const result = await fetchProgramPage(client, source, sourceIndex, page, options);
|
|
3674
|
+
if (result) {
|
|
3675
|
+
remainingPages.push(result);
|
|
3676
|
+
}
|
|
3677
|
+
});
|
|
3678
|
+
return [firstPage, ...remainingPages];
|
|
3679
|
+
}
|
|
3680
|
+
async function fetchProgramPage(client, source, sourceIndex, page, options) {
|
|
3681
|
+
const runWithSlot = options.runWithProgramPageSlot ?? runImmediately;
|
|
3682
|
+
return runWithSlot(async () => {
|
|
3683
|
+
const query = toQuery({
|
|
3684
|
+
platforms: options.platforms,
|
|
3685
|
+
tags: options.tags,
|
|
3686
|
+
updated_since: options.updated_since,
|
|
3687
|
+
page,
|
|
3688
|
+
page_size: MAX_PROGRAMS_PER_SEARCH
|
|
3689
|
+
});
|
|
3690
|
+
if (!tryConsumeBudget(options.budget)) {
|
|
3691
|
+
addUniqueWarning(options.warnings, "Upstream request budget was exhausted before all requested program pages could be fetched.");
|
|
3692
|
+
return undefined;
|
|
3693
|
+
}
|
|
3694
|
+
const api = source.kind === "opportunity" ? await client.getOpportunities(source.level, query) : await client.listPrograms(query);
|
|
3695
|
+
refundBudgetIfNoUpstreamRequest(options.budget, api);
|
|
3696
|
+
const data = readObject(api.data);
|
|
3697
|
+
const meta = readObject(data?.meta);
|
|
3698
|
+
const sourceName = source.kind === "opportunity" ? `opportunities:${source.level}` : "programs";
|
|
3699
|
+
return {
|
|
3700
|
+
sourceName,
|
|
3701
|
+
sourceIndex,
|
|
3702
|
+
page,
|
|
3703
|
+
programs: readArray(data?.programs),
|
|
3704
|
+
totalPages: readNumber(meta?.total_pages),
|
|
3705
|
+
sourceRequest: stripUndefined({
|
|
3706
|
+
source: sourceName,
|
|
3707
|
+
request_id: api.requestId,
|
|
3708
|
+
upstream_request_id: api.upstreamRequestId,
|
|
3709
|
+
...apiSourceMetadata(api),
|
|
3710
|
+
page
|
|
3711
|
+
})
|
|
3712
|
+
};
|
|
3713
|
+
});
|
|
3714
|
+
}
|
|
3715
|
+
function compareProgramPageResults(left, right) {
|
|
3716
|
+
return left.sourceIndex - right.sourceIndex || left.page - right.page;
|
|
3717
|
+
}
|
|
3718
|
+
function toProgramCandidate(rawProgram, webBaseUrl) {
|
|
3719
|
+
const program = addProgramResourceLinks(sanitizeProgram(rawProgram, webBaseUrl));
|
|
3720
|
+
const scopeSummary = readObject(program.scope_summary);
|
|
3721
|
+
const targetCounts = readObject(scopeSummary?.target_counts);
|
|
3722
|
+
const targetActivity = readObject(scopeSummary?.target_activity);
|
|
3723
|
+
const rewardRange = readObject(program.reward_range);
|
|
3724
|
+
const opportunityTag = readObject(program.opportunity_tag);
|
|
3725
|
+
const scopeTags = readStringArrayField(scopeSummary ?? {}, "tags");
|
|
3726
|
+
const normalizedScopeTags = scopeTags.map(normalizeTag);
|
|
3727
|
+
const normalizedSearchSignals = normalizedCandidateValues(program, normalizedScopeTags);
|
|
3728
|
+
const hasApiSurface = tagIncludesAnyNormalized(normalizedScopeTags, ["api", "graphql", "rest"]);
|
|
3729
|
+
const hasWebSurface = (numberField(targetCounts, "wildcard") ?? 0) > 0 ||
|
|
3730
|
+
tagIncludesAnyNormalized(normalizedScopeTags, ["domain", "web", "url", "api", "http", "javascript"]);
|
|
3731
|
+
const hasWeb3Surface = tagIncludesAnyNormalized(normalizedScopeTags, WEB3_TAGS) ||
|
|
3732
|
+
tagIncludesAnyNormalized(normalizedSearchSignals, WEB3_TAGS);
|
|
3733
|
+
const hasMobileSurface = tagIncludesAnyNormalized(normalizedScopeTags, ["android", "ios", "mobile"]);
|
|
3734
|
+
const looksLikeCtf = normalizedSearchSignals.some((value) => value.includes("ctf") || value.includes("capture the flag"));
|
|
3735
|
+
const candidate = {
|
|
3736
|
+
program,
|
|
3737
|
+
scopeTags,
|
|
3738
|
+
normalizedScopeTags,
|
|
3739
|
+
normalizedSearchSignals,
|
|
3740
|
+
publicReportCount: numberField(program, "public_report_count"),
|
|
3741
|
+
bountyMin: numberField(rewardRange, "min"),
|
|
3742
|
+
bountyMax: numberField(rewardRange, "max"),
|
|
3743
|
+
totalTargetCount: numberField(targetCounts, "total"),
|
|
3744
|
+
inScopeTargetCount: numberField(targetCounts, "in_scope"),
|
|
3745
|
+
outOfScopeTargetCount: numberField(targetCounts, "out_of_scope"),
|
|
3746
|
+
eligibleTargetCount: numberField(targetCounts, "eligible_for_bounty"),
|
|
3747
|
+
wildcardCount: numberField(targetCounts, "wildcard") ?? 0,
|
|
3748
|
+
added24h: numberField(targetActivity, "added_24h"),
|
|
3749
|
+
added7d: numberField(targetActivity, "added_7d"),
|
|
3750
|
+
removed7d: numberField(targetActivity, "removed_7d"),
|
|
3751
|
+
opportunityScore: numberField(opportunityTag, "opportunity_score"),
|
|
3752
|
+
opportunityTier: stringField(opportunityTag, "tier"),
|
|
3753
|
+
lastUpdatedTime: timestampField(program, "last_updated"),
|
|
3754
|
+
firstSeenTime: timestampField(program, "first_seen_date"),
|
|
3755
|
+
latestChangeTime: timestampField(targetActivity ?? {}, "latest_change_at"),
|
|
3756
|
+
hasApiSurface,
|
|
3757
|
+
hasWebSurface,
|
|
3758
|
+
hasWeb3Surface,
|
|
3759
|
+
hasMobileSurface,
|
|
3760
|
+
looksMobileOnly: hasMobileSurface && !tagIncludesAnyNormalized(normalizedScopeTags, ["domain", "web", "url", "api", "http", "javascript"]),
|
|
3761
|
+
looksLikeCtf,
|
|
3762
|
+
targetSurfaceScore: 0,
|
|
3763
|
+
huntValueScore: 0
|
|
3764
|
+
};
|
|
3765
|
+
candidate.targetSurfaceScore = calculateTargetSurfaceScore(candidate);
|
|
3766
|
+
candidate.huntValueScore = calculateHuntValueScore(candidate);
|
|
3767
|
+
return candidate;
|
|
3768
|
+
}
|
|
3769
|
+
function programCandidateMatches(candidate, filters) {
|
|
3770
|
+
if (filters.platforms.length > 0 && !filters.platforms.map(normalizeTag).includes(normalizeTag(stringField(candidate.program, "platform") ?? ""))) {
|
|
3771
|
+
return false;
|
|
3772
|
+
}
|
|
3773
|
+
if (filters.tags.length > 0 && !candidateMatchesAnyTag(candidate, filters.tags)) {
|
|
3774
|
+
return false;
|
|
3775
|
+
}
|
|
3776
|
+
if (filters.web3 && !candidateHasWeb3Surface(candidate)) {
|
|
3777
|
+
return false;
|
|
3778
|
+
}
|
|
3779
|
+
if (filters.scope_tags.length > 0 && !candidateMatchesAnyTag(candidate, filters.scope_tags)) {
|
|
3780
|
+
return false;
|
|
3781
|
+
}
|
|
3782
|
+
if (filters.target_types.length > 0 && !candidateMatchesAnyTag(candidate, filters.target_types)) {
|
|
3783
|
+
return false;
|
|
3784
|
+
}
|
|
3785
|
+
if (filters.language_tags.length > 0 && !candidateMatchesAnyTag(candidate, filters.language_tags)) {
|
|
3786
|
+
return false;
|
|
3787
|
+
}
|
|
3788
|
+
if (candidate.wildcardCount < filters.min_wildcards) {
|
|
3789
|
+
return false;
|
|
3790
|
+
}
|
|
3791
|
+
if (filters.require_bounty && (candidate.bountyMax ?? candidate.bountyMin ?? 0) <= 0) {
|
|
3792
|
+
return false;
|
|
3793
|
+
}
|
|
3794
|
+
if (filters.min_bounty_min !== undefined && (candidate.bountyMin ?? 0) < filters.min_bounty_min) {
|
|
3795
|
+
return false;
|
|
3796
|
+
}
|
|
3797
|
+
if (filters.min_bounty_max !== undefined && (candidate.bountyMax ?? 0) < filters.min_bounty_max) {
|
|
3798
|
+
return false;
|
|
3799
|
+
}
|
|
3800
|
+
if (filters.max_bounty_max !== undefined && (candidate.bountyMax ?? Number.POSITIVE_INFINITY) > filters.max_bounty_max) {
|
|
3801
|
+
return false;
|
|
3802
|
+
}
|
|
3803
|
+
if (filters.require_known_report_count && candidate.publicReportCount === undefined) {
|
|
3804
|
+
return false;
|
|
3805
|
+
}
|
|
3806
|
+
if (filters.max_public_report_count !== undefined) {
|
|
3807
|
+
if (candidate.publicReportCount === undefined) {
|
|
3808
|
+
return filters.include_unknown_report_count;
|
|
3809
|
+
}
|
|
3810
|
+
if (candidate.publicReportCount > filters.max_public_report_count) {
|
|
3811
|
+
return false;
|
|
3812
|
+
}
|
|
3813
|
+
}
|
|
3814
|
+
if (filters.exclude_ctf && candidateLooksLikeCtf(candidate)) {
|
|
3815
|
+
return false;
|
|
3816
|
+
}
|
|
3817
|
+
if (filters.exclude_mobile_only && candidateLooksMobileOnly(candidate)) {
|
|
3818
|
+
return false;
|
|
3819
|
+
}
|
|
3820
|
+
if (filters.exclude_non_web && !candidateHasWebSurface(candidate)) {
|
|
3821
|
+
return false;
|
|
3822
|
+
}
|
|
3823
|
+
if (filters.has_api_scope && !candidateHasApiSurface(candidate)) {
|
|
3824
|
+
return false;
|
|
3825
|
+
}
|
|
3826
|
+
if (filters.contest_like === true && !candidateLooksContestLike(candidate)) {
|
|
3827
|
+
return false;
|
|
3828
|
+
}
|
|
3829
|
+
if (filters.vdp_mode === "exclude" && candidateLooksLikeVdp(candidate)) {
|
|
3830
|
+
return false;
|
|
3831
|
+
}
|
|
3832
|
+
if (filters.vdp_mode === "only_likely" && !candidateLooksLikeVdp(candidate)) {
|
|
3833
|
+
return false;
|
|
3834
|
+
}
|
|
3835
|
+
if (filters.vdp_mode === "only_known_no_bounty" && !candidateHasKnownNoBounty(candidate)) {
|
|
3836
|
+
return false;
|
|
3837
|
+
}
|
|
3838
|
+
if (filters.fresh_launch_days !== undefined) {
|
|
3839
|
+
const firstSeen = candidate.firstSeenTime;
|
|
3840
|
+
const cutoff = Date.now() - filters.fresh_launch_days * 24 * 60 * 60 * 1000;
|
|
3841
|
+
if (firstSeen <= 0 || firstSeen < cutoff) {
|
|
3842
|
+
return false;
|
|
3843
|
+
}
|
|
3844
|
+
}
|
|
3845
|
+
if (filters.min_eligible_targets !== undefined && (candidate.eligibleTargetCount ?? 0) < filters.min_eligible_targets) {
|
|
3846
|
+
return false;
|
|
3847
|
+
}
|
|
3848
|
+
if (filters.min_total_targets !== undefined && (candidate.totalTargetCount ?? 0) < filters.min_total_targets) {
|
|
3849
|
+
return false;
|
|
3850
|
+
}
|
|
3851
|
+
if (filters.min_added_24h !== undefined && (candidate.added24h ?? 0) < filters.min_added_24h) {
|
|
3852
|
+
return false;
|
|
3853
|
+
}
|
|
3854
|
+
if (filters.min_added_7d !== undefined && (candidate.added7d ?? 0) < filters.min_added_7d) {
|
|
3855
|
+
return false;
|
|
3856
|
+
}
|
|
3857
|
+
return true;
|
|
3858
|
+
}
|
|
3859
|
+
function compareProgramCandidates(left, right, sortBy) {
|
|
3860
|
+
if (sortBy === "best_hunt_value") {
|
|
3861
|
+
return compareNumbersDesc(huntValueScore(left), huntValueScore(right)) || compareLowestReports(left, right);
|
|
3862
|
+
}
|
|
3863
|
+
if (sortBy === "lowest_reports") {
|
|
3864
|
+
return compareLowestReports(left, right);
|
|
3865
|
+
}
|
|
3866
|
+
if (sortBy === "highest_reward") {
|
|
3867
|
+
return compareNumbersDesc(left.bountyMax, right.bountyMax) || compareLowestReports(left, right);
|
|
3868
|
+
}
|
|
3869
|
+
if (sortBy === "most_wildcards") {
|
|
3870
|
+
return compareNumbersDesc(left.wildcardCount, right.wildcardCount) || compareLowestReports(left, right);
|
|
3871
|
+
}
|
|
3872
|
+
if (sortBy === "most_eligible_targets") {
|
|
3873
|
+
return compareNumbersDesc(left.eligibleTargetCount, right.eligibleTargetCount) || compareLowestReports(left, right);
|
|
3874
|
+
}
|
|
3875
|
+
if (sortBy === "freshest") {
|
|
3876
|
+
return right.firstSeenTime - left.firstSeenTime || compareLowestReports(left, right);
|
|
3877
|
+
}
|
|
3878
|
+
if (sortBy === "recently_updated") {
|
|
3879
|
+
return Math.max(right.lastUpdatedTime, right.latestChangeTime) - Math.max(left.lastUpdatedTime, left.latestChangeTime) || compareLowestReports(left, right);
|
|
3880
|
+
}
|
|
3881
|
+
if (sortBy === "highest_opportunity_score") {
|
|
3882
|
+
return compareNumbersDesc(left.opportunityScore, right.opportunityScore) || compareLowestReports(left, right);
|
|
3883
|
+
}
|
|
3884
|
+
if (sortBy === "most_new_targets") {
|
|
3885
|
+
return compareNumbersDesc(left.added7d, right.added7d) || compareNumbersDesc(left.added24h, right.added24h) || compareLowestReports(left, right);
|
|
3886
|
+
}
|
|
3887
|
+
return (compareNumbersDesc(huntValueScore(left), huntValueScore(right)) ||
|
|
3888
|
+
compareNumbersDesc(left.opportunityScore, right.opportunityScore) ||
|
|
3889
|
+
compareLowestReports(left, right) ||
|
|
3890
|
+
compareNumbersDesc(left.bountyMax, right.bountyMax) ||
|
|
3891
|
+
compareNumbersDesc(left.wildcardCount, right.wildcardCount) ||
|
|
3892
|
+
compareNumbersDesc(left.eligibleTargetCount, right.eligibleTargetCount) ||
|
|
3893
|
+
right.firstSeenTime - left.firstSeenTime ||
|
|
3894
|
+
right.lastUpdatedTime - left.lastUpdatedTime);
|
|
3895
|
+
}
|
|
3896
|
+
async function collectTargetSamples(client, candidates, input, budget) {
|
|
3897
|
+
const samples = {};
|
|
3898
|
+
const sampleCandidates = candidates.slice(0, input.target_sample_programs);
|
|
3899
|
+
const sampleFilters = targetSampleFilters(input);
|
|
3900
|
+
await mapWithConcurrency(sampleCandidates, TARGET_SAMPLE_CONCURRENCY, async (candidate) => {
|
|
3901
|
+
const programId = stringField(candidate.program, "id");
|
|
3902
|
+
if (!programId) {
|
|
3903
|
+
return;
|
|
3904
|
+
}
|
|
3905
|
+
try {
|
|
3906
|
+
if (!tryConsumeBudget(budget)) {
|
|
3907
|
+
samples[programId] = [];
|
|
3908
|
+
return;
|
|
3909
|
+
}
|
|
3910
|
+
const api = await client.getProgramTargets(programId);
|
|
3911
|
+
refundBudgetIfNoUpstreamRequest(budget, api);
|
|
3912
|
+
const data = readObject(api.data);
|
|
3913
|
+
const targets = readArray(data?.targets)
|
|
3914
|
+
.map(sanitizeTarget)
|
|
3915
|
+
.filter((target) => targetMatchesSampleFilters(target, sampleFilters))
|
|
3916
|
+
.slice(0, input.target_sample_size);
|
|
3917
|
+
samples[programId] = targets;
|
|
3918
|
+
}
|
|
3919
|
+
catch {
|
|
3920
|
+
samples[programId] = [];
|
|
3921
|
+
}
|
|
3922
|
+
});
|
|
3923
|
+
return samples;
|
|
3924
|
+
}
|
|
3925
|
+
function targetSampleFilters(input) {
|
|
3926
|
+
return {
|
|
3927
|
+
targetSamplesOnlyWildcards: input.target_samples_only_wildcards,
|
|
3928
|
+
onlyInScopeTargets: input.only_in_scope_targets,
|
|
3929
|
+
onlyBountyEligibleTargets: input.only_bounty_eligible_targets,
|
|
3930
|
+
targetTypes: new Set(input.target_types.map(normalizeTag)),
|
|
3931
|
+
scopeTags: new Set(input.scope_tags.map(normalizeTag)),
|
|
3932
|
+
languageTags: new Set(input.language_tags.map(normalizeTag))
|
|
3933
|
+
};
|
|
3934
|
+
}
|
|
3935
|
+
function targetMatchesSampleFilters(target, filters) {
|
|
3936
|
+
if (filters.targetSamplesOnlyWildcards && readBoolean(target.wildcard) !== true) {
|
|
3937
|
+
return false;
|
|
3938
|
+
}
|
|
3939
|
+
if (filters.onlyInScopeTargets && target.in_scope !== true) {
|
|
3940
|
+
return false;
|
|
3941
|
+
}
|
|
3942
|
+
if (filters.onlyBountyEligibleTargets && target.eligible_for_bounty !== true) {
|
|
3943
|
+
return false;
|
|
3944
|
+
}
|
|
3945
|
+
if (filters.targetTypes.size > 0 && !filters.targetTypes.has(normalizeTag(readString(target.target_type) ?? ""))) {
|
|
3946
|
+
return false;
|
|
3947
|
+
}
|
|
3948
|
+
if (filters.scopeTags.size > 0 && !arraysIntersectNormalized(filters.scopeTags, readStringArrayField(target, "scope_tags"))) {
|
|
3949
|
+
return false;
|
|
3950
|
+
}
|
|
3951
|
+
if (filters.languageTags.size > 0 && !arraysIntersectNormalized(filters.languageTags, readStringArrayField(target, "language_tags"))) {
|
|
3952
|
+
return false;
|
|
3953
|
+
}
|
|
3954
|
+
return true;
|
|
3955
|
+
}
|
|
3956
|
+
function compareLowestReports(left, right) {
|
|
3957
|
+
return (compareNumbersAsc(left.publicReportCount, right.publicReportCount) ||
|
|
3958
|
+
compareNumbersDesc(left.bountyMax, right.bountyMax) ||
|
|
3959
|
+
compareNumbersDesc(left.wildcardCount, right.wildcardCount) ||
|
|
3960
|
+
compareNumbersDesc(left.eligibleTargetCount, right.eligibleTargetCount) ||
|
|
3961
|
+
compareNumbersDesc(left.opportunityScore, right.opportunityScore) ||
|
|
3962
|
+
right.lastUpdatedTime - left.lastUpdatedTime ||
|
|
3963
|
+
right.firstSeenTime - left.firstSeenTime);
|
|
3964
|
+
}
|
|
3965
|
+
function programKey(program, fallback) {
|
|
3966
|
+
const object = readObject(program);
|
|
3967
|
+
const id = readString(object?.id);
|
|
3968
|
+
return id ?? `program:${fallback}`;
|
|
3969
|
+
}
|
|
3970
|
+
function tryConsumeBudget(budget, cost = 1) {
|
|
3971
|
+
if (budget.remaining < cost) {
|
|
3972
|
+
return false;
|
|
3973
|
+
}
|
|
3974
|
+
budget.remaining -= cost;
|
|
3975
|
+
return true;
|
|
3976
|
+
}
|
|
3977
|
+
function refundBudgetIfNoUpstreamRequest(budget, api, cost = 1) {
|
|
3978
|
+
if (api.cached || api.coalesced) {
|
|
3979
|
+
budget.remaining = Math.min(budget.initial, budget.remaining + cost);
|
|
3980
|
+
}
|
|
3981
|
+
}
|
|
3982
|
+
function addUniqueWarning(warnings, warning) {
|
|
3983
|
+
if (warnings && !warnings.includes(warning)) {
|
|
3984
|
+
warnings.push(warning);
|
|
3985
|
+
}
|
|
3986
|
+
}
|
|
3987
|
+
function runImmediately(callback) {
|
|
3988
|
+
return callback();
|
|
3989
|
+
}
|
|
3990
|
+
function createAsyncLimiter(concurrency) {
|
|
3991
|
+
const limit = Math.max(1, concurrency);
|
|
3992
|
+
let active = 0;
|
|
3993
|
+
const queue = [];
|
|
3994
|
+
return async (callback) => {
|
|
3995
|
+
if (active >= limit) {
|
|
3996
|
+
await new Promise((resolve) => queue.push(resolve));
|
|
3997
|
+
}
|
|
3998
|
+
else {
|
|
3999
|
+
active += 1;
|
|
4000
|
+
}
|
|
4001
|
+
try {
|
|
4002
|
+
return await callback();
|
|
4003
|
+
}
|
|
4004
|
+
finally {
|
|
4005
|
+
const next = queue.shift();
|
|
4006
|
+
if (next) {
|
|
4007
|
+
next();
|
|
4008
|
+
}
|
|
4009
|
+
else {
|
|
4010
|
+
active -= 1;
|
|
4011
|
+
}
|
|
4012
|
+
}
|
|
4013
|
+
};
|
|
4014
|
+
}
|
|
4015
|
+
async function mapWithConcurrency(items, concurrency, mapper) {
|
|
4016
|
+
let index = 0;
|
|
4017
|
+
async function worker() {
|
|
4018
|
+
while (index < items.length) {
|
|
4019
|
+
const current = index;
|
|
4020
|
+
index += 1;
|
|
4021
|
+
const item = items[current];
|
|
4022
|
+
if (item !== undefined) {
|
|
4023
|
+
await mapper(item);
|
|
4024
|
+
}
|
|
4025
|
+
}
|
|
4026
|
+
}
|
|
4027
|
+
await Promise.all(Array.from({ length: Math.min(concurrency, items.length) }, () => worker()));
|
|
4028
|
+
}
|
|
4029
|
+
function isRankingPossiblyIncomplete(totalPagesBySource, maxPages) {
|
|
4030
|
+
const totals = Object.values(totalPagesBySource);
|
|
4031
|
+
if (totals.length === 0) {
|
|
4032
|
+
return true;
|
|
4033
|
+
}
|
|
4034
|
+
return totals.some((value) => typeof value !== "number" || value > maxPages);
|
|
4035
|
+
}
|
|
4036
|
+
function numberField(value, field) {
|
|
4037
|
+
return value ? readNumber(value[field]) : undefined;
|
|
4038
|
+
}
|
|
4039
|
+
function stringField(value, field) {
|
|
4040
|
+
return value ? readString(value[field]) : undefined;
|
|
4041
|
+
}
|
|
4042
|
+
function timestampField(value, field) {
|
|
4043
|
+
const text = stringField(value, field);
|
|
4044
|
+
if (!text) {
|
|
4045
|
+
return 0;
|
|
4046
|
+
}
|
|
4047
|
+
const timestamp = Date.parse(text);
|
|
4048
|
+
return Number.isFinite(timestamp) ? timestamp : 0;
|
|
4049
|
+
}
|
|
4050
|
+
function compareNumbersAsc(left, right) {
|
|
4051
|
+
if (left === undefined && right === undefined) {
|
|
4052
|
+
return 0;
|
|
4053
|
+
}
|
|
4054
|
+
if (left === undefined) {
|
|
4055
|
+
return 1;
|
|
4056
|
+
}
|
|
4057
|
+
if (right === undefined) {
|
|
4058
|
+
return -1;
|
|
4059
|
+
}
|
|
4060
|
+
return left - right;
|
|
4061
|
+
}
|
|
4062
|
+
function compareNumbersDesc(left, right) {
|
|
4063
|
+
return compareNumbersAsc(right, left);
|
|
4064
|
+
}
|
|
4065
|
+
function uniqueStrings(values) {
|
|
4066
|
+
return [...new Map(values.map((value) => [normalizeTag(value), value.trim()])).values()].filter(Boolean);
|
|
4067
|
+
}
|
|
4068
|
+
function normalizeTag(value) {
|
|
4069
|
+
return value.trim().toLowerCase();
|
|
4070
|
+
}
|
|
4071
|
+
function tokenSet(value) {
|
|
4072
|
+
return new Set(value.split(/[^a-z0-9]+/).filter((token) => token.length >= 2));
|
|
4073
|
+
}
|
|
4074
|
+
function arraysIntersectNormalized(left, right) {
|
|
4075
|
+
return right.some((value) => left.has(normalizeTag(value)));
|
|
4076
|
+
}
|
|
4077
|
+
function readStringArrayField(value, field) {
|
|
4078
|
+
const fieldValue = value[field];
|
|
4079
|
+
return Array.isArray(fieldValue) ? fieldValue.filter((entry) => typeof entry === "string") : [];
|
|
4080
|
+
}
|
|
4081
|
+
function registerPrompts(server) {
|
|
4082
|
+
server.registerPrompt("find_best_programs_to_hunt", {
|
|
4083
|
+
title: "Find Best Programs To Hunt",
|
|
4084
|
+
description: "Rank fresh BBRadar program data only. Do not invoke external bug bounty skills unless the user explicitly asks for methodology.",
|
|
4085
|
+
argsSchema: {
|
|
4086
|
+
opportunity_level: opportunityLevelSchema.optional(),
|
|
4087
|
+
platforms: z.string().optional().describe("Optional comma-separated platform filters."),
|
|
4088
|
+
tags: z.string().optional().describe("Optional comma-separated scope/language/target-type tags."),
|
|
4089
|
+
max_programs: z.string().optional().describe("Optional maximum shortlist size.")
|
|
4090
|
+
}
|
|
4091
|
+
}, (args) => promptResult(`Use the BBRadar MCP tools only. Do not invoke local bug bounty skills, methodology files, worklogs, or non-BBRadar tools unless the user explicitly asks for methodology. Do not perform active scanning or contact third-party targets. Treat results as fresh API reads; do not describe them as cached.
|
|
4092
|
+
|
|
4093
|
+
Find the best BBRadar program candidates.
|
|
4094
|
+
- Start with find_program_candidates unless the user needs custom filters; pass opportunity_levels from this JSON array unless the user asked for a broader search: ${promptJson([args.opportunity_level ?? "elite"])}.
|
|
4095
|
+
- Use find_language_programs for language asks, find_target_type_programs for API/mobile/source-code/domain asks, find_reward_programs or find_paid_programs for reward asks, find_vdp_programs for VDP/no-bounty asks, find_web3_contests for Web3 contest/audit asks, find_web3_programs for general Web3 requests, and find_wildcard_programs for wildcard-specific requests.
|
|
4096
|
+
- Apply platform filters from this JSON string if provided: ${promptJson(args.platforms ?? "")}.
|
|
4097
|
+
- Apply tag filters from this JSON string if provided: ${promptJson(args.tags ?? "")}.
|
|
4098
|
+
- Keep the shortlist to this JSON value: ${promptJson(args.max_programs ?? "10")}.
|
|
4099
|
+
- Compare freshness, opportunity score, reward range, public report count, target counts, and scope tags.
|
|
4100
|
+
- For exact scope details on one candidate, call get_program_scope_summary or get_program_target_breakdown before falling back to get_program_targets.
|
|
4101
|
+
- Return a ranked list with concise rationale, likely target themes, and BBRadar URLs.
|
|
4102
|
+
- Stay passive-only; do not recommend scanning, probing, exploit attempts, or direct contact with targets.`));
|
|
4103
|
+
server.registerPrompt("summarize_program_scope", {
|
|
4104
|
+
title: "Summarize Program Scope",
|
|
4105
|
+
description: "Summarize in-scope and out-of-scope BBRadar target data for one program.",
|
|
4106
|
+
argsSchema: {
|
|
4107
|
+
program_id: programIdSchema
|
|
4108
|
+
}
|
|
4109
|
+
}, (args) => promptResult(`Use get_program for the program_id in this JSON string: ${promptJson(args.program_id)}. Then use get_program_targets with include_out_of_scope=true, include_ineligible=true, and limit=${MAX_TARGETS_PER_PROGRAM}.
|
|
4110
|
+
|
|
4111
|
+
Summarize the program scope:
|
|
4112
|
+
- Program name, platform, reward range, public report count, first seen date, last updated date, and BBRadar URL.
|
|
4113
|
+
- In-scope bounty-eligible target categories.
|
|
4114
|
+
- Out-of-scope or ineligible target categories.
|
|
4115
|
+
- Wildcards, high-severity target labels, and notable language or scope tags.
|
|
4116
|
+
- Recent target update signals if present.
|
|
4117
|
+
- Do not contact, scan, probe, or validate any listed target.`));
|
|
4118
|
+
server.registerPrompt("prepare_recon_plan", {
|
|
4119
|
+
title: "Prepare Passive Recon Plan",
|
|
4120
|
+
description: "Create a passive-only plan from BBRadar program and target data. Use only when planning is explicitly requested.",
|
|
4121
|
+
argsSchema: {
|
|
4122
|
+
program_id: programIdSchema.optional(),
|
|
4123
|
+
focus: z.string().optional().describe("Optional focus area such as APIs, domains, mobile, or cloud.")
|
|
4124
|
+
}
|
|
4125
|
+
}, (args) => promptResult(`Prepare a passive-only plan from BBRadar data. Program id JSON string, if present: ${promptJson(args.program_id ?? "")}.
|
|
4126
|
+
|
|
4127
|
+
Do not invoke local bug bounty skills, methodology files, worklogs, or non-BBRadar tools unless the user explicitly asks for external methodology.
|
|
4128
|
+
|
|
4129
|
+
Use BBRadar MCP data first:
|
|
4130
|
+
- If a program_id is provided, call get_program and get_program_targets.
|
|
4131
|
+
- If no program_id is provided, call get_opportunities and select a small set of candidate programs before planning.
|
|
4132
|
+
- Focus area JSON string: ${promptJson(args.focus ?? "highest-signal in-scope bounty-eligible targets")}.
|
|
4133
|
+
|
|
4134
|
+
The plan must:
|
|
4135
|
+
- Avoid active scanning, probing, exploit attempts, brute force, or third-party target calls.
|
|
4136
|
+
- Separate allowed passive enrichment ideas from actions that need explicit program authorization.
|
|
4137
|
+
- Prioritize targets by bounty eligibility, freshness, wildcard status, target type, and severity labels.
|
|
4138
|
+
- Include a short checklist of BBRadar follow-up tool calls and decision points.`));
|
|
4139
|
+
}
|
|
4140
|
+
async function runTool(toolName, config, rateLimiter, callback) {
|
|
4141
|
+
const startedAt = Date.now();
|
|
4142
|
+
const decision = rateLimiter.consume(toolName, rateLimitForTool(toolName, config));
|
|
4143
|
+
if (!decision.allowed) {
|
|
4144
|
+
const durationMs = Date.now() - startedAt;
|
|
4145
|
+
recordToolMetric(toolName, durationMs, "rate_limited");
|
|
4146
|
+
return toolResult({
|
|
4147
|
+
request_id: randomUUID(),
|
|
4148
|
+
error: {
|
|
4149
|
+
message: `MCP rate limit exceeded for ${toolName}. Try again after ${decision.resetAt}.`
|
|
4150
|
+
},
|
|
4151
|
+
mcp_rate_limit: toRateLimitPayload(decision),
|
|
4152
|
+
mcp_timing: toolTimingPayload(toolName, durationMs)
|
|
4153
|
+
}, true);
|
|
4154
|
+
}
|
|
4155
|
+
try {
|
|
4156
|
+
const payload = await callback();
|
|
4157
|
+
const durationMs = Date.now() - startedAt;
|
|
4158
|
+
recordToolMetric(toolName, durationMs, "ok");
|
|
4159
|
+
return toolResult({
|
|
4160
|
+
...payload,
|
|
4161
|
+
mcp_rate_limit: toRateLimitPayload(decision),
|
|
4162
|
+
mcp_timing: toolTimingPayload(toolName, durationMs)
|
|
4163
|
+
});
|
|
4164
|
+
}
|
|
4165
|
+
catch (error) {
|
|
4166
|
+
const durationMs = Date.now() - startedAt;
|
|
4167
|
+
recordToolMetric(toolName, durationMs, "error");
|
|
4168
|
+
return toolResult(errorPayload(error, decision, toolName, durationMs), true);
|
|
4169
|
+
}
|
|
4170
|
+
}
|
|
4171
|
+
function toolResult(payload, isError = false) {
|
|
4172
|
+
const resourceLinks = collectResourceLinks(payload);
|
|
4173
|
+
return {
|
|
4174
|
+
content: [
|
|
4175
|
+
{
|
|
4176
|
+
type: "text",
|
|
4177
|
+
text: JSON.stringify(payload)
|
|
4178
|
+
},
|
|
4179
|
+
...resourceLinks
|
|
4180
|
+
],
|
|
4181
|
+
structuredContent: payload,
|
|
4182
|
+
...(isError ? { isError: true } : {})
|
|
4183
|
+
};
|
|
4184
|
+
}
|
|
4185
|
+
function promptResult(text) {
|
|
4186
|
+
return {
|
|
4187
|
+
messages: [
|
|
4188
|
+
{
|
|
4189
|
+
role: "user",
|
|
4190
|
+
content: {
|
|
4191
|
+
type: "text",
|
|
4192
|
+
text
|
|
4193
|
+
}
|
|
4194
|
+
}
|
|
4195
|
+
]
|
|
4196
|
+
};
|
|
4197
|
+
}
|
|
4198
|
+
function promptJson(value) {
|
|
4199
|
+
return JSON.stringify(value);
|
|
4200
|
+
}
|
|
4201
|
+
function withApiMetadata(api, payload) {
|
|
4202
|
+
return stripUndefined({
|
|
4203
|
+
request_id: api.requestId,
|
|
4204
|
+
upstream_request_id: api.upstreamRequestId,
|
|
4205
|
+
cache: stripUndefined({
|
|
4206
|
+
hit: api.cached,
|
|
4207
|
+
coalesced_live_request: api.coalesced,
|
|
4208
|
+
expires_at: api.cacheExpiresAt
|
|
4209
|
+
}),
|
|
4210
|
+
fetched_at: api.fetchedAt,
|
|
4211
|
+
...payload
|
|
4212
|
+
});
|
|
4213
|
+
}
|
|
4214
|
+
function apiSourceMetadata(api) {
|
|
4215
|
+
return stripUndefined({
|
|
4216
|
+
cache_hit: api.cached,
|
|
4217
|
+
cache_expires_at: api.cacheExpiresAt,
|
|
4218
|
+
coalesced_live_request: api.coalesced
|
|
4219
|
+
});
|
|
4220
|
+
}
|
|
4221
|
+
function errorPayload(error, decision, toolName, durationMs) {
|
|
4222
|
+
if (error instanceof BBRadarApiError) {
|
|
4223
|
+
return stripUndefined({
|
|
4224
|
+
request_id: error.requestId,
|
|
4225
|
+
upstream_request_id: error.upstreamRequestId,
|
|
4226
|
+
error: stripUndefined({
|
|
4227
|
+
message: error.message,
|
|
4228
|
+
status: error.status,
|
|
4229
|
+
detail: sanitizeJson(error.detail),
|
|
4230
|
+
errors: sanitizeJson(error.errors)
|
|
4231
|
+
}),
|
|
4232
|
+
mcp_rate_limit: toRateLimitPayload(decision),
|
|
4233
|
+
mcp_timing: toolTimingPayload(toolName, durationMs)
|
|
4234
|
+
});
|
|
4235
|
+
}
|
|
4236
|
+
return {
|
|
4237
|
+
request_id: randomUUID(),
|
|
4238
|
+
error: {
|
|
4239
|
+
message: error instanceof Error ? error.message : String(error)
|
|
4240
|
+
},
|
|
4241
|
+
mcp_rate_limit: toRateLimitPayload(decision),
|
|
4242
|
+
mcp_timing: toolTimingPayload(toolName, durationMs)
|
|
4243
|
+
};
|
|
4244
|
+
}
|
|
4245
|
+
function recordToolMetric(toolName, durationMs, status) {
|
|
4246
|
+
const existing = TOOL_METRICS.get(toolName) ??
|
|
4247
|
+
{
|
|
4248
|
+
calls: 0,
|
|
4249
|
+
errors: 0,
|
|
4250
|
+
totalMs: 0,
|
|
4251
|
+
maxMs: 0,
|
|
4252
|
+
lastMs: 0,
|
|
4253
|
+
lastStatus: status,
|
|
4254
|
+
lastAt: new Date(0).toISOString()
|
|
4255
|
+
};
|
|
4256
|
+
existing.calls += 1;
|
|
4257
|
+
existing.errors += status === "error" || status === "rate_limited" ? 1 : 0;
|
|
4258
|
+
existing.totalMs += durationMs;
|
|
4259
|
+
existing.maxMs = Math.max(existing.maxMs, durationMs);
|
|
4260
|
+
existing.lastMs = durationMs;
|
|
4261
|
+
existing.lastStatus = status;
|
|
4262
|
+
existing.lastAt = new Date().toISOString();
|
|
4263
|
+
TOOL_METRICS.set(toolName, existing);
|
|
4264
|
+
}
|
|
4265
|
+
function toolTimingPayload(toolName, durationMs) {
|
|
4266
|
+
return {
|
|
4267
|
+
tool_name: toolName,
|
|
4268
|
+
duration_ms: durationMs
|
|
4269
|
+
};
|
|
4270
|
+
}
|
|
4271
|
+
function toolMetricsSnapshot() {
|
|
4272
|
+
return Object.fromEntries([...TOOL_METRICS.entries()].map(([toolName, metric]) => [
|
|
4273
|
+
toolName,
|
|
4274
|
+
{
|
|
4275
|
+
calls: metric.calls,
|
|
4276
|
+
errors: metric.errors,
|
|
4277
|
+
avg_ms: metric.calls > 0 ? Math.round(metric.totalMs / metric.calls) : 0,
|
|
4278
|
+
max_ms: metric.maxMs,
|
|
4279
|
+
last_ms: metric.lastMs,
|
|
4280
|
+
last_status: metric.lastStatus,
|
|
4281
|
+
last_at: metric.lastAt
|
|
4282
|
+
}
|
|
4283
|
+
]));
|
|
4284
|
+
}
|
|
4285
|
+
function toRateLimitPayload(decision) {
|
|
4286
|
+
return {
|
|
4287
|
+
remaining: decision.remaining,
|
|
4288
|
+
reset_at: decision.resetAt
|
|
4289
|
+
};
|
|
4290
|
+
}
|
|
4291
|
+
function rateLimitForTool(toolName, config) {
|
|
4292
|
+
return toolName === "export_targets" ? config.exportRateLimitPerMinute : config.defaultRateLimitPerMinute;
|
|
4293
|
+
}
|
|
4294
|
+
function toQuery(values) {
|
|
4295
|
+
return compactRecord(values);
|
|
4296
|
+
}
|
|
4297
|
+
function compactRecord(values) {
|
|
4298
|
+
return Object.fromEntries(Object.entries(values).filter(([, value]) => {
|
|
4299
|
+
if (value === undefined || value === null) {
|
|
4300
|
+
return false;
|
|
4301
|
+
}
|
|
4302
|
+
if (Array.isArray(value)) {
|
|
4303
|
+
return value.length > 0;
|
|
4304
|
+
}
|
|
4305
|
+
return true;
|
|
4306
|
+
}));
|
|
4307
|
+
}
|
|
4308
|
+
function latestAddedTargetsQuery(input) {
|
|
4309
|
+
return {
|
|
4310
|
+
change_type: "added",
|
|
4311
|
+
target_type: input.target_type,
|
|
4312
|
+
include_out_of_scope: input.include_out_of_scope,
|
|
4313
|
+
include_ineligible: input.include_ineligible,
|
|
4314
|
+
recent_changes_page_size: input.recent_changes_page_size,
|
|
4315
|
+
include_full_target_list: input.include_full_target_list,
|
|
4316
|
+
full_target_list_mode: input.full_target_list_mode,
|
|
4317
|
+
full_target_limit: input.full_target_limit,
|
|
4318
|
+
full_list_include_out_of_scope: input.full_list_include_out_of_scope,
|
|
4319
|
+
full_list_include_ineligible: input.full_list_include_ineligible
|
|
4320
|
+
};
|
|
4321
|
+
}
|
|
4322
|
+
function formatTargetList(targets, mode) {
|
|
4323
|
+
if (mode === "identifiers") {
|
|
4324
|
+
return uniqueStrings(targets.map(targetIdentifier).filter((identifier) => identifier !== undefined));
|
|
4325
|
+
}
|
|
4326
|
+
if (mode === "compact") {
|
|
4327
|
+
return targets.map(compactTarget);
|
|
4328
|
+
}
|
|
4329
|
+
return targets;
|
|
4330
|
+
}
|
|
4331
|
+
function compactTarget(target) {
|
|
4332
|
+
return stripUndefined({
|
|
4333
|
+
identifier: targetIdentifier(target),
|
|
4334
|
+
target_type: stringField(target, "target_type"),
|
|
4335
|
+
in_scope: readBoolean(target.in_scope),
|
|
4336
|
+
eligible_for_bounty: readBoolean(target.eligible_for_bounty),
|
|
4337
|
+
wildcard: readBoolean(target.wildcard)
|
|
4338
|
+
});
|
|
4339
|
+
}
|
|
4340
|
+
function targetIdentifier(target) {
|
|
4341
|
+
return stringField(target, "identifier") ?? stringField(target, "display_name") ?? stringField(target, "normalized_identifier");
|
|
4342
|
+
}
|
|
4343
|
+
function targetMatchesRequestedType(target, targetType) {
|
|
4344
|
+
const normalizedTargetType = normalizeTag(targetType);
|
|
4345
|
+
const actualTargetType = normalizeTag(stringField(target, "target_type") ?? "");
|
|
4346
|
+
return actualTargetType === normalizedTargetType || readStringArrayField(target, "scope_tags").map(normalizeTag).includes(normalizedTargetType);
|
|
4347
|
+
}
|
|
4348
|
+
function targetMatchesQuery(target, query, mode) {
|
|
4349
|
+
const identifier = targetIdentifier(target);
|
|
4350
|
+
if (!identifier) {
|
|
4351
|
+
return false;
|
|
4352
|
+
}
|
|
4353
|
+
const normalizedIdentifier = normalizeTargetIdentifierForMatch(identifier);
|
|
4354
|
+
const normalizedQuery = normalizeTargetIdentifierForMatch(query);
|
|
4355
|
+
if (mode === "exact") {
|
|
4356
|
+
return normalizedIdentifier === normalizedQuery;
|
|
4357
|
+
}
|
|
4358
|
+
if (mode === "suffix") {
|
|
4359
|
+
return (normalizedIdentifier === normalizedQuery ||
|
|
4360
|
+
normalizedIdentifier.endsWith(`.${normalizedQuery}`) ||
|
|
4361
|
+
normalizedQuery.endsWith(`.${normalizedIdentifier}`));
|
|
4362
|
+
}
|
|
4363
|
+
if (mode === "wildcard") {
|
|
4364
|
+
return wildcardTargetMatch(normalizedQuery, normalizedIdentifier) || wildcardTargetMatch(normalizedIdentifier, normalizedQuery);
|
|
4365
|
+
}
|
|
4366
|
+
return normalizedIdentifier.includes(normalizedQuery) || normalizedQuery.includes(normalizedIdentifier);
|
|
4367
|
+
}
|
|
4368
|
+
function normalizeTargetIdentifierForMatch(value) {
|
|
4369
|
+
return value
|
|
4370
|
+
.trim()
|
|
4371
|
+
.toLowerCase()
|
|
4372
|
+
.replace(/^https?:\/\//, "")
|
|
4373
|
+
.replace(/^www\./, "")
|
|
4374
|
+
.replace(/\/+$/, "");
|
|
4375
|
+
}
|
|
4376
|
+
function wildcardTargetMatch(pattern, value) {
|
|
4377
|
+
if (!pattern.includes("*")) {
|
|
4378
|
+
return pattern === value || value.endsWith(`.${pattern}`);
|
|
4379
|
+
}
|
|
4380
|
+
const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
|
|
4381
|
+
return new RegExp(`^${escaped}$`).test(value);
|
|
4382
|
+
}
|
|
4383
|
+
function targetIsWildcard(target) {
|
|
4384
|
+
return readBoolean(target.wildcard) === true || (targetIdentifier(target)?.includes("*.") ?? false);
|
|
4385
|
+
}
|
|
4386
|
+
function targetIsDomain(target) {
|
|
4387
|
+
return targetMatchesAnySignal(target, ["domain", "url", "web", "http"]);
|
|
4388
|
+
}
|
|
4389
|
+
function targetIsApi(target) {
|
|
4390
|
+
return targetMatchesAnySignal(target, ["api", "graphql", "rest"]);
|
|
4391
|
+
}
|
|
4392
|
+
function targetIsMobile(target) {
|
|
4393
|
+
return targetMatchesAnySignal(target, ["android", "ios", "mobile"]);
|
|
4394
|
+
}
|
|
4395
|
+
function targetIsSourceCode(target) {
|
|
4396
|
+
return targetMatchesAnySignal(target, ["source-code", "source code", "repository", "repo", "code"]);
|
|
4397
|
+
}
|
|
4398
|
+
function targetMatchesAnySignal(target, signals) {
|
|
4399
|
+
const values = [
|
|
4400
|
+
stringField(target, "target_type"),
|
|
4401
|
+
targetIdentifier(target),
|
|
4402
|
+
...readStringArrayField(target, "scope_tags"),
|
|
4403
|
+
...readStringArrayField(target, "language_tags")
|
|
4404
|
+
]
|
|
4405
|
+
.filter((value) => value !== undefined)
|
|
4406
|
+
.map(normalizeTag);
|
|
4407
|
+
return signals.some((signal) => values.some((value) => value === signal || value.includes(signal)));
|
|
4408
|
+
}
|
|
4409
|
+
function targetHasAllowedScope(target, options) {
|
|
4410
|
+
if (!options.include_out_of_scope && (options.strict_scope_filter ? target.in_scope !== true : target.in_scope === false)) {
|
|
4411
|
+
return false;
|
|
4412
|
+
}
|
|
4413
|
+
if (!options.include_ineligible && (options.strict_scope_filter ? target.eligible_for_bounty !== true : target.eligible_for_bounty === false)) {
|
|
4414
|
+
return false;
|
|
4415
|
+
}
|
|
4416
|
+
return true;
|
|
4417
|
+
}
|
|
4418
|
+
//# sourceMappingURL=server.js.map
|