@chainpatrol/sdk 1.8.0 → 1.10.0

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/index.mjs CHANGED
@@ -543,45 +543,6 @@ z.object({
543
543
  name: z.string().optional().describe("Asset name"),
544
544
  description: z.string().nullish().optional().describe("Asset description")
545
545
  });
546
- z.object({
547
- slug: z.string(),
548
- assetId: z.number(),
549
- name: z.string().optional(),
550
- description: z.string().optional(),
551
- assetGroupId: z.number().nullable().optional(),
552
- updatedByOrganizationId: z.number().optional()
553
- });
554
- z.object({
555
- content: z.string().min(1, { message: "Content must be at least 3 character" }).max(1e3, { message: "Content must be less than 1000 characters" }),
556
- email: z.string().email(),
557
- description: z.string().optional().default("")
558
- });
559
- z.object({
560
- /** Canonicalization algorithm version the client computed its prefixes with. */
561
- algoVersion: z.number().int().positive(),
562
- /** 4-byte (8-hex-char) prefixes that hit the client's local index. */
563
- prefixes: z.array(z.string().regex(/^[0-9a-f]{8}$/)).min(1).max(200)
564
- }).describe("Blocklist confirm request");
565
- z.object({
566
- /** Full 64-char SHA-256 hashes of blocked expressions, grouped by prefix. */
567
- fullHashes: z.record(z.string(), z.array(z.string()))
568
- }).describe("Blocklist confirm response");
569
- z.void().describe("Blocklist version request");
570
- z.object({
571
- /** Canonicalization algorithm version the artifacts + confirm are built with. */
572
- algoVersion: z.number().int().positive(),
573
- snapshot: z.object({
574
- token: z.string(),
575
- url: z.string().url(),
576
- prefixBytes: z.number().int().positive(),
577
- count: z.number().int().nonnegative()
578
- }),
579
- recent: z.object({
580
- token: z.string(),
581
- url: z.string().url(),
582
- windowSeconds: z.number().int().positive()
583
- })
584
- }).describe("Blocklist version response");
585
546
 
586
547
  // ../../node_modules/luxon/src/errors.js
587
548
  var LuxonError = class extends Error {
@@ -1255,17 +1216,17 @@ var PolyDateFormatter = class {
1255
1216
  constructor(dt, intl, opts) {
1256
1217
  this.opts = opts;
1257
1218
  this.originalZone = void 0;
1258
- let z55 = void 0;
1219
+ let z61 = void 0;
1259
1220
  if (this.opts.timeZone) {
1260
1221
  this.dt = dt;
1261
1222
  } else if (dt.zone.type === "fixed") {
1262
1223
  const gmtOffset = -1 * (dt.offset / 60);
1263
1224
  const offsetZ = gmtOffset >= 0 ? `Etc/GMT+${gmtOffset}` : `Etc/GMT${gmtOffset}`;
1264
1225
  if (dt.offset !== 0 && IANAZone.create(offsetZ).valid) {
1265
- z55 = offsetZ;
1226
+ z61 = offsetZ;
1266
1227
  this.dt = dt;
1267
1228
  } else {
1268
- z55 = "UTC";
1229
+ z61 = "UTC";
1269
1230
  this.dt = dt.offset === 0 ? dt : dt.setZone("UTC").plus({ minutes: dt.offset });
1270
1231
  this.originalZone = dt.zone;
1271
1232
  }
@@ -1273,14 +1234,14 @@ var PolyDateFormatter = class {
1273
1234
  this.dt = dt;
1274
1235
  } else if (dt.zone.type === "iana") {
1275
1236
  this.dt = dt;
1276
- z55 = dt.zone.name;
1237
+ z61 = dt.zone.name;
1277
1238
  } else {
1278
- z55 = "UTC";
1239
+ z61 = "UTC";
1279
1240
  this.dt = dt.setZone("UTC").plus({ minutes: dt.offset });
1280
1241
  this.originalZone = dt.zone;
1281
1242
  }
1282
1243
  const intlOpts = __spreadValues({}, this.opts);
1283
- intlOpts.timeZone = intlOpts.timeZone || z55;
1244
+ intlOpts.timeZone = intlOpts.timeZone || z61;
1284
1245
  this.dtf = getCachedDTF(intl, intlOpts);
1285
1246
  }
1286
1247
  format() {
@@ -6996,6 +6957,63 @@ function friendlyDateTime(dateTimeish) {
6996
6957
  );
6997
6958
  }
6998
6959
  }
6960
+ var MAX_ASSET_NOTE_LENGTH = 5e3;
6961
+ var assetNoteBodySchema = z.string().trim().min(1, "Note is required").max(MAX_ASSET_NOTE_LENGTH, `Note must be at most ${MAX_ASSET_NOTE_LENGTH} characters`);
6962
+ var assetNoteTimelineAtSchema = z.coerce.date().refine((value) => value.getTime() <= DateTime.now().toUTC().toMillis(), {
6963
+ message: "Timeline date cannot be in the future"
6964
+ });
6965
+ z.object({
6966
+ assetId: z.number().int().positive(),
6967
+ note: assetNoteBodySchema,
6968
+ timelineAt: assetNoteTimelineAtSchema.optional()
6969
+ });
6970
+ z.object({
6971
+ noteId: z.number().int().positive(),
6972
+ note: assetNoteBodySchema,
6973
+ timelineAt: assetNoteTimelineAtSchema.nullish()
6974
+ });
6975
+ z.object({
6976
+ noteId: z.number().int().positive()
6977
+ });
6978
+ z.object({
6979
+ slug: z.string(),
6980
+ assetId: z.number(),
6981
+ name: z.string().optional(),
6982
+ description: z.string().optional(),
6983
+ assetGroupId: z.number().nullable().optional(),
6984
+ updatedByOrganizationId: z.number().optional()
6985
+ });
6986
+ z.object({
6987
+ content: z.string().min(1, { message: "Content must be at least 3 character" }).max(1e3, { message: "Content must be less than 1000 characters" }),
6988
+ email: z.string().email(),
6989
+ description: z.string().optional().default("")
6990
+ });
6991
+ z.object({
6992
+ /** Canonicalization algorithm version the client computed its prefixes with. */
6993
+ algoVersion: z.number().int().positive(),
6994
+ /** 4-byte (8-hex-char) prefixes that hit the client's local index. */
6995
+ prefixes: z.array(z.string().regex(/^[0-9a-f]{8}$/)).min(1).max(200)
6996
+ }).describe("Blocklist confirm request");
6997
+ z.object({
6998
+ /** Full 64-char SHA-256 hashes of blocked expressions, grouped by prefix. */
6999
+ fullHashes: z.record(z.string(), z.array(z.string()))
7000
+ }).describe("Blocklist confirm response");
7001
+ z.void().describe("Blocklist version request");
7002
+ z.object({
7003
+ /** Canonicalization algorithm version the artifacts + confirm are built with. */
7004
+ algoVersion: z.number().int().positive(),
7005
+ snapshot: z.object({
7006
+ token: z.string(),
7007
+ url: z.string().url(),
7008
+ prefixBytes: z.number().int().positive(),
7009
+ count: z.number().int().nonnegative()
7010
+ }),
7011
+ recent: z.object({
7012
+ token: z.string(),
7013
+ url: z.string().url(),
7014
+ windowSeconds: z.number().int().positive()
7015
+ })
7016
+ }).describe("Blocklist version response");
6999
7017
 
7000
7018
  // ../../internal/database/src/generated/prisma/enums.ts
7001
7019
  var BrandType = {
@@ -11430,6 +11448,50 @@ z.object({
11430
11448
  endDate: z.date()
11431
11449
  })
11432
11450
  });
11451
+ var DATE_ONLY_PATTERN = /^\d{4}-\d{2}-\d{2}$/;
11452
+ var utcDateSchema = (endOfDay) => z.preprocess((value) => {
11453
+ if (typeof value !== "string") {
11454
+ return value;
11455
+ }
11456
+ const parsed = DateTime.fromISO(value, { zone: "utc" });
11457
+ if (!parsed.isValid) {
11458
+ return new Date(Number.NaN);
11459
+ }
11460
+ return endOfDay && DATE_ONLY_PATTERN.test(value) ? parsed.endOf("day").toJSDate() : parsed.toJSDate();
11461
+ }, z.coerce.date());
11462
+ var funnelCountsSchema = z.object({
11463
+ blocked: z.number().int().nonnegative().describe(
11464
+ "Distinct assets whose earliest report for this org falls in the range and whose current status or pending status is BLOCKED"
11465
+ )
11466
+ });
11467
+ z.object({
11468
+ slug: z.string().min(1).describe("Organization slug"),
11469
+ startDate: utcDateSchema(false).describe(
11470
+ "Only include assets whose earliest report was created on or after this date. A date-only value (YYYY-MM-DD) starts at midnight UTC"
11471
+ ),
11472
+ endDate: utcDateSchema(true).describe(
11473
+ "Only include assets whose earliest report was created on or before this date. A date-only value (YYYY-MM-DD) covers the whole day in UTC; pass a full timestamp for a precise cut-off"
11474
+ ),
11475
+ brandIds: z.array(z.number().int().positive()).optional().describe("Only include assets belonging to these brands")
11476
+ }).refine((input) => input.startDate.getTime() <= input.endDate.getTime(), {
11477
+ message: "startDate must be on or before endDate",
11478
+ path: ["startDate"]
11479
+ });
11480
+ z.object({
11481
+ range: z.object({
11482
+ startDate: z.date(),
11483
+ endDate: z.date()
11484
+ }),
11485
+ customer: funnelCountsSchema.describe(
11486
+ "Currently blocked assets first reported by the customer (reportedByCustomer, external reporter, or customer-role user)"
11487
+ ),
11488
+ chainpatrol: funnelCountsSchema.describe(
11489
+ "Currently blocked assets first reported by ChainPatrol staff or automation. Staff and automation are collapsed together."
11490
+ ),
11491
+ totals: funnelCountsSchema.describe(
11492
+ "customer + chainpatrol. This is a first-report blocked cohort and does not equal metrics/found, which counts assets by blockedAt."
11493
+ )
11494
+ });
11433
11495
  z.object({
11434
11496
  slug: z.string().min(1),
11435
11497
  startDate: z.date().optional(),
@@ -11461,8 +11523,8 @@ z.object({
11461
11523
  })
11462
11524
  )
11463
11525
  });
11464
- var DATE_ONLY_PATTERN = /^\d{4}-\d{2}-\d{2}$/;
11465
- var utcDateSchema = (endOfDay) => z.preprocess((value) => {
11526
+ var DATE_ONLY_PATTERN2 = /^\d{4}-\d{2}-\d{2}$/;
11527
+ var utcDateSchema2 = (endOfDay) => z.preprocess((value) => {
11466
11528
  if (typeof value !== "string") {
11467
11529
  return value;
11468
11530
  }
@@ -11470,14 +11532,47 @@ var utcDateSchema = (endOfDay) => z.preprocess((value) => {
11470
11532
  if (!parsed.isValid) {
11471
11533
  return new Date(Number.NaN);
11472
11534
  }
11473
- return endOfDay && DATE_ONLY_PATTERN.test(value) ? parsed.endOf("day").toJSDate() : parsed.toJSDate();
11535
+ return endOfDay && DATE_ONLY_PATTERN2.test(value) ? parsed.endOf("day").toJSDate() : parsed.toJSDate();
11474
11536
  }, z.coerce.date());
11537
+
11538
+ // ../../internal/validation/src/public/metrics/takedown-success-rate.ts
11539
+ z.object({
11540
+ slug: z.string().min(1).describe("Organization slug"),
11541
+ startDate: utcDateSchema2(false).optional().describe(
11542
+ "Only include takedowns first submitted to a provider on or after this date. A date-only value (YYYY-MM-DD) starts at midnight UTC"
11543
+ ),
11544
+ endDate: utcDateSchema2(true).optional().describe(
11545
+ "Only include takedowns first submitted to a provider on or before this date. A date-only value (YYYY-MM-DD) covers the whole day in UTC; pass a full timestamp for a precise cut-off"
11546
+ ),
11547
+ blockLabel: z.string().optional().describe(
11548
+ 'Only include assets blocked with this review label, e.g. "Brand Impersonation", "Employee Impersonation" or "Targeting Org Users"'
11549
+ ),
11550
+ brandType: z.nativeEnum(BrandType).optional().describe("Only include assets belonging to brands of this type"),
11551
+ countryCodes: z.array(z.string().length(2)).optional().describe("Only include assets scanned from these ISO 3166-1 alpha-2 countries"),
11552
+ brandIds: z.array(z.number().int().positive()).optional().describe("Only include assets belonging to these brands")
11553
+ });
11554
+ z.object({
11555
+ byAssetType: z.array(
11556
+ z.object({
11557
+ assetType: z.nativeEnum(AssetType).describe("Asset type the takedowns were filed against"),
11558
+ submitted: z.number().describe("Takedowns whose first submission to a provider fell in the range"),
11559
+ completed: z.number().describe("Of those, how many ended in a takedown"),
11560
+ stillOpen: z.number().describe("Of those, how many have not yet reached an outcome"),
11561
+ successRate: z.number().describe("completed / submitted, 0-1. Still-open takedowns count against it"),
11562
+ resolvedSuccessRate: z.number().nullable().describe(
11563
+ "completed / (submitted - stillOpen), 0-1, or null when nothing has resolved yet. Higher than successRate whenever work is still outstanding"
11564
+ )
11565
+ })
11566
+ ).describe(
11567
+ "Takedown success rate, one entry per asset type, descending by number submitted"
11568
+ )
11569
+ });
11475
11570
  z.object({
11476
11571
  slug: z.string().min(1).describe("Organization slug"),
11477
- startDate: utcDateSchema(false).optional().describe(
11572
+ startDate: utcDateSchema2(false).optional().describe(
11478
11573
  "Only include takedowns completed on or after this date. A date-only value (YYYY-MM-DD) starts at midnight UTC"
11479
11574
  ),
11480
- endDate: utcDateSchema(true).optional().describe(
11575
+ endDate: utcDateSchema2(true).optional().describe(
11481
11576
  "Only include takedowns completed on or before this date. A date-only value (YYYY-MM-DD) covers the whole day in UTC; pass a full timestamp for a precise cut-off"
11482
11577
  ),
11483
11578
  blockLabel: z.string().optional().describe(
@@ -11500,6 +11595,46 @@ z.object({
11500
11595
  "Median time from takedown start to completion, one entry per asset type, ascending by median"
11501
11596
  )
11502
11597
  });
11598
+ var TakedownProviderRoleSchema = z.enum([
11599
+ "HOSTING_PROVIDER",
11600
+ "DOMAIN_REGISTRAR",
11601
+ "TLD_REGISTRAR"
11602
+ ]);
11603
+ z.object({
11604
+ slug: z.string().min(1).describe("Organization slug"),
11605
+ startDate: utcDateSchema2(false).optional().describe(
11606
+ "Only include takedowns completed on or after this date. A date-only value (YYYY-MM-DD) starts at midnight UTC"
11607
+ ),
11608
+ endDate: utcDateSchema2(true).optional().describe(
11609
+ "Only include takedowns completed on or before this date. A date-only value (YYYY-MM-DD) covers the whole day in UTC; pass a full timestamp for a precise cut-off"
11610
+ ),
11611
+ roles: z.array(TakedownProviderRoleSchema).optional().describe(
11612
+ "Only break down these provider roles. Omit for every role. A takedown filed against both a host and a registrar is counted once under each"
11613
+ ),
11614
+ blockLabel: z.string().optional().describe(
11615
+ 'Only include assets blocked with this review label, e.g. "Brand Impersonation", "Employee Impersonation" or "Targeting Org Users"'
11616
+ ),
11617
+ brandType: z.nativeEnum(BrandType).optional().describe("Only include assets belonging to brands of this type"),
11618
+ countryCodes: z.array(z.string().length(2)).optional().describe("Only include assets scanned from these ISO 3166-1 alpha-2 countries"),
11619
+ brandIds: z.array(z.number().int().positive()).optional().describe("Only include assets belonging to these brands")
11620
+ });
11621
+ z.object({
11622
+ byProvider: z.array(
11623
+ z.object({
11624
+ providerId: z.number().describe("Takedown provider id"),
11625
+ providerName: z.string().describe("Takedown provider name"),
11626
+ role: TakedownProviderRoleSchema.describe(
11627
+ "Which provider slot of the takedown this median is computed over"
11628
+ ),
11629
+ medianTakedownMinutes: z.number(),
11630
+ medianTakedownHours: z.number(),
11631
+ medianTakedownDays: z.number(),
11632
+ count: z.number().describe("Number of completed takedowns behind the median")
11633
+ })
11634
+ ).describe(
11635
+ "Median time from takedown start to completion, one entry per provider and role, ascending by median"
11636
+ )
11637
+ });
11503
11638
  z.object({
11504
11639
  slug: z.string().min(1).describe("Organization slug"),
11505
11640
  months: z.number().int().min(2).max(36).default(12).describe("Size of the trailing window in whole months, including the current month"),
@@ -11755,6 +11890,13 @@ z.object({
11755
11890
  brands: z.array(brandItemSchema).describe("List of brands")
11756
11891
  }).describe("Successful operation");
11757
11892
  var FILTERABLE_SUBSCRIPTION_STATUSES = [
11893
+ SubscriptionStatus.PROSPECT,
11894
+ SubscriptionStatus.POC,
11895
+ SubscriptionStatus.ACTIVE,
11896
+ SubscriptionStatus.INTEGRATION,
11897
+ SubscriptionStatus.INACTIVE
11898
+ ];
11899
+ [
11758
11900
  SubscriptionStatus.PROSPECT,
11759
11901
  SubscriptionStatus.POC,
11760
11902
  SubscriptionStatus.ACTIVE,
@@ -11789,7 +11931,9 @@ var userOrgsPendingServiceApprovalFilterSchema = z.object({
11789
11931
  }).optional();
11790
11932
  z.object({
11791
11933
  query: z.string().default(""),
11792
- subscriptionStatus: z.array(UserOrgsSubscriptionStatusSchema).min(1).optional(),
11934
+ subscriptionStatus: z.array(UserOrgsSubscriptionStatusSchema).min(1).optional().describe(
11935
+ "Statuses to include. Defaults to the live set (PROSPECT/POC/ACTIVE/INTEGRATION); pass INACTIVE explicitly to see churned organizations."
11936
+ ),
11793
11937
  services: userOrgsServicesFilterObjectSchema.optional(),
11794
11938
  obligatoryAdminApproval: userOrgsObligatoryAdminApprovalFilterSchema,
11795
11939
  pendingServiceApproval: userOrgsPendingServiceApprovalFilterSchema
@@ -11935,7 +12079,9 @@ var organizationMetricsIncludeFieldSchema = z.enum([
11935
12079
  "newThreats",
11936
12080
  "threatsWatchlisted",
11937
12081
  "takedownsFiled",
12082
+ "takedownsInProgress",
11938
12083
  "takedownsCompleted",
12084
+ "takedownsCancelled",
11939
12085
  "domainThreats",
11940
12086
  "twitterThreats",
11941
12087
  "telegramThreats",
@@ -11968,7 +12114,7 @@ z.object({
11968
12114
  "Shortcut for 'every org the caller is authorized to read'. Reuses the same filter logic as `/user/orgs`: org-scoped API keys are rejected (they're pinned to one org \u2014 use the default form); user API keys / Bearer sessions resolve to the user's memberships (or every active org for staff). Combine with `subscriptionStatus` and `services` to narrow (e.g. only paying customers with takedowns enabled). The resolved org set is still capped at " + String(ORGANIZATION_METRICS_MAX_SLUGS) + " and a 413 is returned if exceeded. Mutually exclusive with `organizationSlug`, `slugs`, and `brandSlug`."
11969
12115
  ),
11970
12116
  subscriptionStatus: z.array(UserOrgsSubscriptionStatusSchema).min(1).optional().describe(
11971
- "Only meaningful with `allMyOrgs: true`. Defaults to the same set `/user/orgs` uses (ACTIVE/POC/PROSPECT/INTEGRATION \u2014 INACTIVE is always excluded)."
12117
+ "Only meaningful with `allMyOrgs: true`. Defaults to the same set `/user/orgs` uses (ACTIVE/POC/PROSPECT/INTEGRATION); pass INACTIVE explicitly to include churned organizations."
11972
12118
  ),
11973
12119
  services: organizationMetricsServicesFilterSchema.describe(
11974
12120
  "Only meaningful with `allMyOrgs: true`. Same shape as `/user/orgs`'s `services` filter \u2014 every service accepts `{ active }`, and **only `takedowns` additionally accepts `automated`** (the one service where manual-vs-automated changes real platform behavior). E.g. `{ takedowns: { active: true, automated: false } }` for 'orgs with takedowns enabled but automation off'."
@@ -11977,7 +12123,7 @@ z.object({
11977
12123
  startDate: z.coerce.date().optional(),
11978
12124
  endDate: z.coerce.date().optional(),
11979
12125
  include: z.array(organizationMetricsIncludeFieldSchema).optional().describe(
11980
- "Subset of metrics to compute. When omitted or empty, every metric is computed (legacy behavior). Each value in `include` costs roughly one Prisma aggregate, so narrow the list when an agent only needs a couple of numbers \u2014 this is the main lever for keeping big-org calls inside the 30s function cap. Fields not in `include` come back as `null`."
12126
+ "Subset of metrics to compute. When omitted or empty, every metric is computed (legacy behavior). Each value in `include` costs roughly one aggregate query, so narrow the list when an agent only needs a couple of numbers \u2014 this is the main lever for keeping big-org calls inside the 30s function cap. Fields not in `include` come back as `null`. `takedownsInProgress` and `takedownsCancelled` count takedowns currently in that status whose `updatedAt` falls in the window (same windowing as `takedownsFiled`), so they reconcile with the takedowns list filtered by status over the same date range."
11981
12127
  )
11982
12128
  }).refine(
11983
12129
  (value) => {
@@ -12005,7 +12151,9 @@ var metricsObjectSchema = z.object({
12005
12151
  newThreats: metricValue,
12006
12152
  threatsWatchlisted: metricValue,
12007
12153
  takedownsFiled: metricValue,
12154
+ takedownsInProgress: metricValue,
12008
12155
  takedownsCompleted: metricValue,
12156
+ takedownsCancelled: metricValue,
12009
12157
  domainThreats: metricValue,
12010
12158
  twitterThreats: metricValue,
12011
12159
  telegramThreats: metricValue,
@@ -12623,6 +12771,74 @@ z.object({
12623
12771
  hiddenMetrics: z.record(z.string(), z.boolean()).default({}),
12624
12772
  overriddenMetrics: z.record(z.string(), z.boolean()).default({})
12625
12773
  });
12774
+ var ONE_PAGER_BUDGETS = {
12775
+ headline: 60,
12776
+ badge: 28,
12777
+ frontLabel: 44,
12778
+ paragraph: 480,
12779
+ paragraphsMax: 2,
12780
+ heroPhrase: 80,
12781
+ heroNarrative: 300,
12782
+ divergenceLine: 220,
12783
+ xp01Line: 220
12784
+ };
12785
+ var onePagerFactPayloadSchema = z.object({
12786
+ /** Raw (not defanged) asset contents the prose may reference. */
12787
+ assetContents: z.array(z.string()),
12788
+ /** Every numeric token (as string) the prose is allowed to contain. */
12789
+ numbers: z.array(z.string()),
12790
+ /** ISO dates the prose may reference. */
12791
+ dates: z.array(z.string()),
12792
+ platforms: z.array(z.string()),
12793
+ groupSize: z.number(),
12794
+ takedownCompletedAt: z.string().nullable(),
12795
+ activeSince: z.string().nullable()
12796
+ });
12797
+ var onePagerStorySchema = z.object({
12798
+ archetypeId: z.string(),
12799
+ archetypeCategory: z.string(),
12800
+ /** 1-5 = placed on the page; null = alternate available for swap. */
12801
+ slot: z.number().int().min(1).max(5).nullable(),
12802
+ frontLabel: z.string().max(ONE_PAGER_BUDGETS.frontLabel),
12803
+ headline: z.string().max(ONE_PAGER_BUDGETS.headline),
12804
+ badge: z.string().max(ONE_PAGER_BUDGETS.badge),
12805
+ paragraphs: z.array(z.string().max(ONE_PAGER_BUDGETS.paragraph)).min(1).max(ONE_PAGER_BUDGETS.paragraphsMax),
12806
+ /** Defanged, display-ready asset identifiers. */
12807
+ assetList: z.array(z.string()).max(10),
12808
+ screenshotUrls: z.array(z.string()),
12809
+ selectedScreenshotUrls: z.array(z.string()).max(2),
12810
+ xp01Line: z.string().max(ONE_PAGER_BUDGETS.xp01Line).nullable(),
12811
+ factPayload: onePagerFactPayloadSchema,
12812
+ proseSource: z.enum(["llm", "template"])
12813
+ });
12814
+ var onePagerLandscapeRowSchema = z.object({
12815
+ platform: z.string(),
12816
+ count: z.number(),
12817
+ sharePct: z.number()
12818
+ });
12819
+ var onePagerFormDataObjectSchema = z.object({
12820
+ orgName: z.string(),
12821
+ orgSlug: z.string(),
12822
+ periodLabel: z.string(),
12823
+ heroStat: z.object({
12824
+ value: z.string(),
12825
+ phrase: z.string().max(ONE_PAGER_BUDGETS.heroPhrase)
12826
+ }),
12827
+ heroNarrative: z.string().max(ONE_PAGER_BUDGETS.heroNarrative),
12828
+ metricsRows: z.array(z.object({ label: z.string(), value: z.string() })).length(4),
12829
+ landscape: z.object({
12830
+ rows: z.array(onePagerLandscapeRowSchema).max(8),
12831
+ otherCount: z.number().nullable(),
12832
+ otherPlatformsLabel: z.string().nullable(),
12833
+ divergenceLine: z.string().max(ONE_PAGER_BUDGETS.divergenceLine).nullable()
12834
+ }),
12835
+ stories: z.array(onePagerStorySchema).min(1).max(6),
12836
+ footer: z.object({ contactName: z.string(), contactLine: z.string() })
12837
+ });
12838
+ onePagerFormDataObjectSchema.refine(
12839
+ (data) => data.stories.some((s2) => s2.slot !== null),
12840
+ { message: "at least one story must be placed", path: ["stories"] }
12841
+ );
12626
12842
  z.object({
12627
12843
  x: z.number().min(0).max(1),
12628
12844
  y: z.number().min(0).max(1),
@@ -13388,20 +13604,20 @@ var convertA98ToXyz65_default = convertA98ToXyz65;
13388
13604
 
13389
13605
  // ../../node_modules/culori/src/a98/convertXyz65ToA98.js
13390
13606
  var gamma = (v) => Math.pow(Math.abs(v), 256 / 563) * Math.sign(v);
13391
- var convertXyz65ToA98 = ({ x, y, z: z55, alpha }) => {
13607
+ var convertXyz65ToA98 = ({ x, y, z: z61, alpha }) => {
13392
13608
  if (x === void 0) x = 0;
13393
13609
  if (y === void 0) y = 0;
13394
- if (z55 === void 0) z55 = 0;
13610
+ if (z61 === void 0) z61 = 0;
13395
13611
  let res = {
13396
13612
  mode: "a98",
13397
13613
  r: gamma(
13398
- x * 2.0415879038107465 - y * 0.5650069742788597 - 0.3447313507783297 * z55
13614
+ x * 2.0415879038107465 - y * 0.5650069742788597 - 0.3447313507783297 * z61
13399
13615
  ),
13400
13616
  g: gamma(
13401
- x * -0.9692436362808798 + y * 1.8759675015077206 + 0.0415550574071756 * z55
13617
+ x * -0.9692436362808798 + y * 1.8759675015077206 + 0.0415550574071756 * z61
13402
13618
  ),
13403
13619
  b: gamma(
13404
- x * 0.0134442806320312 - y * 0.1183623922310184 + 1.0151749943912058 * z55
13620
+ x * 0.0134442806320312 - y * 0.1183623922310184 + 1.0151749943912058 * z61
13405
13621
  )
13406
13622
  };
13407
13623
  if (alpha !== void 0) {
@@ -13468,14 +13684,14 @@ var convertLrgbToRgb = ({ r, g, b, alpha }, mode = "rgb") => {
13468
13684
  var convertLrgbToRgb_default = convertLrgbToRgb;
13469
13685
 
13470
13686
  // ../../node_modules/culori/src/xyz65/convertXyz65ToRgb.js
13471
- var convertXyz65ToRgb = ({ x, y, z: z55, alpha }) => {
13687
+ var convertXyz65ToRgb = ({ x, y, z: z61, alpha }) => {
13472
13688
  if (x === void 0) x = 0;
13473
13689
  if (y === void 0) y = 0;
13474
- if (z55 === void 0) z55 = 0;
13690
+ if (z61 === void 0) z61 = 0;
13475
13691
  let res = convertLrgbToRgb_default({
13476
- r: x * 3.2409699419045226 - y * 1.537383177570094 - 0.4986107602930034 * z55,
13477
- g: x * -0.9692436362808796 + y * 1.8759675015077204 + 0.0415550574071756 * z55,
13478
- b: x * 0.0556300796969936 - y * 0.2039769588889765 + 1.0569715142428784 * z55
13692
+ r: x * 3.2409699419045226 - y * 1.537383177570094 - 0.4986107602930034 * z61,
13693
+ g: x * -0.9692436362808796 + y * 1.8759675015077204 + 0.0415550574071756 * z61,
13694
+ b: x * 0.0556300796969936 - y * 0.2039769588889765 + 1.0569715142428784 * z61
13479
13695
  });
13480
13696
  if (alpha !== void 0) {
13481
13697
  res.alpha = alpha;
@@ -13725,13 +13941,13 @@ var convertLab65ToRgb_default = convertLab65ToRgb;
13725
13941
 
13726
13942
  // ../../node_modules/culori/src/lab65/convertXyz65ToLab65.js
13727
13943
  var f = (value) => value > e ? Math.cbrt(value) : (k * value + 16) / 116;
13728
- var convertXyz65ToLab65 = ({ x, y, z: z55, alpha }) => {
13944
+ var convertXyz65ToLab65 = ({ x, y, z: z61, alpha }) => {
13729
13945
  if (x === void 0) x = 0;
13730
13946
  if (y === void 0) y = 0;
13731
- if (z55 === void 0) z55 = 0;
13947
+ if (z61 === void 0) z61 = 0;
13732
13948
  let f0 = f(x / D65.X);
13733
13949
  let f1 = f(y / D65.Y);
13734
- let f22 = f(z55 / D65.Z);
13950
+ let f22 = f(z61 / D65.Z);
13735
13951
  let res = {
13736
13952
  mode: "lab65",
13737
13953
  l: 116 * f1 - 16,
@@ -14388,10 +14604,10 @@ var convertItpToXyz65_default = convertItpToXyz65;
14388
14604
 
14389
14605
  // ../../node_modules/culori/src/itp/convertXyz65ToItp.js
14390
14606
  var toAbs = (c2 = 0) => Math.max(c2 * YW, 0);
14391
- var convertXyz65ToItp = ({ x, y, z: z55, alpha }) => {
14607
+ var convertXyz65ToItp = ({ x, y, z: z61, alpha }) => {
14392
14608
  const absX = toAbs(x);
14393
14609
  const absY = toAbs(y);
14394
- const absZ = toAbs(z55);
14610
+ const absZ = toAbs(z61);
14395
14611
  const l2 = transferPqEncode(
14396
14612
  0.3592832590121217 * absX + 0.6976051147779502 * absY - 0.0358915932320289 * absZ
14397
14613
  );
@@ -14449,15 +14665,15 @@ var jabPqEncode = (v) => {
14449
14665
  return Math.pow((C1 + C2 * vn3) / (1 + C3 * vn3), p);
14450
14666
  };
14451
14667
  var abs = (v = 0) => Math.max(v * 203, 0);
14452
- var convertXyz65ToJab = ({ x, y, z: z55, alpha }) => {
14668
+ var convertXyz65ToJab = ({ x, y, z: z61, alpha }) => {
14453
14669
  x = abs(x);
14454
14670
  y = abs(y);
14455
- z55 = abs(z55);
14456
- let xp = 1.15 * x - 0.15 * z55;
14671
+ z61 = abs(z61);
14672
+ let xp = 1.15 * x - 0.15 * z61;
14457
14673
  let yp = 0.66 * y + 0.34 * x;
14458
- let l2 = jabPqEncode(0.41478972 * xp + 0.579999 * yp + 0.014648 * z55);
14459
- let m = jabPqEncode(-0.20151 * xp + 1.120649 * yp + 0.0531008 * z55);
14460
- let s2 = jabPqEncode(-0.0166008 * xp + 0.2648 * yp + 0.6684799 * z55);
14674
+ let l2 = jabPqEncode(0.41478972 * xp + 0.579999 * yp + 0.014648 * z61);
14675
+ let m = jabPqEncode(-0.20151 * xp + 1.120649 * yp + 0.0531008 * z61);
14676
+ let s2 = jabPqEncode(-0.0166008 * xp + 0.2648 * yp + 0.6684799 * z61);
14461
14677
  let i = (l2 + m) / 2;
14462
14678
  let res = {
14463
14679
  mode: "jab",
@@ -14643,14 +14859,14 @@ var convertLabToXyz50 = ({ l: l2, a, b, alpha }) => {
14643
14859
  var convertLabToXyz50_default = convertLabToXyz50;
14644
14860
 
14645
14861
  // ../../node_modules/culori/src/xyz50/convertXyz50ToRgb.js
14646
- var convertXyz50ToRgb = ({ x, y, z: z55, alpha }) => {
14862
+ var convertXyz50ToRgb = ({ x, y, z: z61, alpha }) => {
14647
14863
  if (x === void 0) x = 0;
14648
14864
  if (y === void 0) y = 0;
14649
- if (z55 === void 0) z55 = 0;
14865
+ if (z61 === void 0) z61 = 0;
14650
14866
  let res = convertLrgbToRgb_default({
14651
- r: x * 3.1341359569958707 - y * 1.6173863321612538 - 0.4906619460083532 * z55,
14652
- g: x * -0.978795502912089 + y * 1.916254567259524 + 0.03344273116131949 * z55,
14653
- b: x * 0.07195537988411677 - y * 0.2289768264158322 + 1.405386058324125 * z55
14867
+ r: x * 3.1341359569958707 - y * 1.6173863321612538 - 0.4906619460083532 * z61,
14868
+ g: x * -0.978795502912089 + y * 1.916254567259524 + 0.03344273116131949 * z61,
14869
+ b: x * 0.07195537988411677 - y * 0.2289768264158322 + 1.405386058324125 * z61
14654
14870
  });
14655
14871
  if (alpha !== void 0) {
14656
14872
  res.alpha = alpha;
@@ -14681,13 +14897,13 @@ var convertRgbToXyz50_default = convertRgbToXyz50;
14681
14897
 
14682
14898
  // ../../node_modules/culori/src/lab/convertXyz50ToLab.js
14683
14899
  var f2 = (value) => value > e3 ? Math.cbrt(value) : (k3 * value + 16) / 116;
14684
- var convertXyz50ToLab = ({ x, y, z: z55, alpha }) => {
14900
+ var convertXyz50ToLab = ({ x, y, z: z61, alpha }) => {
14685
14901
  if (x === void 0) x = 0;
14686
14902
  if (y === void 0) y = 0;
14687
- if (z55 === void 0) z55 = 0;
14903
+ if (z61 === void 0) z61 = 0;
14688
14904
  let f0 = f2(x / D50.X);
14689
14905
  let f1 = f2(y / D50.Y);
14690
- let f22 = f2(z55 / D50.Z);
14906
+ let f22 = f2(z61 / D50.Z);
14691
14907
  let res = {
14692
14908
  mode: "lab",
14693
14909
  l: 116 * f1 - 16,
@@ -14922,18 +15138,18 @@ var convertLchuvToLuv = ({ l: l2, c: c2, h, alpha }) => {
14922
15138
  var convertLchuvToLuv_default = convertLchuvToLuv;
14923
15139
 
14924
15140
  // ../../node_modules/culori/src/luv/convertXyz50ToLuv.js
14925
- var u_fn = (x, y, z55) => 4 * x / (x + 15 * y + 3 * z55);
14926
- var v_fn = (x, y, z55) => 9 * y / (x + 15 * y + 3 * z55);
15141
+ var u_fn = (x, y, z61) => 4 * x / (x + 15 * y + 3 * z61);
15142
+ var v_fn = (x, y, z61) => 9 * y / (x + 15 * y + 3 * z61);
14927
15143
  var un = u_fn(D50.X, D50.Y, D50.Z);
14928
15144
  var vn = v_fn(D50.X, D50.Y, D50.Z);
14929
15145
  var l_fn = (value) => value <= e3 ? k3 * value : 116 * Math.cbrt(value) - 16;
14930
- var convertXyz50ToLuv = ({ x, y, z: z55, alpha }) => {
15146
+ var convertXyz50ToLuv = ({ x, y, z: z61, alpha }) => {
14931
15147
  if (x === void 0) x = 0;
14932
15148
  if (y === void 0) y = 0;
14933
- if (z55 === void 0) z55 = 0;
15149
+ if (z61 === void 0) z61 = 0;
14934
15150
  let l2 = l_fn(y / D50.Y);
14935
- let u = u_fn(x, y, z55);
14936
- let v = v_fn(x, y, z55);
15151
+ let u = u_fn(x, y, z61);
15152
+ let v = v_fn(x, y, z61);
14937
15153
  if (!isFinite(u) || !isFinite(v)) {
14938
15154
  l2 = u = v = 0;
14939
15155
  } else {
@@ -14954,8 +15170,8 @@ var convertXyz50ToLuv = ({ x, y, z: z55, alpha }) => {
14954
15170
  var convertXyz50ToLuv_default = convertXyz50ToLuv;
14955
15171
 
14956
15172
  // ../../node_modules/culori/src/luv/convertLuvToXyz50.js
14957
- var u_fn2 = (x, y, z55) => 4 * x / (x + 15 * y + 3 * z55);
14958
- var v_fn2 = (x, y, z55) => 9 * y / (x + 15 * y + 3 * z55);
15173
+ var u_fn2 = (x, y, z61) => 4 * x / (x + 15 * y + 3 * z61);
15174
+ var v_fn2 = (x, y, z61) => 9 * y / (x + 15 * y + 3 * z61);
14959
15175
  var un2 = u_fn2(D50.X, D50.Y, D50.Z);
14960
15176
  var vn2 = v_fn2(D50.X, D50.Y, D50.Z);
14961
15177
  var convertLuvToXyz50 = ({ l: l2, u, v, alpha }) => {
@@ -14969,8 +15185,8 @@ var convertLuvToXyz50 = ({ l: l2, u, v, alpha }) => {
14969
15185
  let vp = v / (13 * l2) + vn2;
14970
15186
  let y = D50.Y * (l2 <= 8 ? l2 / k3 : Math.pow((l2 + 16) / 116, 3));
14971
15187
  let x = y * (9 * up) / (4 * vp);
14972
- let z55 = y * (12 - 3 * up - 20 * vp) / (4 * vp);
14973
- let res = { mode: "xyz50", x, y, z: z55 };
15188
+ let z61 = y * (12 - 3 * up - 20 * vp) / (4 * vp);
15189
+ let res = { mode: "xyz50", x, y, z: z61 };
14974
15190
  if (alpha !== void 0) {
14975
15191
  res.alpha = alpha;
14976
15192
  }
@@ -15587,15 +15803,15 @@ var convertP3ToXyz65 = (rgb2) => {
15587
15803
  var convertP3ToXyz65_default = convertP3ToXyz65;
15588
15804
 
15589
15805
  // ../../node_modules/culori/src/p3/convertXyz65ToP3.js
15590
- var convertXyz65ToP3 = ({ x, y, z: z55, alpha }) => {
15806
+ var convertXyz65ToP3 = ({ x, y, z: z61, alpha }) => {
15591
15807
  if (x === void 0) x = 0;
15592
15808
  if (y === void 0) y = 0;
15593
- if (z55 === void 0) z55 = 0;
15809
+ if (z61 === void 0) z61 = 0;
15594
15810
  let res = convertLrgbToRgb_default(
15595
15811
  {
15596
- r: x * 2.4934969119414263 - y * 0.9313836179191242 - 0.402710784450717 * z55,
15597
- g: x * -0.8294889695615749 + y * 1.7626640603183465 + 0.0236246858419436 * z55,
15598
- b: x * 0.0358458302437845 - y * 0.0761723892680418 + 0.9568845240076871 * z55
15812
+ r: x * 2.4934969119414263 - y * 0.9313836179191242 - 0.402710784450717 * z61,
15813
+ g: x * -0.8294889695615749 + y * 1.7626640603183465 + 0.0236246858419436 * z61,
15814
+ b: x * 0.0358458302437845 - y * 0.0761723892680418 + 0.9568845240076871 * z61
15599
15815
  },
15600
15816
  "p3"
15601
15817
  );
@@ -15630,19 +15846,19 @@ var gamma2 = (v) => {
15630
15846
  }
15631
15847
  return 16 * v;
15632
15848
  };
15633
- var convertXyz50ToProphoto = ({ x, y, z: z55, alpha }) => {
15849
+ var convertXyz50ToProphoto = ({ x, y, z: z61, alpha }) => {
15634
15850
  if (x === void 0) x = 0;
15635
15851
  if (y === void 0) y = 0;
15636
- if (z55 === void 0) z55 = 0;
15852
+ if (z61 === void 0) z61 = 0;
15637
15853
  let res = {
15638
15854
  mode: "prophoto",
15639
15855
  r: gamma2(
15640
- x * 1.3457868816471585 - y * 0.2555720873797946 - 0.0511018649755453 * z55
15856
+ x * 1.3457868816471585 - y * 0.2555720873797946 - 0.0511018649755453 * z61
15641
15857
  ),
15642
15858
  g: gamma2(
15643
- x * -0.5446307051249019 + y * 1.5082477428451466 + 0.0205274474364214 * z55
15859
+ x * -0.5446307051249019 + y * 1.5082477428451466 + 0.0205274474364214 * z61
15644
15860
  ),
15645
- b: gamma2(x * 0 + y * 0 + 1.2119675456389452 * z55)
15861
+ b: gamma2(x * 0 + y * 0 + 1.2119675456389452 * z61)
15646
15862
  };
15647
15863
  if (alpha !== void 0) {
15648
15864
  res.alpha = alpha;
@@ -15702,20 +15918,20 @@ var gamma3 = (v) => {
15702
15918
  }
15703
15919
  return 4.5 * v;
15704
15920
  };
15705
- var convertXyz65ToRec2020 = ({ x, y, z: z55, alpha }) => {
15921
+ var convertXyz65ToRec2020 = ({ x, y, z: z61, alpha }) => {
15706
15922
  if (x === void 0) x = 0;
15707
15923
  if (y === void 0) y = 0;
15708
- if (z55 === void 0) z55 = 0;
15924
+ if (z61 === void 0) z61 = 0;
15709
15925
  let res = {
15710
15926
  mode: "rec2020",
15711
15927
  r: gamma3(
15712
- x * 1.7166511879712683 - y * 0.3556707837763925 - 0.2533662813736599 * z55
15928
+ x * 1.7166511879712683 - y * 0.3556707837763925 - 0.2533662813736599 * z61
15713
15929
  ),
15714
15930
  g: gamma3(
15715
- x * -0.6666843518324893 + y * 1.6164812366349395 + 0.0157685458139111 * z55
15931
+ x * -0.6666843518324893 + y * 1.6164812366349395 + 0.0157685458139111 * z61
15716
15932
  ),
15717
15933
  b: gamma3(
15718
- x * 0.0176398574453108 - y * 0.0427706132578085 + 0.9421031212354739 * z55
15934
+ x * 0.0176398574453108 - y * 0.0427706132578085 + 0.9421031212354739 * z61
15719
15935
  )
15720
15936
  };
15721
15937
  if (alpha !== void 0) {
@@ -15868,15 +16084,15 @@ var definition_default26 = definition26;
15868
16084
 
15869
16085
  // ../../node_modules/culori/src/xyz65/convertXyz65ToXyz50.js
15870
16086
  var convertXyz65ToXyz50 = (xyz652) => {
15871
- let { x, y, z: z55, alpha } = xyz652;
16087
+ let { x, y, z: z61, alpha } = xyz652;
15872
16088
  if (x === void 0) x = 0;
15873
16089
  if (y === void 0) y = 0;
15874
- if (z55 === void 0) z55 = 0;
16090
+ if (z61 === void 0) z61 = 0;
15875
16091
  let res = {
15876
16092
  mode: "xyz50",
15877
- x: 1.0479298208405488 * x + 0.0229467933410191 * y - 0.0501922295431356 * z55,
15878
- y: 0.0296278156881593 * x + 0.990434484573249 * y - 0.0170738250293851 * z55,
15879
- z: -0.0092430581525912 * x + 0.0150551448965779 * y + 0.7518742899580008 * z55
16093
+ x: 1.0479298208405488 * x + 0.0229467933410191 * y - 0.0501922295431356 * z61,
16094
+ y: 0.0296278156881593 * x + 0.990434484573249 * y - 0.0170738250293851 * z61,
16095
+ z: -0.0092430581525912 * x + 0.0150551448965779 * y + 0.7518742899580008 * z61
15880
16096
  };
15881
16097
  if (alpha !== void 0) {
15882
16098
  res.alpha = alpha;
@@ -15887,15 +16103,15 @@ var convertXyz65ToXyz50_default = convertXyz65ToXyz50;
15887
16103
 
15888
16104
  // ../../node_modules/culori/src/xyz65/convertXyz50ToXyz65.js
15889
16105
  var convertXyz50ToXyz65 = (xyz502) => {
15890
- let { x, y, z: z55, alpha } = xyz502;
16106
+ let { x, y, z: z61, alpha } = xyz502;
15891
16107
  if (x === void 0) x = 0;
15892
16108
  if (y === void 0) y = 0;
15893
- if (z55 === void 0) z55 = 0;
16109
+ if (z61 === void 0) z61 = 0;
15894
16110
  let res = {
15895
16111
  mode: "xyz65",
15896
- x: 0.9554734527042182 * x - 0.0230985368742614 * y + 0.0632593086610217 * z55,
15897
- y: -0.0283697069632081 * x + 1.0099954580058226 * y + 0.021041398966943 * z55,
15898
- z: 0.0123140016883199 * x - 0.0205076964334779 * y + 1.3303659366080753 * z55
16112
+ x: 0.9554734527042182 * x - 0.0230985368742614 * y + 0.0632593086610217 * z61,
16113
+ y: -0.0283697069632081 * x + 1.0099954580058226 * y + 0.021041398966943 * z61,
16114
+ z: 0.0123140016883199 * x - 0.0205076964334779 * y + 1.3303659366080753 * z61
15899
16115
  };
15900
16116
  if (alpha !== void 0) {
15901
16117
  res.alpha = alpha;
@@ -16165,6 +16381,571 @@ z.string().optional().nullable().transform((value) => {
16165
16381
  }
16166
16382
  );
16167
16383
 
16384
+ // ../../internal/utils/src/industry.ts
16385
+ var Industry = {
16386
+ // Finance
16387
+ BANKING: "banking",
16388
+ FINANCIAL_SERVICES: "financial_services",
16389
+ FINTECH: "fintech",
16390
+ PAYMENTS: "payments"};
16391
+
16392
+ // ../../internal/validation/src/dark-web/templates.ts
16393
+ var BRAND = { kind: "brand" };
16394
+ var lit = (value) => ({ kind: "literal", value });
16395
+ var phrase = (...tokens) => ({ type: "phrase", tokens });
16396
+ var bn = (slop, ...words) => ({
16397
+ type: "phrase",
16398
+ tokens: [BRAND, ...words.map(lit)],
16399
+ slop
16400
+ });
16401
+ var nb = (slop, ...words) => ({
16402
+ type: "phrase",
16403
+ tokens: [...words.map(lit), BRAND],
16404
+ slop
16405
+ });
16406
+ var word = (value) => ({ type: "term", value, match: "word" });
16407
+ var prefix = (value) => ({
16408
+ type: "term",
16409
+ value,
16410
+ match: "prefix"
16411
+ });
16412
+ var or = (...children) => ({
16413
+ type: "group",
16414
+ op: "OR",
16415
+ children
16416
+ });
16417
+ var and = (...children) => ({
16418
+ type: "group",
16419
+ op: "AND",
16420
+ children
16421
+ });
16422
+ var notOneOf = (...children) => ({
16423
+ type: "group",
16424
+ op: "OR",
16425
+ children,
16426
+ negated: true
16427
+ });
16428
+ var brandWith = (...values) => and(phrase(BRAND), or(...values.map(word)));
16429
+ var SCAN_TEMPLATES = [
16430
+ {
16431
+ id: "p2p-instant-transfer",
16432
+ category: "payment-fraud",
16433
+ title: "P2P and instant transfer abuse",
16434
+ description: "Actors advertising instant peer-to-peer transfers into or out of the brand, a common way to move stolen funds before a hold clears.",
16435
+ // Source rows: 1, 55. The original ("[brand]"~3) was proximity on one
16436
+ // position, a no-op; dropped rather than ported.
16437
+ query: and(
16438
+ or(
16439
+ phrase(BRAND, lit("to"), BRAND),
16440
+ bn(2, "instant"),
16441
+ bn(2, "anyone"),
16442
+ bn(1, "pay"),
16443
+ bn(3, "pay anyone"),
16444
+ bn(2, "p2p"),
16445
+ bn(3, "instant transfer"),
16446
+ bn(3, "receive from anyone"),
16447
+ bn(4, "phone number"),
16448
+ bn(2, "member to member"),
16449
+ nb(3, "send")
16450
+ ),
16451
+ notOneOf(word("rate"), word("price"))
16452
+ )
16453
+ },
16454
+ {
16455
+ id: "transfers-ach-direct-debit",
16456
+ category: "payment-fraud",
16457
+ title: "Transfers, ACH and direct debit",
16458
+ description: "Discussion of pushing or pulling funds via ACH, direct debit or bill pay, including chatter about pending and settlement timing.",
16459
+ // Source rows: 6, 8, 18, 41. Row 18 was missing an OR before "Direct Debit".
16460
+ query: or(
16461
+ bn(1, "transfer"),
16462
+ nb(1, "transfer"),
16463
+ bn(5, "ach"),
16464
+ nb(4, "pull"),
16465
+ nb(4, "push"),
16466
+ bn(4, "pending"),
16467
+ bn(4, "settle"),
16468
+ nb(4, "link"),
16469
+ brandWith("billpay", "acctverify"),
16470
+ and(phrase(BRAND), or(phrase(lit("direct debit")), phrase(lit("direct deposit"))))
16471
+ )
16472
+ },
16473
+ {
16474
+ id: "wallet-linking",
16475
+ category: "payment-fraud",
16476
+ title: "Wallet linking and instant funding",
16477
+ description: "Linking the brand to mobile wallets for instant funding, and 'linkable' account trading where a name match is being sold.",
16478
+ // Source rows: 19, 44, 51
16479
+ query: and(
16480
+ or(
16481
+ bn(5, "apple pay"),
16482
+ bn(5, "apple"),
16483
+ bn(3, "apple linkable"),
16484
+ bn(2, "linkable"),
16485
+ bn(2, "linkables"),
16486
+ nb(2, "linkable"),
16487
+ nb(2, "link"),
16488
+ and(phrase(BRAND), phrase(lit("name match")))
16489
+ ),
16490
+ notOneOf(word("espn"), word("airtag"))
16491
+ )
16492
+ },
16493
+ {
16494
+ id: "card-data-exposure",
16495
+ category: "payment-fraud",
16496
+ title: "Card data and virtual cards",
16497
+ description: "Brand-issued card details being traded: expiry, CVV, virtual card numbers and card activation.",
16498
+ // Source rows: 43
16499
+ query: and(
16500
+ brandWith("exp", "cvv", "vcc", "debit"),
16501
+ notOneOf(word("checking"), word("limits"))
16502
+ )
16503
+ },
16504
+ {
16505
+ id: "card-loading",
16506
+ category: "payment-fraud",
16507
+ title: "Card and account loading",
16508
+ description: "'Loading' services that push stolen funds onto brand accounts or cards for a cut.",
16509
+ // Source rows: 42
16510
+ query: or(
16511
+ nb(1, "loading"),
16512
+ nb(1, "load"),
16513
+ bn(1, "loads"),
16514
+ bn(1, "loading"),
16515
+ bn(3, "drop"),
16516
+ bn(2, "cash"),
16517
+ and(
16518
+ phrase(BRAND),
16519
+ or(
16520
+ phrase(lit("loading method")),
16521
+ phrase(lit("loading guide")),
16522
+ phrase(lit("loading tut")),
16523
+ phrase(lit("loading tutorial"))
16524
+ )
16525
+ )
16526
+ )
16527
+ },
16528
+ {
16529
+ id: "cross-platform-cashout",
16530
+ category: "payment-fraud",
16531
+ title: "Cross-platform cash-out",
16532
+ description: "Moving funds from the brand into other consumer payment apps to break the trail.",
16533
+ // Source rows: 56
16534
+ query: and(
16535
+ phrase(BRAND),
16536
+ or(
16537
+ phrase(lit("cash app")),
16538
+ word("venmo"),
16539
+ phrase(lit("transfer to paypal")),
16540
+ phrase(lit("visa money transfer"))
16541
+ )
16542
+ )
16543
+ },
16544
+ {
16545
+ id: "crypto-offramp",
16546
+ category: "payment-fraud",
16547
+ title: "Crypto off-ramp",
16548
+ description: "Converting funds held at the brand into cryptocurrency through exchanges and P2P markets.",
16549
+ // Source rows: 63
16550
+ query: and(
16551
+ phrase(BRAND),
16552
+ or(
16553
+ word("crypto"),
16554
+ word("moonpay"),
16555
+ word("switchere"),
16556
+ word("kraken"),
16557
+ word("paxful"),
16558
+ word("noones"),
16559
+ word("coinomi"),
16560
+ word("coinzoom"),
16561
+ word("bitcoin"),
16562
+ word("btc")
16563
+ )
16564
+ )
16565
+ },
16566
+ {
16567
+ id: "mobile-check-deposit",
16568
+ category: "deposit-fraud",
16569
+ title: "Mobile check deposit fraud",
16570
+ description: "Check fraud against mobile deposit, traded under slang including 'glass', 'MCD', 'slips' and 'persy'.",
16571
+ // Source rows: 2, 13, 35, 46
16572
+ industries: [
16573
+ Industry.BANKING,
16574
+ Industry.FINANCIAL_SERVICES,
16575
+ Industry.FINTECH,
16576
+ Industry.PAYMENTS
16577
+ ],
16578
+ query: or(
16579
+ bn(5, "checks"),
16580
+ bn(5, "check"),
16581
+ bn(5, "mobile deposit"),
16582
+ nb(5, "mobile deposit"),
16583
+ bn(5, "slip"),
16584
+ bn(5, "slips"),
16585
+ bn(5, "glass"),
16586
+ bn(5, "md"),
16587
+ bn(5, "mcd"),
16588
+ bn(5, "treasury"),
16589
+ nb(5, "treasury"),
16590
+ bn(5, "persy"),
16591
+ bn(5, "deposit limit"),
16592
+ and(phrase(BRAND), or(prefix("stimm"), word("mcd"), phrase(lit("pop on"))))
16593
+ )
16594
+ },
16595
+ {
16596
+ id: "atm-abuse",
16597
+ category: "deposit-fraud",
16598
+ title: "ATM abuse and 'box work'",
16599
+ description: "ATM deposit and withdrawal manipulation, including dispute-driven 'box work' against cash machines.",
16600
+ // Source rows: 9, 47, 57, 70
16601
+ industries: [
16602
+ Industry.BANKING,
16603
+ Industry.FINANCIAL_SERVICES,
16604
+ Industry.FINTECH,
16605
+ Industry.PAYMENTS
16606
+ ],
16607
+ query: or(
16608
+ bn(2, "atm"),
16609
+ bn(2, "withdrawal"),
16610
+ and(phrase(BRAND), phrase(lit("box work"))),
16611
+ and(
16612
+ phrase(BRAND),
16613
+ or(
16614
+ phrase(lit("atm deposit")),
16615
+ phrase(lit("atm withdrawal")),
16616
+ phrase(lit("atm withdraw")),
16617
+ phrase(lit("atm limit")),
16618
+ phrase(lit("atm card")),
16619
+ phrase(lit("cash withdrawal fee"))
16620
+ )
16621
+ )
16622
+ )
16623
+ },
16624
+ {
16625
+ id: "retail-cashout-network",
16626
+ category: "deposit-fraud",
16627
+ title: "Retail cash-out network",
16628
+ description: "Cash-out through retail and ATM partner networks, where funds are pulled at a storefront rather than a branch.",
16629
+ // Source rows: 30
16630
+ industries: [
16631
+ Industry.BANKING,
16632
+ Industry.FINANCIAL_SERVICES,
16633
+ Industry.FINTECH,
16634
+ Industry.PAYMENTS
16635
+ ],
16636
+ query: or(
16637
+ bn(5, "allpoint"),
16638
+ bn(5, "walgreens"),
16639
+ bn(5, "cvs"),
16640
+ bn(5, "7-eleven"),
16641
+ bn(5, "7eleven"),
16642
+ bn(5, "711"),
16643
+ bn(5, "circle k"),
16644
+ bn(5, "target"),
16645
+ bn(5, "visa plus alliance"),
16646
+ bn(3, "moneypass"),
16647
+ bn(3, "vanilladirect"),
16648
+ bn(3, "vanilla"),
16649
+ bn(3, "dollar general"),
16650
+ bn(3, "dollar tree"),
16651
+ bn(3, "safeway"),
16652
+ bn(3, "walmart"),
16653
+ bn(3, "speedway")
16654
+ )
16655
+ },
16656
+ {
16657
+ id: "otp-bots",
16658
+ category: "account-takeover",
16659
+ title: "OTP bots and 2FA bypass",
16660
+ description: "Automated one-time-passcode interception services targeting the brand's login and step-up flows.",
16661
+ // Source rows: 34
16662
+ query: or(
16663
+ bn(2, "otp"),
16664
+ nb(1, "otp"),
16665
+ phrase(BRAND, lit("otp bot")),
16666
+ bn(2, "2fa"),
16667
+ bn(2, "code"),
16668
+ bn(2, "bypass"),
16669
+ bn(2, "bot"),
16670
+ bn(2, "bots")
16671
+ )
16672
+ },
16673
+ {
16674
+ id: "remote-access-tooling",
16675
+ category: "account-takeover",
16676
+ title: "Remote-access tooling",
16677
+ description: "Legitimate remote-management tools named alongside the brand, the standard toolkit for support-impersonation account takeover.",
16678
+ // Source rows: 29, 64. Truncated by the export; recoverable portion only.
16679
+ query: and(
16680
+ phrase(BRAND),
16681
+ or(
16682
+ word("action1"),
16683
+ phrase(lit("ammy admin")),
16684
+ word("ammyadmin"),
16685
+ word("anydesk"),
16686
+ phrase(lit("any desk")),
16687
+ word("atera"),
16688
+ word("beanywhere"),
16689
+ phrase(lit("chrome remote desktop")),
16690
+ word("connectwise"),
16691
+ word("screenconnect"),
16692
+ word("dameware"),
16693
+ word("domotz"),
16694
+ word("dwservice"),
16695
+ word("fleetdeck"),
16696
+ word("intelliadmin"),
16697
+ phrase(lit("itarian endpoint manager")),
16698
+ phrase(lit("level.io")),
16699
+ word("meshcentral"),
16700
+ word("mremoteng"),
16701
+ phrase(lit("n-able")),
16702
+ word("ngrok"),
16703
+ phrase(lit("pdq deploy")),
16704
+ word("pulseway"),
16705
+ word("realvnc"),
16706
+ phrase(lit("remote utilities")),
16707
+ word("rport")
16708
+ )
16709
+ )
16710
+ },
16711
+ {
16712
+ id: "antidetect-kyc-evasion",
16713
+ category: "account-takeover",
16714
+ title: "Anti-detect and KYC evasion tooling",
16715
+ description: "Fingerprint spoofing, residential proxies and emulators used to defeat the brand's device and identity checks.",
16716
+ // Source row: 40. The original filtered to Telegram OR Dread; DarkOwl's
16717
+ // `source` takes one value, so precision comes from the tooling terms.
16718
+ query: and(
16719
+ phrase(BRAND),
16720
+ or(
16721
+ word("tlo"),
16722
+ word("scamalytics"),
16723
+ word("burner"),
16724
+ prefix("prox"),
16725
+ word("vpn"),
16726
+ word("vps"),
16727
+ word("antidetect"),
16728
+ phrase(lit("anti-detect")),
16729
+ prefix("emulat")
16730
+ )
16731
+ )
16732
+ },
16733
+ {
16734
+ id: "brand-domains-sso",
16735
+ category: "account-takeover",
16736
+ title: "Brand domains and SSO endpoints",
16737
+ description: "The brand's own domains and identity-provider endpoints appearing in credential and access chatter.",
16738
+ // Source row: 31, as proximity not literal hostnames — the index tokenizes
16739
+ // `chime.okta.com`, so a glued literal would never match. Row 23's
16740
+ // "[brand] pass*"~1 is omitted: no wildcards inside a phrase.
16741
+ query: and(
16742
+ or(bn(2, "okta"), bn(2, "login"), bn(2, "portal"), bn(1, "bank")),
16743
+ notOneOf(word("zendesk"))
16744
+ )
16745
+ },
16746
+ {
16747
+ id: "account-opening-abuse",
16748
+ category: "onboarding-abuse",
16749
+ title: "Account opening and self-registration",
16750
+ description: "Bulk or fraudulent account creation against the brand, including 'self-reg' and 'drop' accounts opened on stolen identities.",
16751
+ // Source rows: 5, 17, 54
16752
+ query: or(
16753
+ bn(5, "enroll"),
16754
+ nb(5, "enroll"),
16755
+ bn(5, "open"),
16756
+ nb(5, "open"),
16757
+ bn(5, "drop"),
16758
+ nb(5, "drop"),
16759
+ nb(5, "make"),
16760
+ nb(5, "create"),
16761
+ nb(5, "fresh"),
16762
+ nb(5, "register"),
16763
+ bn(5, "self-reg"),
16764
+ bn(5, "selfreg"),
16765
+ bn(5, "self-registered"),
16766
+ bn(5, "self register"),
16767
+ bn(1, "fullz")
16768
+ )
16769
+ },
16770
+ {
16771
+ id: "identity-documents",
16772
+ category: "onboarding-abuse",
16773
+ title: "Identity documents and fullz",
16774
+ description: "Trade in identity packages and lookup services used to pass onboarding checks. Anchored to the brand at the root, since the source query was brand-independent.",
16775
+ // Source row: 15. Lexicon-only, so the renderer AND-s the anchor on.
16776
+ query: or(
16777
+ word("fullz"),
16778
+ phrase(lit("get pros")),
16779
+ phrase(lit("driver's license")),
16780
+ phrase(lit("tlo bot")),
16781
+ phrase(lit("tlo plug")),
16782
+ phrase(lit("need dl")),
16783
+ phrase(lit("get id"))
16784
+ )
16785
+ },
16786
+ {
16787
+ id: "referral-bonus-abuse",
16788
+ category: "onboarding-abuse",
16789
+ title: "Referral and bonus abuse",
16790
+ description: "Methods for farming the brand's referral bonuses and promotional offers, often paired with fake direct deposits.",
16791
+ // Source rows: 48
16792
+ query: and(
16793
+ or(bn(3, "referral"), bn(3, "bonus"), bn(3, "promotion"), bn(3, "offer")),
16794
+ or(
16795
+ word("bypass"),
16796
+ word("dd"),
16797
+ phrase(lit("direct deposit")),
16798
+ phrase(lit("work around")),
16799
+ word("trick"),
16800
+ word("method"),
16801
+ word("glitch"),
16802
+ word("tutorial"),
16803
+ word("tut"),
16804
+ word("wave"),
16805
+ word("sauce")
16806
+ )
16807
+ )
16808
+ },
16809
+ {
16810
+ id: "insider-recruitment",
16811
+ category: "insider-threat",
16812
+ title: "Insider recruitment",
16813
+ description: "Actors seeking or advertising an employee on the inside ('inny'), the highest-severity signal in this catalog.",
16814
+ // Source rows: 7, 38
16815
+ query: and(
16816
+ or(
16817
+ bn(4, "inny"),
16818
+ bn(4, "insider"),
16819
+ and(
16820
+ phrase(BRAND),
16821
+ or(
16822
+ word("insider"),
16823
+ word("inny"),
16824
+ phrase(lit("in office")),
16825
+ word("employee"),
16826
+ phrase(lit("i work at")),
16827
+ phrase(lit("i am an employee")),
16828
+ phrase(lit("i can get access")),
16829
+ phrase(lit("i have access to")),
16830
+ phrase(lit("employed at")),
16831
+ word("occupation")
16832
+ )
16833
+ )
16834
+ ),
16835
+ notOneOf(phrase(lit("welcome to insider chat")))
16836
+ )
16837
+ },
16838
+ {
16839
+ id: "mule-crew-recruitment",
16840
+ category: "insider-threat",
16841
+ title: "Mule and cash-out crew recruitment",
16842
+ description: "Recruitment of account holders to receive and forward funds, priced as a revenue split ('50/50', '60/40').",
16843
+ // Source rows: 39
16844
+ query: or(
16845
+ bn(5, "cl"),
16846
+ bn(5, "mule"),
16847
+ bn(5, "tap in"),
16848
+ bn(5, "50/50"),
16849
+ bn(5, "60/40"),
16850
+ bn(5, "striker")
16851
+ )
16852
+ },
16853
+ {
16854
+ id: "methods-and-tutorials",
16855
+ category: "tradecraft",
16856
+ title: "Methods, 'sauce' and tutorials",
16857
+ description: "Fraud methods being taught or sold against the brand, under the usual slang: sauce, glitch, tut, wave, motion, tap in.",
16858
+ // Source rows: 4, 21, 59
16859
+ query: or(
16860
+ bn(3, "sauce"),
16861
+ bn(3, "method"),
16862
+ bn(3, "guide"),
16863
+ bn(3, "tut"),
16864
+ bn(3, "tutorial"),
16865
+ bn(4, "hack"),
16866
+ bn(4, "glitch"),
16867
+ bn(4, "wave"),
16868
+ bn(4, "motion"),
16869
+ bn(4, "walkthrough"),
16870
+ bn(4, "walk through"),
16871
+ bn(2, "tap in"),
16872
+ bn(2, "tapin"),
16873
+ nb(2, "tapn")
16874
+ )
16875
+ },
16876
+ {
16877
+ id: "hack-leak-chatter",
16878
+ category: "tradecraft",
16879
+ title: "Hack and leak chatter",
16880
+ description: "Claims of having hacked, breached or leaked the brand, whether credible or not.",
16881
+ // Source rows: 28
16882
+ query: or(
16883
+ bn(4, "hack"),
16884
+ bn(4, "glitch"),
16885
+ nb(4, "hacking"),
16886
+ bn(4, "leak"),
16887
+ bn(4, "leaked")
16888
+ )
16889
+ },
16890
+ {
16891
+ id: "support-impersonation",
16892
+ category: "brand-abuse",
16893
+ title: "Support impersonation",
16894
+ description: "Actors impersonating the brand's support or service staff to social-engineer customers.",
16895
+ // Source rows: 36
16896
+ query: or(
16897
+ bn(1, "agent"),
16898
+ bn(1, "support"),
16899
+ bn(1, "service"),
16900
+ bn(1, "customer"),
16901
+ nb(1, "impersonate"),
16902
+ bn(1, "impersonation"),
16903
+ bn(3, "scam"),
16904
+ nb(1, "scam")
16905
+ )
16906
+ },
16907
+ {
16908
+ id: "social-impersonation",
16909
+ category: "brand-abuse",
16910
+ title: "Social account impersonation",
16911
+ description: "The brand discussed alongside social platforms, where fake support and giveaway accounts are staged.",
16912
+ // Source rows: 25
16913
+ query: or(
16914
+ bn(5, "instagram"),
16915
+ bn(5, "insta"),
16916
+ bn(5, "facebook"),
16917
+ bn(5, "twitter"),
16918
+ bn(5, "tiktok")
16919
+ )
16920
+ },
16921
+ {
16922
+ id: "vendor-supply-chain",
16923
+ category: "data-exposure",
16924
+ title: "Vendor and BPO supply chain",
16925
+ description: "Outsourced support and processing vendors named alongside the brand, where a third-party breach exposes brand customer data.",
16926
+ // Source row: 60, truncated by the export. Generic BPO/CX providers; a
16927
+ // per-org vendor list would be materially better.
16928
+ query: and(
16929
+ phrase(BRAND),
16930
+ or(
16931
+ word("zendesk"),
16932
+ word("teleperformance"),
16933
+ word("concentrix"),
16934
+ word("taskus"),
16935
+ word("genpact"),
16936
+ word("ubiquity"),
16937
+ word("hioperator"),
16938
+ word("galileo"),
16939
+ word("fiserv"),
16940
+ phrase(lit("etech global"))
16941
+ )
16942
+ )
16943
+ }
16944
+ ];
16945
+ new Map(
16946
+ SCAN_TEMPLATES.map((template) => [template.id, template])
16947
+ );
16948
+
16168
16949
  // src/client.ts
16169
16950
  function trimTrailingSlashes(url) {
16170
16951
  return url.replace(/\/+$/, "");