@bbradar/mcp 0.1.16 → 0.1.19

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
@@ -34,9 +34,31 @@ const STALE_PROGRAM_ID_RESOLVE_PAGES = 1;
34
34
  const STALE_PROGRAM_ID_RESOLVE_BUDGET = 8;
35
35
  const PROGRAM_FALLBACK_CACHE_MAX_ENTRIES = 200;
36
36
  const MAX_RESOLVE_SEARCH_QUERIES = 4;
37
- const MAX_DIRECT_HANDLE_RESOLUTION_REQUESTS = 8;
37
+ const MAX_DIRECT_HANDLE_RESOLUTION_REQUESTS = 96;
38
38
  const SDK_VERSION = "1.29.0";
39
- const PROGRAM_ID_PLATFORM_GUESSES = ["HackerOne", "Bugcrowd", "Intigriti", "HackenProof", "YesWeHack", "Cantina", "Code4rena", "Immunefi"];
39
+ const PROGRAM_ID_PLATFORM_GUESSES = [
40
+ "HackerOne",
41
+ "BugCrowd",
42
+ "Intigriti",
43
+ "HackenProof",
44
+ "YesWeHack",
45
+ "Immunefi",
46
+ "Cantina",
47
+ "Code4rena",
48
+ "Huntr",
49
+ "GObugfree",
50
+ "Bug Bounty Switzerland",
51
+ "Inspectiv",
52
+ "Compass Security",
53
+ "BugRap",
54
+ "IssueHunt",
55
+ "PatchDay",
56
+ "Remedy",
57
+ "Standoff365",
58
+ "Sherlock",
59
+ "CodeHawks"
60
+ ];
61
+ const PRIMARY_PROGRAM_ID_PLATFORM_GUESSES = PROGRAM_ID_PLATFORM_GUESSES.slice(0, 8);
40
62
  const WEB3_TAGS = ["web3", "crypto", "blockchain", "smart-contract", "smart contract", "defi", "nft", "ethereum", "solana", "solidity"];
41
63
  const WEB3_CONTEST_SIGNALS = ["contest", "audit", "competitive", "code4rena", "sherlock", "cantina", "codehawks", "hats", "spearbit", "warden"];
42
64
  const VDP_SIGNALS = ["vdp", "disclosure", "responsible disclosure", "vulnerability disclosure", "no bounty", "non-bounty", "no reward"];
@@ -150,7 +172,7 @@ function stringListSchema(description = "Optional filter values. Empty or omitte
150
172
  .describe(description);
151
173
  }
152
174
  function platformListSchema() {
153
- return stringListSchema("Optional platform filters such as HackerOne, Bugcrowd, Intigriti, or HackenProof. Empty or omitted means all platforms.");
175
+ return stringListSchema("Optional platform filters such as HackerOne, BugCrowd, Intigriti, HackenProof, Immunefi, YesWeHack, Cantina, or GObugfree. Empty or omitted means all platforms.");
154
176
  }
155
177
  function tagListSchema() {
156
178
  return stringListSchema("Optional combined scope/language/target-type tags. Empty or omitted means all tags.");
@@ -206,7 +228,7 @@ const programIdSchema = z
206
228
  .trim()
207
229
  .min(3)
208
230
  .max(256)
209
- .regex(/^[A-Za-z0-9_-]+:[^?#]+$/, "Use platform:handle, for example HackerOne:example-handle.")
231
+ .regex(/^[^:/?#\u0000-\u001F\u007F]+:[^?#\u0000-\u001F\u007F]+$/, "Use platform:handle, for example HackerOne:example-handle.")
210
232
  .refine((value) => !/[\u0000-\u001F\u007F]/.test(value), {
211
233
  message: "program_id cannot contain control characters."
212
234
  })
@@ -3162,7 +3184,8 @@ async function findProgramsByTarget(client, config, input) {
3162
3184
  budget,
3163
3185
  warnings,
3164
3186
  source: "target_query_program_id_guess",
3165
- maxProgramIds: 1,
3187
+ maxProgramIds: queryLooksLikeTargetIdentifier(input.target_query) || looksLikeDirectProgramHandleQuery(input.target_query) ? MAX_DIRECT_HANDLE_RESOLUTION_REQUESTS : 4,
3188
+ maxHandleVariants: 1,
3166
3189
  stopOnFirstMatch: true
3167
3190
  });
3168
3191
  sourceRequests.push(...directHandles.sourceRequests);
@@ -5346,7 +5369,8 @@ function programResolutionMatch(candidate, query) {
5346
5369
  };
5347
5370
  }
5348
5371
  function hasHighConfidenceProgramResolutionMatch(programs, webBaseUrl, query) {
5349
- return programs.some((program) => programResolutionMatch(toProgramCandidate(program, webBaseUrl), query).score >= 85);
5372
+ const threshold = looksLikeDirectProgramHandleQuery(query) ? 95 : 85;
5373
+ return programs.some((program) => programResolutionMatch(toProgramCandidate(program, webBaseUrl), query).score >= threshold);
5350
5374
  }
5351
5375
  function bestProgramResolutionScore(programs, webBaseUrl, query) {
5352
5376
  return programs.reduce((best, program) => Math.max(best, programResolutionMatch(toProgramCandidate(program, webBaseUrl), query).score), 0);
@@ -5357,6 +5381,7 @@ function programResolutionSearchQueries(query) {
5357
5381
  function programResolutionQueryVariants(query) {
5358
5382
  const normalized = normalizeTag(query);
5359
5383
  const meaningfulTokens = [...meaningfulProgramQueryTokens(query)];
5384
+ const searchTokens = meaningfulTokens.filter((token) => token.length >= 3);
5360
5385
  const meaningfulPhrase = meaningfulTokens.join(" ");
5361
5386
  const singleTokenNameVariants = meaningfulTokens.length === 1
5362
5387
  ? [
@@ -5370,7 +5395,7 @@ function programResolutionQueryVariants(query) {
5370
5395
  normalized,
5371
5396
  meaningfulPhrase,
5372
5397
  meaningfulTokens.join("-"),
5373
- ...meaningfulTokens,
5398
+ ...searchTokens,
5374
5399
  ...singleTokenNameVariants
5375
5400
  ];
5376
5401
  return uniqueStrings(values.filter((value) => value.length >= 2));
@@ -5598,15 +5623,18 @@ async function collectProgramsForResolution(client, options) {
5598
5623
  const collections = [];
5599
5624
  const queryCollections = [];
5600
5625
  const queries = options.searchQueries.length > 0 ? options.searchQueries : [undefined];
5601
- const primaryDirectProgramIds = new Set(directProgramIdCandidates(options.query, options.platforms).slice(0, 1));
5626
+ const primaryDirectPlatforms = options.platforms.length > 0 ? options.platforms : PRIMARY_PROGRAM_ID_PLATFORM_GUESSES;
5627
+ const primaryDirectProgramIds = new Set(directProgramIdCandidates(options.query, primaryDirectPlatforms, { maxHandleVariants: 1 }));
5602
5628
  if (primaryDirectProgramIds.size > 0 && options.budget.remaining > 0) {
5603
5629
  const primaryDirectHandles = await collectDirectHandlePrograms(client, {
5604
5630
  query: options.query,
5605
- platforms: options.platforms,
5631
+ platforms: primaryDirectPlatforms,
5606
5632
  budget: options.budget,
5607
5633
  warnings: options.warnings,
5608
5634
  source: "program_id_handle_guess",
5609
- maxProgramIds: 1,
5635
+ maxProgramIds: MAX_DIRECT_HANDLE_RESOLUTION_REQUESTS,
5636
+ maxHandleVariants: 1,
5637
+ acceptEmptyTargetsAsPrograms: true,
5610
5638
  stopOnFirstMatch: true
5611
5639
  });
5612
5640
  if (primaryDirectHandles.programs.length > 0 || primaryDirectHandles.requestsScanned > 0) {
@@ -5645,6 +5673,8 @@ async function collectProgramsForResolution(client, options) {
5645
5673
  warnings: options.warnings,
5646
5674
  source: "program_id_handle_guess",
5647
5675
  skipProgramIds: primaryDirectProgramIds,
5676
+ maxHandleVariants: 4,
5677
+ acceptEmptyTargetsAsPrograms: true,
5648
5678
  stopOnFirstMatch: true
5649
5679
  });
5650
5680
  if (directHandles.programs.length > 0 || directHandles.requestsScanned > 0) {
@@ -5715,28 +5745,32 @@ function mergeProgramCollections(collections) {
5715
5745
  };
5716
5746
  }
5717
5747
  async function collectDirectHandlePrograms(client, options) {
5718
- const programIds = directProgramIdCandidates(options.query, options.platforms)
5748
+ const programIds = directProgramIdCandidates(options.query, options.platforms, { maxHandleVariants: options.maxHandleVariants })
5719
5749
  .filter((programId) => !options.skipProgramIds?.has(programId))
5720
5750
  .slice(0, options.maxProgramIds ?? MAX_DIRECT_HANDLE_RESOLUTION_REQUESTS);
5721
5751
  const programs = [];
5722
5752
  const sourceRequests = [];
5723
5753
  let requestsScanned = 0;
5724
5754
  for (const programId of programIds) {
5725
- if (!tryConsumeBudget(options.budget)) {
5726
- break;
5727
- }
5728
5755
  requestsScanned += 1;
5729
5756
  try {
5730
- const api = await client.getProgram(programId);
5731
- programs.push(api.data);
5757
+ const targetsApi = await client.getProgramTargets(programId);
5758
+ const targetsData = readObject(targetsApi.data);
5759
+ const hasTargetsArray = Array.isArray(targetsData?.targets);
5760
+ const targets = readArray(targetsData?.targets).map(sanitizeTarget);
5761
+ const programEndpointExists = targets.length > 0 || (options.acceptEmptyTargetsAsPrograms === true && hasTargetsArray);
5762
+ if (programEndpointExists) {
5763
+ programs.push(syntheticProgramFromProgramId(programId, targets));
5764
+ }
5732
5765
  sourceRequests.push({
5733
5766
  source: options.source,
5767
+ endpoint: "program_targets",
5734
5768
  program_id: programId,
5735
- request_id: api.requestId,
5736
- upstream_request_id: api.upstreamRequestId,
5737
- ...apiSourceMetadata(api)
5769
+ request_id: targetsApi.requestId,
5770
+ upstream_request_id: targetsApi.upstreamRequestId,
5771
+ ...apiSourceMetadata(targetsApi)
5738
5772
  });
5739
- if (options.stopOnFirstMatch) {
5773
+ if (programEndpointExists && options.stopOnFirstMatch) {
5740
5774
  break;
5741
5775
  }
5742
5776
  }
@@ -5744,49 +5778,12 @@ async function collectDirectHandlePrograms(client, options) {
5744
5778
  if (error instanceof BBRadarApiError && error.status === 404) {
5745
5779
  sourceRequests.push({
5746
5780
  source: options.source,
5747
- endpoint: "program_detail",
5781
+ endpoint: "program_targets",
5748
5782
  program_id: programId,
5749
5783
  request_id: error.requestId,
5750
5784
  upstream_request_id: error.upstreamRequestId,
5751
5785
  status: error.status
5752
5786
  });
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
5787
  continue;
5791
5788
  }
5792
5789
  throw error;
@@ -5804,11 +5801,12 @@ function syntheticProgramFromProgramId(programId, targets) {
5804
5801
  const separatorIndex = programId.indexOf(":");
5805
5802
  const platform = separatorIndex >= 0 ? programId.slice(0, separatorIndex) : undefined;
5806
5803
  const handle = separatorIndex >= 0 ? programId.slice(separatorIndex + 1) : programId;
5804
+ const displayHandle = handle.split("/").filter(Boolean).at(-1) ?? handle;
5807
5805
  const scopeTags = uniqueStrings(targets.flatMap((target) => readStringArrayField(target, "scope_tags")));
5808
5806
  return stripUndefined({
5809
5807
  id: programId,
5810
5808
  handle,
5811
- name: titleizeProgramHandle(handle),
5809
+ name: titleizeProgramHandle(displayHandle),
5812
5810
  platform,
5813
5811
  scope_tags: scopeTags,
5814
5812
  target_counts: {
@@ -5827,38 +5825,75 @@ function titleizeProgramHandle(handle) {
5827
5825
  .map((part) => part.slice(0, 1).toUpperCase() + part.slice(1))
5828
5826
  .join(" ");
5829
5827
  }
5830
- function directProgramIdCandidates(query, platforms) {
5828
+ function directProgramIdCandidates(query, platforms, options = {}) {
5831
5829
  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);
5830
+ const handles = directProgramHandleVariants(query).slice(0, options.maxHandleVariants ?? Number.POSITIVE_INFINITY);
5831
+ return uniqueStrings(handles.flatMap((handle) => platformGuesses.flatMap((platform) => normalizeTag(platform) === "bugcrowd"
5832
+ ? [`${platform}:engagements/${handle}`, `${platform}:${handle}`]
5833
+ : [`${platform}:${handle}`]))).slice(0, MAX_DIRECT_HANDLE_RESOLUTION_REQUESTS);
5834
5834
  }
5835
5835
  function canonicalProgramIdPlatform(platform) {
5836
5836
  const normalized = normalizeTag(platform).replace(/[^a-z0-9]/g, "");
5837
5837
  const knownPlatforms = {
5838
5838
  hackerone: "HackerOne",
5839
- bugcrowd: "Bugcrowd",
5839
+ bugcrowd: "BugCrowd",
5840
5840
  intigriti: "Intigriti",
5841
5841
  hackenproof: "HackenProof",
5842
5842
  yeswehack: "YesWeHack",
5843
+ immunefi: "Immunefi",
5843
5844
  cantina: "Cantina",
5844
5845
  code4rena: "Code4rena",
5845
- immunefi: "Immunefi"
5846
+ huntr: "Huntr",
5847
+ gobugfree: "GObugfree",
5848
+ bugbountyswitzerland: "Bug Bounty Switzerland",
5849
+ inspectiv: "Inspectiv",
5850
+ compasssecurity: "Compass Security",
5851
+ bugrap: "BugRap",
5852
+ issuehunt: "IssueHunt",
5853
+ patchday: "PatchDay",
5854
+ remedy: "Remedy",
5855
+ standoff365: "Standoff365",
5856
+ sherlock: "Sherlock",
5857
+ codehawks: "CodeHawks"
5846
5858
  };
5847
5859
  return knownPlatforms[normalized] ?? platform.trim();
5848
5860
  }
5849
5861
  function directProgramHandleVariants(query) {
5850
5862
  const handleText = query.includes(":") ? query.slice(query.indexOf(":") + 1) : query;
5851
5863
  const normalized = normalizeTargetIdentifierForMatch(handleText);
5864
+ const literal = directProgramLiteralHandleVariant(handleText);
5852
5865
  const dashed = normalized.replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
5853
5866
  const compact = dashed.replace(/-/g, "");
5854
5867
  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));
5868
+ const brandTokens = targetBrandTokens(handleText);
5869
+ const literalVariants = [literal, dashed, compact, tokens.join("-"), tokens.join("")];
5870
+ const values = queryLooksLikeTargetIdentifier(handleText) ? [...brandTokens, ...literalVariants] : [...literalVariants, ...brandTokens];
5871
+ return uniqueStrings(values).filter(isDirectProgramHandleVariant);
5872
+ }
5873
+ function looksLikeDirectProgramHandleQuery(query) {
5874
+ const handleText = query.includes(":") ? query.slice(query.indexOf(":") + 1) : query;
5875
+ return isDirectProgramHandleVariant(directProgramLiteralHandleVariant(handleText));
5876
+ }
5877
+ function directProgramLiteralHandleVariant(value) {
5878
+ const normalized = value
5879
+ .trim()
5880
+ .replace(/\\/g, "/")
5881
+ .replace(/^['"`<]+|['"`>]+$/g, "")
5882
+ .replace(/^[a-z][a-z0-9+.-]*:\/\//i, "")
5883
+ .replace(/^www\./i, "");
5884
+ const queryStart = normalized.search(/[?#]/);
5885
+ return (queryStart >= 0 ? normalized.slice(0, queryStart) : normalized).replace(/\/+$/, "");
5886
+ }
5887
+ function isDirectProgramHandleVariant(value) {
5888
+ if (!value) {
5889
+ return false;
5890
+ }
5891
+ if (value.length < 2 || value.length > 150 || /[\u0000-\u001F\u007F?#:]/.test(value)) {
5892
+ return false;
5893
+ }
5894
+ return value
5895
+ .split("/")
5896
+ .every((segment) => segment.length > 0 && segment !== "." && segment !== ".." && !/^[\s.]+$/.test(segment));
5862
5897
  }
5863
5898
  async function collectProgramSources(client, sources, options) {
5864
5899
  const pages = [];
@@ -6633,7 +6668,7 @@ function targetMatchesQuery(target, query, mode) {
6633
6668
  }
6634
6669
  return (containsMatch ||
6635
6670
  profile.values.some((value) => queryValues.some((queryValue) => targetSuffixMatch(value, queryValue) || targetWildcardMatch(value, queryValue))) ||
6636
- targetTokensMatch(profile.tokens, query));
6671
+ (!queryLooksLikeTargetIdentifier(query) && targetTokensMatch(profile.tokens, query)));
6637
6672
  }
6638
6673
  function upstreamTargetMatchMode(mode) {
6639
6674
  return mode === "smart" ? "contains" : mode;
@@ -6671,7 +6706,11 @@ function targetSearchProfile(target) {
6671
6706
  };
6672
6707
  }
6673
6708
  function targetQueryVariants(query) {
6674
- return targetStringVariants(query);
6709
+ const variants = targetStringVariants(query);
6710
+ if (!queryLooksLikeTargetIdentifier(query)) {
6711
+ return variants;
6712
+ }
6713
+ return variants.filter(targetValueCanContainQuery);
6675
6714
  }
6676
6715
  function targetApiSearchQueries(query) {
6677
6716
  const normalized = normalizeTargetIdentifierForMatch(query);