@bbradar/mcp 0.1.16 → 0.1.20
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 +37 -0
- package/dist/bbradarClient.d.ts +108 -2
- package/dist/bbradarClient.js +490 -45
- package/dist/bbradarClient.js.map +1 -1
- package/dist/cache.d.ts +5 -1
- package/dist/cache.js +22 -4
- package/dist/cache.js.map +1 -1
- package/dist/config.d.ts +5 -0
- package/dist/config.js +7 -0
- package/dist/config.js.map +1 -1
- package/dist/server.js +641 -182
- package/dist/server.js.map +1 -1
- package/dist/url.js +1 -1
- package/dist/url.js.map +1 -1
- package/package.json +1 -1
package/dist/server.js
CHANGED
|
@@ -5,7 +5,7 @@ import { BBRadarApiError } from "./bbradarClient.js";
|
|
|
5
5
|
import { stableStringify, stripUndefined } from "./json.js";
|
|
6
6
|
import { FixedWindowRateLimiter } from "./rateLimit.js";
|
|
7
7
|
import { readArray, readBoolean, readNumber, readObject, readString, sanitizeJson, sanitizeChange, sanitizeProgram, sanitizeTarget, sanitizeTargetsForExport } from "./sanitize.js";
|
|
8
|
-
const SERVER_VERSION = "0.1.
|
|
8
|
+
const SERVER_VERSION = "0.1.19";
|
|
9
9
|
const MAX_PROGRAMS_PER_SEARCH = 100;
|
|
10
10
|
const MAX_TARGET_EXPORT = 500;
|
|
11
11
|
const MAX_RECENT_CHANGES = 100;
|
|
@@ -20,7 +20,7 @@ const MAX_FIND_TARGET_SAMPLE_PROGRAMS = 10;
|
|
|
20
20
|
const MAX_FIND_TARGET_SAMPLES = 20;
|
|
21
21
|
const MAX_FIND_UPSTREAM_REQUEST_BUDGET = 50;
|
|
22
22
|
const DEFAULT_FIND_UPSTREAM_REQUEST_BUDGET = 20;
|
|
23
|
-
const DEFAULT_RESOLVE_UPSTREAM_REQUEST_BUDGET =
|
|
23
|
+
const DEFAULT_RESOLVE_UPSTREAM_REQUEST_BUDGET = 12;
|
|
24
24
|
const MAX_ACCEPTED_NUMERIC_LIMIT = 1_000;
|
|
25
25
|
const PROGRAM_COLLECTION_CONCURRENCY = 4;
|
|
26
26
|
const TARGET_SAMPLE_CONCURRENCY = 3;
|
|
@@ -31,12 +31,52 @@ const MAX_FILTER_TARGET_SAMPLE_PROGRAMS = 25;
|
|
|
31
31
|
const MAX_LOCAL_EXPORT_RESOURCES = 25;
|
|
32
32
|
const EXPORT_PREVIEW_LIMIT = 25;
|
|
33
33
|
const STALE_PROGRAM_ID_RESOLVE_PAGES = 1;
|
|
34
|
-
const STALE_PROGRAM_ID_RESOLVE_BUDGET =
|
|
34
|
+
const STALE_PROGRAM_ID_RESOLVE_BUDGET = 12;
|
|
35
35
|
const PROGRAM_FALLBACK_CACHE_MAX_ENTRIES = 200;
|
|
36
36
|
const MAX_RESOLVE_SEARCH_QUERIES = 4;
|
|
37
|
-
const
|
|
37
|
+
const FAST_RESOLVE_SEARCH_QUERIES = 2;
|
|
38
|
+
const MAX_DIRECT_HANDLE_RESOLUTION_REQUESTS = 96;
|
|
38
39
|
const SDK_VERSION = "1.29.0";
|
|
39
|
-
const
|
|
40
|
+
const CORE_TOOL_NAMES = new Set([
|
|
41
|
+
"search_programs",
|
|
42
|
+
"get_program",
|
|
43
|
+
"resolve_program",
|
|
44
|
+
"run_program_name_action",
|
|
45
|
+
"get_program_eligible_targets_by_name",
|
|
46
|
+
"get_program_targets",
|
|
47
|
+
"get_recent_changes",
|
|
48
|
+
"get_latest_added_targets",
|
|
49
|
+
"get_opportunities",
|
|
50
|
+
"find_programs",
|
|
51
|
+
"find_programs_by_target",
|
|
52
|
+
"list_filters",
|
|
53
|
+
"get_bbradar_guide",
|
|
54
|
+
"get_mcp_status",
|
|
55
|
+
"export_targets"
|
|
56
|
+
]);
|
|
57
|
+
const PROGRAM_ID_PLATFORM_GUESSES = [
|
|
58
|
+
"HackerOne",
|
|
59
|
+
"BugCrowd",
|
|
60
|
+
"Intigriti",
|
|
61
|
+
"HackenProof",
|
|
62
|
+
"YesWeHack",
|
|
63
|
+
"Immunefi",
|
|
64
|
+
"Cantina",
|
|
65
|
+
"Code4rena",
|
|
66
|
+
"Huntr",
|
|
67
|
+
"GObugfree",
|
|
68
|
+
"Bug Bounty Switzerland",
|
|
69
|
+
"Inspectiv",
|
|
70
|
+
"Compass Security",
|
|
71
|
+
"BugRap",
|
|
72
|
+
"IssueHunt",
|
|
73
|
+
"PatchDay",
|
|
74
|
+
"Remedy",
|
|
75
|
+
"Standoff365",
|
|
76
|
+
"Sherlock",
|
|
77
|
+
"CodeHawks"
|
|
78
|
+
];
|
|
79
|
+
const PRIMARY_PROGRAM_ID_PLATFORM_GUESSES = PROGRAM_ID_PLATFORM_GUESSES.slice(0, 8);
|
|
40
80
|
const WEB3_TAGS = ["web3", "crypto", "blockchain", "smart-contract", "smart contract", "defi", "nft", "ethereum", "solana", "solidity"];
|
|
41
81
|
const WEB3_CONTEST_SIGNALS = ["contest", "audit", "competitive", "code4rena", "sherlock", "cantina", "codehawks", "hats", "spearbit", "warden"];
|
|
42
82
|
const VDP_SIGNALS = ["vdp", "disclosure", "responsible disclosure", "vulnerability disclosure", "no bounty", "non-bounty", "no reward"];
|
|
@@ -150,7 +190,7 @@ function stringListSchema(description = "Optional filter values. Empty or omitte
|
|
|
150
190
|
.describe(description);
|
|
151
191
|
}
|
|
152
192
|
function platformListSchema() {
|
|
153
|
-
return stringListSchema("Optional platform filters such as HackerOne,
|
|
193
|
+
return stringListSchema("Optional platform filters such as HackerOne, BugCrowd, Intigriti, HackenProof, Immunefi, YesWeHack, Cantina, or GObugfree. Empty or omitted means all platforms.");
|
|
154
194
|
}
|
|
155
195
|
function tagListSchema() {
|
|
156
196
|
return stringListSchema("Optional combined scope/language/target-type tags. Empty or omitted means all tags.");
|
|
@@ -206,7 +246,7 @@ const programIdSchema = z
|
|
|
206
246
|
.trim()
|
|
207
247
|
.min(3)
|
|
208
248
|
.max(256)
|
|
209
|
-
.regex(/^[
|
|
249
|
+
.regex(/^[^:/?#\u0000-\u001F\u007F]+:[^?#\u0000-\u001F\u007F]+$/, "Use platform:handle, for example HackerOne:example-handle.")
|
|
210
250
|
.refine((value) => !/[\u0000-\u001F\u007F]/.test(value), {
|
|
211
251
|
message: "program_id cannot contain control characters."
|
|
212
252
|
})
|
|
@@ -324,12 +364,26 @@ const programCandidatePresetInputSchema = {
|
|
|
324
364
|
output_mode: programListModeSchema.default("compact"),
|
|
325
365
|
upstream_request_budget: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(30)
|
|
326
366
|
};
|
|
367
|
+
function createProfiledToolRegistrar(server, profile) {
|
|
368
|
+
return (name, toolConfig, callback) => {
|
|
369
|
+
const registeredTool = server.registerTool(name, toolConfig, callback);
|
|
370
|
+
if (profile === "core" && !CORE_TOOL_NAMES.has(name)) {
|
|
371
|
+
registeredTool.disable();
|
|
372
|
+
}
|
|
373
|
+
return registeredTool;
|
|
374
|
+
};
|
|
375
|
+
}
|
|
327
376
|
export function createBbradarServer(client, config) {
|
|
328
|
-
const
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
377
|
+
const instructions = config.toolProfile === "core"
|
|
378
|
+
? [
|
|
379
|
+
"Use BBRadar only for passive bug bounty intelligence; never scan or contact targets.",
|
|
380
|
+
"This server uses the compact core tool profile. Start with get_bbradar_guide when unsure.",
|
|
381
|
+
"Use resolve_program or run_program_name_action for a named program.",
|
|
382
|
+
"Use find_programs for general discovery and find_programs_by_target for reverse target lookup.",
|
|
383
|
+
"Default to bounty-eligible in-scope data unless the user explicitly requests broader results.",
|
|
384
|
+
"Prefer compact/identifier outputs and disclose sampled ranking scope when present."
|
|
385
|
+
]
|
|
386
|
+
: [
|
|
333
387
|
"Use BBRadar only for passive bug bounty intelligence; never scan or contact targets.",
|
|
334
388
|
"When unsure which BBRadar tool to use, call get_bbradar_guide first. It is local-only and cheap.",
|
|
335
389
|
"Simple default path: find_program_candidates for discovery, run_program_name_action for one named program, get_program_eligible_targets_by_name for target lists by program name.",
|
|
@@ -338,14 +392,20 @@ export function createBbradarServer(client, config) {
|
|
|
338
392
|
"Default to bounty-eligible in-scope data. Include out-of-scope, ineligible, VDP, disclosure-only, or no-bounty data only when the user explicitly asks.",
|
|
339
393
|
"Recently updated programs across platforms: find_recently_updated_programs. Do not infer a platform such as Immunefi unless the user explicitly names it.",
|
|
340
394
|
"Freshness: find_recently_updated_programs, get_latest_added_targets, check_program_new_targets, check_watchlist_new_targets, find_recent_by_type, get_program_scope_delta.",
|
|
341
|
-
"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.",
|
|
395
|
+
"Discovery: find_program_candidates, find_wildcard_programs, find_recently_added_wildcards, find_low_competition_high_reward, 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.",
|
|
342
396
|
"Details: get_program_brief, get_program_scope_summary, get_program_target_breakdown, find_programs_by_target, compare_programs_compact.",
|
|
343
397
|
"Prefer compact/identifier outputs and disclose sampled ranking scope when present."
|
|
344
|
-
]
|
|
398
|
+
];
|
|
399
|
+
const server = new McpServer({
|
|
400
|
+
name: "bbradar.io",
|
|
401
|
+
version: SERVER_VERSION
|
|
402
|
+
}, {
|
|
403
|
+
instructions: instructions.join("\n")
|
|
345
404
|
});
|
|
405
|
+
const registerTool = createProfiledToolRegistrar(server, config.toolProfile);
|
|
346
406
|
const rateLimiter = new FixedWindowRateLimiter();
|
|
347
407
|
const exportStore = new Map();
|
|
348
|
-
|
|
408
|
+
registerTool("search_programs", {
|
|
349
409
|
title: "Search BBRadar Programs",
|
|
350
410
|
description: `List active programs. page_size max ${MAX_PROGRAMS_PER_SEARCH}.`,
|
|
351
411
|
inputSchema: {
|
|
@@ -371,7 +431,7 @@ export function createBbradarServer(client, config) {
|
|
|
371
431
|
meta: sanitizeJson(data?.meta)
|
|
372
432
|
});
|
|
373
433
|
}));
|
|
374
|
-
|
|
434
|
+
registerTool("get_program", {
|
|
375
435
|
title: "Get BBRadar Program",
|
|
376
436
|
description: "Get one program by platform:handle program_id, handle, or human program name. Names are resolved automatically when unambiguous.",
|
|
377
437
|
inputSchema: {
|
|
@@ -407,7 +467,7 @@ export function createBbradarServer(client, config) {
|
|
|
407
467
|
}
|
|
408
468
|
}));
|
|
409
469
|
}));
|
|
410
|
-
|
|
470
|
+
registerTool("resolve_program", {
|
|
411
471
|
title: "Resolve Program",
|
|
412
472
|
description: "Resolve partial name/handle text to likely program_ids. Use this when the user gives a program name fragment like Nado.",
|
|
413
473
|
inputSchema: {
|
|
@@ -431,7 +491,7 @@ export function createBbradarServer(client, config) {
|
|
|
431
491
|
},
|
|
432
492
|
annotations: readOnlyAnnotations
|
|
433
493
|
}, (args) => runTool("resolve_program", config, rateLimiter, async () => resolveProgram(client, config, args)));
|
|
434
|
-
|
|
494
|
+
registerTool("run_program_name_action", {
|
|
435
495
|
title: "Run Program Name Action",
|
|
436
496
|
description: "Start here for one named program. Resolves a partial program name, then runs targets, new_targets, scope_delta, brief, or target_breakdown.",
|
|
437
497
|
inputSchema: {
|
|
@@ -465,7 +525,7 @@ export function createBbradarServer(client, config) {
|
|
|
465
525
|
},
|
|
466
526
|
annotations: readOnlyAnnotations
|
|
467
527
|
}, (args) => runTool("run_program_name_action", config, rateLimiter, async () => runProgramNameAction(client, config, args)));
|
|
468
|
-
|
|
528
|
+
registerTool("get_program_eligible_targets_by_name", {
|
|
469
529
|
title: "Get Eligible Targets By Program Name",
|
|
470
530
|
description: "Fast one-call path for user asks like 'find in-scope eligible targets for Aztec': resolve the program name, fetch targets, and return filtered in-scope bounty-eligible targets. Prefer this over separate resolve_program + get_program_targets calls when the user asks for a target list by name.",
|
|
471
531
|
inputSchema: {
|
|
@@ -496,7 +556,7 @@ export function createBbradarServer(client, config) {
|
|
|
496
556
|
},
|
|
497
557
|
annotations: readOnlyAnnotations
|
|
498
558
|
}, (args) => runTool("get_program_eligible_targets_by_name", config, rateLimiter, async () => getProgramEligibleTargetsByName(client, config, args)));
|
|
499
|
-
|
|
559
|
+
registerTool("get_program_targets", {
|
|
500
560
|
title: "Get Program Targets",
|
|
501
561
|
description: `Get active targets for one program_id, handle, or human program name. This is a final source for eligible target lists; do not call summary/breakdown unless grouped counts are needed. For a program name query, get_program_eligible_targets_by_name is the simplest path. limit max ${MAX_TARGETS_PER_PROGRAM}.`,
|
|
502
562
|
inputSchema: {
|
|
@@ -518,7 +578,7 @@ export function createBbradarServer(client, config) {
|
|
|
518
578
|
}, (args) => runTool("get_program_targets", config, rateLimiter, async () => {
|
|
519
579
|
return runProgramIdTool(client, config, args.program_id, async (programId) => getProgramTargetsPayload(client, args, programId), async (indexFallback) => getProgramTargetsExportFallbackPayload(client, args, indexFallback.resolvedProgramId));
|
|
520
580
|
}));
|
|
521
|
-
|
|
581
|
+
registerTool("get_recent_changes", {
|
|
522
582
|
title: "Get Recent Target Changes",
|
|
523
583
|
description: `List recent target changes. page_size max ${MAX_RECENT_CHANGES}.`,
|
|
524
584
|
inputSchema: {
|
|
@@ -557,15 +617,22 @@ export function createBbradarServer(client, config) {
|
|
|
557
617
|
facets: sanitizeJson(data?.facets)
|
|
558
618
|
});
|
|
559
619
|
}));
|
|
560
|
-
|
|
620
|
+
registerTool("get_latest_added_targets", {
|
|
561
621
|
title: "Get Latest Added Targets",
|
|
562
|
-
description: "Latest added targets by type,
|
|
622
|
+
description: "Latest added targets by type with optional platform, reward, and report-count filters. For fast wildcard discovery use target_type=wildcard and include_full_target_list=false.",
|
|
563
623
|
inputSchema: {
|
|
564
624
|
target_type: filterValueSchema().default("domain"),
|
|
625
|
+
platforms: platformListSchema(),
|
|
626
|
+
min_reward: z.number().int().min(0).optional(),
|
|
627
|
+
reward_threshold_mode: rewardThresholdModeSchema.default("max_at_least"),
|
|
628
|
+
max_public_report_count: z.number().int().min(0).optional(),
|
|
629
|
+
require_known_report_count: z.boolean().default(false),
|
|
630
|
+
include_unknown_report_count: z.boolean().default(true),
|
|
565
631
|
include_out_of_scope: z.boolean().default(false).describe("Include out-of-scope."),
|
|
566
632
|
include_ineligible: z.boolean().default(false).describe("Include ineligible."),
|
|
567
633
|
recent_changes_page_size: recentChangesPageSizeSchema.default(MAX_RECENT_CHANGES),
|
|
568
|
-
|
|
634
|
+
max_results: z.number().int().min(1).max(MAX_RECENT_CHANGES).default(10),
|
|
635
|
+
include_full_target_list: z.boolean().default(true).describe("Fetch the newest matching program's complete target list. Set false for the fastest cross-program discovery."),
|
|
569
636
|
full_target_list_mode: targetListModeSchema.default("identifiers"),
|
|
570
637
|
full_target_limit: z.number().int().min(1).max(MAX_TARGETS_PER_PROGRAM).default(MAX_TARGETS_PER_PROGRAM),
|
|
571
638
|
full_list_include_out_of_scope: z.boolean().default(false),
|
|
@@ -577,13 +644,14 @@ export function createBbradarServer(client, config) {
|
|
|
577
644
|
query: jsonRecordSchema().optional(),
|
|
578
645
|
latest_added_at: z.string().optional(),
|
|
579
646
|
program: programOutputSchema().optional(),
|
|
647
|
+
matches: z.array(jsonRecordSchema()).optional(),
|
|
580
648
|
new_targets: z.array(z.unknown()).optional(),
|
|
581
649
|
full_targets: z.array(z.unknown()).optional(),
|
|
582
650
|
meta: jsonRecordSchema().optional()
|
|
583
651
|
},
|
|
584
652
|
annotations: readOnlyAnnotations
|
|
585
653
|
}, (args) => runTool("get_latest_added_targets", config, rateLimiter, async () => getLatestAddedTargets(client, config, args)));
|
|
586
|
-
|
|
654
|
+
registerTool("get_program_scope_summary", {
|
|
587
655
|
title: "Get Program Scope Summary",
|
|
588
656
|
description: "Grouped compact scope for one program.",
|
|
589
657
|
inputSchema: {
|
|
@@ -602,7 +670,7 @@ export function createBbradarServer(client, config) {
|
|
|
602
670
|
},
|
|
603
671
|
annotations: readOnlyAnnotations
|
|
604
672
|
}, (args) => runTool("get_program_scope_summary", config, rateLimiter, async () => runProgramIdTool(client, config, args.program_id, (programId) => getProgramScopeSummary(client, config, { ...args, program_id: programId }), (indexFallback) => getProgramScopeSummaryFromIndexFallback(client, indexFallback, { ...args, program_id: indexFallback.resolvedProgramId }))));
|
|
605
|
-
|
|
673
|
+
registerTool("get_program_target_breakdown", {
|
|
606
674
|
title: "Get Program Target Breakdown",
|
|
607
675
|
description: "Target type, scope tag, language tag, and bucket counts for one program.",
|
|
608
676
|
inputSchema: {
|
|
@@ -621,7 +689,7 @@ export function createBbradarServer(client, config) {
|
|
|
621
689
|
},
|
|
622
690
|
annotations: readOnlyAnnotations
|
|
623
691
|
}, (args) => runTool("get_program_target_breakdown", config, rateLimiter, async () => runProgramIdTool(client, config, args.program_id, (programId) => getProgramTargetBreakdown(client, config, { ...args, program_id: programId }), (indexFallback) => getProgramTargetBreakdownFromIndexFallback(client, indexFallback, { ...args, program_id: indexFallback.resolvedProgramId }))));
|
|
624
|
-
|
|
692
|
+
registerTool("get_program_scope_delta", {
|
|
625
693
|
title: "Get Program Scope Delta",
|
|
626
694
|
description: "Compact recent scope changes for one program.",
|
|
627
695
|
inputSchema: {
|
|
@@ -647,7 +715,7 @@ export function createBbradarServer(client, config) {
|
|
|
647
715
|
},
|
|
648
716
|
annotations: readOnlyAnnotations
|
|
649
717
|
}, (args) => runTool("get_program_scope_delta", config, rateLimiter, async () => runProgramIdTool(client, config, args.program_id, (programId) => getProgramScopeDelta(client, config, { ...args, program_id: programId }), (indexFallback) => getProgramScopeDeltaFromIndexedProgram(client, config, { ...args, program_id: indexFallback.resolvedProgramId }, indexFallback.program))));
|
|
650
|
-
|
|
718
|
+
registerTool("get_recent_target_activity", {
|
|
651
719
|
title: "Get Recent Target Activity",
|
|
652
720
|
description: "Recent target changes grouped by program.",
|
|
653
721
|
inputSchema: {
|
|
@@ -673,7 +741,7 @@ export function createBbradarServer(client, config) {
|
|
|
673
741
|
},
|
|
674
742
|
annotations: readOnlyAnnotations
|
|
675
743
|
}, (args) => runTool("get_recent_target_activity", config, rateLimiter, async () => getRecentTargetActivity(client, config, args)));
|
|
676
|
-
|
|
744
|
+
registerTool("check_program_new_targets", {
|
|
677
745
|
title: "Check Program New Targets",
|
|
678
746
|
description: "Yes/no new in-scope targets for one program.",
|
|
679
747
|
inputSchema: {
|
|
@@ -698,7 +766,7 @@ export function createBbradarServer(client, config) {
|
|
|
698
766
|
},
|
|
699
767
|
annotations: readOnlyAnnotations
|
|
700
768
|
}, (args) => runTool("check_program_new_targets", config, rateLimiter, async () => runProgramIdTool(client, config, args.program_id, (programId) => checkProgramNewTargets(client, config, { ...args, program_id: programId }), (indexFallback) => checkProgramNewTargetsFromIndexedProgram(client, config, { ...args, program_id: indexFallback.resolvedProgramId }, indexFallback.program))));
|
|
701
|
-
|
|
769
|
+
registerTool("check_watchlist_new_targets", {
|
|
702
770
|
title: "Check Watchlist New Targets",
|
|
703
771
|
description: "New in-scope targets across many known program_ids.",
|
|
704
772
|
inputSchema: {
|
|
@@ -719,7 +787,7 @@ export function createBbradarServer(client, config) {
|
|
|
719
787
|
},
|
|
720
788
|
annotations: readOnlyAnnotations
|
|
721
789
|
}, (args) => runTool("check_watchlist_new_targets", config, rateLimiter, async () => checkWatchlistNewTargets(client, config, args)));
|
|
722
|
-
|
|
790
|
+
registerTool("get_opportunities", {
|
|
723
791
|
title: "Get BBRadar Opportunities",
|
|
724
792
|
description: `List programs in one opportunity tier. page_size max ${MAX_PROGRAMS_PER_SEARCH}.`,
|
|
725
793
|
inputSchema: {
|
|
@@ -750,7 +818,7 @@ export function createBbradarServer(client, config) {
|
|
|
750
818
|
meta: sanitizeJson(data?.meta)
|
|
751
819
|
});
|
|
752
820
|
}));
|
|
753
|
-
|
|
821
|
+
registerTool("find_programs", {
|
|
754
822
|
title: "Find BBRadar Programs",
|
|
755
823
|
description: "Ranked program discovery with filters and compact output.",
|
|
756
824
|
inputSchema: {
|
|
@@ -798,24 +866,30 @@ export function createBbradarServer(client, config) {
|
|
|
798
866
|
...args,
|
|
799
867
|
target_samples_only_wildcards: args.min_wildcards > 0
|
|
800
868
|
})));
|
|
801
|
-
|
|
869
|
+
registerTool("find_wildcard_programs", {
|
|
802
870
|
title: "Find Wildcard Programs",
|
|
803
|
-
description: "Wildcard program shortlist.",
|
|
871
|
+
description: "Wildcard program shortlist with platform, minimum reward, freshness, and report-count filters.",
|
|
804
872
|
inputSchema: {
|
|
805
873
|
platforms: platformListSchema(),
|
|
806
874
|
tags: tagListSchema(),
|
|
807
875
|
updated_since: isoDateTimeSchema.optional(),
|
|
876
|
+
min_reward: z.number().int().min(0).optional(),
|
|
877
|
+
reward_threshold_mode: rewardThresholdModeSchema.default("max_at_least"),
|
|
808
878
|
max_public_report_count: z.number().int().min(0).default(25),
|
|
809
879
|
require_known_report_count: z.boolean().default(true),
|
|
810
880
|
include_unknown_report_count: z.boolean().default(false),
|
|
811
881
|
exclude_ctf: z.boolean().default(true),
|
|
812
882
|
require_bounty: z.boolean().default(true),
|
|
813
883
|
min_wildcards: z.number().int().min(1).max(100).default(1),
|
|
884
|
+
min_added_24h: z.number().int().min(0).optional(),
|
|
885
|
+
min_added_7d: z.number().int().min(0).optional(),
|
|
886
|
+
sort_by: programSortSchema.default("best_hunt_value"),
|
|
814
887
|
max_pages: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(3),
|
|
815
888
|
max_results: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(10),
|
|
816
889
|
include_target_samples: z.boolean().default(false),
|
|
817
890
|
target_sample_programs: z.number().int().min(0).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(3),
|
|
818
891
|
target_sample_size: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(5),
|
|
892
|
+
output_mode: programListModeSchema.default("compact"),
|
|
819
893
|
upstream_request_budget: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(DEFAULT_FIND_UPSTREAM_REQUEST_BUDGET)
|
|
820
894
|
},
|
|
821
895
|
outputSchema: findProgramsOutputShape(),
|
|
@@ -829,8 +903,8 @@ export function createBbradarServer(client, config) {
|
|
|
829
903
|
web3: false,
|
|
830
904
|
opportunity_levels: [],
|
|
831
905
|
updated_since: args.updated_since,
|
|
832
|
-
min_bounty_min: undefined,
|
|
833
|
-
min_bounty_max: undefined,
|
|
906
|
+
min_bounty_min: args.min_reward !== undefined && args.reward_threshold_mode === "min_at_least" ? args.min_reward : undefined,
|
|
907
|
+
min_bounty_max: args.min_reward !== undefined && args.reward_threshold_mode === "max_at_least" ? args.min_reward : undefined,
|
|
834
908
|
max_bounty_max: undefined,
|
|
835
909
|
require_bounty: args.require_bounty,
|
|
836
910
|
max_public_report_count: args.max_public_report_count,
|
|
@@ -845,20 +919,21 @@ export function createBbradarServer(client, config) {
|
|
|
845
919
|
min_wildcards: args.min_wildcards,
|
|
846
920
|
min_eligible_targets: undefined,
|
|
847
921
|
min_total_targets: undefined,
|
|
848
|
-
min_added_24h:
|
|
849
|
-
min_added_7d:
|
|
922
|
+
min_added_24h: args.min_added_24h,
|
|
923
|
+
min_added_7d: args.min_added_7d,
|
|
850
924
|
max_pages: args.max_pages,
|
|
851
925
|
max_results: args.max_results,
|
|
852
|
-
sort_by:
|
|
926
|
+
sort_by: args.sort_by,
|
|
853
927
|
include_target_samples: args.include_target_samples,
|
|
854
928
|
target_sample_programs: args.target_sample_programs,
|
|
855
929
|
target_sample_size: args.target_sample_size,
|
|
856
930
|
only_in_scope_targets: true,
|
|
857
931
|
only_bounty_eligible_targets: true,
|
|
858
932
|
target_samples_only_wildcards: true,
|
|
933
|
+
output_mode: args.output_mode,
|
|
859
934
|
upstream_request_budget: args.upstream_request_budget
|
|
860
935
|
})));
|
|
861
|
-
|
|
936
|
+
registerTool("find_web3_programs", {
|
|
862
937
|
title: "Find Web3 Programs",
|
|
863
938
|
description: "Web3/blockchain/smart-contract program shortlist.",
|
|
864
939
|
inputSchema: {
|
|
@@ -919,7 +994,7 @@ export function createBbradarServer(client, config) {
|
|
|
919
994
|
target_samples_only_wildcards: args.min_wildcards > 0,
|
|
920
995
|
upstream_request_budget: args.upstream_request_budget
|
|
921
996
|
})));
|
|
922
|
-
|
|
997
|
+
registerTool("find_reward_programs", {
|
|
923
998
|
title: "Find Reward Programs",
|
|
924
999
|
description: "Programs matching reward thresholds.",
|
|
925
1000
|
inputSchema: {
|
|
@@ -948,7 +1023,7 @@ export function createBbradarServer(client, config) {
|
|
|
948
1023
|
outputSchema: findProgramsOutputShape(),
|
|
949
1024
|
annotations: readOnlyAnnotations
|
|
950
1025
|
}, (args) => runTool("find_reward_programs", config, rateLimiter, async () => findRewardPrograms(client, config, args)));
|
|
951
|
-
|
|
1026
|
+
registerTool("find_web3_contests", {
|
|
952
1027
|
title: "Find Web3 Contests",
|
|
953
1028
|
description: "Web3 contest or competitive audit shortlist.",
|
|
954
1029
|
inputSchema: {
|
|
@@ -971,7 +1046,7 @@ export function createBbradarServer(client, config) {
|
|
|
971
1046
|
outputSchema: findProgramsOutputShape(),
|
|
972
1047
|
annotations: readOnlyAnnotations
|
|
973
1048
|
}, (args) => runTool("find_web3_contests", config, rateLimiter, async () => findWeb3Contests(client, config, args)));
|
|
974
|
-
|
|
1049
|
+
registerTool("find_language_programs", {
|
|
975
1050
|
title: "Find Language Programs",
|
|
976
1051
|
description: "Language-specific program shortlist.",
|
|
977
1052
|
inputSchema: {
|
|
@@ -998,7 +1073,7 @@ export function createBbradarServer(client, config) {
|
|
|
998
1073
|
outputSchema: findProgramsOutputShape(),
|
|
999
1074
|
annotations: readOnlyAnnotations
|
|
1000
1075
|
}, (args) => runTool("find_language_programs", config, rateLimiter, async () => findLanguagePrograms(client, config, args)));
|
|
1001
|
-
|
|
1076
|
+
registerTool("find_target_type_programs", {
|
|
1002
1077
|
title: "Find Target Type Programs",
|
|
1003
1078
|
description: "Target-type program shortlist.",
|
|
1004
1079
|
inputSchema: {
|
|
@@ -1024,7 +1099,7 @@ export function createBbradarServer(client, config) {
|
|
|
1024
1099
|
outputSchema: findProgramsOutputShape(),
|
|
1025
1100
|
annotations: readOnlyAnnotations
|
|
1026
1101
|
}, (args) => runTool("find_target_type_programs", config, rateLimiter, async () => findTargetTypePrograms(client, config, args)));
|
|
1027
|
-
|
|
1102
|
+
registerTool("find_vdp_programs", {
|
|
1028
1103
|
title: "Find VDP Programs",
|
|
1029
1104
|
description: "VDP/disclosure-only/no-bounty program shortlist.",
|
|
1030
1105
|
inputSchema: {
|
|
@@ -1044,7 +1119,7 @@ export function createBbradarServer(client, config) {
|
|
|
1044
1119
|
outputSchema: findProgramsOutputShape(),
|
|
1045
1120
|
annotations: readOnlyAnnotations
|
|
1046
1121
|
}, (args) => runTool("find_vdp_programs", config, rateLimiter, async () => findVdpPrograms(client, config, args)));
|
|
1047
|
-
|
|
1122
|
+
registerTool("find_paid_programs", {
|
|
1048
1123
|
title: "Find Paid Programs",
|
|
1049
1124
|
description: "Paid bounty program shortlist.",
|
|
1050
1125
|
inputSchema: {
|
|
@@ -1067,7 +1142,7 @@ export function createBbradarServer(client, config) {
|
|
|
1067
1142
|
outputSchema: findProgramsOutputShape(),
|
|
1068
1143
|
annotations: readOnlyAnnotations
|
|
1069
1144
|
}, (args) => runTool("find_paid_programs", config, rateLimiter, async () => findPaidPrograms(client, config, args)));
|
|
1070
|
-
|
|
1145
|
+
registerTool("find_stack_matches", {
|
|
1071
1146
|
title: "Find Stack Matches",
|
|
1072
1147
|
description: "Combined stack/language/type/reward shortlist.",
|
|
1073
1148
|
inputSchema: {
|
|
@@ -1097,7 +1172,7 @@ export function createBbradarServer(client, config) {
|
|
|
1097
1172
|
outputSchema: findProgramsOutputShape(),
|
|
1098
1173
|
annotations: readOnlyAnnotations
|
|
1099
1174
|
}, (args) => runTool("find_stack_matches", config, rateLimiter, async () => findStackMatches(client, config, args)));
|
|
1100
|
-
|
|
1175
|
+
registerTool("find_low_noise_programs", {
|
|
1101
1176
|
title: "Find Low Noise Programs",
|
|
1102
1177
|
description: "Low-public-report program shortlist.",
|
|
1103
1178
|
inputSchema: {
|
|
@@ -1123,7 +1198,7 @@ export function createBbradarServer(client, config) {
|
|
|
1123
1198
|
outputSchema: findProgramsOutputShape(),
|
|
1124
1199
|
annotations: readOnlyAnnotations
|
|
1125
1200
|
}, (args) => runTool("find_low_noise_programs", config, rateLimiter, async () => findLowNoisePrograms(client, config, args)));
|
|
1126
|
-
|
|
1201
|
+
registerTool("find_recent_by_type", {
|
|
1127
1202
|
title: "Find Recent By Type",
|
|
1128
1203
|
description: "Recent changes by target type grouped by program.",
|
|
1129
1204
|
inputSchema: {
|
|
@@ -1149,7 +1224,7 @@ export function createBbradarServer(client, config) {
|
|
|
1149
1224
|
},
|
|
1150
1225
|
annotations: readOnlyAnnotations
|
|
1151
1226
|
}, (args) => runTool("find_recent_by_type", config, rateLimiter, async () => findRecentByType(client, config, args)));
|
|
1152
|
-
|
|
1227
|
+
registerTool("find_programs_by_target", {
|
|
1153
1228
|
title: "Find Programs By Target",
|
|
1154
1229
|
description: "Reverse lookup target/domain ownership. Accepts domains, URLs, wildcards, target names, and partial target text.",
|
|
1155
1230
|
inputSchema: {
|
|
@@ -1172,21 +1247,21 @@ export function createBbradarServer(client, config) {
|
|
|
1172
1247
|
},
|
|
1173
1248
|
annotations: readOnlyAnnotations
|
|
1174
1249
|
}, (args) => runTool("find_programs_by_target", config, rateLimiter, async () => findProgramsByTarget(client, config, args)));
|
|
1175
|
-
|
|
1250
|
+
registerTool("find_program_candidates", {
|
|
1176
1251
|
title: "Find BBRadar Program Candidates",
|
|
1177
1252
|
description: "Start here for broad program discovery. Default ranked hunt-candidate preset with safe filters and compact output.",
|
|
1178
1253
|
inputSchema: programCandidatePresetInputSchema,
|
|
1179
1254
|
outputSchema: findProgramsOutputShape(),
|
|
1180
1255
|
annotations: readOnlyAnnotations
|
|
1181
1256
|
}, (args) => runTool("find_program_candidates", config, rateLimiter, async () => findProgramCandidates(client, config, args)));
|
|
1182
|
-
|
|
1257
|
+
registerTool("find_hunt_candidates", {
|
|
1183
1258
|
title: "Find BBRadar Hunt Candidates",
|
|
1184
1259
|
description: "Legacy alias for find_program_candidates. Prefer find_program_candidates for new calls.",
|
|
1185
1260
|
inputSchema: programCandidatePresetInputSchema,
|
|
1186
1261
|
outputSchema: findProgramsOutputShape(),
|
|
1187
1262
|
annotations: readOnlyAnnotations
|
|
1188
1263
|
}, (args) => runTool("find_hunt_candidates", config, rateLimiter, async () => findProgramCandidates(client, config, args)));
|
|
1189
|
-
|
|
1264
|
+
registerTool("find_fresh_hunt_candidates", {
|
|
1190
1265
|
title: "Find Fresh Hunt Candidates",
|
|
1191
1266
|
description: "Freshness-first hunt shortlist.",
|
|
1192
1267
|
inputSchema: {
|
|
@@ -1255,7 +1330,7 @@ export function createBbradarServer(client, config) {
|
|
|
1255
1330
|
output_mode: args.output_mode,
|
|
1256
1331
|
upstream_request_budget: args.upstream_request_budget
|
|
1257
1332
|
})));
|
|
1258
|
-
|
|
1333
|
+
registerTool("find_recently_updated_programs", {
|
|
1259
1334
|
title: "Find Recently Updated Programs",
|
|
1260
1335
|
description: "Recently updated program shortlist across all platforms by default. Platform filters are applied only when explicitly provided.",
|
|
1261
1336
|
inputSchema: {
|
|
@@ -1280,7 +1355,7 @@ export function createBbradarServer(client, config) {
|
|
|
1280
1355
|
outputSchema: findProgramsOutputShape(),
|
|
1281
1356
|
annotations: readOnlyAnnotations
|
|
1282
1357
|
}, (args) => runTool("find_recently_updated_programs", config, rateLimiter, async () => findRecentlyUpdatedPrograms(client, config, args)));
|
|
1283
|
-
|
|
1358
|
+
registerTool("list_filters", {
|
|
1284
1359
|
title: "List BBRadar Filters",
|
|
1285
1360
|
description: "Discover observed filter values.",
|
|
1286
1361
|
inputSchema: {
|
|
@@ -1299,7 +1374,7 @@ export function createBbradarServer(client, config) {
|
|
|
1299
1374
|
},
|
|
1300
1375
|
annotations: readOnlyAnnotations
|
|
1301
1376
|
}, (args) => runTool("list_filters", config, rateLimiter, async () => listFilters(client, args)));
|
|
1302
|
-
|
|
1377
|
+
registerTool("get_bbradar_guide", {
|
|
1303
1378
|
title: "Get BBRadar Tool Guide",
|
|
1304
1379
|
description: "Local quick-start guide for choosing BBRadar tools. Call this when the user's request is ambiguous or when a smaller model may need a decision tree.",
|
|
1305
1380
|
inputSchema: {
|
|
@@ -1312,9 +1387,9 @@ export function createBbradarServer(client, config) {
|
|
|
1312
1387
|
annotations: readOnlyAnnotations
|
|
1313
1388
|
}, (args) => runTool("get_bbradar_guide", config, rateLimiter, async () => ({
|
|
1314
1389
|
request_id: randomUUID(),
|
|
1315
|
-
guide: bbradarGuide(args.topic)
|
|
1390
|
+
guide: bbradarGuide(args.topic, config.toolProfile)
|
|
1316
1391
|
})));
|
|
1317
|
-
|
|
1392
|
+
registerTool("get_mcp_status", {
|
|
1318
1393
|
title: "Get BBRadar MCP Status",
|
|
1319
1394
|
description: "Local MCP status and timing metrics.",
|
|
1320
1395
|
inputSchema: {},
|
|
@@ -1333,10 +1408,14 @@ export function createBbradarServer(client, config) {
|
|
|
1333
1408
|
web_base_url: config.webBaseUrl,
|
|
1334
1409
|
response_cache_enabled: config.cacheTtlMs > 0 && config.cacheMaxEntries > 0,
|
|
1335
1410
|
cache_ttl_ms: config.cacheTtlMs,
|
|
1411
|
+
negative_cache_ttl_ms: config.negativeCacheTtlMs,
|
|
1336
1412
|
cache_max_entries: config.cacheMaxEntries,
|
|
1337
1413
|
local_program_index_enabled: false,
|
|
1338
1414
|
default_rate_limit_per_minute: config.defaultRateLimitPerMinute,
|
|
1339
1415
|
export_rate_limit_per_minute: config.exportRateLimitPerMinute,
|
|
1416
|
+
max_concurrent_upstream_requests: config.maxConcurrentRequests,
|
|
1417
|
+
compact_tool_text: config.compactToolText,
|
|
1418
|
+
tool_profile: config.toolProfile,
|
|
1340
1419
|
request_timeout_ms: config.requestTimeoutMs,
|
|
1341
1420
|
validate_on_startup: config.validateOnStartup,
|
|
1342
1421
|
transport: "stdio",
|
|
@@ -1345,7 +1424,7 @@ export function createBbradarServer(client, config) {
|
|
|
1345
1424
|
client: client.diagnostics()
|
|
1346
1425
|
}
|
|
1347
1426
|
})));
|
|
1348
|
-
|
|
1427
|
+
registerTool("compare_programs", {
|
|
1349
1428
|
title: "Compare BBRadar Programs",
|
|
1350
1429
|
description: "Compare programs.",
|
|
1351
1430
|
inputSchema: {
|
|
@@ -1359,7 +1438,7 @@ export function createBbradarServer(client, config) {
|
|
|
1359
1438
|
outputSchema: decisionOutputShape(),
|
|
1360
1439
|
annotations: readOnlyAnnotations
|
|
1361
1440
|
}, (args) => runTool("compare_programs", config, rateLimiter, async () => comparePrograms(client, config, args)));
|
|
1362
|
-
|
|
1441
|
+
registerTool("compare_programs_compact", {
|
|
1363
1442
|
title: "Compare Programs Compact",
|
|
1364
1443
|
description: "Compact program comparison.",
|
|
1365
1444
|
inputSchema: {
|
|
@@ -1372,7 +1451,7 @@ export function createBbradarServer(client, config) {
|
|
|
1372
1451
|
outputSchema: decisionOutputShape(),
|
|
1373
1452
|
annotations: readOnlyAnnotations
|
|
1374
1453
|
}, (args) => runTool("compare_programs_compact", config, rateLimiter, async () => comparePrograms(client, config, { ...args, output_mode: "compact" })));
|
|
1375
|
-
|
|
1454
|
+
registerTool("summarize_program_activity", {
|
|
1376
1455
|
title: "Summarize Program Activity",
|
|
1377
1456
|
description: "Summarize one program's activity.",
|
|
1378
1457
|
inputSchema: {
|
|
@@ -1387,7 +1466,7 @@ export function createBbradarServer(client, config) {
|
|
|
1387
1466
|
},
|
|
1388
1467
|
annotations: readOnlyAnnotations
|
|
1389
1468
|
}, (args) => runTool("summarize_program_activity", config, rateLimiter, async () => runProgramIdTool(client, config, args.program_id, (programId) => summarizeProgramActivity(client, config, programId, args.recent_changes_limit))));
|
|
1390
|
-
|
|
1469
|
+
registerTool("get_program_brief", {
|
|
1391
1470
|
title: "Get Program Brief",
|
|
1392
1471
|
description: "Compact worth-hunting brief for one program.",
|
|
1393
1472
|
inputSchema: {
|
|
@@ -1410,17 +1489,28 @@ export function createBbradarServer(client, config) {
|
|
|
1410
1489
|
},
|
|
1411
1490
|
annotations: readOnlyAnnotations
|
|
1412
1491
|
}, (args) => runTool("get_program_brief", config, rateLimiter, async () => runProgramIdTool(client, config, args.program_id, (programId) => getProgramBrief(client, config, { ...args, program_id: programId }))));
|
|
1413
|
-
|
|
1492
|
+
registerTool("find_recently_added_wildcards", {
|
|
1414
1493
|
title: "Find Recently Added Wildcards",
|
|
1415
|
-
description: "Recently added wildcard programs.",
|
|
1494
|
+
description: "Recently added wildcard programs with platform, minimum reward, and report-count filters. Returns bounded wildcard target samples.",
|
|
1416
1495
|
inputSchema: {
|
|
1417
1496
|
platforms: platformListSchema(),
|
|
1418
1497
|
tags: tagListSchema(),
|
|
1498
|
+
min_added_24h: z.number().int().min(0).optional(),
|
|
1419
1499
|
min_added_7d: z.number().int().min(1).default(1),
|
|
1500
|
+
min_reward: z.number().int().min(0).optional(),
|
|
1501
|
+
reward_threshold_mode: rewardThresholdModeSchema.default("max_at_least"),
|
|
1420
1502
|
max_public_report_count: z.number().int().min(0).optional(),
|
|
1503
|
+
require_known_report_count: z.boolean().default(false),
|
|
1504
|
+
include_unknown_report_count: z.boolean().default(false),
|
|
1421
1505
|
require_bounty: z.boolean().default(true),
|
|
1506
|
+
min_wildcards: z.number().int().min(1).max(100).default(1),
|
|
1507
|
+
sort_by: programSortSchema.default("most_new_targets"),
|
|
1422
1508
|
max_pages: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(3),
|
|
1423
1509
|
max_results: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(10),
|
|
1510
|
+
include_target_samples: z.boolean().default(true),
|
|
1511
|
+
target_sample_programs: z.number().int().min(0).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(3),
|
|
1512
|
+
target_sample_size: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(5),
|
|
1513
|
+
output_mode: programListModeSchema.default("compact"),
|
|
1424
1514
|
upstream_request_budget: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(DEFAULT_FIND_UPSTREAM_REQUEST_BUDGET)
|
|
1425
1515
|
},
|
|
1426
1516
|
outputSchema: findProgramsOutputShape(),
|
|
@@ -1434,37 +1524,38 @@ export function createBbradarServer(client, config) {
|
|
|
1434
1524
|
web3: false,
|
|
1435
1525
|
opportunity_levels: [],
|
|
1436
1526
|
updated_since: undefined,
|
|
1437
|
-
min_bounty_min: undefined,
|
|
1438
|
-
min_bounty_max: undefined,
|
|
1527
|
+
min_bounty_min: args.min_reward !== undefined && args.reward_threshold_mode === "min_at_least" ? args.min_reward : undefined,
|
|
1528
|
+
min_bounty_max: args.min_reward !== undefined && args.reward_threshold_mode === "max_at_least" ? args.min_reward : undefined,
|
|
1439
1529
|
max_bounty_max: undefined,
|
|
1440
1530
|
require_bounty: args.require_bounty,
|
|
1441
1531
|
max_public_report_count: args.max_public_report_count,
|
|
1442
|
-
require_known_report_count:
|
|
1443
|
-
include_unknown_report_count:
|
|
1532
|
+
require_known_report_count: args.require_known_report_count,
|
|
1533
|
+
include_unknown_report_count: args.include_unknown_report_count,
|
|
1444
1534
|
exclude_ctf: true,
|
|
1445
1535
|
exclude_mobile_only: false,
|
|
1446
1536
|
exclude_non_web: false,
|
|
1447
1537
|
has_api_scope: false,
|
|
1448
1538
|
fresh_launch_days: undefined,
|
|
1449
|
-
min_wildcards:
|
|
1539
|
+
min_wildcards: args.min_wildcards,
|
|
1450
1540
|
min_eligible_targets: undefined,
|
|
1451
1541
|
min_total_targets: undefined,
|
|
1452
|
-
min_added_24h:
|
|
1542
|
+
min_added_24h: args.min_added_24h,
|
|
1453
1543
|
min_added_7d: args.min_added_7d,
|
|
1454
1544
|
max_pages: args.max_pages,
|
|
1455
1545
|
max_results: args.max_results,
|
|
1456
|
-
sort_by:
|
|
1457
|
-
include_target_samples:
|
|
1458
|
-
target_sample_programs:
|
|
1459
|
-
target_sample_size:
|
|
1546
|
+
sort_by: args.sort_by,
|
|
1547
|
+
include_target_samples: args.include_target_samples,
|
|
1548
|
+
target_sample_programs: args.target_sample_programs,
|
|
1549
|
+
target_sample_size: args.target_sample_size,
|
|
1460
1550
|
only_in_scope_targets: true,
|
|
1461
1551
|
only_bounty_eligible_targets: true,
|
|
1462
1552
|
target_samples_only_wildcards: true,
|
|
1553
|
+
output_mode: args.output_mode,
|
|
1463
1554
|
upstream_request_budget: args.upstream_request_budget
|
|
1464
1555
|
})));
|
|
1465
|
-
|
|
1556
|
+
registerTool("find_low_competition_high_reward", {
|
|
1466
1557
|
title: "Find Low Competition High Reward",
|
|
1467
|
-
description: "
|
|
1558
|
+
description: "Low-report, high-reward shortlist with optional wildcard and technology filters.",
|
|
1468
1559
|
inputSchema: {
|
|
1469
1560
|
platforms: platformListSchema(),
|
|
1470
1561
|
tags: tagListSchema(),
|
|
@@ -1473,6 +1564,7 @@ export function createBbradarServer(client, config) {
|
|
|
1473
1564
|
max_public_report_count: z.number().int().min(0).default(10),
|
|
1474
1565
|
min_bounty_max: z.number().int().min(0).default(5_000),
|
|
1475
1566
|
min_eligible_targets: z.number().int().min(0).default(1),
|
|
1567
|
+
min_wildcards: z.number().int().min(0).max(100).default(0),
|
|
1476
1568
|
exclude_ctf: z.boolean().default(true),
|
|
1477
1569
|
exclude_mobile_only: z.boolean().default(false),
|
|
1478
1570
|
exclude_non_web: z.boolean().default(false),
|
|
@@ -1480,6 +1572,10 @@ export function createBbradarServer(client, config) {
|
|
|
1480
1572
|
fresh_launch_days: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).optional(),
|
|
1481
1573
|
max_pages: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(3),
|
|
1482
1574
|
max_results: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(10),
|
|
1575
|
+
include_target_samples: z.boolean().default(true),
|
|
1576
|
+
target_sample_programs: z.number().int().min(0).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(3),
|
|
1577
|
+
target_sample_size: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(5),
|
|
1578
|
+
output_mode: programListModeSchema.default("compact"),
|
|
1483
1579
|
upstream_request_budget: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(DEFAULT_FIND_UPSTREAM_REQUEST_BUDGET)
|
|
1484
1580
|
},
|
|
1485
1581
|
outputSchema: findProgramsOutputShape(),
|
|
@@ -1505,7 +1601,7 @@ export function createBbradarServer(client, config) {
|
|
|
1505
1601
|
exclude_non_web: args.exclude_non_web,
|
|
1506
1602
|
has_api_scope: args.has_api_scope,
|
|
1507
1603
|
fresh_launch_days: args.fresh_launch_days,
|
|
1508
|
-
min_wildcards:
|
|
1604
|
+
min_wildcards: args.min_wildcards,
|
|
1509
1605
|
min_eligible_targets: args.min_eligible_targets,
|
|
1510
1606
|
min_total_targets: undefined,
|
|
1511
1607
|
min_added_24h: undefined,
|
|
@@ -1513,15 +1609,16 @@ export function createBbradarServer(client, config) {
|
|
|
1513
1609
|
max_pages: args.max_pages,
|
|
1514
1610
|
max_results: args.max_results,
|
|
1515
1611
|
sort_by: "best_hunt_value",
|
|
1516
|
-
include_target_samples:
|
|
1517
|
-
target_sample_programs:
|
|
1518
|
-
target_sample_size:
|
|
1612
|
+
include_target_samples: args.include_target_samples,
|
|
1613
|
+
target_sample_programs: args.target_sample_programs,
|
|
1614
|
+
target_sample_size: args.target_sample_size,
|
|
1519
1615
|
only_in_scope_targets: true,
|
|
1520
1616
|
only_bounty_eligible_targets: true,
|
|
1521
|
-
target_samples_only_wildcards:
|
|
1617
|
+
target_samples_only_wildcards: args.min_wildcards > 0,
|
|
1618
|
+
output_mode: args.output_mode,
|
|
1522
1619
|
upstream_request_budget: args.upstream_request_budget
|
|
1523
1620
|
})));
|
|
1524
|
-
|
|
1621
|
+
registerTool("get_program_delta", {
|
|
1525
1622
|
title: "Get Program Delta",
|
|
1526
1623
|
description: "Recent target changes for one program.",
|
|
1527
1624
|
inputSchema: {
|
|
@@ -1539,7 +1636,7 @@ export function createBbradarServer(client, config) {
|
|
|
1539
1636
|
},
|
|
1540
1637
|
annotations: readOnlyAnnotations
|
|
1541
1638
|
}, (args) => runTool("get_program_delta", config, rateLimiter, async () => runProgramIdTool(client, config, args.program_id, (programId) => getProgramDelta(client, config, { ...args, program_id: programId }))));
|
|
1542
|
-
|
|
1639
|
+
registerTool("export_targets", {
|
|
1543
1640
|
title: "Export BBRadar Targets",
|
|
1544
1641
|
description: `Read-only target export. limit max ${MAX_TARGET_EXPORT}.`,
|
|
1545
1642
|
inputSchema: {
|
|
@@ -1596,11 +1693,14 @@ export function createBbradarServer(client, config) {
|
|
|
1596
1693
|
});
|
|
1597
1694
|
}
|
|
1598
1695
|
}));
|
|
1599
|
-
registerPrompts(server);
|
|
1696
|
+
registerPrompts(server, config.toolProfile);
|
|
1600
1697
|
registerResources(server, client, config, exportStore);
|
|
1601
1698
|
return server;
|
|
1602
1699
|
}
|
|
1603
|
-
function bbradarGuide(topic) {
|
|
1700
|
+
function bbradarGuide(topic, profile) {
|
|
1701
|
+
if (profile === "core") {
|
|
1702
|
+
return coreBbradarGuide(topic);
|
|
1703
|
+
}
|
|
1604
1704
|
const primaryEntryPoints = [
|
|
1605
1705
|
{
|
|
1606
1706
|
tool: "find_program_candidates",
|
|
@@ -1652,6 +1752,27 @@ function bbradarGuide(topic) {
|
|
|
1652
1752
|
first_tool: "find_recently_updated_programs",
|
|
1653
1753
|
then: "Use get_latest_added_targets for global added targets, or check_program_new_targets when the user names one program."
|
|
1654
1754
|
},
|
|
1755
|
+
{
|
|
1756
|
+
user_goal: "latest added wildcard targets with platform, reward, or report-count filters",
|
|
1757
|
+
first_tool: "get_latest_added_targets",
|
|
1758
|
+
arguments: {
|
|
1759
|
+
target_type: "wildcard",
|
|
1760
|
+
platforms: ["requested platform"],
|
|
1761
|
+
min_reward: 5000,
|
|
1762
|
+
max_public_report_count: 10,
|
|
1763
|
+
include_full_target_list: false
|
|
1764
|
+
}
|
|
1765
|
+
},
|
|
1766
|
+
{
|
|
1767
|
+
user_goal: "wildcard programs with low report count and good rewards",
|
|
1768
|
+
first_tool: "find_wildcard_programs",
|
|
1769
|
+
arguments: {
|
|
1770
|
+
platforms: ["requested platform"],
|
|
1771
|
+
min_reward: 5000,
|
|
1772
|
+
max_public_report_count: 10,
|
|
1773
|
+
include_target_samples: true
|
|
1774
|
+
}
|
|
1775
|
+
},
|
|
1655
1776
|
{
|
|
1656
1777
|
user_goal: "compare a short list",
|
|
1657
1778
|
first_tool: "compare_programs_compact",
|
|
@@ -1669,8 +1790,8 @@ function bbradarGuide(topic) {
|
|
|
1669
1790
|
}
|
|
1670
1791
|
];
|
|
1671
1792
|
const advancedTools = {
|
|
1672
|
-
discovery: ["find_stack_matches", "find_language_programs", "find_target_type_programs", "find_reward_programs", "find_paid_programs", "find_web3_contests", "find_low_noise_programs"],
|
|
1673
|
-
freshness: ["find_recently_updated_programs", "find_fresh_hunt_candidates", "get_latest_added_targets", "get_recent_target_activity", "get_program_scope_delta", "check_watchlist_new_targets"],
|
|
1793
|
+
discovery: ["find_wildcard_programs", "find_low_competition_high_reward", "find_stack_matches", "find_language_programs", "find_target_type_programs", "find_reward_programs", "find_paid_programs", "find_web3_contests", "find_low_noise_programs"],
|
|
1794
|
+
freshness: ["find_recently_added_wildcards", "find_recently_updated_programs", "find_fresh_hunt_candidates", "get_latest_added_targets", "get_recent_target_activity", "get_program_scope_delta", "check_watchlist_new_targets"],
|
|
1674
1795
|
details: ["get_program_brief", "get_program_scope_summary", "get_program_target_breakdown", "find_programs_by_target"],
|
|
1675
1796
|
exports: ["export_targets"]
|
|
1676
1797
|
};
|
|
@@ -1702,6 +1823,20 @@ function bbradarGuide(topic) {
|
|
|
1702
1823
|
{
|
|
1703
1824
|
user_says: "who owns https://api.example.com/v1/users",
|
|
1704
1825
|
call: { tool: "find_programs_by_target", arguments: { target_query: "https://api.example.com/v1/users" } }
|
|
1826
|
+
},
|
|
1827
|
+
{
|
|
1828
|
+
user_says: "latest HackerOne wildcard targets with rewards over 5000",
|
|
1829
|
+
call: {
|
|
1830
|
+
tool: "get_latest_added_targets",
|
|
1831
|
+
arguments: { target_type: "wildcard", platforms: ["HackerOne"], min_reward: 5000, include_full_target_list: false }
|
|
1832
|
+
}
|
|
1833
|
+
},
|
|
1834
|
+
{
|
|
1835
|
+
user_says: "wildcard programs with under 10 reports and good rewards",
|
|
1836
|
+
call: {
|
|
1837
|
+
tool: "find_wildcard_programs",
|
|
1838
|
+
arguments: { max_public_report_count: 10, min_reward: 5000, include_target_samples: true }
|
|
1839
|
+
}
|
|
1705
1840
|
}
|
|
1706
1841
|
];
|
|
1707
1842
|
return {
|
|
@@ -1722,6 +1857,67 @@ function bbradarGuide(topic) {
|
|
|
1722
1857
|
examples
|
|
1723
1858
|
};
|
|
1724
1859
|
}
|
|
1860
|
+
function coreBbradarGuide(topic) {
|
|
1861
|
+
const decisionTree = [
|
|
1862
|
+
{
|
|
1863
|
+
user_goal: "find programs to hunt",
|
|
1864
|
+
first_tool: "find_programs",
|
|
1865
|
+
arguments: { max_results: 10, output_mode: "compact" }
|
|
1866
|
+
},
|
|
1867
|
+
{
|
|
1868
|
+
user_goal: "named program summary or action",
|
|
1869
|
+
first_tool: "run_program_name_action",
|
|
1870
|
+
arguments: { query: "program name", action: "brief" }
|
|
1871
|
+
},
|
|
1872
|
+
{
|
|
1873
|
+
user_goal: "named program targets",
|
|
1874
|
+
first_tool: "get_program_eligible_targets_by_name",
|
|
1875
|
+
arguments: { query: "program name", target_list_mode: "identifiers" }
|
|
1876
|
+
},
|
|
1877
|
+
{
|
|
1878
|
+
user_goal: "find which program owns a target",
|
|
1879
|
+
first_tool: "find_programs_by_target",
|
|
1880
|
+
arguments: { target_query: "domain, URL, wildcard, package/repo name, or short target phrase", match_mode: "smart" }
|
|
1881
|
+
},
|
|
1882
|
+
{
|
|
1883
|
+
user_goal: "fresh or recently changed scope",
|
|
1884
|
+
first_tool: "get_recent_changes",
|
|
1885
|
+
then: "Use get_latest_added_targets when only newly added targets are needed."
|
|
1886
|
+
},
|
|
1887
|
+
{
|
|
1888
|
+
user_goal: "export targets",
|
|
1889
|
+
first_tool: "export_targets",
|
|
1890
|
+
then: "Use the returned bbradar://exports resource_uri for the full local export payload."
|
|
1891
|
+
},
|
|
1892
|
+
{
|
|
1893
|
+
user_goal: "unknown filter values",
|
|
1894
|
+
first_tool: "list_filters",
|
|
1895
|
+
then: "Reuse observed platforms, tags, target types, and language tags in find_programs."
|
|
1896
|
+
}
|
|
1897
|
+
];
|
|
1898
|
+
return {
|
|
1899
|
+
topic,
|
|
1900
|
+
tool_profile: "core",
|
|
1901
|
+
rule_zero: "BBRadar is passive intelligence only. Never scan or contact targets.",
|
|
1902
|
+
defaults: {
|
|
1903
|
+
output_mode: "compact",
|
|
1904
|
+
target_list_mode: "identifiers",
|
|
1905
|
+
target_match_mode: "smart",
|
|
1906
|
+
include_out_of_scope: false,
|
|
1907
|
+
include_ineligible: false,
|
|
1908
|
+
require_bounty: true
|
|
1909
|
+
},
|
|
1910
|
+
primary_entry_points: [
|
|
1911
|
+
"find_programs",
|
|
1912
|
+
"run_program_name_action",
|
|
1913
|
+
"get_program_eligible_targets_by_name",
|
|
1914
|
+
"find_programs_by_target",
|
|
1915
|
+
"get_bbradar_guide"
|
|
1916
|
+
],
|
|
1917
|
+
decision_tree: decisionTree.filter((entry) => topic === "start" || guideEntryMatchesTopic(topic, entry)),
|
|
1918
|
+
available_tools: [...CORE_TOOL_NAMES]
|
|
1919
|
+
};
|
|
1920
|
+
}
|
|
1725
1921
|
function guideEntryMatchesTopic(topic, entry) {
|
|
1726
1922
|
const goal = typeof entry.user_goal === "string" ? entry.user_goal : "";
|
|
1727
1923
|
const firstTool = typeof entry.first_tool === "string" ? entry.first_tool : "";
|
|
@@ -3156,13 +3352,27 @@ async function findProgramsByTarget(client, config, input) {
|
|
|
3156
3352
|
};
|
|
3157
3353
|
const sourceRequests = [];
|
|
3158
3354
|
const matches = [];
|
|
3355
|
+
const directHandleFirst = !queryLooksLikeTargetIdentifier(input.target_query) &&
|
|
3356
|
+
!/\s/.test(input.target_query.trim()) &&
|
|
3357
|
+
looksLikeDirectProgramHandleQuery(input.target_query);
|
|
3358
|
+
if (input.use_api_search && !directHandleFirst) {
|
|
3359
|
+
const directResult = await searchProgramsByTargetApi(client, config, input, budget, warnings, sourceRequests);
|
|
3360
|
+
if (directResult) {
|
|
3361
|
+
return directResult;
|
|
3362
|
+
}
|
|
3363
|
+
}
|
|
3159
3364
|
const directHandles = await collectDirectHandlePrograms(client, {
|
|
3160
3365
|
query: input.target_query,
|
|
3161
3366
|
platforms: input.platforms,
|
|
3162
3367
|
budget,
|
|
3163
3368
|
warnings,
|
|
3164
3369
|
source: "target_query_program_id_guess",
|
|
3165
|
-
maxProgramIds:
|
|
3370
|
+
maxProgramIds: input.use_api_search && directHandleFirst
|
|
3371
|
+
? 4
|
|
3372
|
+
: queryLooksLikeTargetIdentifier(input.target_query) || looksLikeDirectProgramHandleQuery(input.target_query)
|
|
3373
|
+
? MAX_DIRECT_HANDLE_RESOLUTION_REQUESTS
|
|
3374
|
+
: 4,
|
|
3375
|
+
maxHandleVariants: 1,
|
|
3166
3376
|
stopOnFirstMatch: true
|
|
3167
3377
|
});
|
|
3168
3378
|
sourceRequests.push(...directHandles.sourceRequests);
|
|
@@ -3181,8 +3391,8 @@ async function findProgramsByTarget(client, config, input) {
|
|
|
3181
3391
|
}
|
|
3182
3392
|
});
|
|
3183
3393
|
}
|
|
3184
|
-
if (input.use_api_search) {
|
|
3185
|
-
const directResult = await searchProgramsByTargetApi(client, config, input, warnings);
|
|
3394
|
+
if (input.use_api_search && directHandleFirst) {
|
|
3395
|
+
const directResult = await searchProgramsByTargetApi(client, config, input, budget, warnings, sourceRequests);
|
|
3186
3396
|
if (directResult) {
|
|
3187
3397
|
return directResult;
|
|
3188
3398
|
}
|
|
@@ -3287,13 +3497,18 @@ function programsByTargetResult(input, warnings, sourceRequests, matches, budget
|
|
|
3287
3497
|
meta: options.meta
|
|
3288
3498
|
});
|
|
3289
3499
|
}
|
|
3290
|
-
async function searchProgramsByTargetApi(client, config, input, warnings) {
|
|
3500
|
+
async function searchProgramsByTargetApi(client, config, input, budget, warnings, sourceRequests) {
|
|
3291
3501
|
const searchQueries = targetApiSearchQueries(input.target_query);
|
|
3292
3502
|
const grouped = new Map();
|
|
3293
|
-
const sourceRequests = [];
|
|
3294
3503
|
let rawMatchesScanned = 0;
|
|
3504
|
+
let searchQueriesChecked = 0;
|
|
3295
3505
|
let lastMeta;
|
|
3296
3506
|
for (const searchQuery of searchQueries) {
|
|
3507
|
+
if (!tryConsumeBudget(budget)) {
|
|
3508
|
+
addUniqueWarning(warnings, "Upstream request budget was exhausted before all target search variants could be checked.");
|
|
3509
|
+
break;
|
|
3510
|
+
}
|
|
3511
|
+
searchQueriesChecked += 1;
|
|
3297
3512
|
let api;
|
|
3298
3513
|
try {
|
|
3299
3514
|
api = await client.searchTargets({
|
|
@@ -3306,8 +3521,10 @@ async function searchProgramsByTargetApi(client, config, input, warnings) {
|
|
|
3306
3521
|
page: 1,
|
|
3307
3522
|
page_size: Math.max(input.max_results * input.max_targets_per_program, input.max_results)
|
|
3308
3523
|
});
|
|
3524
|
+
refundBudgetIfNoUpstreamRequest(budget, api);
|
|
3309
3525
|
}
|
|
3310
3526
|
catch (error) {
|
|
3527
|
+
refundBudgetIfNoUpstreamError(budget, error);
|
|
3311
3528
|
if (error instanceof BBRadarApiError && (error.status === 404 || error.status === 405)) {
|
|
3312
3529
|
warnings.push("Direct target search API is unavailable; fell back to sampled program target lists.");
|
|
3313
3530
|
return undefined;
|
|
@@ -3348,15 +3565,93 @@ async function searchProgramsByTargetApi(client, config, input, warnings) {
|
|
|
3348
3565
|
if (!targetMatchesQuery(target, input.target_query, input.match_mode)) {
|
|
3349
3566
|
continue;
|
|
3350
3567
|
}
|
|
3351
|
-
const existing = grouped.get(programId) ?? {
|
|
3352
|
-
|
|
3568
|
+
const existing = grouped.get(programId) ?? {
|
|
3569
|
+
program: addProgramResourceLinks({ ...program, id: programId }),
|
|
3570
|
+
targets: [],
|
|
3571
|
+
targetKeys: new Set()
|
|
3572
|
+
};
|
|
3573
|
+
const targetKey = stableStringify({
|
|
3574
|
+
identifier: targetIdentifier(target),
|
|
3575
|
+
target_type: stringField(target, "target_type")
|
|
3576
|
+
});
|
|
3577
|
+
if (!existing.targetKeys.has(targetKey)) {
|
|
3578
|
+
existing.targetKeys.add(targetKey);
|
|
3579
|
+
existing.targets.push(target);
|
|
3580
|
+
}
|
|
3353
3581
|
grouped.set(programId, existing);
|
|
3354
3582
|
}
|
|
3355
3583
|
}
|
|
3356
|
-
if (grouped.size >= input.max_results
|
|
3584
|
+
if (grouped.size >= input.max_results ||
|
|
3585
|
+
(grouped.size > 0 && input.match_mode === "smart")) {
|
|
3357
3586
|
break;
|
|
3358
3587
|
}
|
|
3359
3588
|
}
|
|
3589
|
+
if (grouped.size > 0 && input.match_mode === "smart") {
|
|
3590
|
+
await mapWithConcurrency([...grouped.entries()].slice(0, input.max_results), TARGET_SAMPLE_CONCURRENCY, async ([programId, entry]) => {
|
|
3591
|
+
if (!tryConsumeBudget(budget)) {
|
|
3592
|
+
addUniqueWarning(warnings, "Upstream request budget was exhausted before matched program target lists could be hydrated.");
|
|
3593
|
+
return;
|
|
3594
|
+
}
|
|
3595
|
+
try {
|
|
3596
|
+
const targetsApi = await client.getProgramTargets(programId);
|
|
3597
|
+
refundBudgetIfNoUpstreamRequest(budget, targetsApi);
|
|
3598
|
+
sourceRequests.push({
|
|
3599
|
+
source: "program_targets",
|
|
3600
|
+
program_id: programId,
|
|
3601
|
+
request_id: targetsApi.requestId,
|
|
3602
|
+
upstream_request_id: targetsApi.upstreamRequestId,
|
|
3603
|
+
...apiSourceMetadata(targetsApi)
|
|
3604
|
+
});
|
|
3605
|
+
const targetsData = readObject(targetsApi.data);
|
|
3606
|
+
const hydratedTargets = readArray(targetsData?.targets).map(sanitizeTarget);
|
|
3607
|
+
const hydratedTargetOrder = new Map(hydratedTargets.map((target, index) => [
|
|
3608
|
+
stableStringify({
|
|
3609
|
+
identifier: targetIdentifier(target),
|
|
3610
|
+
target_type: stringField(target, "target_type")
|
|
3611
|
+
}),
|
|
3612
|
+
index
|
|
3613
|
+
]));
|
|
3614
|
+
for (const target of hydratedTargets) {
|
|
3615
|
+
if (!targetIdentifier(target) ||
|
|
3616
|
+
!targetHasAllowedScope(target, {
|
|
3617
|
+
include_out_of_scope: input.include_out_of_scope,
|
|
3618
|
+
include_ineligible: input.include_ineligible,
|
|
3619
|
+
strict_scope_filter: false
|
|
3620
|
+
}) ||
|
|
3621
|
+
!targetMatchesQuery(target, input.target_query, input.match_mode)) {
|
|
3622
|
+
continue;
|
|
3623
|
+
}
|
|
3624
|
+
const targetKey = stableStringify({
|
|
3625
|
+
identifier: targetIdentifier(target),
|
|
3626
|
+
target_type: stringField(target, "target_type")
|
|
3627
|
+
});
|
|
3628
|
+
if (!entry.targetKeys.has(targetKey)) {
|
|
3629
|
+
entry.targetKeys.add(targetKey);
|
|
3630
|
+
entry.targets.push(target);
|
|
3631
|
+
}
|
|
3632
|
+
}
|
|
3633
|
+
entry.targets.sort((left, right) => {
|
|
3634
|
+
const leftKey = stableStringify({
|
|
3635
|
+
identifier: targetIdentifier(left),
|
|
3636
|
+
target_type: stringField(left, "target_type")
|
|
3637
|
+
});
|
|
3638
|
+
const rightKey = stableStringify({
|
|
3639
|
+
identifier: targetIdentifier(right),
|
|
3640
|
+
target_type: stringField(right, "target_type")
|
|
3641
|
+
});
|
|
3642
|
+
return ((hydratedTargetOrder.get(leftKey) ?? Number.MAX_SAFE_INTEGER) -
|
|
3643
|
+
(hydratedTargetOrder.get(rightKey) ?? Number.MAX_SAFE_INTEGER));
|
|
3644
|
+
});
|
|
3645
|
+
}
|
|
3646
|
+
catch (error) {
|
|
3647
|
+
refundBudgetIfNoUpstreamError(budget, error);
|
|
3648
|
+
if (!isProgramNotFoundError(error)) {
|
|
3649
|
+
throw error;
|
|
3650
|
+
}
|
|
3651
|
+
addUniqueWarning(warnings, `Could not hydrate target matches for ${programId}; returned direct target-search matches only.`);
|
|
3652
|
+
}
|
|
3653
|
+
});
|
|
3654
|
+
}
|
|
3360
3655
|
const matches = [...grouped.values()].slice(0, input.max_results).map((entry) => stripUndefined({
|
|
3361
3656
|
program: formatProgram(entry.program, "compact"),
|
|
3362
3657
|
match_count: entry.targets.length,
|
|
@@ -3384,6 +3679,14 @@ async function searchProgramsByTargetApi(client, config, input, warnings) {
|
|
|
3384
3679
|
},
|
|
3385
3680
|
matches,
|
|
3386
3681
|
programs: matches.map((match) => readObject(match.program)).filter((program) => program !== undefined),
|
|
3682
|
+
upstream_budget: {
|
|
3683
|
+
used: budget.initial - budget.remaining,
|
|
3684
|
+
remaining: budget.remaining
|
|
3685
|
+
},
|
|
3686
|
+
ranking_scope: {
|
|
3687
|
+
mode: "target_search_api",
|
|
3688
|
+
possibly_incomplete: grouped.size < input.max_results && searchQueriesChecked < searchQueries.length
|
|
3689
|
+
},
|
|
3387
3690
|
meta: stripUndefined({
|
|
3388
3691
|
...(lastMeta ?? {}),
|
|
3389
3692
|
returned: matches.length,
|
|
@@ -3480,7 +3783,8 @@ async function listFilters(client, input) {
|
|
|
3480
3783
|
}
|
|
3481
3784
|
}
|
|
3482
3785
|
}
|
|
3483
|
-
catch {
|
|
3786
|
+
catch (error) {
|
|
3787
|
+
refundBudgetIfNoUpstreamError(budget, error);
|
|
3484
3788
|
// Facets are best-effort; list_filters should still return observed program filters.
|
|
3485
3789
|
}
|
|
3486
3790
|
if (input.include_target_facets && input.target_sample_programs > 0) {
|
|
@@ -3616,11 +3920,15 @@ async function comparePrograms(client, config, input) {
|
|
|
3616
3920
|
});
|
|
3617
3921
|
}
|
|
3618
3922
|
async function summarizeProgramActivity(client, config, programId, recentChangesLimit) {
|
|
3619
|
-
const programApi = await
|
|
3923
|
+
const [programApi, changes] = await Promise.all([
|
|
3924
|
+
client.getProgram(programId),
|
|
3925
|
+
recentChangesLimit > 0
|
|
3926
|
+
? fetchProgramChanges(client, config, programId, recentChangesLimit, true, true, true)
|
|
3927
|
+
: Promise.resolve(undefined)
|
|
3928
|
+
]);
|
|
3620
3929
|
const program = addProgramResourceLinks(sanitizeProgram(programApi.data, config.webBaseUrl));
|
|
3621
3930
|
const scopeSummary = readObject(program.scope_summary);
|
|
3622
3931
|
const targetActivity = readObject(scopeSummary?.target_activity);
|
|
3623
|
-
const changes = recentChangesLimit > 0 ? await fetchProgramChanges(client, config, programId, recentChangesLimit, true, true, true) : undefined;
|
|
3624
3932
|
return {
|
|
3625
3933
|
request_id: randomUUID(),
|
|
3626
3934
|
source_requests: [
|
|
@@ -3647,12 +3955,14 @@ async function getLatestAddedTargets(client, config, input) {
|
|
|
3647
3955
|
include_removed: false,
|
|
3648
3956
|
include_ineligible: input.include_ineligible,
|
|
3649
3957
|
include_out_of_scope: input.include_out_of_scope,
|
|
3958
|
+
platforms: input.platforms,
|
|
3650
3959
|
tags: [input.target_type],
|
|
3651
3960
|
page: 1,
|
|
3652
3961
|
page_size: input.recent_changes_page_size
|
|
3653
3962
|
});
|
|
3654
3963
|
const recentData = readObject(recentApi.data);
|
|
3655
3964
|
const changes = readArray(recentData?.results).map((change) => sanitizeChange(change, config.webBaseUrl));
|
|
3965
|
+
const platformFilters = new Set(input.platforms.map(normalizeTag));
|
|
3656
3966
|
const matchingChanges = changes
|
|
3657
3967
|
.filter((change) => {
|
|
3658
3968
|
const target = readObject(change.target);
|
|
@@ -3661,6 +3971,7 @@ async function getLatestAddedTargets(client, config, input) {
|
|
|
3661
3971
|
programId !== undefined &&
|
|
3662
3972
|
target !== undefined &&
|
|
3663
3973
|
targetMatchesRequestedType(target, input.target_type) &&
|
|
3974
|
+
latestAddedProgramMatches(change, input, platformFilters) &&
|
|
3664
3975
|
targetHasAllowedScope(target, {
|
|
3665
3976
|
include_out_of_scope: input.include_out_of_scope,
|
|
3666
3977
|
include_ineligible: input.include_ineligible,
|
|
@@ -3668,6 +3979,11 @@ async function getLatestAddedTargets(client, config, input) {
|
|
|
3668
3979
|
}));
|
|
3669
3980
|
})
|
|
3670
3981
|
.sort((left, right) => timestampField(right, "changed_at") - timestampField(left, "changed_at"));
|
|
3982
|
+
const limitedMatches = matchingChanges.slice(0, input.max_results);
|
|
3983
|
+
const matches = limitedMatches.map((change, index) => ({
|
|
3984
|
+
rank: index + 1,
|
|
3985
|
+
...formatChange(change, "compact")
|
|
3986
|
+
}));
|
|
3671
3987
|
const latest = matchingChanges[0];
|
|
3672
3988
|
const sourceRequests = [
|
|
3673
3989
|
{
|
|
@@ -3682,9 +3998,12 @@ async function getLatestAddedTargets(client, config, input) {
|
|
|
3682
3998
|
request_id: randomUUID(),
|
|
3683
3999
|
source_requests: sourceRequests,
|
|
3684
4000
|
query: latestAddedTargetsQuery(input),
|
|
4001
|
+
matches: [],
|
|
3685
4002
|
new_targets: [],
|
|
3686
4003
|
meta: {
|
|
3687
4004
|
recent_changes_scanned: changes.length,
|
|
4005
|
+
matching_changes_scanned: 0,
|
|
4006
|
+
matches_returned: 0,
|
|
3688
4007
|
no_match: true
|
|
3689
4008
|
}
|
|
3690
4009
|
};
|
|
@@ -3727,11 +4046,14 @@ async function getLatestAddedTargets(client, config, input) {
|
|
|
3727
4046
|
query: latestAddedTargetsQuery(input),
|
|
3728
4047
|
latest_added_at: latestAddedAt,
|
|
3729
4048
|
program,
|
|
4049
|
+
matches,
|
|
3730
4050
|
new_targets: formatTargetList(latestNewTargets, input.full_target_list_mode),
|
|
3731
4051
|
full_targets: input.include_full_target_list ? formatTargetList(limitedFullTargets, input.full_target_list_mode) : undefined,
|
|
3732
4052
|
meta: stripUndefined({
|
|
3733
4053
|
recent_changes_scanned: changes.length,
|
|
3734
4054
|
matching_changes_scanned: matchingChanges.length,
|
|
4055
|
+
matches_returned: matches.length,
|
|
4056
|
+
matches_has_more: matches.length < matchingChanges.length,
|
|
3735
4057
|
new_targets_returned: formatTargetList(latestNewTargets, input.full_target_list_mode).length,
|
|
3736
4058
|
full_targets_total_active: totalActiveTargets,
|
|
3737
4059
|
full_targets_total_after_filters: totalAfterFilters,
|
|
@@ -3740,6 +4062,39 @@ async function getLatestAddedTargets(client, config, input) {
|
|
|
3740
4062
|
})
|
|
3741
4063
|
});
|
|
3742
4064
|
}
|
|
4065
|
+
function latestAddedProgramMatches(change, input, platformFilters) {
|
|
4066
|
+
const rawProgram = readObject(change.program);
|
|
4067
|
+
if (!rawProgram) {
|
|
4068
|
+
return false;
|
|
4069
|
+
}
|
|
4070
|
+
const program = addProgramResourceLinks(rawProgram);
|
|
4071
|
+
const rewardRange = readObject(program.reward_range);
|
|
4072
|
+
const platform = normalizeTag(stringField(program, "platform") ?? "");
|
|
4073
|
+
const bountyMin = numberField(rewardRange, "min");
|
|
4074
|
+
const bountyMax = numberField(rewardRange, "max");
|
|
4075
|
+
const publicReportCount = numberField(program, "public_report_count");
|
|
4076
|
+
if (platformFilters.size > 0 && !platformFilters.has(platform)) {
|
|
4077
|
+
return false;
|
|
4078
|
+
}
|
|
4079
|
+
if (input.min_reward !== undefined) {
|
|
4080
|
+
const reward = input.reward_threshold_mode === "min_at_least" ? bountyMin : bountyMax;
|
|
4081
|
+
if ((reward ?? 0) < input.min_reward) {
|
|
4082
|
+
return false;
|
|
4083
|
+
}
|
|
4084
|
+
}
|
|
4085
|
+
if (input.require_known_report_count && publicReportCount === undefined) {
|
|
4086
|
+
return false;
|
|
4087
|
+
}
|
|
4088
|
+
if (input.max_public_report_count !== undefined) {
|
|
4089
|
+
if (publicReportCount === undefined) {
|
|
4090
|
+
return input.include_unknown_report_count;
|
|
4091
|
+
}
|
|
4092
|
+
if (publicReportCount > input.max_public_report_count) {
|
|
4093
|
+
return false;
|
|
4094
|
+
}
|
|
4095
|
+
}
|
|
4096
|
+
return true;
|
|
4097
|
+
}
|
|
3743
4098
|
async function getProgramTargetsPayload(client, input, programId) {
|
|
3744
4099
|
const api = await client.getProgramTargets(programId);
|
|
3745
4100
|
const data = readObject(api.data);
|
|
@@ -4296,9 +4651,11 @@ async function getProgramTargetBreakdownFromIndexFallback(client, indexFallback,
|
|
|
4296
4651
|
});
|
|
4297
4652
|
}
|
|
4298
4653
|
async function getProgramScopeDelta(client, config, input) {
|
|
4299
|
-
const programApi = await
|
|
4654
|
+
const [programApi, changesPayload] = await Promise.all([
|
|
4655
|
+
client.getProgram(input.program_id),
|
|
4656
|
+
fetchProgramChanges(client, config, input.program_id, input.page_size, input.include_removed || input.change_type === "removed", input.include_ineligible, input.include_out_of_scope, input.max_targets)
|
|
4657
|
+
]);
|
|
4300
4658
|
const program = addProgramResourceLinks(sanitizeProgram(programApi.data, config.webBaseUrl));
|
|
4301
|
-
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, input.max_targets);
|
|
4302
4659
|
const filteredChanges = filterProgramScopeDeltaChanges(changesPayload.changes, input);
|
|
4303
4660
|
const limitedChanges = filteredChanges.slice(0, input.max_targets);
|
|
4304
4661
|
return stripUndefined({
|
|
@@ -4753,9 +5110,11 @@ async function getProgramBrief(client, config, input) {
|
|
|
4753
5110
|
});
|
|
4754
5111
|
}
|
|
4755
5112
|
async function getProgramDelta(client, config, input) {
|
|
4756
|
-
const programApi = await
|
|
5113
|
+
const [programApi, changes] = await Promise.all([
|
|
5114
|
+
client.getProgram(input.program_id),
|
|
5115
|
+
fetchProgramChanges(client, config, input.program_id, input.page_size, input.include_removed, input.include_ineligible, input.include_out_of_scope)
|
|
5116
|
+
]);
|
|
4757
5117
|
const program = addProgramResourceLinks(sanitizeProgram(programApi.data, config.webBaseUrl));
|
|
4758
|
-
const changes = await fetchProgramChanges(client, config, input.program_id, input.page_size, input.include_removed, input.include_ineligible, input.include_out_of_scope);
|
|
4759
5118
|
return {
|
|
4760
5119
|
request_id: randomUUID(),
|
|
4761
5120
|
source_requests: [
|
|
@@ -5346,7 +5705,11 @@ function programResolutionMatch(candidate, query) {
|
|
|
5346
5705
|
};
|
|
5347
5706
|
}
|
|
5348
5707
|
function hasHighConfidenceProgramResolutionMatch(programs, webBaseUrl, query) {
|
|
5349
|
-
|
|
5708
|
+
const threshold = looksLikeDirectProgramHandleQuery(query) ? 95 : 85;
|
|
5709
|
+
return programs.some((program) => programResolutionMatch(toProgramCandidate(program, webBaseUrl), query).score >= threshold);
|
|
5710
|
+
}
|
|
5711
|
+
function hasViableProgramResolutionMatch(programs, scorePrograms) {
|
|
5712
|
+
return scorePrograms ? scorePrograms(programs) >= 80 : programs.length > 0;
|
|
5350
5713
|
}
|
|
5351
5714
|
function bestProgramResolutionScore(programs, webBaseUrl, query) {
|
|
5352
5715
|
return programs.reduce((best, program) => Math.max(best, programResolutionMatch(toProgramCandidate(program, webBaseUrl), query).score), 0);
|
|
@@ -5357,6 +5720,7 @@ function programResolutionSearchQueries(query) {
|
|
|
5357
5720
|
function programResolutionQueryVariants(query) {
|
|
5358
5721
|
const normalized = normalizeTag(query);
|
|
5359
5722
|
const meaningfulTokens = [...meaningfulProgramQueryTokens(query)];
|
|
5723
|
+
const searchTokens = meaningfulTokens.filter((token) => token.length >= 3);
|
|
5360
5724
|
const meaningfulPhrase = meaningfulTokens.join(" ");
|
|
5361
5725
|
const singleTokenNameVariants = meaningfulTokens.length === 1
|
|
5362
5726
|
? [
|
|
@@ -5370,7 +5734,7 @@ function programResolutionQueryVariants(query) {
|
|
|
5370
5734
|
normalized,
|
|
5371
5735
|
meaningfulPhrase,
|
|
5372
5736
|
meaningfulTokens.join("-"),
|
|
5373
|
-
...
|
|
5737
|
+
...searchTokens,
|
|
5374
5738
|
...singleTokenNameVariants
|
|
5375
5739
|
];
|
|
5376
5740
|
return uniqueStrings(values.filter((value) => value.length >= 2));
|
|
@@ -5486,7 +5850,13 @@ async function findPrograms(client, config, input) {
|
|
|
5486
5850
|
initial: input.upstream_request_budget,
|
|
5487
5851
|
remaining: input.upstream_request_budget
|
|
5488
5852
|
};
|
|
5489
|
-
const upstreamTags = uniqueStrings([
|
|
5853
|
+
const upstreamTags = uniqueStrings([
|
|
5854
|
+
...input.tags,
|
|
5855
|
+
...input.scope_tags,
|
|
5856
|
+
...input.target_types,
|
|
5857
|
+
...input.language_tags,
|
|
5858
|
+
...(input.min_wildcards > 0 ? ["wildcard"] : [])
|
|
5859
|
+
]);
|
|
5490
5860
|
const collected = await collectPrograms(client, {
|
|
5491
5861
|
platforms: input.platforms,
|
|
5492
5862
|
tags: upstreamTags,
|
|
@@ -5598,15 +5968,43 @@ async function collectProgramsForResolution(client, options) {
|
|
|
5598
5968
|
const collections = [];
|
|
5599
5969
|
const queryCollections = [];
|
|
5600
5970
|
const queries = options.searchQueries.length > 0 ? options.searchQueries : [undefined];
|
|
5601
|
-
const
|
|
5971
|
+
const fastQueries = queries.slice(0, FAST_RESOLVE_SEARCH_QUERIES);
|
|
5972
|
+
const remainingQueries = queries.slice(FAST_RESOLVE_SEARCH_QUERIES);
|
|
5973
|
+
const primaryDirectPlatforms = options.platforms.length > 0 ? options.platforms : PRIMARY_PROGRAM_ID_PLATFORM_GUESSES;
|
|
5974
|
+
const primaryDirectProgramIds = new Set(directProgramIdCandidates(options.query, primaryDirectPlatforms, { maxHandleVariants: 1 }));
|
|
5975
|
+
for (const search of fastQueries) {
|
|
5976
|
+
if (options.budget.remaining <= 0) {
|
|
5977
|
+
addUniqueWarning(options.warnings, "Program name search stopped at the request budget; pass a higher upstream_request_budget for a deeper resolution attempt.");
|
|
5978
|
+
break;
|
|
5979
|
+
}
|
|
5980
|
+
const collection = await collectPrograms(client, {
|
|
5981
|
+
search,
|
|
5982
|
+
platforms: options.platforms,
|
|
5983
|
+
tags: options.tags,
|
|
5984
|
+
updated_since: undefined,
|
|
5985
|
+
opportunity_levels: options.opportunity_levels,
|
|
5986
|
+
max_pages: 1,
|
|
5987
|
+
budget: options.budget,
|
|
5988
|
+
warnings: options.warnings
|
|
5989
|
+
});
|
|
5990
|
+
collections.push(collection);
|
|
5991
|
+
queryCollections.push({ search, collection });
|
|
5992
|
+
const merged = mergeProgramCollections(collections);
|
|
5993
|
+
if (options.isHighConfidenceMatch?.(merged.programs) ||
|
|
5994
|
+
hasViableProgramResolutionMatch(merged.programs, options.scorePrograms)) {
|
|
5995
|
+
return merged;
|
|
5996
|
+
}
|
|
5997
|
+
}
|
|
5602
5998
|
if (primaryDirectProgramIds.size > 0 && options.budget.remaining > 0) {
|
|
5603
5999
|
const primaryDirectHandles = await collectDirectHandlePrograms(client, {
|
|
5604
6000
|
query: options.query,
|
|
5605
|
-
platforms:
|
|
6001
|
+
platforms: primaryDirectPlatforms,
|
|
5606
6002
|
budget: options.budget,
|
|
5607
6003
|
warnings: options.warnings,
|
|
5608
6004
|
source: "program_id_handle_guess",
|
|
5609
|
-
maxProgramIds:
|
|
6005
|
+
maxProgramIds: MAX_DIRECT_HANDLE_RESOLUTION_REQUESTS,
|
|
6006
|
+
maxHandleVariants: 1,
|
|
6007
|
+
acceptEmptyTargetsAsPrograms: true,
|
|
5610
6008
|
stopOnFirstMatch: true
|
|
5611
6009
|
});
|
|
5612
6010
|
if (primaryDirectHandles.programs.length > 0 || primaryDirectHandles.requestsScanned > 0) {
|
|
@@ -5616,7 +6014,7 @@ async function collectProgramsForResolution(client, options) {
|
|
|
5616
6014
|
return mergeProgramCollections(collections);
|
|
5617
6015
|
}
|
|
5618
6016
|
}
|
|
5619
|
-
for (const search of
|
|
6017
|
+
for (const search of remainingQueries) {
|
|
5620
6018
|
if (options.budget.remaining <= 0) {
|
|
5621
6019
|
addUniqueWarning(options.warnings, "Program name search stopped at the request budget; pass a higher upstream_request_budget for a deeper resolution attempt.");
|
|
5622
6020
|
break;
|
|
@@ -5637,7 +6035,9 @@ async function collectProgramsForResolution(client, options) {
|
|
|
5637
6035
|
return mergeProgramCollections(collections);
|
|
5638
6036
|
}
|
|
5639
6037
|
}
|
|
5640
|
-
|
|
6038
|
+
const searchedCollection = collections.length > 0 ? mergeProgramCollections(collections) : undefined;
|
|
6039
|
+
if (options.budget.remaining > 0 &&
|
|
6040
|
+
(!searchedCollection || !hasViableProgramResolutionMatch(searchedCollection.programs, options.scorePrograms))) {
|
|
5641
6041
|
const directHandles = await collectDirectHandlePrograms(client, {
|
|
5642
6042
|
query: options.query,
|
|
5643
6043
|
platforms: options.platforms,
|
|
@@ -5645,6 +6045,8 @@ async function collectProgramsForResolution(client, options) {
|
|
|
5645
6045
|
warnings: options.warnings,
|
|
5646
6046
|
source: "program_id_handle_guess",
|
|
5647
6047
|
skipProgramIds: primaryDirectProgramIds,
|
|
6048
|
+
maxHandleVariants: 4,
|
|
6049
|
+
acceptEmptyTargetsAsPrograms: true,
|
|
5648
6050
|
stopOnFirstMatch: true
|
|
5649
6051
|
});
|
|
5650
6052
|
if (directHandles.programs.length > 0 || directHandles.requestsScanned > 0) {
|
|
@@ -5715,7 +6117,7 @@ function mergeProgramCollections(collections) {
|
|
|
5715
6117
|
};
|
|
5716
6118
|
}
|
|
5717
6119
|
async function collectDirectHandlePrograms(client, options) {
|
|
5718
|
-
const programIds = directProgramIdCandidates(options.query, options.platforms)
|
|
6120
|
+
const programIds = directProgramIdCandidates(options.query, options.platforms, { maxHandleVariants: options.maxHandleVariants })
|
|
5719
6121
|
.filter((programId) => !options.skipProgramIds?.has(programId))
|
|
5720
6122
|
.slice(0, options.maxProgramIds ?? MAX_DIRECT_HANDLE_RESOLUTION_REQUESTS);
|
|
5721
6123
|
const programs = [];
|
|
@@ -5723,70 +6125,45 @@ async function collectDirectHandlePrograms(client, options) {
|
|
|
5723
6125
|
let requestsScanned = 0;
|
|
5724
6126
|
for (const programId of programIds) {
|
|
5725
6127
|
if (!tryConsumeBudget(options.budget)) {
|
|
6128
|
+
addUniqueWarning(options.warnings, "Upstream request budget was exhausted before all direct program handle guesses could be checked.");
|
|
5726
6129
|
break;
|
|
5727
6130
|
}
|
|
5728
6131
|
requestsScanned += 1;
|
|
5729
6132
|
try {
|
|
5730
|
-
const
|
|
5731
|
-
|
|
6133
|
+
const targetsApi = await client.getProgramTargets(programId);
|
|
6134
|
+
refundBudgetIfNoUpstreamRequest(options.budget, targetsApi);
|
|
6135
|
+
const targetsData = readObject(targetsApi.data);
|
|
6136
|
+
const hasTargetsArray = Array.isArray(targetsData?.targets);
|
|
6137
|
+
const targets = readArray(targetsData?.targets).map(sanitizeTarget);
|
|
6138
|
+
const programEndpointExists = targets.length > 0 || (options.acceptEmptyTargetsAsPrograms === true && hasTargetsArray);
|
|
6139
|
+
if (programEndpointExists) {
|
|
6140
|
+
programs.push(syntheticProgramFromProgramId(programId, targets));
|
|
6141
|
+
}
|
|
5732
6142
|
sourceRequests.push({
|
|
5733
6143
|
source: options.source,
|
|
6144
|
+
endpoint: "program_targets",
|
|
5734
6145
|
program_id: programId,
|
|
5735
|
-
request_id:
|
|
5736
|
-
upstream_request_id:
|
|
5737
|
-
...apiSourceMetadata(
|
|
6146
|
+
request_id: targetsApi.requestId,
|
|
6147
|
+
upstream_request_id: targetsApi.upstreamRequestId,
|
|
6148
|
+
...apiSourceMetadata(targetsApi)
|
|
5738
6149
|
});
|
|
5739
|
-
if (options.stopOnFirstMatch) {
|
|
6150
|
+
if (programEndpointExists && options.stopOnFirstMatch) {
|
|
5740
6151
|
break;
|
|
5741
6152
|
}
|
|
5742
6153
|
}
|
|
5743
6154
|
catch (error) {
|
|
6155
|
+
refundBudgetIfNoUpstreamError(options.budget, error);
|
|
5744
6156
|
if (error instanceof BBRadarApiError && error.status === 404) {
|
|
5745
6157
|
sourceRequests.push({
|
|
5746
6158
|
source: options.source,
|
|
5747
|
-
endpoint: "
|
|
6159
|
+
endpoint: "program_targets",
|
|
5748
6160
|
program_id: programId,
|
|
5749
6161
|
request_id: error.requestId,
|
|
5750
6162
|
upstream_request_id: error.upstreamRequestId,
|
|
5751
|
-
status: error.status
|
|
6163
|
+
status: error.status,
|
|
6164
|
+
cache_hit: error.cached,
|
|
6165
|
+
cache_expires_at: error.cacheExpiresAt
|
|
5752
6166
|
});
|
|
5753
|
-
if (!tryConsumeBudget(options.budget)) {
|
|
5754
|
-
break;
|
|
5755
|
-
}
|
|
5756
|
-
requestsScanned += 1;
|
|
5757
|
-
try {
|
|
5758
|
-
const targetsApi = await client.getProgramTargets(programId);
|
|
5759
|
-
const targetsData = readObject(targetsApi.data);
|
|
5760
|
-
const targets = readArray(targetsData?.targets).map(sanitizeTarget);
|
|
5761
|
-
if (targets.length > 0) {
|
|
5762
|
-
programs.push(syntheticProgramFromProgramId(programId, targets));
|
|
5763
|
-
}
|
|
5764
|
-
sourceRequests.push({
|
|
5765
|
-
source: options.source,
|
|
5766
|
-
endpoint: "program_targets",
|
|
5767
|
-
program_id: programId,
|
|
5768
|
-
request_id: targetsApi.requestId,
|
|
5769
|
-
upstream_request_id: targetsApi.upstreamRequestId,
|
|
5770
|
-
...apiSourceMetadata(targetsApi)
|
|
5771
|
-
});
|
|
5772
|
-
if (targets.length > 0 && options.stopOnFirstMatch) {
|
|
5773
|
-
break;
|
|
5774
|
-
}
|
|
5775
|
-
}
|
|
5776
|
-
catch (targetError) {
|
|
5777
|
-
if (targetError instanceof BBRadarApiError && targetError.status === 404) {
|
|
5778
|
-
sourceRequests.push({
|
|
5779
|
-
source: options.source,
|
|
5780
|
-
endpoint: "program_targets",
|
|
5781
|
-
program_id: programId,
|
|
5782
|
-
request_id: targetError.requestId,
|
|
5783
|
-
upstream_request_id: targetError.upstreamRequestId,
|
|
5784
|
-
status: targetError.status
|
|
5785
|
-
});
|
|
5786
|
-
continue;
|
|
5787
|
-
}
|
|
5788
|
-
throw targetError;
|
|
5789
|
-
}
|
|
5790
6167
|
continue;
|
|
5791
6168
|
}
|
|
5792
6169
|
throw error;
|
|
@@ -5804,11 +6181,12 @@ function syntheticProgramFromProgramId(programId, targets) {
|
|
|
5804
6181
|
const separatorIndex = programId.indexOf(":");
|
|
5805
6182
|
const platform = separatorIndex >= 0 ? programId.slice(0, separatorIndex) : undefined;
|
|
5806
6183
|
const handle = separatorIndex >= 0 ? programId.slice(separatorIndex + 1) : programId;
|
|
6184
|
+
const displayHandle = handle.split("/").filter(Boolean).at(-1) ?? handle;
|
|
5807
6185
|
const scopeTags = uniqueStrings(targets.flatMap((target) => readStringArrayField(target, "scope_tags")));
|
|
5808
6186
|
return stripUndefined({
|
|
5809
6187
|
id: programId,
|
|
5810
6188
|
handle,
|
|
5811
|
-
name: titleizeProgramHandle(
|
|
6189
|
+
name: titleizeProgramHandle(displayHandle),
|
|
5812
6190
|
platform,
|
|
5813
6191
|
scope_tags: scopeTags,
|
|
5814
6192
|
target_counts: {
|
|
@@ -5827,38 +6205,75 @@ function titleizeProgramHandle(handle) {
|
|
|
5827
6205
|
.map((part) => part.slice(0, 1).toUpperCase() + part.slice(1))
|
|
5828
6206
|
.join(" ");
|
|
5829
6207
|
}
|
|
5830
|
-
function directProgramIdCandidates(query, platforms) {
|
|
6208
|
+
function directProgramIdCandidates(query, platforms, options = {}) {
|
|
5831
6209
|
const platformGuesses = (platforms.length > 0 ? platforms : PROGRAM_ID_PLATFORM_GUESSES).map(canonicalProgramIdPlatform);
|
|
5832
|
-
const handles = directProgramHandleVariants(query);
|
|
5833
|
-
return uniqueStrings(
|
|
6210
|
+
const handles = directProgramHandleVariants(query).slice(0, options.maxHandleVariants ?? Number.POSITIVE_INFINITY);
|
|
6211
|
+
return uniqueStrings(handles.flatMap((handle) => platformGuesses.flatMap((platform) => normalizeTag(platform) === "bugcrowd"
|
|
6212
|
+
? [`${platform}:engagements/${handle}`, `${platform}:${handle}`]
|
|
6213
|
+
: [`${platform}:${handle}`]))).slice(0, MAX_DIRECT_HANDLE_RESOLUTION_REQUESTS);
|
|
5834
6214
|
}
|
|
5835
6215
|
function canonicalProgramIdPlatform(platform) {
|
|
5836
6216
|
const normalized = normalizeTag(platform).replace(/[^a-z0-9]/g, "");
|
|
5837
6217
|
const knownPlatforms = {
|
|
5838
6218
|
hackerone: "HackerOne",
|
|
5839
|
-
bugcrowd: "
|
|
6219
|
+
bugcrowd: "BugCrowd",
|
|
5840
6220
|
intigriti: "Intigriti",
|
|
5841
6221
|
hackenproof: "HackenProof",
|
|
5842
6222
|
yeswehack: "YesWeHack",
|
|
6223
|
+
immunefi: "Immunefi",
|
|
5843
6224
|
cantina: "Cantina",
|
|
5844
6225
|
code4rena: "Code4rena",
|
|
5845
|
-
|
|
6226
|
+
huntr: "Huntr",
|
|
6227
|
+
gobugfree: "GObugfree",
|
|
6228
|
+
bugbountyswitzerland: "Bug Bounty Switzerland",
|
|
6229
|
+
inspectiv: "Inspectiv",
|
|
6230
|
+
compasssecurity: "Compass Security",
|
|
6231
|
+
bugrap: "BugRap",
|
|
6232
|
+
issuehunt: "IssueHunt",
|
|
6233
|
+
patchday: "PatchDay",
|
|
6234
|
+
remedy: "Remedy",
|
|
6235
|
+
standoff365: "Standoff365",
|
|
6236
|
+
sherlock: "Sherlock",
|
|
6237
|
+
codehawks: "CodeHawks"
|
|
5846
6238
|
};
|
|
5847
6239
|
return knownPlatforms[normalized] ?? platform.trim();
|
|
5848
6240
|
}
|
|
5849
6241
|
function directProgramHandleVariants(query) {
|
|
5850
6242
|
const handleText = query.includes(":") ? query.slice(query.indexOf(":") + 1) : query;
|
|
5851
6243
|
const normalized = normalizeTargetIdentifierForMatch(handleText);
|
|
6244
|
+
const literal = directProgramLiteralHandleVariant(handleText);
|
|
5852
6245
|
const dashed = normalized.replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
5853
6246
|
const compact = dashed.replace(/-/g, "");
|
|
5854
6247
|
const tokens = [...meaningfulProgramQueryTokens(handleText)];
|
|
5855
|
-
|
|
5856
|
-
|
|
5857
|
-
|
|
5858
|
-
|
|
5859
|
-
|
|
5860
|
-
|
|
5861
|
-
|
|
6248
|
+
const brandTokens = targetBrandTokens(handleText);
|
|
6249
|
+
const literalVariants = [literal, dashed, compact, tokens.join("-"), tokens.join("")];
|
|
6250
|
+
const values = queryLooksLikeTargetIdentifier(handleText) ? [...brandTokens, ...literalVariants] : [...literalVariants, ...brandTokens];
|
|
6251
|
+
return uniqueStrings(values).filter(isDirectProgramHandleVariant);
|
|
6252
|
+
}
|
|
6253
|
+
function looksLikeDirectProgramHandleQuery(query) {
|
|
6254
|
+
const handleText = query.includes(":") ? query.slice(query.indexOf(":") + 1) : query;
|
|
6255
|
+
return isDirectProgramHandleVariant(directProgramLiteralHandleVariant(handleText));
|
|
6256
|
+
}
|
|
6257
|
+
function directProgramLiteralHandleVariant(value) {
|
|
6258
|
+
const normalized = value
|
|
6259
|
+
.trim()
|
|
6260
|
+
.replace(/\\/g, "/")
|
|
6261
|
+
.replace(/^['"`<]+|['"`>]+$/g, "")
|
|
6262
|
+
.replace(/^[a-z][a-z0-9+.-]*:\/\//i, "")
|
|
6263
|
+
.replace(/^www\./i, "");
|
|
6264
|
+
const queryStart = normalized.search(/[?#]/);
|
|
6265
|
+
return (queryStart >= 0 ? normalized.slice(0, queryStart) : normalized).replace(/\/+$/, "");
|
|
6266
|
+
}
|
|
6267
|
+
function isDirectProgramHandleVariant(value) {
|
|
6268
|
+
if (!value) {
|
|
6269
|
+
return false;
|
|
6270
|
+
}
|
|
6271
|
+
if (value.length < 2 || value.length > 150 || /[\u0000-\u001F\u007F?#:]/.test(value)) {
|
|
6272
|
+
return false;
|
|
6273
|
+
}
|
|
6274
|
+
return value
|
|
6275
|
+
.split("/")
|
|
6276
|
+
.every((segment) => segment.length > 0 && segment !== "." && segment !== ".." && !/^[\s.]+$/.test(segment));
|
|
5862
6277
|
}
|
|
5863
6278
|
async function collectProgramSources(client, sources, options) {
|
|
5864
6279
|
const pages = [];
|
|
@@ -6160,7 +6575,8 @@ async function collectTargetSamples(client, candidates, input, budget) {
|
|
|
6160
6575
|
.slice(0, input.target_sample_size);
|
|
6161
6576
|
samples[programId] = targets;
|
|
6162
6577
|
}
|
|
6163
|
-
catch {
|
|
6578
|
+
catch (error) {
|
|
6579
|
+
refundBudgetIfNoUpstreamError(budget, error);
|
|
6164
6580
|
samples[programId] = [];
|
|
6165
6581
|
}
|
|
6166
6582
|
});
|
|
@@ -6223,6 +6639,11 @@ function refundBudgetIfNoUpstreamRequest(budget, api, cost = 1) {
|
|
|
6223
6639
|
budget.remaining = Math.min(budget.initial, budget.remaining + cost);
|
|
6224
6640
|
}
|
|
6225
6641
|
}
|
|
6642
|
+
function refundBudgetIfNoUpstreamError(budget, error, cost = 1) {
|
|
6643
|
+
if (error instanceof BBRadarApiError && (error.cached || error.coalesced)) {
|
|
6644
|
+
budget.remaining = Math.min(budget.initial, budget.remaining + cost);
|
|
6645
|
+
}
|
|
6646
|
+
}
|
|
6226
6647
|
function addUniqueWarning(warnings, warning) {
|
|
6227
6648
|
if (warnings && !warnings.includes(warning)) {
|
|
6228
6649
|
warnings.push(warning);
|
|
@@ -6325,7 +6746,12 @@ function readStringArrayField(value, field) {
|
|
|
6325
6746
|
const fieldValue = value[field];
|
|
6326
6747
|
return Array.isArray(fieldValue) ? fieldValue.filter((entry) => typeof entry === "string") : [];
|
|
6327
6748
|
}
|
|
6328
|
-
function registerPrompts(server) {
|
|
6749
|
+
function registerPrompts(server, profile) {
|
|
6750
|
+
const discoveryToolInstructions = profile === "core"
|
|
6751
|
+
? `- Start with find_programs and pass opportunity_levels from the requested value below unless the user asked for a broader search.
|
|
6752
|
+
- Express language, target type, reward, Web3, wildcard, VDP, and platform needs through find_programs filters.`
|
|
6753
|
+
: `- Start with find_program_candidates unless the user needs custom filters; pass opportunity_levels from the requested value below.
|
|
6754
|
+
- 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 only for explicit 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.`;
|
|
6329
6755
|
server.registerPrompt("find_best_programs_to_hunt", {
|
|
6330
6756
|
title: "Find Best Programs To Hunt",
|
|
6331
6757
|
description: "Rank fresh BBRadar program data only. Do not invoke external bug bounty skills unless the user explicitly asks for methodology.",
|
|
@@ -6338,8 +6764,8 @@ function registerPrompts(server) {
|
|
|
6338
6764
|
}, (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.
|
|
6339
6765
|
|
|
6340
6766
|
Find the best BBRadar program candidates.
|
|
6341
|
-
|
|
6342
|
-
- Use
|
|
6767
|
+
${discoveryToolInstructions}
|
|
6768
|
+
- Use this opportunity level JSON array: ${promptJson([args.opportunity_level ?? "elite"])}.
|
|
6343
6769
|
- Apply platform filters from this JSON string if provided: ${promptJson(args.platforms ?? "")}.
|
|
6344
6770
|
- Apply tag filters from this JSON string if provided: ${promptJson(args.tags ?? "")}.
|
|
6345
6771
|
- Keep the shortlist to this JSON value: ${promptJson(args.max_programs ?? "10")}.
|
|
@@ -6399,7 +6825,7 @@ async function runTool(toolName, config, rateLimiter, callback) {
|
|
|
6399
6825
|
},
|
|
6400
6826
|
mcp_rate_limit: toRateLimitPayload(decision),
|
|
6401
6827
|
mcp_timing: toolTimingPayload(toolName, durationMs)
|
|
6402
|
-
}, true);
|
|
6828
|
+
}, true, config.compactToolText);
|
|
6403
6829
|
}
|
|
6404
6830
|
try {
|
|
6405
6831
|
const payload = await callback();
|
|
@@ -6409,21 +6835,21 @@ async function runTool(toolName, config, rateLimiter, callback) {
|
|
|
6409
6835
|
...payload,
|
|
6410
6836
|
mcp_rate_limit: toRateLimitPayload(decision),
|
|
6411
6837
|
mcp_timing: toolTimingPayload(toolName, durationMs)
|
|
6412
|
-
});
|
|
6838
|
+
}, false, config.compactToolText);
|
|
6413
6839
|
}
|
|
6414
6840
|
catch (error) {
|
|
6415
6841
|
const durationMs = Date.now() - startedAt;
|
|
6416
6842
|
recordToolMetric(toolName, durationMs, "error");
|
|
6417
|
-
return toolResult(errorPayload(error, decision, toolName, durationMs), true);
|
|
6843
|
+
return toolResult(errorPayload(error, decision, toolName, durationMs), true, config.compactToolText);
|
|
6418
6844
|
}
|
|
6419
6845
|
}
|
|
6420
|
-
function toolResult(payload, isError = false) {
|
|
6846
|
+
function toolResult(payload, isError = false, compactText = false) {
|
|
6421
6847
|
const resourceLinks = collectResourceLinks(payload);
|
|
6422
6848
|
return {
|
|
6423
6849
|
content: [
|
|
6424
6850
|
{
|
|
6425
6851
|
type: "text",
|
|
6426
|
-
text: JSON.stringify(payload)
|
|
6852
|
+
text: compactText ? compactToolText(payload, isError) : JSON.stringify(payload)
|
|
6427
6853
|
},
|
|
6428
6854
|
...resourceLinks
|
|
6429
6855
|
],
|
|
@@ -6431,6 +6857,28 @@ function toolResult(payload, isError = false) {
|
|
|
6431
6857
|
...(isError ? { isError: true } : {})
|
|
6432
6858
|
};
|
|
6433
6859
|
}
|
|
6860
|
+
function compactToolText(payload, isError) {
|
|
6861
|
+
if (isError) {
|
|
6862
|
+
return JSON.stringify(stripUndefined({
|
|
6863
|
+
request_id: payload.request_id,
|
|
6864
|
+
error: payload.error,
|
|
6865
|
+
mcp_timing: payload.mcp_timing
|
|
6866
|
+
}));
|
|
6867
|
+
}
|
|
6868
|
+
const meta = readObject(payload.meta);
|
|
6869
|
+
const returned = numberField(meta, "returned") ??
|
|
6870
|
+
[
|
|
6871
|
+
readArray(payload.programs).length,
|
|
6872
|
+
readArray(payload.targets).length,
|
|
6873
|
+
readArray(payload.changes).length
|
|
6874
|
+
].find((count) => count > 0);
|
|
6875
|
+
return JSON.stringify(stripUndefined({
|
|
6876
|
+
request_id: payload.request_id,
|
|
6877
|
+
summary: "Full tool result is available in structuredContent.",
|
|
6878
|
+
returned,
|
|
6879
|
+
mcp_timing: payload.mcp_timing
|
|
6880
|
+
}));
|
|
6881
|
+
}
|
|
6434
6882
|
function promptResult(text) {
|
|
6435
6883
|
return {
|
|
6436
6884
|
messages: [
|
|
@@ -6570,18 +7018,25 @@ function compactRecord(values) {
|
|
|
6570
7018
|
}));
|
|
6571
7019
|
}
|
|
6572
7020
|
function latestAddedTargetsQuery(input) {
|
|
6573
|
-
return {
|
|
7021
|
+
return stripUndefined({
|
|
6574
7022
|
change_type: "added",
|
|
6575
7023
|
target_type: input.target_type,
|
|
7024
|
+
platforms: input.platforms.length > 0 ? input.platforms : undefined,
|
|
7025
|
+
min_reward: input.min_reward,
|
|
7026
|
+
reward_threshold_mode: input.reward_threshold_mode,
|
|
7027
|
+
max_public_report_count: input.max_public_report_count,
|
|
7028
|
+
require_known_report_count: input.require_known_report_count,
|
|
7029
|
+
include_unknown_report_count: input.include_unknown_report_count,
|
|
6576
7030
|
include_out_of_scope: input.include_out_of_scope,
|
|
6577
7031
|
include_ineligible: input.include_ineligible,
|
|
6578
7032
|
recent_changes_page_size: input.recent_changes_page_size,
|
|
7033
|
+
max_results: input.max_results,
|
|
6579
7034
|
include_full_target_list: input.include_full_target_list,
|
|
6580
7035
|
full_target_list_mode: input.full_target_list_mode,
|
|
6581
7036
|
full_target_limit: input.full_target_limit,
|
|
6582
7037
|
full_list_include_out_of_scope: input.full_list_include_out_of_scope,
|
|
6583
7038
|
full_list_include_ineligible: input.full_list_include_ineligible
|
|
6584
|
-
};
|
|
7039
|
+
});
|
|
6585
7040
|
}
|
|
6586
7041
|
function formatTargetList(targets, mode) {
|
|
6587
7042
|
if (mode === "identifiers") {
|
|
@@ -6633,7 +7088,7 @@ function targetMatchesQuery(target, query, mode) {
|
|
|
6633
7088
|
}
|
|
6634
7089
|
return (containsMatch ||
|
|
6635
7090
|
profile.values.some((value) => queryValues.some((queryValue) => targetSuffixMatch(value, queryValue) || targetWildcardMatch(value, queryValue))) ||
|
|
6636
|
-
targetTokensMatch(profile.tokens, query));
|
|
7091
|
+
(!queryLooksLikeTargetIdentifier(query) && targetTokensMatch(profile.tokens, query)));
|
|
6637
7092
|
}
|
|
6638
7093
|
function upstreamTargetMatchMode(mode) {
|
|
6639
7094
|
return mode === "smart" ? "contains" : mode;
|
|
@@ -6671,7 +7126,11 @@ function targetSearchProfile(target) {
|
|
|
6671
7126
|
};
|
|
6672
7127
|
}
|
|
6673
7128
|
function targetQueryVariants(query) {
|
|
6674
|
-
|
|
7129
|
+
const variants = targetStringVariants(query);
|
|
7130
|
+
if (!queryLooksLikeTargetIdentifier(query)) {
|
|
7131
|
+
return variants;
|
|
7132
|
+
}
|
|
7133
|
+
return variants.filter(targetValueCanContainQuery);
|
|
6675
7134
|
}
|
|
6676
7135
|
function targetApiSearchQueries(query) {
|
|
6677
7136
|
const normalized = normalizeTargetIdentifierForMatch(query);
|