@bbradar/mcp 0.1.13 → 0.1.16

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/dist/server.js CHANGED
@@ -20,6 +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 = 4;
23
24
  const MAX_ACCEPTED_NUMERIC_LIMIT = 1_000;
24
25
  const PROGRAM_COLLECTION_CONCURRENCY = 4;
25
26
  const TARGET_SAMPLE_CONCURRENCY = 3;
@@ -30,10 +31,12 @@ const MAX_FILTER_TARGET_SAMPLE_PROGRAMS = 25;
30
31
  const MAX_LOCAL_EXPORT_RESOURCES = 25;
31
32
  const EXPORT_PREVIEW_LIMIT = 25;
32
33
  const STALE_PROGRAM_ID_RESOLVE_PAGES = 1;
33
- const STALE_PROGRAM_ID_RESOLVE_BUDGET = 1;
34
+ const STALE_PROGRAM_ID_RESOLVE_BUDGET = 8;
34
35
  const PROGRAM_FALLBACK_CACHE_MAX_ENTRIES = 200;
35
36
  const MAX_RESOLVE_SEARCH_QUERIES = 4;
37
+ const MAX_DIRECT_HANDLE_RESOLUTION_REQUESTS = 8;
36
38
  const SDK_VERSION = "1.29.0";
39
+ const PROGRAM_ID_PLATFORM_GUESSES = ["HackerOne", "Bugcrowd", "Intigriti", "HackenProof", "YesWeHack", "Cantina", "Code4rena", "Immunefi"];
37
40
  const WEB3_TAGS = ["web3", "crypto", "blockchain", "smart-contract", "smart contract", "defi", "nft", "ethereum", "solana", "solidity"];
38
41
  const WEB3_CONTEST_SIGNALS = ["contest", "audit", "competitive", "code4rena", "sherlock", "cantina", "codehawks", "hats", "spearbit", "warden"];
39
42
  const VDP_SIGNALS = ["vdp", "disclosure", "responsible disclosure", "vulnerability disclosure", "no bounty", "non-bounty", "no reward"];
@@ -44,6 +47,15 @@ const PROGRAM_RESOLUTION_STOP_WORDS = new Set([
44
47
  "targets",
45
48
  "scope",
46
49
  "scopes",
50
+ "network",
51
+ "networks",
52
+ "protocol",
53
+ "protocols",
54
+ "bug",
55
+ "bugs",
56
+ "bounty",
57
+ "bounties",
58
+ "security",
47
59
  "new",
48
60
  "latest",
49
61
  "recent",
@@ -406,7 +418,7 @@ export function createBbradarServer(client, config) {
406
418
  max_pages: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(3),
407
419
  max_results: z.number().int().min(1).max(25).default(10),
408
420
  output_mode: programListModeSchema.default("compact"),
409
- upstream_request_budget: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(DEFAULT_FIND_UPSTREAM_REQUEST_BUDGET)
421
+ upstream_request_budget: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(DEFAULT_RESOLVE_UPSTREAM_REQUEST_BUDGET)
410
422
  },
411
423
  outputSchema: {
412
424
  ...programListOutputShape(),
@@ -439,7 +451,7 @@ export function createBbradarServer(client, config) {
439
451
  target_list_mode: targetListModeSchema.default("identifiers"),
440
452
  max_resolve_pages: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(3),
441
453
  output_mode: programListModeSchema.default("compact"),
442
- upstream_request_budget: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(DEFAULT_FIND_UPSTREAM_REQUEST_BUDGET)
454
+ upstream_request_budget: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(DEFAULT_RESOLVE_UPSTREAM_REQUEST_BUDGET)
443
455
  },
444
456
  outputSchema: {
445
457
  ...apiEnvelopeOutputShape(),
@@ -462,7 +474,7 @@ export function createBbradarServer(client, config) {
462
474
  tags: tagListSchema(),
463
475
  opportunity_levels: z.array(opportunityLevelSchema).max(4).default([]),
464
476
  max_resolve_pages: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(3),
465
- upstream_request_budget: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(DEFAULT_FIND_UPSTREAM_REQUEST_BUDGET),
477
+ upstream_request_budget: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(DEFAULT_RESOLVE_UPSTREAM_REQUEST_BUDGET),
466
478
  include_out_of_scope: z.boolean().default(false),
467
479
  include_ineligible: z.boolean().default(false),
468
480
  strict_scope_filter: z.boolean().default(true),
@@ -2126,14 +2138,13 @@ async function resolveProgramNameIdentifier(client, config, requestedProgramName
2126
2138
  }
2127
2139
  const sourceRequests = readArray(resolution.source_requests).filter((request) => readObject(request) !== undefined);
2128
2140
  const warnings = readArray(resolution.warnings).filter((warning) => typeof warning === "string");
2129
- const fallbackWarning = `Resolved program name ${requestedProgramName} to program_id ${match.resolvedProgramId}.`;
2130
2141
  return {
2131
2142
  requestedProgramId: requestedProgramName,
2132
2143
  resolvedProgramId: match.resolvedProgramId,
2133
2144
  query,
2134
2145
  reason: "program_name",
2135
2146
  sourceRequests,
2136
- warnings: uniqueStrings([...warnings, fallbackWarning]),
2147
+ warnings: uniqueStrings(warnings),
2137
2148
  resolution
2138
2149
  };
2139
2150
  }
@@ -2347,6 +2358,7 @@ async function resolveProgram(client, config, input) {
2347
2358
  const maxPages = clampLimit("max_pages", input.max_pages, MAX_FIND_PROGRAM_PAGES, warnings);
2348
2359
  const searchQueries = programResolutionSearchQueries(input.query);
2349
2360
  const collected = await collectProgramsForResolution(client, {
2361
+ query: input.query,
2350
2362
  searchQueries,
2351
2363
  isHighConfidenceMatch: (programs) => hasHighConfidenceProgramResolutionMatch(programs, config.webBaseUrl, input.query),
2352
2364
  scorePrograms: (programs) => bestProgramResolutionScore(programs, config.webBaseUrl, input.query),
@@ -2357,7 +2369,9 @@ async function resolveProgram(client, config, input) {
2357
2369
  budget,
2358
2370
  warnings
2359
2371
  });
2360
- const matches = collected.programs
2372
+ const sourceRequests = [...collected.sourceRequests];
2373
+ let targetLookup;
2374
+ let matches = collected.programs
2361
2375
  .map((program) => {
2362
2376
  const candidate = toProgramCandidate(program, config.webBaseUrl);
2363
2377
  const match = programResolutionMatch(candidate, input.query);
@@ -2366,14 +2380,58 @@ async function resolveProgram(client, config, input) {
2366
2380
  .filter((entry) => entry.match.score > 0)
2367
2381
  .sort((left, right) => right.match.score - left.match.score || compareProgramCandidates(left.candidate, right.candidate, "best_hunt_value"))
2368
2382
  .slice(0, input.max_results);
2383
+ if (matches.length === 0 && queryLooksLikeTargetIdentifier(input.query)) {
2384
+ targetLookup = await findProgramsByTarget(client, config, {
2385
+ target_query: input.query,
2386
+ match_mode: "smart",
2387
+ platforms: input.platforms,
2388
+ tags: input.tags,
2389
+ use_api_search: true,
2390
+ max_pages: 1,
2391
+ max_results: input.max_results,
2392
+ include_out_of_scope: false,
2393
+ include_ineligible: false,
2394
+ max_targets_per_program: 3,
2395
+ target_list_mode: "identifiers",
2396
+ upstream_request_budget: Math.max(1, budget.remaining)
2397
+ });
2398
+ sourceRequests.push(...readArray(targetLookup.source_requests).filter((request) => readObject(request) !== undefined));
2399
+ for (const warning of readArray(targetLookup.warnings)) {
2400
+ if (typeof warning === "string") {
2401
+ addUniqueWarning(warnings, warning);
2402
+ }
2403
+ }
2404
+ const targetBudget = readObject(targetLookup.upstream_budget);
2405
+ const targetBudgetUsed = numberField(targetBudget, "used") ?? 0;
2406
+ budget.remaining = Math.max(0, budget.remaining - targetBudgetUsed);
2407
+ matches = readArray(targetLookup.matches)
2408
+ .map((entry) => {
2409
+ const matchEntry = readObject(entry);
2410
+ const program = readObject(matchEntry?.program);
2411
+ if (!program) {
2412
+ return undefined;
2413
+ }
2414
+ return {
2415
+ candidate: toProgramCandidate(program, config.webBaseUrl),
2416
+ match: {
2417
+ score: 75,
2418
+ fields: ["targets"],
2419
+ reasons: ["target search matched query"]
2420
+ }
2421
+ };
2422
+ })
2423
+ .filter((entry) => entry !== undefined)
2424
+ .slice(0, input.max_results);
2425
+ }
2369
2426
  const programs = matches.map((entry) => formatProgram(entry.candidate.program, input.output_mode));
2370
2427
  const payload = stripUndefined({
2371
2428
  request_id: randomUUID(),
2372
- source_requests: collected.sourceRequests,
2429
+ source_requests: sourceRequests,
2373
2430
  warnings: warnings.length > 0 ? warnings : undefined,
2374
2431
  query: {
2375
2432
  text: input.query,
2376
2433
  search_queries: searchQueries,
2434
+ target_search_queries: readObject(targetLookup?.filters)?.target_search_queries,
2377
2435
  platforms: input.platforms.length > 0 ? input.platforms : undefined,
2378
2436
  tags: input.tags.length > 0 ? input.tags : undefined
2379
2437
  },
@@ -2398,7 +2456,8 @@ async function resolveProgram(client, config, input) {
2398
2456
  meta: {
2399
2457
  returned: programs.length,
2400
2458
  programs_scanned: collected.programsScanned,
2401
- upstream_requests_scanned: collected.requestsScanned
2459
+ upstream_requests_scanned: collected.requestsScanned,
2460
+ target_lookup_returned: numberField(readObject(targetLookup?.meta), "returned")
2402
2461
  }
2403
2462
  });
2404
2463
  rememberResolveProgram(config, input, payload);
@@ -3091,16 +3150,43 @@ function findRecentByType(client, config, input) {
3091
3150
  }
3092
3151
  async function findProgramsByTarget(client, config, input) {
3093
3152
  const warnings = [];
3153
+ const budget = {
3154
+ initial: input.upstream_request_budget,
3155
+ remaining: input.upstream_request_budget
3156
+ };
3157
+ const sourceRequests = [];
3158
+ const matches = [];
3159
+ const directHandles = await collectDirectHandlePrograms(client, {
3160
+ query: input.target_query,
3161
+ platforms: input.platforms,
3162
+ budget,
3163
+ warnings,
3164
+ source: "target_query_program_id_guess",
3165
+ maxProgramIds: 1,
3166
+ stopOnFirstMatch: true
3167
+ });
3168
+ sourceRequests.push(...directHandles.sourceRequests);
3169
+ await appendProgramTargetMatches(client, input, budget, warnings, sourceRequests, matches, directHandles.programs.map((program) => toProgramCandidate(program, config.webBaseUrl)));
3170
+ if (matches.length > 0 && input.match_mode === "smart") {
3171
+ return programsByTargetResult(input, warnings, sourceRequests, matches, budget, {
3172
+ source: "direct_program_handle_targets",
3173
+ rankingScope: {
3174
+ mode: "direct_program_handle",
3175
+ possibly_incomplete: false
3176
+ },
3177
+ meta: {
3178
+ returned: matches.length,
3179
+ programs_scanned: directHandles.programsScanned,
3180
+ target_lists_checked: sourceRequests.filter((request) => request.source === "program_targets").length
3181
+ }
3182
+ });
3183
+ }
3094
3184
  if (input.use_api_search) {
3095
3185
  const directResult = await searchProgramsByTargetApi(client, config, input, warnings);
3096
3186
  if (directResult) {
3097
3187
  return directResult;
3098
3188
  }
3099
3189
  }
3100
- const budget = {
3101
- initial: input.upstream_request_budget,
3102
- remaining: input.upstream_request_budget
3103
- };
3104
3190
  const maxPages = clampLimit("max_pages", input.max_pages, MAX_FIND_PROGRAM_PAGES, warnings);
3105
3191
  const collected = await collectPrograms(client, {
3106
3192
  platforms: input.platforms,
@@ -3111,11 +3197,27 @@ async function findProgramsByTarget(client, config, input) {
3111
3197
  budget,
3112
3198
  warnings
3113
3199
  });
3114
- const sourceRequests = [...collected.sourceRequests];
3115
- const matches = [];
3200
+ sourceRequests.push(...collected.sourceRequests);
3116
3201
  const candidates = collected.programs
3117
3202
  .map((program) => toProgramCandidate(program, config.webBaseUrl))
3118
3203
  .sort((left, right) => compareProgramCandidates(left, right, "best_hunt_value"));
3204
+ await appendProgramTargetMatches(client, input, budget, warnings, sourceRequests, matches, candidates);
3205
+ return programsByTargetResult(input, warnings, sourceRequests, matches, budget, {
3206
+ source: "sampled_program_targets",
3207
+ rankingScope: {
3208
+ mode: "sampled",
3209
+ max_pages_scanned_per_source: maxPages,
3210
+ upstream_total_pages: collected.totalPagesBySource,
3211
+ possibly_incomplete: isRankingPossiblyIncomplete(collected.totalPagesBySource, maxPages)
3212
+ },
3213
+ meta: {
3214
+ returned: matches.length,
3215
+ programs_scanned: directHandles.programsScanned + collected.programsScanned,
3216
+ target_lists_checked: sourceRequests.filter((request) => request.source === "program_targets").length
3217
+ }
3218
+ });
3219
+ }
3220
+ async function appendProgramTargetMatches(client, input, budget, warnings, sourceRequests, matches, candidates) {
3119
3221
  for (const candidate of candidates) {
3120
3222
  if (matches.length >= input.max_results) {
3121
3223
  break;
@@ -3125,7 +3227,9 @@ async function findProgramsByTarget(client, config, input) {
3125
3227
  continue;
3126
3228
  }
3127
3229
  if (!tryConsumeBudget(budget)) {
3128
- addUniqueWarning(warnings, "Upstream request budget was exhausted before all candidate target lists could be checked.");
3230
+ if (matches.length === 0) {
3231
+ addUniqueWarning(warnings, "Upstream request budget was exhausted before all candidate target lists could be checked.");
3232
+ }
3129
3233
  break;
3130
3234
  }
3131
3235
  const targetsApi = await client.getProgramTargets(programId);
@@ -3157,6 +3261,8 @@ async function findProgramsByTarget(client, config, input) {
3157
3261
  total_targets_checked: targets.length
3158
3262
  }));
3159
3263
  }
3264
+ }
3265
+ function programsByTargetResult(input, warnings, sourceRequests, matches, budget, options) {
3160
3266
  return stripUndefined({
3161
3267
  request_id: randomUUID(),
3162
3268
  source_requests: sourceRequests,
@@ -3166,7 +3272,7 @@ async function findProgramsByTarget(client, config, input) {
3166
3272
  match_mode: input.match_mode,
3167
3273
  platforms: input.platforms.length > 0 ? input.platforms : undefined,
3168
3274
  tags: input.tags.length > 0 ? input.tags : undefined,
3169
- source: "sampled_program_targets",
3275
+ source: options.source,
3170
3276
  include_out_of_scope: input.include_out_of_scope,
3171
3277
  include_ineligible: input.include_ineligible,
3172
3278
  target_list_mode: input.target_list_mode
@@ -3177,63 +3283,79 @@ async function findProgramsByTarget(client, config, input) {
3177
3283
  used: budget.initial - budget.remaining,
3178
3284
  remaining: budget.remaining
3179
3285
  },
3180
- ranking_scope: {
3181
- mode: "sampled",
3182
- max_pages_scanned_per_source: maxPages,
3183
- upstream_total_pages: collected.totalPagesBySource,
3184
- possibly_incomplete: isRankingPossiblyIncomplete(collected.totalPagesBySource, maxPages)
3185
- },
3186
- meta: {
3187
- returned: matches.length,
3188
- programs_scanned: collected.programsScanned,
3189
- target_lists_checked: sourceRequests.filter((request) => request.source === "program_targets").length
3190
- }
3286
+ ranking_scope: options.rankingScope,
3287
+ meta: options.meta
3191
3288
  });
3192
3289
  }
3193
3290
  async function searchProgramsByTargetApi(client, config, input, warnings) {
3194
- let api;
3195
- try {
3196
- api = await client.searchTargets({
3197
- q: input.target_query,
3198
- match_mode: upstreamTargetMatchMode(input.match_mode),
3199
- platforms: input.platforms,
3200
- tags: input.tags,
3201
- include_out_of_scope: input.include_out_of_scope,
3202
- include_ineligible: input.include_ineligible,
3203
- page: 1,
3204
- page_size: Math.max(input.max_results * input.max_targets_per_program, input.max_results)
3205
- });
3206
- }
3207
- catch (error) {
3208
- if (error instanceof BBRadarApiError && (error.status === 404 || error.status === 405)) {
3209
- warnings.push("Direct target search API is unavailable; fell back to sampled program target lists.");
3210
- return undefined;
3211
- }
3212
- throw error;
3213
- }
3214
- const data = readObject(api.data);
3215
- const rawMatches = readTargetSearchRows(data);
3291
+ const searchQueries = targetApiSearchQueries(input.target_query);
3216
3292
  const grouped = new Map();
3217
- for (const row of rawMatches) {
3218
- const program = addProgramResourceLinks(sanitizeProgram(readObject(row.program) ?? readObject(readObject(row.target)?.program) ?? row, config.webBaseUrl));
3219
- const programId = stringField(program, "id") ?? stringField(row, "program_id");
3220
- const target = sanitizeTarget(readObject(row.target) ?? row);
3221
- if (!programId || !targetIdentifier(target)) {
3222
- continue;
3293
+ const sourceRequests = [];
3294
+ let rawMatchesScanned = 0;
3295
+ let lastMeta;
3296
+ for (const searchQuery of searchQueries) {
3297
+ let api;
3298
+ try {
3299
+ api = await client.searchTargets({
3300
+ q: searchQuery,
3301
+ match_mode: upstreamTargetMatchMode(input.match_mode),
3302
+ platforms: input.platforms,
3303
+ tags: input.tags,
3304
+ include_out_of_scope: input.include_out_of_scope,
3305
+ include_ineligible: input.include_ineligible,
3306
+ page: 1,
3307
+ page_size: Math.max(input.max_results * input.max_targets_per_program, input.max_results)
3308
+ });
3223
3309
  }
3224
- if (!targetHasAllowedScope(target, {
3225
- include_out_of_scope: input.include_out_of_scope,
3226
- include_ineligible: input.include_ineligible,
3227
- strict_scope_filter: false
3228
- })) {
3229
- continue;
3310
+ catch (error) {
3311
+ if (error instanceof BBRadarApiError && (error.status === 404 || error.status === 405)) {
3312
+ warnings.push("Direct target search API is unavailable; fell back to sampled program target lists.");
3313
+ return undefined;
3314
+ }
3315
+ throw error;
3230
3316
  }
3231
- if (!targetMatchesQuery(target, input.target_query, input.match_mode)) {
3232
- continue;
3317
+ const data = readObject(api.data);
3318
+ const rawMatches = readTargetSearchRows(data);
3319
+ rawMatchesScanned += rawMatches.length;
3320
+ lastMeta = readObject(sanitizeJson(data?.meta)) ?? lastMeta;
3321
+ sourceRequests.push({
3322
+ source: "target_search",
3323
+ query: searchQuery,
3324
+ api_match_mode: upstreamTargetMatchMode(input.match_mode),
3325
+ request_id: api.requestId,
3326
+ upstream_request_id: api.upstreamRequestId,
3327
+ ...apiSourceMetadata(api)
3328
+ });
3329
+ for (const row of rawMatches) {
3330
+ const rowProgram = sanitizeProgram(readObject(row.program) ?? row, config.webBaseUrl);
3331
+ const rowProgramId = stringField(rowProgram, "id") ?? stringField(row, "program_id");
3332
+ const targets = targetSearchRowTargets(row);
3333
+ for (const targetSource of targets) {
3334
+ const target = sanitizeTarget(targetSource);
3335
+ const nestedProgram = sanitizeProgram(readObject(targetSource.program), config.webBaseUrl);
3336
+ const program = stringField(nestedProgram, "id") ? nestedProgram : rowProgram;
3337
+ const programId = stringField(program, "id") ?? rowProgramId ?? stringField(targetSource, "program_id");
3338
+ if (!programId || !targetIdentifier(target)) {
3339
+ continue;
3340
+ }
3341
+ if (!targetHasAllowedScope(target, {
3342
+ include_out_of_scope: input.include_out_of_scope,
3343
+ include_ineligible: input.include_ineligible,
3344
+ strict_scope_filter: false
3345
+ })) {
3346
+ continue;
3347
+ }
3348
+ if (!targetMatchesQuery(target, input.target_query, input.match_mode)) {
3349
+ continue;
3350
+ }
3351
+ const existing = grouped.get(programId) ?? { program: addProgramResourceLinks({ ...program, id: programId }), targets: [] };
3352
+ existing.targets.push(target);
3353
+ grouped.set(programId, existing);
3354
+ }
3355
+ }
3356
+ if (grouped.size >= input.max_results) {
3357
+ break;
3233
3358
  }
3234
- const existing = grouped.get(programId) ?? { program: { ...program, id: programId }, targets: [] };
3235
- existing.targets.push(target);
3236
- grouped.set(programId, existing);
3237
3359
  }
3238
3360
  const matches = [...grouped.values()].slice(0, input.max_results).map((entry) => stripUndefined({
3239
3361
  program: formatProgram(entry.program, "compact"),
@@ -3245,21 +3367,13 @@ async function searchProgramsByTargetApi(client, config, input, warnings) {
3245
3367
  warnings.push("Direct target search returned no smart matches; fell back to sampled program target lists.");
3246
3368
  return undefined;
3247
3369
  }
3248
- const sanitizedMeta = readObject(sanitizeJson(data?.meta));
3249
3370
  return stripUndefined({
3250
3371
  request_id: randomUUID(),
3251
- source_requests: [
3252
- {
3253
- source: "target_search",
3254
- api_match_mode: upstreamTargetMatchMode(input.match_mode),
3255
- request_id: api.requestId,
3256
- upstream_request_id: api.upstreamRequestId,
3257
- ...apiSourceMetadata(api)
3258
- }
3259
- ],
3372
+ source_requests: sourceRequests,
3260
3373
  warnings: warnings.length > 0 ? warnings : undefined,
3261
3374
  filters: {
3262
3375
  target_query: input.target_query,
3376
+ target_search_queries: searchQueries.length > 1 ? searchQueries : undefined,
3263
3377
  match_mode: input.match_mode,
3264
3378
  platforms: input.platforms.length > 0 ? input.platforms : undefined,
3265
3379
  tags: input.tags.length > 0 ? input.tags : undefined,
@@ -3271,19 +3385,52 @@ async function searchProgramsByTargetApi(client, config, input, warnings) {
3271
3385
  matches,
3272
3386
  programs: matches.map((match) => readObject(match.program)).filter((program) => program !== undefined),
3273
3387
  meta: stripUndefined({
3274
- ...(sanitizedMeta ?? {}),
3388
+ ...(lastMeta ?? {}),
3275
3389
  returned: matches.length,
3276
- target_search_rows_scanned: rawMatches.length
3390
+ target_search_rows_scanned: rawMatchesScanned
3277
3391
  })
3278
3392
  });
3279
3393
  }
3280
3394
  function readTargetSearchRows(data) {
3281
- return [data?.results, data?.matches, data?.targets]
3395
+ return [data?.results, data?.matches, data?.programs, data?.targets]
3282
3396
  .map(readArray)
3283
3397
  .find((rows) => rows.length > 0)
3284
3398
  ?.map((row) => readObject(row))
3285
3399
  .filter((row) => row !== undefined) ?? [];
3286
3400
  }
3401
+ function targetSearchRowTargets(row) {
3402
+ const directTarget = readObject(row.target);
3403
+ const arrays = [
3404
+ row.targets,
3405
+ row.matched_targets,
3406
+ row.matching_targets,
3407
+ row.target_matches,
3408
+ row.scope
3409
+ ]
3410
+ .flatMap(readArray)
3411
+ .map(targetSearchEntryToTarget)
3412
+ .filter((target) => target !== undefined);
3413
+ if (directTarget) {
3414
+ return [directTarget, ...arrays];
3415
+ }
3416
+ if (arrays.length > 0) {
3417
+ return arrays;
3418
+ }
3419
+ if (targetIdentifier(row)) {
3420
+ return [row];
3421
+ }
3422
+ return [];
3423
+ }
3424
+ function targetSearchEntryToTarget(entry) {
3425
+ if (typeof entry === "string") {
3426
+ return { identifier: entry };
3427
+ }
3428
+ const object = readObject(entry);
3429
+ if (!object) {
3430
+ return undefined;
3431
+ }
3432
+ return readObject(object.target) ?? object;
3433
+ }
3287
3434
  function rewardThresholdFilters(minReward, mode) {
3288
3435
  return {
3289
3436
  min_bounty_min: minReward !== undefined && mode === "min_at_least" ? minReward : undefined,
@@ -5451,9 +5598,27 @@ async function collectProgramsForResolution(client, options) {
5451
5598
  const collections = [];
5452
5599
  const queryCollections = [];
5453
5600
  const queries = options.searchQueries.length > 0 ? options.searchQueries : [undefined];
5601
+ const primaryDirectProgramIds = new Set(directProgramIdCandidates(options.query, options.platforms).slice(0, 1));
5602
+ if (primaryDirectProgramIds.size > 0 && options.budget.remaining > 0) {
5603
+ const primaryDirectHandles = await collectDirectHandlePrograms(client, {
5604
+ query: options.query,
5605
+ platforms: options.platforms,
5606
+ budget: options.budget,
5607
+ warnings: options.warnings,
5608
+ source: "program_id_handle_guess",
5609
+ maxProgramIds: 1,
5610
+ stopOnFirstMatch: true
5611
+ });
5612
+ if (primaryDirectHandles.programs.length > 0 || primaryDirectHandles.requestsScanned > 0) {
5613
+ collections.push(primaryDirectHandles);
5614
+ }
5615
+ if (primaryDirectHandles.programs.length > 0 && options.isHighConfidenceMatch?.(mergeProgramCollections(collections).programs)) {
5616
+ return mergeProgramCollections(collections);
5617
+ }
5618
+ }
5454
5619
  for (const search of queries) {
5455
5620
  if (options.budget.remaining <= 0) {
5456
- addUniqueWarning(options.warnings, "Upstream request budget was exhausted before all program name search variants could be checked.");
5621
+ addUniqueWarning(options.warnings, "Program name search stopped at the request budget; pass a higher upstream_request_budget for a deeper resolution attempt.");
5457
5622
  break;
5458
5623
  }
5459
5624
  const collection = await collectPrograms(client, {
@@ -5472,6 +5637,23 @@ async function collectProgramsForResolution(client, options) {
5472
5637
  return mergeProgramCollections(collections);
5473
5638
  }
5474
5639
  }
5640
+ if (options.budget.remaining > 0) {
5641
+ const directHandles = await collectDirectHandlePrograms(client, {
5642
+ query: options.query,
5643
+ platforms: options.platforms,
5644
+ budget: options.budget,
5645
+ warnings: options.warnings,
5646
+ source: "program_id_handle_guess",
5647
+ skipProgramIds: primaryDirectProgramIds,
5648
+ stopOnFirstMatch: true
5649
+ });
5650
+ if (directHandles.programs.length > 0 || directHandles.requestsScanned > 0) {
5651
+ collections.push(directHandles);
5652
+ }
5653
+ if (directHandles.programs.length > 0 && options.isHighConfidenceMatch?.(mergeProgramCollections(collections).programs)) {
5654
+ return mergeProgramCollections(collections);
5655
+ }
5656
+ }
5475
5657
  if (options.max_pages > 1) {
5476
5658
  const search = bestResolutionDeepSearch(queryCollections, options.scorePrograms) ?? queries[0];
5477
5659
  if (options.budget.remaining <= 0) {
@@ -5532,6 +5714,152 @@ function mergeProgramCollections(collections) {
5532
5714
  totalPagesBySource
5533
5715
  };
5534
5716
  }
5717
+ async function collectDirectHandlePrograms(client, options) {
5718
+ const programIds = directProgramIdCandidates(options.query, options.platforms)
5719
+ .filter((programId) => !options.skipProgramIds?.has(programId))
5720
+ .slice(0, options.maxProgramIds ?? MAX_DIRECT_HANDLE_RESOLUTION_REQUESTS);
5721
+ const programs = [];
5722
+ const sourceRequests = [];
5723
+ let requestsScanned = 0;
5724
+ for (const programId of programIds) {
5725
+ if (!tryConsumeBudget(options.budget)) {
5726
+ break;
5727
+ }
5728
+ requestsScanned += 1;
5729
+ try {
5730
+ const api = await client.getProgram(programId);
5731
+ programs.push(api.data);
5732
+ sourceRequests.push({
5733
+ source: options.source,
5734
+ program_id: programId,
5735
+ request_id: api.requestId,
5736
+ upstream_request_id: api.upstreamRequestId,
5737
+ ...apiSourceMetadata(api)
5738
+ });
5739
+ if (options.stopOnFirstMatch) {
5740
+ break;
5741
+ }
5742
+ }
5743
+ catch (error) {
5744
+ if (error instanceof BBRadarApiError && error.status === 404) {
5745
+ sourceRequests.push({
5746
+ source: options.source,
5747
+ endpoint: "program_detail",
5748
+ program_id: programId,
5749
+ request_id: error.requestId,
5750
+ upstream_request_id: error.upstreamRequestId,
5751
+ status: error.status
5752
+ });
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
+ continue;
5791
+ }
5792
+ throw error;
5793
+ }
5794
+ }
5795
+ return {
5796
+ programs,
5797
+ sourceRequests,
5798
+ requestsScanned,
5799
+ programsScanned: programs.length,
5800
+ totalPagesBySource: programs.length > 0 ? { [options.source]: 1 } : {}
5801
+ };
5802
+ }
5803
+ function syntheticProgramFromProgramId(programId, targets) {
5804
+ const separatorIndex = programId.indexOf(":");
5805
+ const platform = separatorIndex >= 0 ? programId.slice(0, separatorIndex) : undefined;
5806
+ const handle = separatorIndex >= 0 ? programId.slice(separatorIndex + 1) : programId;
5807
+ const scopeTags = uniqueStrings(targets.flatMap((target) => readStringArrayField(target, "scope_tags")));
5808
+ return stripUndefined({
5809
+ id: programId,
5810
+ handle,
5811
+ name: titleizeProgramHandle(handle),
5812
+ platform,
5813
+ scope_tags: scopeTags,
5814
+ target_counts: {
5815
+ total: targets.length,
5816
+ in_scope: targets.filter((target) => target.in_scope === true).length,
5817
+ out_of_scope: targets.filter((target) => target.in_scope === false).length,
5818
+ eligible_for_bounty: targets.filter((target) => target.eligible_for_bounty === true).length,
5819
+ wildcard: targets.filter(targetIsWildcard).length
5820
+ }
5821
+ });
5822
+ }
5823
+ function titleizeProgramHandle(handle) {
5824
+ return handle
5825
+ .split(/[-_.\s]+/)
5826
+ .filter(Boolean)
5827
+ .map((part) => part.slice(0, 1).toUpperCase() + part.slice(1))
5828
+ .join(" ");
5829
+ }
5830
+ function directProgramIdCandidates(query, platforms) {
5831
+ const platformGuesses = (platforms.length > 0 ? platforms : PROGRAM_ID_PLATFORM_GUESSES).map(canonicalProgramIdPlatform);
5832
+ const handles = directProgramHandleVariants(query);
5833
+ return uniqueStrings(platformGuesses.flatMap((platform) => handles.map((handle) => `${platform}:${handle}`))).slice(0, MAX_DIRECT_HANDLE_RESOLUTION_REQUESTS);
5834
+ }
5835
+ function canonicalProgramIdPlatform(platform) {
5836
+ const normalized = normalizeTag(platform).replace(/[^a-z0-9]/g, "");
5837
+ const knownPlatforms = {
5838
+ hackerone: "HackerOne",
5839
+ bugcrowd: "Bugcrowd",
5840
+ intigriti: "Intigriti",
5841
+ hackenproof: "HackenProof",
5842
+ yeswehack: "YesWeHack",
5843
+ cantina: "Cantina",
5844
+ code4rena: "Code4rena",
5845
+ immunefi: "Immunefi"
5846
+ };
5847
+ return knownPlatforms[normalized] ?? platform.trim();
5848
+ }
5849
+ function directProgramHandleVariants(query) {
5850
+ const handleText = query.includes(":") ? query.slice(query.indexOf(":") + 1) : query;
5851
+ const normalized = normalizeTargetIdentifierForMatch(handleText);
5852
+ const dashed = normalized.replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
5853
+ const compact = dashed.replace(/-/g, "");
5854
+ const tokens = [...meaningfulProgramQueryTokens(handleText)];
5855
+ return uniqueStrings([
5856
+ ...targetBrandTokens(handleText),
5857
+ dashed,
5858
+ compact,
5859
+ tokens.join("-"),
5860
+ tokens.join("")
5861
+ ]).filter((handle) => /^[a-z0-9][a-z0-9_.-]{3,100}$/i.test(handle));
5862
+ }
5535
5863
  async function collectProgramSources(client, sources, options) {
5536
5864
  const pages = [];
5537
5865
  await mapWithConcurrency(sources.map((source, sourceIndex) => ({ source, sourceIndex })), PROGRAM_COLLECTION_CONCURRENCY, async ({ source, sourceIndex }) => {
@@ -6299,7 +6627,7 @@ function targetMatchesQuery(target, query, mode) {
6299
6627
  if (mode === "wildcard") {
6300
6628
  return profile.values.some((value) => queryValues.some((queryValue) => targetWildcardMatch(value, queryValue)));
6301
6629
  }
6302
- const containsMatch = profile.values.some((value) => queryValues.some((queryValue) => value.includes(queryValue) || queryValue.includes(value)));
6630
+ const containsMatch = profile.values.some((value) => queryValues.some((queryValue) => targetContainsMatch(value, queryValue)));
6303
6631
  if (mode === "contains") {
6304
6632
  return containsMatch;
6305
6633
  }
@@ -6345,6 +6673,19 @@ function targetSearchProfile(target) {
6345
6673
  function targetQueryVariants(query) {
6346
6674
  return targetStringVariants(query);
6347
6675
  }
6676
+ function targetApiSearchQueries(query) {
6677
+ const normalized = normalizeTargetIdentifierForMatch(query);
6678
+ const brandDomains = targetBrandTokens(query).flatMap((token) => [`${token}.com`, `*.${token}.com`]);
6679
+ return uniqueStrings([
6680
+ normalized,
6681
+ ...targetQueryVariants(query),
6682
+ ...brandDomains
6683
+ ].filter((entry) => entry !== undefined && entry.length >= 2)).slice(0, MAX_RESOLVE_SEARCH_QUERIES);
6684
+ }
6685
+ function queryLooksLikeTargetIdentifier(query) {
6686
+ const normalized = normalizeTargetIdentifierForMatch(query);
6687
+ return /[./*:]/.test(normalized);
6688
+ }
6348
6689
  function targetStringVariants(value) {
6349
6690
  const normalized = normalizeTargetIdentifierForMatch(value);
6350
6691
  if (!normalized) {
@@ -6373,6 +6714,15 @@ function targetSuffixMatch(value, query) {
6373
6714
  function targetWildcardMatch(value, query) {
6374
6715
  return wildcardTargetMatch(query, value) || wildcardTargetMatch(value, query);
6375
6716
  }
6717
+ function targetContainsMatch(value, query) {
6718
+ if (value.includes(query)) {
6719
+ return true;
6720
+ }
6721
+ return targetValueCanContainQuery(value) && query.includes(value);
6722
+ }
6723
+ function targetValueCanContainQuery(value) {
6724
+ return value.includes(".") || value.includes("/") || value.includes("*") || value.includes(":");
6725
+ }
6376
6726
  function targetTokensMatch(targetTokens, query) {
6377
6727
  const queryTokens = [...targetTokenSet(query)];
6378
6728
  if (queryTokens.length === 0) {
@@ -6389,6 +6739,17 @@ function targetTokenSet(value) {
6389
6739
  const meaningfulTokens = tokens.filter((token) => !TARGET_QUERY_STOP_WORDS.has(token));
6390
6740
  return new Set(meaningfulTokens.length > 0 ? meaningfulTokens : tokens);
6391
6741
  }
6742
+ function targetBrandTokens(value) {
6743
+ const normalized = stripLeadingTargetWildcard(normalizeTargetIdentifierForMatch(value));
6744
+ const host = targetHostVariant(normalized) ?? (normalized.includes(".") && !normalized.includes("/") ? normalized : undefined);
6745
+ const hostBrand = host
6746
+ ?.split(".")
6747
+ .filter(Boolean)
6748
+ .slice(-2, -1)
6749
+ .find((token) => token.length >= 3 && !TARGET_QUERY_STOP_WORDS.has(token));
6750
+ const textTokens = [...targetTokenSet(normalized)].filter((token) => token.length >= 4);
6751
+ return uniqueStrings([hostBrand, ...textTokens].filter((entry) => entry !== undefined));
6752
+ }
6392
6753
  function normalizeTargetIdentifierForMatch(value) {
6393
6754
  const normalized = value
6394
6755
  .trim()