@osqd/bothandlerjs 0.3.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/CHANGELOG.md +516 -0
- package/LICENSE +102 -0
- package/README.md +308 -0
- package/bin/bothandlerjs.mjs +14 -0
- package/dist/actions/index.d.ts +22 -0
- package/dist/actions/types.d.ts +59 -0
- package/dist/adapters/fastify.d.ts +43 -0
- package/dist/adapters/fetch.d.ts +51 -0
- package/dist/adapters/index.cjs +723 -0
- package/dist/adapters/index.cjs.map +1 -0
- package/dist/adapters/index.d.ts +8 -0
- package/dist/adapters/index.js +692 -0
- package/dist/adapters/index.js.map +1 -0
- package/dist/adapters/koa.d.ts +27 -0
- package/dist/adapters/node.d.ts +29 -0
- package/dist/adapters/shared.d.ts +39 -0
- package/dist/audit.d.ts +172 -0
- package/dist/challenge/index.d.ts +176 -0
- package/dist/challenge/language.d.ts +57 -0
- package/dist/challenge/page.d.ts +42 -0
- package/dist/challenge/pow.d.ts +33 -0
- package/dist/challenge/token.d.ts +72 -0
- package/dist/cli.cjs +13388 -0
- package/dist/cli.cjs.map +1 -0
- package/dist/cli.d.ts +5 -0
- package/dist/cli.js +13358 -0
- package/dist/cli.js.map +1 -0
- package/dist/client/index.cjs +139 -0
- package/dist/client/index.cjs.map +1 -0
- package/dist/client/index.d.ts +63 -0
- package/dist/client/index.js +112 -0
- package/dist/client/index.js.map +1 -0
- package/dist/config.d.ts +316 -0
- package/dist/core.d.ts +443 -0
- package/dist/corpus/adversarial.d.ts +2 -0
- package/dist/corpus/advertising-email.d.ts +2 -0
- package/dist/corpus/ai-crawlers.d.ts +2 -0
- package/dist/corpus/benign-bots.d.ts +2 -0
- package/dist/corpus/cdn-gateways.d.ts +2 -0
- package/dist/corpus/cookies.d.ts +49 -0
- package/dist/corpus/crawlers-regional.d.ts +2 -0
- package/dist/corpus/crawlers-vertical.d.ts +2 -0
- package/dist/corpus/headers.d.ts +403 -0
- package/dist/corpus/humans-apps.d.ts +2 -0
- package/dist/corpus/humans-browsers.d.ts +2 -0
- package/dist/corpus/humans.d.ts +2 -0
- package/dist/corpus/index.cjs +4196 -0
- package/dist/corpus/index.cjs.map +1 -0
- package/dist/corpus/index.d.ts +37 -0
- package/dist/corpus/index.js +4132 -0
- package/dist/corpus/index.js.map +1 -0
- package/dist/corpus/infrastructure.d.ts +17 -0
- package/dist/corpus/libraries-extended.d.ts +2 -0
- package/dist/corpus/ranges.d.ts +31 -0
- package/dist/corpus/reputation.d.ts +21 -0
- package/dist/corpus/runner.d.ts +130 -0
- package/dist/corpus/schema.d.ts +185 -0
- package/dist/corpus/tooling.d.ts +2 -0
- package/dist/corpus/unwanted.d.ts +2 -0
- package/dist/crawler-ranges.d.ts +113 -0
- package/dist/dashboard/client.generated.d.ts +7 -0
- package/dist/dashboard/feed.d.ts +186 -0
- package/dist/dashboard/index.d.ts +5 -0
- package/dist/dashboard/page.d.ts +51 -0
- package/dist/dashboard/parse-request.d.ts +28 -0
- package/dist/dashboard/preview.d.ts +45 -0
- package/dist/dashboard/server.d.ts +70 -0
- package/dist/dashboard/types.d.ts +714 -0
- package/dist/detectors/accept-signature.d.ts +6 -0
- package/dist/detectors/browsing-coherence.d.ts +44 -0
- package/dist/detectors/cadence.d.ts +28 -0
- package/dist/detectors/clearance.d.ts +25 -0
- package/dist/detectors/client-hints.d.ts +19 -0
- package/dist/detectors/client-signals.d.ts +27 -0
- package/dist/detectors/crawl-breadth.d.ts +28 -0
- package/dist/detectors/crawler-verification.d.ts +39 -0
- package/dist/detectors/fetch-metadata.d.ts +20 -0
- package/dist/detectors/header-integrity.d.ts +26 -0
- package/dist/detectors/header-order.d.ts +27 -0
- package/dist/detectors/identity-rotation.d.ts +34 -0
- package/dist/detectors/index.d.ts +56 -0
- package/dist/detectors/ip-intelligence.d.ts +30 -0
- package/dist/detectors/known-bots.d.ts +94 -0
- package/dist/detectors/probe-signature.d.ts +50 -0
- package/dist/detectors/rate-anomaly.d.ts +36 -0
- package/dist/detectors/self-identified.d.ts +33 -0
- package/dist/detectors/session-integrity.d.ts +24 -0
- package/dist/detectors/tls-fingerprint.d.ts +34 -0
- package/dist/detectors/trap.d.ts +98 -0
- package/dist/detectors/types.d.ts +70 -0
- package/dist/detectors/ua-coherence.d.ts +32 -0
- package/dist/evidence.d.ts +45 -0
- package/dist/facts.d.ts +46 -0
- package/dist/index.cjs +9600 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +63 -0
- package/dist/index.js +9444 -0
- package/dist/index.js.map +1 -0
- package/dist/internal/async.d.ts +16 -0
- package/dist/internal/clock.d.ts +17 -0
- package/dist/internal/crypto.d.ts +26 -0
- package/dist/internal/dns.d.ts +100 -0
- package/dist/internal/emitter.d.ts +18 -0
- package/dist/internal/http.d.ts +22 -0
- package/dist/internal/ip.d.ts +73 -0
- package/dist/internal/lru.d.ts +34 -0
- package/dist/internal/matcher.d.ts +41 -0
- package/dist/internal/pattern.d.ts +14 -0
- package/dist/internal/ua.d.ts +92 -0
- package/dist/metrics.d.ts +120 -0
- package/dist/notify/hub.d.ts +70 -0
- package/dist/notify/index.d.ts +7 -0
- package/dist/notify/redact.d.ts +48 -0
- package/dist/notify/sinks.d.ts +62 -0
- package/dist/notify/types.d.ts +56 -0
- package/dist/policy/index.d.ts +7 -0
- package/dist/policy/match.d.ts +20 -0
- package/dist/policy/policy.d.ts +123 -0
- package/dist/policy/presets.d.ts +161 -0
- package/dist/policy/types.d.ts +155 -0
- package/dist/robots.d.ts +83 -0
- package/dist/state.d.ts +161 -0
- package/dist/stores/index.d.ts +5 -0
- package/dist/stores/memory.d.ts +33 -0
- package/dist/stores/redis.d.ts +59 -0
- package/dist/stores/types.d.ts +37 -0
- package/dist/types.d.ts +250 -0
- package/docs/challenge/index.md +152 -0
- package/docs/challenge/localisation.md +105 -0
- package/docs/concepts/actors.md +113 -0
- package/docs/concepts/evidence.md +133 -0
- package/docs/concepts/the-guard.md +121 -0
- package/docs/concepts/threat-model.md +120 -0
- package/docs/concepts/verdicts.md +108 -0
- package/docs/course/01-first-assessment.md +178 -0
- package/docs/course/02-proof-and-suspicion.md +164 -0
- package/docs/course/03-verdicts-and-scores.md +160 -0
- package/docs/course/04-the-guard.md +200 -0
- package/docs/course/05-detectors.md +178 -0
- package/docs/course/06-identity.md +161 -0
- package/docs/course/07-actors.md +178 -0
- package/docs/course/08-traps.md +183 -0
- package/docs/course/09-rules.md +224 -0
- package/docs/course/10-actions-and-presets.md +216 -0
- package/docs/course/11-the-challenge.md +227 -0
- package/docs/course/12-going-live.md +211 -0
- package/docs/course/13-operating-it.md +238 -0
- package/docs/course/14-scaling.md +195 -0
- package/docs/course/15-extending.md +259 -0
- package/docs/course/16-proving-it.md +266 -0
- package/docs/course/index.md +112 -0
- package/docs/design/decisions.md +216 -0
- package/docs/detection/client-signals.md +96 -0
- package/docs/detection/detectors.md +376 -0
- package/docs/detection/index.md +134 -0
- package/docs/detection/signatures.md +111 -0
- package/docs/detection/verification.md +123 -0
- package/docs/detection/writing-a-detector.md +145 -0
- package/docs/index.md +100 -0
- package/docs/integration/adapters.md +138 -0
- package/docs/integration/client-ip.md +114 -0
- package/docs/integration/index.md +55 -0
- package/docs/integration/stores.md +76 -0
- package/docs/operations/audit.md +116 -0
- package/docs/operations/dashboard.md +725 -0
- package/docs/operations/index.md +101 -0
- package/docs/operations/metrics.md +74 -0
- package/docs/operations/notifications.md +72 -0
- package/docs/operations/runtime-changes.md +118 -0
- package/docs/policy/actions.md +196 -0
- package/docs/policy/index.md +84 -0
- package/docs/policy/presets.md +191 -0
- package/docs/policy/robots.md +128 -0
- package/docs/policy/rules.md +134 -0
- package/docs/reference/api.md +126 -0
- package/docs/reference/configuration.md +175 -0
- package/docs/start/choosing-a-policy.md +127 -0
- package/docs/start/first-integration.md +127 -0
- package/docs/start/installation.md +63 -0
- package/docs/start/upgrading.md +72 -0
- package/docs/testing/cli.md +119 -0
- package/docs/testing/corpus.md +129 -0
- package/docs/testing/index.md +61 -0
- package/docs/testing/replay.md +102 -0
- package/docs/testing/try-it.md +189 -0
- package/package.json +145 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/adapters/index.ts","../../src/internal/http.ts","../../src/internal/ip.ts","../../src/facts.ts","../../src/adapters/shared.ts","../../src/adapters/node.ts","../../src/adapters/fetch.ts","../../src/adapters/fastify.ts","../../src/adapters/koa.ts"],"sourcesContent":["export { botHandler } from \"./node.js\";\nexport type { NodeMiddleware, NodeMiddlewareOptions, NextFunction } from \"./node.js\";\nexport { createFetchAdapter, withBotHandler } from \"./fetch.js\";\nexport type { FetchAdapterOptions, FetchDecision } from \"./fetch.js\";\nexport { fastifyBotHandler } from \"./fastify.js\";\nexport type { FastifyAdapterOptions, FastifyLikeRequest, FastifyLikeReply } from \"./fastify.js\";\nexport { koaBotHandler } from \"./koa.js\";\nexport type { KoaLikeContext, KoaAdapterOptions } from \"./koa.js\";\n","/** Cookie parsing and serialisation, plus small header helpers. No dependencies. */\n\n/** Longest Cookie header we will parse. Beyond this it is a payload, not a session. */\nconst MAX_COOKIE_HEADER = 8192;\nconst MAX_COOKIES = 64;\n\n/**\n * Parses a `Cookie` header into a null-prototype bag.\n *\n * Null-prototype matters: a request carrying `__proto__=x` would otherwise hit the\n * prototype setter on a plain object and vanish, and any later `cookies.constructor`\n * lookup would find `Object`'s rather than `undefined`. Neither is a vulnerability by\n * itself; both are the kind of surprise that becomes one.\n */\nexport function parseCookies(header: string | undefined): Record<string, string> {\n const cookies: Record<string, string> = Object.create(null) as Record<string, string>;\n if (header === undefined || header.length === 0) return cookies;\n const source = header.length > MAX_COOKIE_HEADER ? header.slice(0, MAX_COOKIE_HEADER) : header;\n\n let count = 0;\n for (const pair of source.split(\";\")) {\n if (count >= MAX_COOKIES) break;\n const equals = pair.indexOf(\"=\");\n if (equals <= 0) continue;\n const name = pair.slice(0, equals).trim();\n if (name.length === 0) continue;\n const rawValue = pair.slice(equals + 1).trim();\n const value = rawValue.startsWith('\"') && rawValue.endsWith('\"') ? rawValue.slice(1, -1) : rawValue;\n try {\n cookies[name] = decodeURIComponent(value);\n } catch {\n // A malformed percent-escape is not worth rejecting the whole header over.\n cookies[name] = value;\n }\n count++;\n }\n return cookies;\n}\n\nexport interface CookieOptions {\n maxAgeMs?: number;\n path?: string;\n domain?: string;\n secure?: boolean;\n httpOnly?: boolean;\n sameSite?: \"Lax\" | \"Strict\" | \"None\";\n}\n\n/** Builds a `Set-Cookie` value. Rejects names and values that would let a caller inject attributes. */\nexport function serializeCookie(name: string, value: string, options: CookieOptions = {}): string {\n if (!/^[A-Za-z0-9!#$%&'*+.^_`|~-]+$/.test(name)) throw new TypeError(`Invalid cookie name: ${name}`);\n const parts = [`${name}=${encodeURIComponent(value)}`];\n if (options.maxAgeMs !== undefined) parts.push(`Max-Age=${Math.floor(options.maxAgeMs / 1000)}`);\n parts.push(`Path=${options.path ?? \"/\"}`);\n if (options.domain !== undefined) {\n if (!/^[A-Za-z0-9.-]+$/.test(options.domain)) throw new TypeError(`Invalid cookie domain: ${options.domain}`);\n parts.push(`Domain=${options.domain}`);\n }\n if (options.secure !== false) parts.push(\"Secure\");\n if (options.httpOnly !== false) parts.push(\"HttpOnly\");\n // `SameSite=None` without `Secure` is rejected by browsers, so it is never a valid\n // combination to emit — fail loudly here rather than shipping a cookie nothing keeps.\n const sameSite = options.sameSite ?? \"Lax\";\n if (sameSite === \"None\" && options.secure === false) {\n throw new TypeError(\"SameSite=None requires Secure\");\n }\n parts.push(`SameSite=${sameSite}`);\n return parts.join(\"; \");\n}\n\n/** Joins multi-value headers the way the rest of the library expects to see them. */\nexport function joinHeaderValue(value: string | string[] | undefined): string | undefined {\n if (value === undefined) return undefined;\n return Array.isArray(value) ? value.join(\", \") : value;\n}\n","/**\n * IP parsing, normalisation and CIDR matching, with no dependencies.\n *\n * Everything works on raw byte arrays rather than strings, because string\n * comparison of IPs is a classic source of bypasses: `::ffff:127.0.0.1`,\n * `0177.0.0.1` and `127.0.0.001` are all the same host to the network stack but\n * three different strings to a naive allowlist.\n */\n\n/** An IP as bytes: 4 for IPv4, 16 for IPv6. IPv4-mapped IPv6 is folded to 4. */\nexport type IpBytes = Uint8Array;\n\n/**\n * Parses an IPv4 or IPv6 literal into bytes, or `null` if it is not a valid\n * address. Deliberately strict: no octal, no hex, no shorthand octets, no zone\n * ids. Anything ambiguous is rejected rather than guessed at.\n */\nexport function parseIp(value: string): IpBytes | null {\n const input = value.trim();\n if (input.length === 0 || input.length > 45) return null;\n // Bracketed form from a Host/Forwarded header: [2001:db8::1]\n const bare = input.startsWith(\"[\") && input.endsWith(\"]\") ? input.slice(1, -1) : input;\n if (bare.includes(\":\")) return parseIpv6(bare);\n return parseIpv4(bare);\n}\n\nfunction parseIpv4(value: string): IpBytes | null {\n const parts = value.split(\".\");\n if (parts.length !== 4) return null;\n const bytes = new Uint8Array(4);\n for (let i = 0; i < 4; i++) {\n const part = parts[i]!;\n // Reject empty, over-long, non-digit and leading-zero forms outright.\n if (part.length === 0 || part.length > 3) return null;\n if (!/^\\d+$/.test(part)) return null;\n if (part.length > 1 && part[0] === \"0\") return null;\n const n = Number(part);\n if (n > 255) return null;\n bytes[i] = n;\n }\n return bytes;\n}\n\nfunction parseIpv6(value: string): IpBytes | null {\n // Strip a zone id (`%eth0`) — it is a local routing detail, not part of identity.\n const withoutZone = value.split(\"%\")[0]!;\n const doubleColon = withoutZone.indexOf(\"::\");\n if (doubleColon !== withoutZone.lastIndexOf(\"::\")) return null;\n\n const [headText, tailText] =\n doubleColon === -1\n ? [withoutZone, \"\"]\n : [withoutZone.slice(0, doubleColon), withoutZone.slice(doubleColon + 2)];\n\n const head = headText.length > 0 ? headText.split(\":\") : [];\n const tail = tailText.length > 0 ? tailText.split(\":\") : [];\n\n // A trailing dotted-quad (`::ffff:192.0.2.1`) occupies the last two groups.\n let embedded: IpBytes | null = null;\n const groups = [...head, ...tail];\n const last = groups[groups.length - 1];\n if (last !== undefined && last.includes(\".\")) {\n embedded = parseIpv4(last);\n if (!embedded) return null;\n if (tail.length > 0) tail.pop();\n else head.pop();\n }\n\n const groupCount = head.length + tail.length + (embedded ? 2 : 0);\n if (doubleColon === -1 ? groupCount !== 8 : groupCount > 7) return null;\n\n const bytes = new Uint8Array(16);\n let offset = 0;\n for (const group of head) {\n if (!writeGroup(bytes, offset, group)) return null;\n offset += 2;\n }\n // The gap the `::` stands for, left as the zeroes the array already holds.\n offset = 16 - tail.length * 2 - (embedded ? 4 : 0);\n for (const group of tail) {\n if (!writeGroup(bytes, offset, group)) return null;\n offset += 2;\n }\n if (embedded) bytes.set(embedded, 12);\n\n // Fold IPv4-mapped (::ffff:0:0/96) down to a plain v4 address so that one host\n // has exactly one representation regardless of which stack accepted it.\n if (isIpv4Mapped(bytes)) return bytes.slice(12, 16);\n return bytes;\n}\n\nfunction writeGroup(bytes: Uint8Array, offset: number, group: string): boolean {\n if (group.length === 0 || group.length > 4 || !/^[0-9a-fA-F]+$/.test(group)) return false;\n const n = Number.parseInt(group, 16);\n bytes[offset] = n >> 8;\n bytes[offset + 1] = n & 0xff;\n return true;\n}\n\nfunction isIpv4Mapped(bytes: Uint8Array): boolean {\n for (let i = 0; i < 10; i++) if (bytes[i] !== 0) return false;\n return bytes[10] === 0xff && bytes[11] === 0xff;\n}\n\n/** Canonical string form, so the same host always produces the same actor key. */\nexport function formatIp(bytes: IpBytes): string {\n if (bytes.length === 4) return `${bytes[0]}.${bytes[1]}.${bytes[2]}.${bytes[3]}`;\n const groups: string[] = [];\n for (let i = 0; i < 16; i += 2) groups.push(((bytes[i]! << 8) | bytes[i + 1]!).toString(16));\n // RFC 5952: compress the longest run of zero groups, leftmost on a tie.\n let bestStart = -1;\n let bestLen = 0;\n for (let i = 0; i < 8; i++) {\n if (groups[i] !== \"0\") continue;\n let j = i;\n while (j < 8 && groups[j] === \"0\") j++;\n if (j - i > bestLen) {\n bestLen = j - i;\n bestStart = i;\n }\n i = j;\n }\n if (bestLen < 2) return groups.join(\":\");\n return `${groups.slice(0, bestStart).join(\":\")}::${groups.slice(bestStart + bestLen).join(\":\")}`;\n}\n\n/** Normalises any accepted spelling of an address to its canonical form. Returns `null` if unparseable. */\nexport function normalizeIp(value: string): string | null {\n const bytes = parseIp(value);\n return bytes ? formatIp(bytes) : null;\n}\n\nexport interface Cidr {\n readonly bytes: IpBytes;\n readonly prefix: number;\n readonly source: string;\n}\n\n/** Parses `\"10.0.0.0/8\"`, `\"2001:db8::/32\"`, or a bare address (treated as a /32 or /128). */\nexport function parseCidr(value: string): Cidr | null {\n const slash = value.lastIndexOf(\"/\");\n const addressText = slash === -1 ? value : value.slice(0, slash);\n const bytes = parseIp(addressText);\n if (!bytes) return null;\n const maxPrefix = bytes.length * 8;\n if (slash === -1) return { bytes, prefix: maxPrefix, source: value };\n const prefixText = value.slice(slash + 1);\n if (!/^\\d{1,3}$/.test(prefixText)) return null;\n const prefix = Number(prefixText);\n if (prefix > maxPrefix) return null;\n return { bytes, prefix, source: value };\n}\n\n/** True when `ip` falls inside `cidr`. Address families never match across each other. */\nexport function cidrContains(cidr: Cidr, ip: IpBytes): boolean {\n if (cidr.bytes.length !== ip.length) return false;\n const fullBytes = cidr.prefix >> 3;\n for (let i = 0; i < fullBytes; i++) if (cidr.bytes[i] !== ip[i]) return false;\n const remainder = cidr.prefix & 7;\n if (remainder === 0) return true;\n const mask = 0xff << (8 - remainder);\n return (cidr.bytes[fullBytes]! & mask) === (ip[fullBytes]! & mask);\n}\n\n/**\n * One range plus the position it was added at, so that when several ranges match the\n * same address the answer does not depend on how the index happens to be laid out.\n */\ninterface IndexedCidr {\n readonly cidr: Cidr;\n readonly order: number;\n}\n\n/**\n * Ranges for one address family, indexed on the first two bytes.\n *\n * A linear scan is fine for the handful of CIDRs in an allowlist and catastrophic for\n * the list this library actually asks operators to supply: AWS publishes around seven\n * thousand IPv4 prefixes, GCP and Azure comparable numbers, and `datacenterRanges` is\n * documented as the place to put them. Scanning that on every request measured at\n * ~87µs — several times the cost of the entire rest of an assessment — so the size of\n * a range list silently became the dominant term in request latency.\n *\n * Bucketing by the leading bytes fixes that without changing any answer. A prefix of\n * /16 or longer pins both leading bytes, so it is reachable from exactly one bucket;\n * a /8 to /15 pins only the first; anything shorter than /8 spans buckets and stays in\n * a list checked on every lookup. Real range lists are overwhelmingly /16 and longer,\n * which is why this collapses to a handful of comparisons.\n */\nclass FamilyIndex {\n /** Prefix < 8. Spans first bytes, so it is always scanned. Realistically empty. */\n private readonly wide: IndexedCidr[] = [];\n private readonly buckets = new Map<number, { mid: IndexedCidr[]; deep: Map<number, IndexedCidr[]> }>();\n\n add(entry: IndexedCidr): void {\n const { cidr } = entry;\n if (cidr.prefix < 8) {\n this.wide.push(entry);\n return;\n }\n const first = cidr.bytes[0]!;\n let bucket = this.buckets.get(first);\n if (bucket === undefined) {\n bucket = { mid: [], deep: new Map() };\n this.buckets.set(first, bucket);\n }\n if (cidr.prefix < 16) {\n bucket.mid.push(entry);\n return;\n }\n const second = cidr.bytes[1]!;\n let deep = bucket.deep.get(second);\n if (deep === undefined) {\n deep = [];\n bucket.deep.set(second, deep);\n }\n deep.push(entry);\n }\n\n /** The earliest-added range containing `ip`, or `undefined`. */\n find(ip: IpBytes): IndexedCidr | undefined {\n let best = scan(this.wide, ip, undefined);\n const bucket = this.buckets.get(ip[0]!);\n if (bucket !== undefined) {\n best = scan(bucket.mid, ip, best);\n const deep = bucket.deep.get(ip[1]!);\n if (deep !== undefined) best = scan(deep, ip, best);\n }\n return best;\n }\n}\n\nfunction scan(entries: readonly IndexedCidr[], ip: IpBytes, best: IndexedCidr | undefined): IndexedCidr | undefined {\n let winner = best;\n for (let i = 0; i < entries.length; i++) {\n const entry = entries[i]!;\n if (winner !== undefined && entry.order > winner.order) continue;\n if (cidrContains(entry.cidr, ip)) winner = entry;\n }\n return winner;\n}\n\n/**\n * A compiled set of CIDR ranges. Building it once and reusing it keeps the hot\n * path free of string parsing — matching is a handful of byte comparisons, and stays\n * that way as the list grows into the thousands.\n */\nexport class IpRangeSet {\n private readonly v4 = new FamilyIndex();\n private readonly v6 = new FamilyIndex();\n private count = 0;\n /**\n * Every range that parsed, in the order it was added.\n *\n * Kept as plain text beside the index rather than reconstructed from it. The index\n * is shaped for lookups — bucketed by leading byte, prefix-split — and walking it\n * back into a list would be both slower and, for anything that wants to *show* the\n * set, wrong: what an operator recognises is the string they wrote, not a normalised\n * form of it. These sets are configuration-sized, so the array costs nothing.\n */\n private readonly sources: string[] = [];\n /** Ranges that failed to parse, surfaced so a typo in config is loud rather than silent. */\n readonly invalid: string[] = [];\n\n constructor(ranges: Iterable<string> = []) {\n for (const range of ranges) this.add(range);\n }\n\n add(range: string): void {\n const cidr = parseCidr(range);\n if (!cidr) {\n this.invalid.push(range);\n return;\n }\n this.sources.push(cidr.source);\n (cidr.bytes.length === 4 ? this.v4 : this.v6).add({ cidr, order: this.count++ });\n }\n\n get size(): number {\n return this.count;\n }\n\n /** The ranges in this set, as written, oldest first. What a reader can act on. */\n entries(): readonly string[] {\n return this.sources;\n }\n\n /**\n * Returns the matching range's original text, or `undefined`. Useful for explaining\n * a decision. When several ranges match, the one added first wins, so the\n * explanation does not depend on the index's internal layout.\n */\n match(ip: string | IpBytes): string | undefined {\n const bytes = typeof ip === \"string\" ? parseIp(ip) : ip;\n if (!bytes) return undefined;\n return (bytes.length === 4 ? this.v4 : this.v6).find(bytes)?.cidr.source;\n }\n\n contains(ip: string | IpBytes): boolean {\n const bytes = typeof ip === \"string\" ? parseIp(ip) : ip;\n if (!bytes) return false;\n return (bytes.length === 4 ? this.v4 : this.v6).find(bytes) !== undefined;\n }\n}\n\n/** IANA special-purpose ranges: loopback, private, link-local, CGNAT, documentation. */\nexport const SPECIAL_USE_RANGES = [\n \"0.0.0.0/8\",\n \"10.0.0.0/8\",\n \"100.64.0.0/10\",\n \"127.0.0.0/8\",\n \"169.254.0.0/16\",\n \"172.16.0.0/12\",\n \"192.0.0.0/24\",\n \"192.0.2.0/24\",\n \"192.168.0.0/16\",\n \"198.18.0.0/15\",\n \"198.51.100.0/24\",\n \"203.0.113.0/24\",\n \"224.0.0.0/4\",\n \"240.0.0.0/4\",\n \"::1/128\",\n \"fc00::/7\",\n \"fe80::/10\",\n \"2001:db8::/32\",\n] as const;\n\nconst SPECIAL_USE = new IpRangeSet(SPECIAL_USE_RANGES);\n\n/** True for loopback/private/link-local/documentation addresses — never public clients. */\nexport function isSpecialUse(ip: string): boolean {\n return SPECIAL_USE.contains(ip);\n}\n\n/**\n * Masks an address to a coarse network for rate accounting: /24 for IPv4, /64 for\n * IPv6. IPv6 clients routinely get a whole /64 to themselves and rotate the host\n * bits freely, so per-address counting is trivially defeated there.\n */\nexport function networkKey(ip: string): string {\n const bytes = parseIp(ip);\n if (!bytes) return ip;\n // Build the masked address at full width. Slicing to three bytes and formatting\n // that would produce a 3-byte array, which `formatIp` reads as IPv6.\n if (bytes.length === 4) {\n const masked = new Uint8Array([bytes[0]!, bytes[1]!, bytes[2]!, 0]);\n return `${formatIp(masked)}/24`;\n }\n const masked = new Uint8Array(16);\n masked.set(bytes.subarray(0, 8), 0);\n return `${formatIp(masked)}/64`;\n}\n","import { joinHeaderValue, parseCookies } from \"./internal/http.js\";\nimport { normalizeIp } from \"./internal/ip.js\";\nimport type { RequestFacts } from \"./types.js\";\n\n/** Longest URL we will parse. Anything beyond this is a payload, not a path. */\nconst MAX_URL_LENGTH = 8192;\n/** Cap on query parameters kept. */\nconst MAX_QUERY_PARAMS = 64;\n\nexport interface FactsInput {\n method?: string | undefined;\n /** Request target, path plus optional query — what `req.url` gives you. */\n url?: string | undefined;\n /** Raw header map. Values may be arrays; names may be any case. */\n headers: Record<string, string | string[] | undefined>;\n /**\n * Header names in wire order. Node exposes them via `req.rawHeaders` (alternating\n * name/value) — pass that array directly, or a name-only list.\n */\n rawHeaders?: readonly string[] | undefined;\n /** Socket address. Pass the *socket's* address; forwarding is resolved separately. */\n ip: string;\n timestamp?: number | undefined;\n protocol?: \"http\" | \"https\" | undefined;\n /**\n * The HTTP version of the connection **this process accepted** — Node's\n * `request.httpVersion`, not the version the client negotiated with your edge.\n *\n * The distinction has teeth. HTTP/2 forbids connection-specific headers, and\n * `header-integrity` treats one as a deterministic protocol violation. If you set\n * this from a forwarded header while the request itself arrived over HTTP/1.1 from\n * a proxy — which adds `Connection: keep-alive` — you will manufacture that\n * violation for every real browser behind that proxy, and it is a `certain` verdict,\n * so it can block. Report the connection you actually have, or leave it unset.\n */\n httpVersion?: string | undefined;\n tlsFingerprint?: string | undefined;\n /** See {@link RequestFacts.partialHeaders}. Set it when the source cannot supply every header. */\n partialHeaders?: boolean | undefined;\n extra?: Record<string, unknown> | undefined;\n}\n\n/**\n * Reduces a request to the facts detectors are allowed to see.\n *\n * Normalisation happens exactly once, here, and every detector reads the result. That\n * is partly performance — lowercasing a header map per detector would be absurd — but\n * mostly correctness: if one detector reads `req.headers['User-Agent']` and another\n * reads `req.headers['user-agent']`, they will eventually disagree about the same\n * request, and the bug will be invisible.\n *\n * Every field is bounded. Each one is attacker-controlled, and this runs on every\n * request to your site.\n */\nexport function createFacts(input: FactsInput): RequestFacts {\n const rawUrl = input.url ?? \"/\";\n const url = rawUrl.length > MAX_URL_LENGTH ? rawUrl.slice(0, MAX_URL_LENGTH) : rawUrl;\n const queryStart = url.indexOf(\"?\");\n const rawPath = queryStart === -1 ? url : url.slice(0, queryStart);\n\n const headers: Record<string, string | undefined> = Object.create(null) as Record<string, string | undefined>;\n for (const [name, value] of Object.entries(input.headers)) {\n const joined = joinHeaderValue(value);\n if (joined !== undefined) headers[name.toLowerCase()] = joined;\n }\n\n const facts: RequestFacts = {\n method: (input.method ?? \"GET\").toUpperCase(),\n path: normalizePath(rawPath),\n query: parseQuery(queryStart === -1 ? \"\" : url.slice(queryStart + 1)),\n headers,\n headerOrder: extractOrder(input.rawHeaders, headers),\n ip: normalizeIp(input.ip) ?? input.ip,\n timestamp: input.timestamp ?? Date.now(),\n };\n\n // Only parse cookies that exist. Most bot traffic carries none, and building an\n // empty bag for every one of those requests is pure garbage.\n const cookieHeader = headers[\"cookie\"];\n if (cookieHeader !== undefined) facts.cookies = parseCookies(cookieHeader);\n\n if (input.protocol !== undefined) facts.protocol = input.protocol;\n if (input.httpVersion !== undefined) facts.httpVersion = input.httpVersion;\n if (input.tlsFingerprint !== undefined) facts.tlsFingerprint = input.tlsFingerprint;\n if (input.partialHeaders === true) facts.partialHeaders = true;\n if (input.extra !== undefined) facts.extra = input.extra;\n\n return facts;\n}\n\n/**\n * Decodes and normalises a path.\n *\n * Rule matching is done on this value, so `/admin`, `/%61dmin` and `/./admin` must\n * not be three different paths as far as a policy is concerned — otherwise a rule\n * scoped to a path is trivially side-stepped by spelling it differently. Decoding is\n * single-pass: repeatedly decoding until it stops changing is how `%2525` becomes `%`\n * and how path-traversal filters get bypassed.\n */\nfunction normalizePath(rawPath: string): string {\n let path = rawPath;\n try {\n path = decodeURIComponent(rawPath);\n } catch {\n // Malformed percent-encoding. Keep the raw form: it is still a fact about the\n // request, and guessing at an intended decoding would be inventing one.\n }\n path = path.replace(/\\\\/g, \"/\").replace(/\\/{2,}/g, \"/\");\n if (!path.startsWith(\"/\")) path = `/${path}`;\n\n // Resolve `.` and `..` segments so a rule on a path prefix cannot be walked around.\n if (path.includes(\"./\")) {\n const resolved: string[] = [];\n for (const segment of path.split(\"/\")) {\n if (segment === \"\" || segment === \".\") continue;\n if (segment === \"..\") resolved.pop();\n else resolved.push(segment);\n }\n path = `/${resolved.join(\"/\")}`;\n }\n return path.length > 1 && path.endsWith(\"/\") ? path.slice(0, -1) : path;\n}\n\nfunction parseQuery(search: string): Record<string, string> {\n // Null-prototype: a literal `?__proto__=x` becomes an ordinary own key instead of\n // hitting the prototype setter and vanishing, so a detector can actually see it.\n const query: Record<string, string> = Object.create(null) as Record<string, string>;\n if (search.length === 0) return query;\n let count = 0;\n for (const [key, value] of new URLSearchParams(search)) {\n if (count++ >= MAX_QUERY_PARAMS) break;\n query[key] = value.length > 1024 ? value.slice(0, 1024) : value;\n }\n return query;\n}\n\n/**\n * Extracts wire-order header names.\n *\n * Both documented input shapes have to be told apart: Node's `rawHeaders`, which\n * alternates name and value, and a plain list of names.\n *\n * Asking whether the even-indexed entries *look like* header names cannot do it, and\n * used to get the common case backwards. Every entry of a name-only list looks like a\n * header name, so any such list of even length was read as Node-style and every second\n * name was discarded — silently halving the header order, which is a fingerprint, for\n * a shape the documentation invites callers to pass.\n *\n * The header map decides it instead. In a name-only list every entry is a header that\n * was actually received; in an alternating list the odd entries are values, which are\n * almost never also header names. That distinguishes the two even for a single-header\n * request, where any shape-based guess is ambiguous.\n */\nconst MAX_ORDERED_HEADERS = 64;\n\nfunction extractOrder(rawHeaders: readonly string[] | undefined, headers: Record<string, string | undefined>): readonly string[] {\n if (!rawHeaders || rawHeaders.length === 0) return EMPTY_ORDER;\n\n let isNodeStyle = rawHeaders.length % 2 === 0;\n if (isNodeStyle) {\n let everyEntryIsAHeader = true;\n for (let i = 0; i < rawHeaders.length; i++) {\n const entry = rawHeaders[i]!;\n const known = headers[entry.toLowerCase()] !== undefined;\n // An even entry that is neither a received header nor even a valid token cannot\n // be a name in either shape; fall back to reading the list as names rather than\n // dropping half of something unrecognised.\n if (i % 2 === 0 && !known && !isHeaderName(entry)) {\n isNodeStyle = false;\n break;\n }\n if (!known) everyEntryIsAHeader = false;\n }\n if (everyEntryIsAHeader) isNodeStyle = false;\n }\n\n // One pass, one array. The previous version walked the input three times —\n // `every`, then `filter`, then `slice().map()` — allocating at each step.\n const step = isNodeStyle ? 2 : 1;\n const order: string[] = [];\n for (let i = 0; i < rawHeaders.length && order.length < MAX_ORDERED_HEADERS; i += step) {\n order.push(rawHeaders[i]!.toLowerCase());\n }\n return order;\n}\n\nconst EMPTY_ORDER: readonly string[] = Object.freeze([]);\n\n/** RFC 9110 token characters. A scan rather than a regex: this runs once per header. */\nfunction isHeaderName(value: string): boolean {\n if (value.length === 0) return false;\n for (let i = 0; i < value.length; i++) {\n const code = value.charCodeAt(i);\n const ok =\n (code >= 0x30 && code <= 0x39) || // 0-9\n (code >= 0x41 && code <= 0x5a) || // A-Z\n (code >= 0x61 && code <= 0x7a) || // a-z\n code === 0x21 || (code >= 0x23 && code <= 0x27) || code === 0x2a || code === 0x2b ||\n code === 0x2d || code === 0x2e || code === 0x5e || code === 0x5f || code === 0x60 ||\n code === 0x7c || code === 0x7e;\n if (!ok) return false;\n }\n return true;\n}\n","import type { IncomingMessage } from \"node:http\";\nimport type { ActionOutcome } from \"../actions/types.js\";\n\n/** Largest challenge-solution body we will read. A solution is a few hundred bytes. */\nexport const MAX_VERIFY_BODY = 4096;\n\n/**\n * Waits, with the timer unreferenced.\n *\n * The `unref` is what stops a pending `delay` action from holding a process open at\n * shutdown — which in a serverless runtime is the difference between a request that\n * finishes and an invocation that is billed until it is killed.\n */\nexport function pause(ms: number): Promise<void> {\n return new Promise((resolve) => {\n const timer = setTimeout(resolve, ms);\n (timer as { unref?: () => void }).unref?.();\n });\n}\n\n/** True when the outcome means the request should reach the application. */\nexport function isContinue(outcome: ActionOutcome): outcome is Extract<ActionOutcome, { kind: \"continue\" }> {\n return outcome.kind === \"continue\";\n}\n\n/**\n * Parses a JSON body defensively.\n *\n * Returns `undefined` rather than throwing on anything malformed. This parses input\n * from a client that is, by construction, already under suspicion.\n */\nexport function parseJson(text: string): unknown {\n if (text.length === 0 || text.length > MAX_VERIFY_BODY) return undefined;\n try {\n return JSON.parse(text) as unknown;\n } catch {\n return undefined;\n }\n}\n\n/**\n * Reads at most {@link MAX_VERIFY_BODY} bytes from a Node request stream.\n *\n * The cap is enforced by destroying the socket rather than by buffering and then\n * discarding: a client that keeps sending after the limit is spending its own\n * bandwidth against a buffer that stopped growing. Errors resolve to an empty string,\n * because a malformed submission is a rejected challenge, not an exception.\n */\nexport function readBoundedBody(request: IncomingMessage): Promise<string> {\n return new Promise((resolve) => {\n let data = \"\";\n let bytes = 0;\n request.on(\"data\", (chunk: Buffer) => {\n bytes += chunk.length;\n if (bytes > MAX_VERIFY_BODY) {\n request.destroy();\n return;\n }\n data += chunk.toString(\"utf8\");\n });\n request.on(\"end\", () => resolve(data));\n request.on(\"error\", () => resolve(\"\"));\n });\n}\n\n/** The JSON body of a challenge verification response, shared by every adapter. */\nexport function verificationBody(outcome: { ok: true } | { ok: false; reason: string }): string {\n return JSON.stringify(outcome.ok ? { ok: true } : { ok: false, error: outcome.reason });\n}\n","import { createFacts } from \"../facts.js\";\nimport { pause, parseJson, readBoundedBody, verificationBody } from \"./shared.js\";\nimport type { IncomingMessage, ServerResponse } from \"node:http\";\nimport type { BotHandler } from \"../core.js\";\nimport type { RequestFacts } from \"../types.js\";\n\nexport type NextFunction = (error?: unknown) => void;\nexport type NodeMiddleware = (request: IncomingMessage, response: ServerResponse, next: NextFunction) => void;\n\nexport interface NodeMiddlewareOptions {\n /**\n * Extracts a TLS fingerprint your edge computed, e.g. `\"x-ja3-hash\"` or\n * Cloudflare's `\"cf-ja3-hash\"`. Only set it if the header cannot be forged by a\n * client — that is, your edge always overwrites it.\n */\n tlsFingerprintHeader?: string;\n /** Also handle the challenge verification endpoint. Default true. */\n mountChallengeEndpoint?: boolean;\n /** Extra facts, e.g. a session id for a better actor key. */\n enrich?: (request: IncomingMessage, facts: RequestFacts) => RequestFacts;\n}\n\n/**\n * Connect/Express middleware. Also works with a bare `node:http` server.\n *\n * Mount it early — ahead of your routes, behind anything that terminates TLS and\n * sets forwarded headers — and after your body parser only if you need one, since\n * this reads no body except on its own verification endpoint.\n *\n * `next()` is always reached unless a response was actually produced, and any\n * unexpected error inside the engine results in the request being served rather than\n * refused. A bot filter that fails closed is an outage with extra steps.\n */\nexport function botHandler(handler: BotHandler, options: NodeMiddlewareOptions = {}): NodeMiddleware {\n const mountChallenge = options.mountChallengeEndpoint ?? true;\n\n return function botHandlerMiddleware(request, response, next): void {\n let handedOff = false;\n void (async (): Promise<void> => {\n try {\n const headers: Record<string, string | undefined> = {};\n for (const [name, value] of Object.entries(request.headers)) {\n headers[name] = Array.isArray(value) ? value.join(\", \") : value;\n }\n\n const ip = handler.resolveIp(request.socket.remoteAddress, headers);\n const fingerprint = options.tlsFingerprintHeader !== undefined ? headers[options.tlsFingerprintHeader.toLowerCase()] : undefined;\n\n let facts = createFacts({\n method: request.method,\n url: request.url,\n headers: request.headers,\n rawHeaders: request.rawHeaders,\n ip,\n httpVersion: request.httpVersion,\n // `encrypted` is present only on a TLS socket. Behind a terminating proxy\n // the forwarded protocol header is the only truth available.\n protocol: (request.socket as { encrypted?: boolean }).encrypted === true || headers[\"x-forwarded-proto\"] === \"https\" ? \"https\" : \"http\",\n ...(fingerprint !== undefined ? { tlsFingerprint: fingerprint } : {}),\n });\n if (options.enrich) facts = options.enrich(request, facts);\n\n if (mountChallenge && handler.isChallengeEndpoint(facts)) {\n await serveVerification(handler, request, response, facts);\n return;\n }\n\n const { outcome } = await handler.handle(facts);\n\n if (outcome.kind === \"drop\") {\n request.socket.destroy();\n return;\n }\n\n if (outcome.kind === \"respond\") {\n response.statusCode = outcome.status;\n for (const [name, value] of Object.entries(outcome.headers)) response.setHeader(name, value);\n response.end(outcome.body);\n return;\n }\n\n for (const [name, value] of Object.entries(outcome.requestHeaders ?? {})) {\n request.headers[name] = value;\n }\n if (outcome.responseHeaders) {\n for (const [name, value] of Object.entries(outcome.responseHeaders)) response.setHeader(name, value);\n }\n if (outcome.delayMs !== undefined) await pause(outcome.delayMs);\n handedOff = true;\n next();\n } catch (error) {\n // Fail open, loudly: reported to the operator, invisible to the visitor.\n // `next(error)` — which this used to call — routes into Express's error\n // handler and answers 500, which is failing *closed* and contradicts the\n // guarantee stated above.\n //\n // `handedOff` guards the case where the throw came from downstream rather\n // than from us: once `next()` has run, the rest of the application owns the\n // request, and calling `next` a second time would run it twice.\n failOpen(handler, error, \"adapter:node\");\n if (!handedOff && !response.headersSent) next();\n }\n })();\n };\n}\n\n/**\n * Reports an error the engine raised and says whether the request can still be served.\n *\n * Every adapter promises the same thing in its own doc comment: an unexpected failure\n * inside detection serves the request rather than refusing it, because a bot filter\n * that fails closed is an outage with extra steps. Handing the error to the\n * framework's error path does the opposite — Express, Koa and Fastify all turn it into\n * a 500 — so a bug in this library became a broken page for a real visitor. The error\n * is reported through the operator's own `onError`, which is where it is useful, and\n * the visitor gets their page.\n */\nfunction failOpen(handler: BotHandler, error: unknown, source: string): void {\n handler.config.onError(error, { source });\n}\n\nasync function serveVerification(handler: BotHandler, request: IncomingMessage, response: ServerResponse, facts: RequestFacts): Promise<void> {\n const outcome = await handler.verifyChallenge(facts, parseJson(await readBoundedBody(request)));\n response.setHeader(\"content-type\", \"application/json; charset=utf-8\");\n response.setHeader(\"cache-control\", \"no-store\");\n if (outcome.ok) response.setHeader(\"set-cookie\", outcome.setCookie);\n response.statusCode = outcome.ok ? 200 : outcome.status;\n response.end(verificationBody(outcome));\n}\n","import { createFacts } from \"../facts.js\";\nimport { MAX_VERIFY_BODY, parseJson, pause } from \"./shared.js\";\nimport type { BotHandler } from \"../core.js\";\nimport type { HandleResult } from \"../core.js\";\nimport type { RequestFacts } from \"../types.js\";\n\nexport interface FetchAdapterOptions {\n /**\n * Headers to read the client address from, in order of preference.\n *\n * Only headers your edge *overwrites* belong here. One a client can set is one it\n * can use to choose its own identity — and with it the address you rate-limit,\n * allowlist, denylist and block on. `x-forwarded-for` is deliberately **not** in the\n * default list: it is appended to rather than replaced, so its leftmost entry is\n * whatever the client wrote. List it explicitly only if you know your edge replaces\n * the whole header.\n */\n ipHeaders?: readonly string[];\n /** Reads the address from the platform's own context object, e.g. a Workers `ConnInfo`. */\n clientIp?: (request: Request, context: unknown) => string | undefined;\n /** Header carrying an edge-computed JA3/JA4 fingerprint. */\n tlsFingerprintHeader?: string;\n mountChallengeEndpoint?: boolean;\n enrich?: (request: Request, facts: RequestFacts) => RequestFacts;\n}\n\n/**\n * Address headers trusted without being asked for.\n *\n * Both are single-valued and written by the edge that terminates the connection, so a\n * client cannot choose what they say. `x-forwarded-for` used to be here and is not any\n * more: it is a chain a proxy *appends* to, so its leftmost entry — the one this\n * adapter reads — is supplied by the client. Trusting it by default let anyone pick\n * their own address and walk straight past a denylist, a rate limit and every\n * behavioural signal keyed on the actor. The other three adapters never did: they go\n * through `resolveIp`, where reading a forwarded header at all requires\n * `proxy.trustProxy` and the configuration warns at length about exactly this. This\n * one now agrees with them.\n */\nconst DEFAULT_IP_HEADERS = [\"cf-connecting-ip\", \"x-real-ip\"] as const;\n\nexport interface FetchDecision {\n /** Serve this instead of calling your handler. `undefined` means carry on. */\n response?: Response | undefined;\n /** The request to pass on, carrying the verdict headers. */\n request: Request;\n /**\n * What the engine concluded — absent for a challenge verification request, which is\n * answered without being assessed.\n *\n * Reported as missing rather than filled in with a placeholder assessment: \"we did\n * not judge this request\" and \"we judged it and found nothing\" must not look the\n * same to whatever is reading this.\n */\n result?: HandleResult | undefined;\n}\n\n/**\n * Web-standard adapter, for Cloudflare Workers, Deno, Bun, Vercel Edge and anything\n * else built on `Request`/`Response`.\n *\n * Returns a decision rather than wrapping your handler, so the same primitive fits a\n * router, a middleware chain or a plain `fetch` export. {@link withBotHandler} wraps it\n * for the common case.\n *\n * Note that the engine uses `node:crypto` for HMAC and hashing. On Workers that means\n * enabling `nodejs_compat`; Deno and Bun provide it natively.\n */\nexport function createFetchAdapter(handler: BotHandler, options: FetchAdapterOptions = {}) {\n const ipHeaders = options.ipHeaders ?? DEFAULT_IP_HEADERS;\n const mountChallenge = options.mountChallengeEndpoint ?? true;\n let warnedAboutAddress = false;\n\n return async function evaluate(request: Request, context: unknown = undefined): Promise<FetchDecision> {\n try {\n return await decide(request, context);\n } catch (error) {\n // Fail open, loudly — the same promise the node, Koa and Fastify adapters make,\n // and the one this adapter was not keeping. An error escaping here reaches the\n // runtime's own handler, and Workers, Deno and Bun all answer 500: a detection\n // bug charged to the visitor. Returning the request unchanged, with no verdict,\n // serves the page instead; `result` is absent, which already means \"we did not\n // judge this request\" to everything downstream.\n handler.config.onError(error, { source: \"adapter:fetch\" });\n return { request };\n }\n };\n\n async function decide(request: Request, context: unknown): Promise<FetchDecision> {\n const headers: Record<string, string | undefined> = {};\n const order: string[] = [];\n for (const [name, value] of request.headers) {\n headers[name] = value;\n order.push(name);\n }\n\n const url = new URL(request.url);\n const ip = options.clientIp?.(request, context) ?? firstHeaderAddress(headers, ipHeaders) ?? \"\";\n // An empty address is not a harmless blank: it becomes the actor key, so every\n // visitor collapses into one actor and every rate limit and behavioural signal\n // becomes site-wide. Said once, because it is a deployment mistake rather than a\n // per-request event, and a warning on every request is a warning nobody reads.\n if (ip === \"\" && !warnedAboutAddress) {\n warnedAboutAddress = true;\n // Through the handler rather than straight to the callback, so a subscriber —\n // and the dashboard's notices panel — hears about it too.\n handler.warn(\n `The fetch adapter could not determine a client address: none of ${ipHeaders.join(\", \")} is present and no \\`clientIp\\` was supplied. ` +\n `Every request will be tracked under one empty actor key, so rate limits and behavioural detection apply to your whole site at once. ` +\n `Pass \\`clientIp\\` to read the address from your platform's connection info, or list the header your edge sets.`,\n );\n }\n const fingerprint = options.tlsFingerprintHeader !== undefined ? headers[options.tlsFingerprintHeader.toLowerCase()] : undefined;\n\n let facts = createFacts({\n method: request.method,\n url: `${url.pathname}${url.search}`,\n headers,\n // The Fetch API normalises and sorts headers, so wire order is genuinely\n // unavailable here. Passing the sorted order would be worse than passing none:\n // the header-order detector would compare against an ordering no client chose.\n rawHeaders: [],\n ip,\n protocol: url.protocol === \"https:\" ? \"https\" : \"http\",\n ...(fingerprint !== undefined ? { tlsFingerprint: fingerprint } : {}),\n });\n if (options.enrich) facts = options.enrich(request, facts);\n\n if (mountChallenge && handler.isChallengeEndpoint(facts)) {\n const body = await readBounded(request);\n const outcome = await handler.verifyChallenge(facts, parseJson(body));\n const responseHeaders = new Headers({ \"content-type\": \"application/json; charset=utf-8\", \"cache-control\": \"no-store\" });\n if (outcome.ok) responseHeaders.set(\"set-cookie\", outcome.setCookie);\n // Deliberately not assessed. `assess` records the request against the actor, so\n // running it here inflated the request count and arrival cadence of the one\n // client that is trying to answer a challenge — feeding the rate and cadence\n // detectors with the evidence of its own compliance. The comment here already\n // said the request was not assessed; the code called `assess` anyway.\n return {\n response: new Response(JSON.stringify(outcome.ok ? { ok: true } : { ok: false, error: outcome.reason }), {\n status: outcome.ok ? 200 : outcome.status,\n headers: responseHeaders,\n }),\n request,\n };\n }\n\n const result = await handler.handle(facts);\n const { outcome } = result;\n\n if (outcome.kind === \"drop\") {\n // A Fetch runtime cannot close a connection without answering, so the nearest\n // equivalent is an empty 444-style refusal. Say so rather than pretend.\n return { response: new Response(null, { status: 444 }), request, result };\n }\n\n if (outcome.kind === \"respond\") {\n return { response: new Response(outcome.body, { status: outcome.status, headers: outcome.headers }), request, result };\n }\n\n if (outcome.delayMs !== undefined) await pause(outcome.delayMs);\n\n // Requests are immutable, so tagging means constructing a new one.\n const tagged = outcome.requestHeaders\n ? new Request(request, { headers: mergeHeaders(request.headers, outcome.requestHeaders) })\n : request;\n\n return { request: tagged, result };\n }\n}\n\n/** Wraps a handler. The common case: one call, one line. */\nexport function withBotHandler(\n handler: BotHandler,\n next: (request: Request, context: unknown) => Response | Promise<Response>,\n options: FetchAdapterOptions = {},\n): (request: Request, context?: unknown) => Promise<Response> {\n const evaluate = createFetchAdapter(handler, options);\n return async (request, context = undefined) => {\n const decision = await evaluate(request, context);\n if (decision.response) return decision.response;\n const response = await next(decision.request, context);\n const extra = decision.result?.outcome.kind === \"continue\" ? decision.result.outcome.responseHeaders : undefined;\n if (!extra) return response;\n const merged = new Response(response.body, response);\n for (const [name, value] of Object.entries(extra)) merged.headers.set(name, value);\n return merged;\n };\n}\n\nfunction firstHeaderAddress(headers: Record<string, string | undefined>, names: readonly string[]): string | undefined {\n for (const name of names) {\n const value = headers[name];\n if (value === undefined) continue;\n // A forwarded chain is left-to-right, oldest first. The leftmost entry is the\n // one the client itself can write, so it is taken only from headers the caller\n // has declared trustworthy by listing them.\n const first = value.split(\",\")[0]?.trim();\n if (first !== undefined && first.length > 0) return first;\n }\n return undefined;\n}\n\nfunction mergeHeaders(original: Headers, additions: Record<string, string>): Headers {\n const merged = new Headers(original);\n for (const [name, value] of Object.entries(additions)) merged.set(name, value);\n return merged;\n}\n\nasync function readBounded(request: Request): Promise<string> {\n const text = await request.clone().text();\n return text.length > MAX_VERIFY_BODY ? \"\" : text;\n}\n","import { createFacts } from \"../facts.js\";\nimport { pause, parseJson, readBoundedBody, verificationBody } from \"./shared.js\";\nimport type { BotHandler } from \"../core.js\";\nimport type { RequestFacts } from \"../types.js\";\n\n/**\n * The parts of Fastify's request and reply this adapter touches, described\n * structurally so the library takes no dependency on Fastify or its types.\n */\nexport interface FastifyLikeRequest {\n method: string;\n url: string;\n headers: Record<string, string | string[] | undefined>;\n raw: import(\"node:http\").IncomingMessage;\n}\n\nexport interface FastifyLikeReply {\n code(status: number): FastifyLikeReply;\n header(name: string, value: string): FastifyLikeReply;\n send(body: unknown): unknown;\n hijack?(): void;\n}\n\nexport interface FastifyAdapterOptions {\n tlsFingerprintHeader?: string;\n /**\n * Also serve the challenge verification endpoint. Default true.\n *\n * Fastify parses bodies after `onRequest`, so this reads the raw stream itself.\n * That is why it must run here rather than as a route: by the time a route handler\n * sees the request, the body has been consumed by a parser that does not know about\n * this endpoint.\n */\n mountChallengeEndpoint?: boolean;\n enrich?: (request: FastifyLikeRequest, facts: RequestFacts) => RequestFacts;\n}\n\n/**\n * An `onRequest` hook for Fastify.\n *\n * ```ts\n * fastify.addHook(\"onRequest\", fastifyBotHandler(handler));\n * ```\n *\n * `onRequest` rather than `preHandler` on purpose: it is the earliest hook, so a\n * refused request never reaches routing, validation or your body parser — none of\n * which should be doing work for traffic that has already been decided about.\n */\nexport function fastifyBotHandler(handler: BotHandler, options: FastifyAdapterOptions = {}) {\n const mountChallenge = options.mountChallengeEndpoint ?? true;\n\n return async function onRequest(request: FastifyLikeRequest, reply: FastifyLikeReply): Promise<void> {\n try {\n await evaluate(handler, options, mountChallenge, request, reply);\n } catch (error) {\n // Returning normally lets routing continue. Letting the throw reach Fastify\n // would answer 500 instead — a detection bug charged to the visitor, which is\n // the one thing every adapter here promises not to do.\n handler.config.onError(error, { source: \"adapter:fastify\" });\n }\n };\n}\n\nasync function evaluate(\n handler: BotHandler,\n options: FastifyAdapterOptions,\n mountChallenge: boolean,\n request: FastifyLikeRequest,\n reply: FastifyLikeReply,\n): Promise<void> {\n const headers: Record<string, string | undefined> = {};\n for (const [name, value] of Object.entries(request.headers)) {\n headers[name] = Array.isArray(value) ? value.join(\", \") : value;\n }\n\n const ip = handler.resolveIp(request.raw.socket.remoteAddress, headers);\n const fingerprint = options.tlsFingerprintHeader !== undefined ? headers[options.tlsFingerprintHeader.toLowerCase()] : undefined;\n\n let facts = createFacts({\n method: request.method,\n url: request.url,\n headers: request.headers,\n rawHeaders: request.raw.rawHeaders,\n ip,\n httpVersion: request.raw.httpVersion,\n protocol: (request.raw.socket as { encrypted?: boolean }).encrypted === true || headers[\"x-forwarded-proto\"] === \"https\" ? \"https\" : \"http\",\n ...(fingerprint !== undefined ? { tlsFingerprint: fingerprint } : {}),\n });\n if (options.enrich) facts = options.enrich(request, facts);\n\n if (mountChallenge && handler.isChallengeEndpoint(facts)) {\n const verification = await handler.verifyChallenge(facts, parseJson(await readBoundedBody(request.raw)));\n reply.code(verification.ok ? 200 : verification.status);\n reply.header(\"content-type\", \"application/json; charset=utf-8\");\n reply.header(\"cache-control\", \"no-store\");\n if (verification.ok) reply.header(\"set-cookie\", verification.setCookie);\n reply.send(verificationBody(verification));\n return;\n }\n\n const { outcome } = await handler.handle(facts);\n\n if (outcome.kind === \"drop\") {\n // `hijack` tells Fastify this route will not produce a reply, so it does not\n // try to serialise one for a socket that is about to disappear.\n reply.hijack?.();\n request.raw.socket.destroy();\n return;\n }\n\n if (outcome.kind === \"respond\") {\n reply.code(outcome.status);\n for (const [name, value] of Object.entries(outcome.headers)) reply.header(name, value);\n reply.send(outcome.body);\n return;\n }\n\n for (const [name, value] of Object.entries(outcome.requestHeaders ?? {})) {\n request.headers[name] = value;\n }\n if (outcome.responseHeaders) {\n for (const [name, value] of Object.entries(outcome.responseHeaders)) reply.header(name, value);\n }\n if (outcome.delayMs !== undefined) await pause(outcome.delayMs);\n}\n","import { createFacts } from \"../facts.js\";\nimport { pause, parseJson, readBoundedBody, verificationBody } from \"./shared.js\";\nimport type { BotHandler } from \"../core.js\";\n\n/** The parts of a Koa context this adapter touches, described structurally. */\nexport interface KoaLikeContext {\n method: string;\n url: string;\n headers: Record<string, string | string[] | undefined>;\n req: import(\"node:http\").IncomingMessage;\n status: number;\n body: unknown;\n set(name: string, value: string): void;\n secure?: boolean;\n}\n\nexport interface KoaAdapterOptions {\n /** Also serve the challenge verification endpoint. Default true. */\n mountChallengeEndpoint?: boolean;\n}\n\n/**\n * Koa middleware.\n *\n * ```ts\n * app.use(koaBotHandler(handler));\n * ```\n *\n * Place it above everything else. Koa's downstream-then-upstream flow means anything\n * mounted before this still runs for a request that is about to be refused.\n */\nexport function koaBotHandler(handler: BotHandler, options: KoaAdapterOptions = {}) {\n const mountChallenge = options.mountChallengeEndpoint ?? true;\n\n return async function botHandlerMiddleware(context: KoaLikeContext, next: () => Promise<void>): Promise<void> {\n let proceed: boolean;\n try {\n proceed = await evaluate(handler, mountChallenge, context);\n } catch (error) {\n // Serve the request rather than let a detection bug become a 500. The try\n // covers the engine only: `next()` runs the rest of your application, and\n // catching *its* failures here would both swallow them and call `next` twice.\n handler.config.onError(error, { source: \"adapter:koa\" });\n proceed = true;\n }\n if (proceed) await next();\n };\n}\n\n/** Runs the engine and applies its outcome. Returns whether the request continues downstream. */\nasync function evaluate(handler: BotHandler, mountChallenge: boolean, context: KoaLikeContext): Promise<boolean> {\n const headers: Record<string, string | undefined> = {};\n for (const [name, value] of Object.entries(context.headers)) {\n headers[name] = Array.isArray(value) ? value.join(\", \") : value;\n }\n\n const facts = createFacts({\n method: context.method,\n url: context.url,\n headers: context.headers,\n rawHeaders: context.req.rawHeaders,\n ip: handler.resolveIp(context.req.socket.remoteAddress, headers),\n httpVersion: context.req.httpVersion,\n protocol:\n context.secure === true || (context.req.socket as { encrypted?: boolean }).encrypted === true || headers[\"x-forwarded-proto\"] === \"https\" ? \"https\" : \"http\",\n });\n\n if (mountChallenge && handler.isChallengeEndpoint(facts)) {\n const verification = await handler.verifyChallenge(facts, parseJson(await readBoundedBody(context.req)));\n context.status = verification.ok ? 200 : verification.status;\n context.set(\"content-type\", \"application/json; charset=utf-8\");\n context.set(\"cache-control\", \"no-store\");\n if (verification.ok) context.set(\"set-cookie\", verification.setCookie);\n context.body = verificationBody(verification);\n return false;\n }\n\n const { outcome } = await handler.handle(facts);\n\n if (outcome.kind === \"drop\") {\n context.req.socket.destroy();\n return false;\n }\n\n if (outcome.kind === \"respond\") {\n context.status = outcome.status;\n for (const [name, value] of Object.entries(outcome.headers)) context.set(name, value);\n context.body = outcome.body;\n return false;\n }\n\n for (const [name, value] of Object.entries(outcome.requestHeaders ?? {})) {\n context.req.headers[name] = value;\n }\n if (outcome.responseHeaders) {\n for (const [name, value] of Object.entries(outcome.responseHeaders)) context.set(name, value);\n }\n if (outcome.delayMs !== undefined) await pause(outcome.delayMs);\n return true;\n}\n"],"mappings":"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","names":["evaluate","outcome","evaluate"]}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { botHandler } from "./node.js";
|
|
2
|
+
export type { NodeMiddleware, NodeMiddlewareOptions, NextFunction } from "./node.js";
|
|
3
|
+
export { createFetchAdapter, withBotHandler } from "./fetch.js";
|
|
4
|
+
export type { FetchAdapterOptions, FetchDecision } from "./fetch.js";
|
|
5
|
+
export { fastifyBotHandler } from "./fastify.js";
|
|
6
|
+
export type { FastifyAdapterOptions, FastifyLikeRequest, FastifyLikeReply } from "./fastify.js";
|
|
7
|
+
export { koaBotHandler } from "./koa.js";
|
|
8
|
+
export type { KoaLikeContext, KoaAdapterOptions } from "./koa.js";
|