@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 +443 -82
- package/dist/server.js.map +1 -1
- package/package.json +1 -1
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 =
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
3218
|
-
|
|
3219
|
-
|
|
3220
|
-
|
|
3221
|
-
|
|
3222
|
-
|
|
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
|
-
|
|
3225
|
-
|
|
3226
|
-
|
|
3227
|
-
|
|
3228
|
-
|
|
3229
|
-
|
|
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
|
-
|
|
3232
|
-
|
|
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
|
-
...(
|
|
3388
|
+
...(lastMeta ?? {}),
|
|
3275
3389
|
returned: matches.length,
|
|
3276
|
-
target_search_rows_scanned:
|
|
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, "
|
|
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
|
|
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()
|