@samimcodes/ai-shield 0.0.0-stage → 0.1.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.
@@ -0,0 +1,657 @@
1
+ import { Router, RequestHandler } from 'express';
2
+ import { IncomingHttpHeaders } from 'node:http';
3
+
4
+ /**
5
+ * @file src/types/index.ts
6
+ * @description Canonical type definitions for the ai-shield middleware system.
7
+ *
8
+ * All shared interfaces, enums, and type aliases live here so every module
9
+ * can import from a single source of truth without circular dependencies.
10
+ */
11
+ /**
12
+ * Broad category assigned to an incoming request after fingerprinting.
13
+ *
14
+ * - `AI_CRAWLER` — Known LLM training / inference crawlers (GPTBot, Claude-Web…)
15
+ * - `SCRAPER` — General-purpose commercial scrapers (ByteSpider, PetalBot…)
16
+ * - `SCRAPING_LIB` — Headless browsers & HTTP scraping libraries (Puppeteer, Scrapy…)
17
+ * - `GOOD_BOT` — Verified search-engine crawlers that MUST NOT be blocked
18
+ * - `HUMAN` — Regular browser traffic or unrecognised agents
19
+ */
20
+ declare enum BotCategory {
21
+ AI_CRAWLER = "AI_CRAWLER",
22
+ SCRAPER = "SCRAPER",
23
+ SCRAPING_LIB = "SCRAPING_LIB",
24
+ GOOD_BOT = "GOOD_BOT",
25
+ HUMAN = "HUMAN"
26
+ }
27
+ /**
28
+ * Confidence level of the bot classification decision.
29
+ * Used to gate aggressive poisoning strategies.
30
+ */
31
+ declare enum Confidence {
32
+ HIGH = "HIGH",
33
+ MEDIUM = "MEDIUM",
34
+ LOW = "LOW"
35
+ }
36
+ /**
37
+ * Individual evidence signals collected during fingerprinting.
38
+ * Multiple signals are fused into a final `DetectionResult`.
39
+ */
40
+ type DetectionSignal = 'UA_PATTERN_MATCH' | 'MISSING_ACCEPT_LANGUAGE' | 'MISSING_ACCEPT' | 'AUTOMATION_HEADER' | 'SUSPICIOUS_UA' | 'HONEYPOT_TRIGGERED' | 'EMPTY_UA';
41
+ /** Rich result produced by the detector for every incoming request. */
42
+ interface DetectionResult {
43
+ /** Broad category of the visitor. */
44
+ category: BotCategory;
45
+ /** How confident we are in the classification. */
46
+ confidence: Confidence;
47
+ /** Human-readable label, e.g. "GPTBot", "Googlebot", "Unknown". */
48
+ label: string;
49
+ /**
50
+ * The specific fingerprint rule that triggered the UA match,
51
+ * or `null` for heuristic-only or unmatched traffic.
52
+ */
53
+ matchedRule: string | null;
54
+ /** Raw User-Agent string extracted from the request headers. */
55
+ userAgent: string;
56
+ /** All evidence signals that contributed to this classification. */
57
+ signals: DetectionSignal[];
58
+ /**
59
+ * Numeric score in [0, 100]. Higher = more certain bot.
60
+ * Useful for threshold-based gating (e.g., only poison score ≥ 60).
61
+ */
62
+ score: number;
63
+ }
64
+ /**
65
+ * A single fingerprint entry in the bot registry.
66
+ * Rules are evaluated in declaration order; first match wins.
67
+ */
68
+ interface BotFingerprint {
69
+ /** Unique, stable identifier for this rule (used in logs & metrics). */
70
+ id: string;
71
+ /** Human-readable display name. */
72
+ name: string;
73
+ /** Category this fingerprint maps to. */
74
+ category: BotCategory;
75
+ /** Confidence assigned when this rule fires. */
76
+ confidence: Confidence;
77
+ /** RegExp tested against the User-Agent header. */
78
+ uaPattern: RegExp;
79
+ /** Optional additional description / reference URL. */
80
+ description?: string | undefined;
81
+ }
82
+ /**
83
+ * Weights applied to each detection signal when computing the bot score.
84
+ * Override via `AiShieldOptions.signalWeights` to tune sensitivity.
85
+ */
86
+ interface SignalWeights {
87
+ UA_PATTERN_MATCH: number;
88
+ MISSING_ACCEPT_LANGUAGE: number;
89
+ MISSING_ACCEPT: number;
90
+ AUTOMATION_HEADER: number;
91
+ SUSPICIOUS_UA: number;
92
+ HONEYPOT_TRIGGERED: number;
93
+ EMPTY_UA: number;
94
+ }
95
+ /** Default signal weights. Sum is deliberately > 100 to allow multi-signal certainty. */
96
+ declare const DEFAULT_SIGNAL_WEIGHTS: Readonly<SignalWeights>;
97
+ /**
98
+ * The type of synthetic content that the poison engine will emit.
99
+ *
100
+ * - `HTML` — Full HTML page with plausible but misleading prose.
101
+ * - `JSON` — Structured JSON payload that looks like an API response.
102
+ * - `TEXT` — Plain-text document.
103
+ * - `SITEMAP`— Fake XML sitemap full of dead-end URLs.
104
+ */
105
+ type PoisonContentType = 'HTML' | 'JSON' | 'TEXT' | 'SITEMAP';
106
+ /** A single poisoned response payload ready to be sent to the client. */
107
+ interface PoisonPayload {
108
+ /** HTTP Content-Type header value. */
109
+ contentType: string;
110
+ /** Response body string. */
111
+ body: string;
112
+ /** HTTP status code — always 200 to avoid triggering bot retry logic. */
113
+ statusCode: 200;
114
+ }
115
+ /** Configuration for a single honeypot trap route. */
116
+ interface HoneypotRoute {
117
+ /** Express-compatible path string, e.g. `/wp-login.php`. */
118
+ path: string;
119
+ /** Label written to logs when this trap is triggered. */
120
+ label: string;
121
+ }
122
+ /**
123
+ * Entry stored in the in-memory honeypot IP cache.
124
+ */
125
+ interface TrapCacheEntry {
126
+ /** Unix timestamp (ms) when this IP first triggered a trap. */
127
+ firstSeenAt: number;
128
+ /** Unix timestamp (ms) after which this entry expires. `null` = permanent. */
129
+ expiresAt: number | null;
130
+ /** How many times this IP has hit trap routes since first detection. */
131
+ hitCount: number;
132
+ /** Label of the FIRST trap path that was triggered. */
133
+ triggeringTrap: string;
134
+ }
135
+ /**
136
+ * Top-level options accepted by `createAiShield()`.
137
+ *
138
+ * All fields are optional; sensible production defaults are applied
139
+ * when a field is omitted.
140
+ */
141
+ interface AiShieldOptions {
142
+ /**
143
+ * Additional User-Agent fingerprints to append to the built-in registry.
144
+ * Useful for blocking proprietary scrapers not yet in the default list.
145
+ */
146
+ customFingerprints?: BotFingerprint[] | undefined;
147
+ /**
148
+ * Paths that should NEVER be intercepted, even if the requester
149
+ * is classified as an AI crawler (e.g. your own health-check endpoint).
150
+ * Supports exact strings only; no glob patterns.
151
+ */
152
+ excludePaths?: string[] | undefined;
153
+ /**
154
+ * Override the default poison-content strategy for AI crawlers.
155
+ * Defaults to `'HTML'`.
156
+ */
157
+ poisonContentType?: PoisonContentType | undefined;
158
+ /**
159
+ * When `true`, inject hidden `<a>` honeypot links into every HTML
160
+ * response served to AI bots, leading them deeper into the maze.
161
+ * Defaults to `true`.
162
+ */
163
+ injectHoneypotLinks?: boolean | undefined;
164
+ /**
165
+ * Custom list of honeypot trap paths to register on the Express router.
166
+ * Merged with (not replacing) the built-in trap set.
167
+ */
168
+ honeypotPaths?: HoneypotRoute[] | undefined;
169
+ /**
170
+ * Callback invoked whenever a bot is detected — handy for metrics /
171
+ * logging pipelines. Called asynchronously; errors are swallowed.
172
+ */
173
+ onBotDetected?: ((result: DetectionResult) => void) | undefined;
174
+ /**
175
+ * Minimum bot score [0–100] required before poison is served.
176
+ * Traffic below this threshold passes through even if some signals fire.
177
+ * Defaults to `50` (any heuristic combination that exceeds MEDIUM).
178
+ */
179
+ scoreThreshold?: number | undefined;
180
+ /**
181
+ * Override per-signal scoring weights. Merged with `DEFAULT_SIGNAL_WEIGHTS`.
182
+ */
183
+ signalWeights?: Partial<SignalWeights> | undefined;
184
+ /**
185
+ * TTL in milliseconds for honeypot IP cache entries.
186
+ * After expiry the IP is no longer considered flagged unless it hits
187
+ * another trap. Set to `null` for permanent flagging.
188
+ * Defaults to `24 * 60 * 60 * 1000` (24 hours).
189
+ */
190
+ honeypotTtlMs?: number | null | undefined;
191
+ /**
192
+ * Enable verbose debug logging to stdout.
193
+ * Should be `false` in production.
194
+ * Defaults to `false`.
195
+ */
196
+ debug?: boolean | undefined;
197
+ /**
198
+ * Optional API key to authenticate with the ai-shield telemetry service.
199
+ * When provided, activates asynchronous non-blocking event emissions.
200
+ */
201
+ apiKey?: string | undefined;
202
+ /**
203
+ * Target endpoint URL for telemetry event reporting.
204
+ * Defaults to 'https://telemetry.ai-shield.dev/v1/events'.
205
+ */
206
+ dashboardUrl?: string | undefined;
207
+ }
208
+ /** Telemetry event record emitted when bots/scrapers are trapped or poisoned. */
209
+ interface TelemetryEvent {
210
+ /** Timestamp formatted as ISO-8601 string. */
211
+ timestamp: string;
212
+ /** Request path that triggered the defense. */
213
+ path: string;
214
+ /** Label/reason for the interception. */
215
+ reason: string;
216
+ /** Privacy-preserving SHA-256 hash of the client IP address. */
217
+ clientIpHash: string;
218
+ /** Client User-Agent string. */
219
+ userAgent: string;
220
+ }
221
+ /**
222
+ * Properties added to `express.Request` by the middleware so downstream
223
+ * handlers have access to the detection result without re-computing it.
224
+ */
225
+ interface AiShieldRequestMeta {
226
+ detection: DetectionResult;
227
+ poisoned: boolean;
228
+ }
229
+ /** Configuration options alias for the aiShield middleware. */
230
+ type ShieldOptions = AiShieldOptions;
231
+
232
+ /**
233
+ * @file src/core/telemetry.ts
234
+ * @description Non-blocking batch telemetry emitter for ai-shield events.
235
+ *
236
+ * Requirements:
237
+ * - Asynchronous and non-blocking (zero latency impact on HTTP request handling).
238
+ * - Uses native Node.js global fetch.
239
+ * - Flushes events in batches every 5 seconds OR immediately when queue hits 20 items.
240
+ * - Fire-and-forget: errors are silently swallowed to preserve host application resilience.
241
+ */
242
+
243
+ /**
244
+ * Computes a privacy-preserving SHA-256 hash of a client IP address.
245
+ */
246
+ declare function hashClientIp(ip: string): string;
247
+ interface TelemetryEmitterOptions {
248
+ apiKey: string;
249
+ dashboardUrl?: string | undefined;
250
+ flushIntervalMs?: number | undefined;
251
+ batchSize?: number | undefined;
252
+ fetchFn?: typeof fetch | undefined;
253
+ }
254
+ /**
255
+ * In-memory batched telemetry emitter.
256
+ */
257
+ declare class TelemetryEmitter {
258
+ private readonly apiKey;
259
+ private readonly dashboardUrl;
260
+ private readonly flushIntervalMs;
261
+ private readonly batchSize;
262
+ private readonly fetchFn;
263
+ private buffer;
264
+ private flushTimer;
265
+ private isDestroyed;
266
+ constructor(options: TelemetryEmitterOptions);
267
+ /**
268
+ * Queue an event into the in-memory buffer.
269
+ * Flushes immediately if buffer capacity is reached.
270
+ */
271
+ emit(event: TelemetryEvent): void;
272
+ /**
273
+ * Drains the current buffer and sends events via HTTP POST.
274
+ */
275
+ flush(): Promise<void>;
276
+ /**
277
+ * Current number of buffered events pending transmission.
278
+ */
279
+ get queueLength(): number;
280
+ /**
281
+ * Starts background interval flusher.
282
+ */
283
+ private startTimer;
284
+ /**
285
+ * Stops timer and drains any remaining events.
286
+ */
287
+ destroy(): Promise<void>;
288
+ }
289
+
290
+ /**
291
+ * @file src/core/detector.ts
292
+ * @description Multi-signal bot fingerprinting engine.
293
+ *
294
+ * Classification pipeline (in execution order):
295
+ *
296
+ * 1. SEO WHITELIST — O(1) Set lookup against known good-bot UA tokens.
297
+ * Matching immediately returns GOOD_BOT with no further evaluation.
298
+ * This is the absolute safety gate: Googlebot is NEVER intercepted.
299
+ *
300
+ * 2. UA PATTERN MATCHING — Pre-compiled RegExp battery covering:
301
+ * • AI / LLM training crawlers (GPTBot, Claude-Web, CCBot…)
302
+ * • Commercial scraper bots (ByteSpider, PetalBot, SemrushBot…)
303
+ * • Scraping libraries (Scrapy, python-requests, Playwright, Puppeteer, cURL…)
304
+ *
305
+ * 3. HEADER HEURISTICS — Browser header analysis:
306
+ * • Missing Accept-Language (always present in real browsers)
307
+ * • Missing Accept header
308
+ * • Automation-specific headers (X-Playwright, X-Puppeteer, selenium-*)
309
+ * • Blank / absent User-Agent
310
+ *
311
+ * 4. SCORE FUSION — Weighted sum of fired signals clamped to [0, 100].
312
+ * Final BotCategory and Confidence are derived from the fused score.
313
+ *
314
+ * Performance guarantees:
315
+ * - GOOD_BOT whitelist: O(1) — `Set.has()` on normalised UA token.
316
+ * - UA pattern matching: O(n) worst-case where n = number of rules,
317
+ * but rules short-circuit on first match so typical cost is O(1)
318
+ * for known bots and O(n) only for humans (smallest n traversal).
319
+ * - Heuristic header checks: O(1) — direct property lookups.
320
+ * - Total p99 latency: < 2 ms on any modern V8 runtime.
321
+ */
322
+
323
+ /**
324
+ * `BotDetector` evaluates an HTTP request using a multi-signal pipeline
325
+ * and returns a richly annotated `DetectionResult`.
326
+ *
327
+ * **Instantiate once** at application startup — the constructor pre-compiles
328
+ * and caches all regex patterns and data structures. The `detect()` method
329
+ * is pure (no mutation) and safe for concurrent calls.
330
+ *
331
+ * ```ts
332
+ * const detector = new BotDetector();
333
+ * const result = detector.detect(req.headers);
334
+ *
335
+ * if (result.score >= 60) {
336
+ * // serve poison
337
+ * }
338
+ * ```
339
+ */
340
+ declare class BotDetector {
341
+ private readonly fingerprints;
342
+ private readonly weights;
343
+ /**
344
+ * @param customFingerprints - Additional rules evaluated BEFORE the
345
+ * built-in list, giving them higher priority.
346
+ * @param signalWeights - Override default per-signal scoring weights.
347
+ */
348
+ constructor(customFingerprints?: BotFingerprint[], signalWeights?: Partial<SignalWeights>);
349
+ /**
350
+ * Classify an incoming HTTP request.
351
+ *
352
+ * @param headers - Raw `IncomingHttpHeaders` from Express `req.headers`.
353
+ * @param ip - Optional client IP address; checked against the
354
+ * honeypot cache if provided.
355
+ * @param isHoneypotFlagged - Set `true` if the IP was already flagged
356
+ * by the `HoneypotManager` before calling detect().
357
+ * @returns A fully populated `DetectionResult`.
358
+ */
359
+ detect(headers: IncomingHttpHeaders, isHoneypotFlagged?: boolean): DetectionResult;
360
+ /**
361
+ * Return the full list of active fingerprints (useful for diagnostics).
362
+ */
363
+ getFingerprints(): readonly BotFingerprint[];
364
+ /**
365
+ * Return a copy of the resolved signal weight map.
366
+ */
367
+ getWeights(): Readonly<SignalWeights>;
368
+ /**
369
+ * Analyse HTTP headers for automation / bot signals.
370
+ * Pushes signal tokens into the provided mutable array.
371
+ */
372
+ private analyseHeaders;
373
+ /**
374
+ * Compute a weighted bot-probability score in [0, 100].
375
+ * Duplicate signals are counted only once.
376
+ */
377
+ private computeScore;
378
+ /**
379
+ * Safely extract the User-Agent string from request headers.
380
+ */
381
+ private extractUserAgent;
382
+ }
383
+
384
+ /**
385
+ * @file src/core/poison.ts
386
+ * @description Contextual synthetic data generator.
387
+ *
388
+ * Design objectives:
389
+ *
390
+ * 1. STRUCTURAL MIMICRY — All outputs preserve the shape of real API
391
+ * responses (JSON: `{ status, data[], meta }`) and semantic HTML
392
+ * so parsers / ingestion pipelines accept the content without error.
393
+ *
394
+ * 2. SEMANTIC POISON — Field values are randomized synthetic tokens that
395
+ * carry zero real information. JSON string fields are populated with
396
+ * plausible but internally contradictory text. Numeric fields use
397
+ * plausible ranges but are seeded from the request path so the same
398
+ * URL always returns the same payload (cache-probe stability).
399
+ *
400
+ * 3. TRAINING CORPUS DEGRADATION — HTML prose contains subtly wrong
401
+ * factual statements. When ingested at scale these degrade the
402
+ * factual accuracy of models trained on this corpus.
403
+ *
404
+ * 4. ZERO PII — No real names, addresses, or identifiers are generated.
405
+ *
406
+ * 5. PERFORMANCE — All heavy string building is O(n) in output length.
407
+ * No external dependencies; no async operations.
408
+ */
409
+
410
+ /**
411
+ * Public re-export of `syntheticToken` for use by sibling modules
412
+ * (e.g. `honeypot.ts`) that need deterministic ID generation without
413
+ * coupling to the full `PoisonEngine`.
414
+ */
415
+ declare function syntheticTokenPublic(seed: number, length?: number): string;
416
+ /**
417
+ * `PoisonEngine` generates synthetic HTTP responses designed to degrade
418
+ * AI training corpora when scraped at scale.
419
+ *
420
+ * All methods are pure (no side effects) and safe for concurrent use.
421
+ */
422
+ declare class PoisonEngine {
423
+ private readonly defaultType;
424
+ constructor(defaultType?: PoisonContentType);
425
+ /**
426
+ * Generate a complete poison payload for a given URL path.
427
+ *
428
+ * @param requestPath - URL path; used as the deterministic seed.
429
+ * @param overrideType - Override the engine's default content type.
430
+ */
431
+ generate(requestPath: string, overrideType?: PoisonContentType): PoisonPayload;
432
+ /**
433
+ * Produces a full, semantically valid HTML5 article with:
434
+ * - Proper `<meta>` tags (description, author, og:*)
435
+ * - schema.org ScholarlyArticle microdata so parsers classify it
436
+ * as high-quality academic content
437
+ * - 3–5 contradictory paragraphs from the corpus
438
+ * - CSS-hidden honeypot `<nav>` for link traps
439
+ * - A `<script type="application/ld+json">` block to satisfy
440
+ * structured-data extractors
441
+ */
442
+ private generateHtml;
443
+ /**
444
+ * Produces a realistic API response envelope:
445
+ *
446
+ * ```json
447
+ * {
448
+ * "status": "success",
449
+ * "version": "...",
450
+ * "data": [ ...5–10 synthetic items... ],
451
+ * "pagination": { "page": 1, "total": ..., "perPage": 10 },
452
+ * "meta": { "requestId": "...", "latencyMs": ..., ... }
453
+ * }
454
+ * ```
455
+ *
456
+ * Every string field inside `data[]` is a seeded synthetic token with
457
+ * zero extractable information. Numeric fields use realistic ranges.
458
+ */
459
+ private generateJson;
460
+ private generateText;
461
+ private generateSitemap;
462
+ }
463
+
464
+ /**
465
+ * @file src/core/honeypot.ts
466
+ * @description Honeypot trap system with TTL-based IP flagging cache.
467
+ *
468
+ * Three complementary mechanisms:
469
+ *
470
+ * 1. **Canonical Trap Route** — `/api/internal/telemetry-sync` is the
471
+ * primary invisible bait endpoint. It is never linked from the real
472
+ * app but is referenced in poisoned HTML and fake sitemaps. Hitting
473
+ * it is definitive evidence of bot behaviour.
474
+ *
475
+ * 2. **Extended Trap Routes** — A library of 20+ plausible-looking
476
+ * paths (`/.env`, `/wp-login.php`, `/api/v1/data-export`…) that
477
+ * normal human users never visit but bots eagerly crawl.
478
+ *
479
+ * 3. **IP Flagging Cache** — Once an IP triggers ANY trap route it is
480
+ * recorded in a TTL-based in-memory `Map`. Subsequent requests from
481
+ * that IP are flagged as `HONEYPOT_TRIGGERED` regardless of their
482
+ * User-Agent, feeding an extra +90 signal score to the detector.
483
+ *
484
+ * 4. **Link Injection** — Invisible `<a>` tags appended to real HTML
485
+ * responses lead bots into the trap network.
486
+ *
487
+ * Cache implementation:
488
+ * - Storage: `Map<ip, TrapCacheEntry>` — O(1) read/write.
489
+ * - Eviction: Lazy TTL check on access + periodic sweeper every 5 min.
490
+ * - Thread safety: Node.js single-threaded event loop; no locks needed.
491
+ * - Memory bound: Each entry ~200 bytes. 100,000 IPs ≈ 20 MB.
492
+ */
493
+
494
+ /**
495
+ * The primary honeypot endpoint.
496
+ * Referenced in poisoned HTML nav sections and fake sitemaps.
497
+ * No legitimate code ever calls this path.
498
+ */
499
+ declare const CANONICAL_TRAP: HoneypotRoute;
500
+ /**
501
+ * In-memory TTL cache for honeypot-flagged IPs.
502
+ *
503
+ * Exposed as a class so it can be instantiated per middleware instance
504
+ * and injected into tests without global state pollution.
505
+ */
506
+ declare class IpTrapCache {
507
+ private readonly store;
508
+ private readonly ttlMs;
509
+ private sweepTimer;
510
+ /**
511
+ * @param ttlMs - Time-to-live in milliseconds. `null` = permanent.
512
+ */
513
+ constructor(ttlMs?: number | null);
514
+ /**
515
+ * Record a trap hit for the given IP address.
516
+ *
517
+ * If the IP is already in the cache its `hitCount` is incremented and
518
+ * the expiry is NOT refreshed (first-seen semantics).
519
+ */
520
+ flag(ip: string, trapLabel: string): void;
521
+ /**
522
+ * Check whether an IP is currently flagged.
523
+ * Performs lazy TTL eviction on access.
524
+ *
525
+ * @returns `true` if the IP is flagged and its entry has not expired.
526
+ */
527
+ isFlagged(ip: string): boolean;
528
+ /**
529
+ * Retrieve the cache entry for an IP, or `undefined` if not present
530
+ * (or expired).
531
+ */
532
+ getEntry(ip: string): Readonly<TrapCacheEntry> | undefined;
533
+ /**
534
+ * Remove a specific IP from the cache (e.g. for allow-listing).
535
+ */
536
+ unflag(ip: string): boolean;
537
+ /**
538
+ * Return the current number of flagged IPs (including expired entries
539
+ * not yet swept).
540
+ */
541
+ get size(): number;
542
+ /**
543
+ * Iterate over all currently active (non-expired) cache entries.
544
+ */
545
+ entries(): IterableIterator<[string, TrapCacheEntry]>;
546
+ /**
547
+ * Remove all expired entries from the store.
548
+ * Called automatically by the background sweeper.
549
+ */
550
+ sweep(): number;
551
+ /**
552
+ * Start the background sweep timer.
553
+ * Safe to call multiple times (idempotent).
554
+ */
555
+ startSweeper(intervalMs?: number): void;
556
+ /**
557
+ * Stop and clear the background sweeper.
558
+ */
559
+ stopSweeper(): void;
560
+ /** Empty the cache completely. Useful for testing. */
561
+ clear(): void;
562
+ }
563
+ /**
564
+ * Manages honeypot trap routes, IP flagging, and HTML link injection.
565
+ *
566
+ * ```ts
567
+ * const honeypot = new HoneypotManager(poisonEngine, { debug: true });
568
+ * app.use(honeypot.buildRouter());
569
+ *
570
+ * // In middleware, before serving:
571
+ * const flagged = honeypot.ipCache.isFlagged(req.ip ?? '');
572
+ * const result = detector.detect(req.headers, flagged);
573
+ * ```
574
+ */
575
+ declare class HoneypotManager {
576
+ private readonly poison;
577
+ private readonly trapRoutes;
578
+ private readonly onTrap;
579
+ private readonly debug;
580
+ /** Public read-only access to the IP trap cache. */
581
+ readonly ipCache: IpTrapCache;
582
+ constructor(poison: PoisonEngine, options?: {
583
+ customRoutes?: HoneypotRoute[] | undefined;
584
+ onTrap?: ((route: HoneypotRoute, ip: string, userAgent: string) => void) | undefined;
585
+ ipCache?: IpTrapCache | undefined;
586
+ ttlMs?: number | null | undefined;
587
+ debug?: boolean | undefined;
588
+ });
589
+ /**
590
+ * Build an Express `Router` pre-wired with all trap routes.
591
+ * Mount this router on your app BEFORE any real routes.
592
+ *
593
+ * Important: mount order means this runs after body parsing but
594
+ * before your application logic.
595
+ */
596
+ buildRouter(): Router;
597
+ /**
598
+ * Inject hidden honeypot `<a>` links into an HTML string.
599
+ *
600
+ * Links are CSS-hidden (`display:none`) and `aria-hidden="true"`.
601
+ * They are inserted immediately before `</body>` or appended to the end.
602
+ *
603
+ * @param html - Original HTML response body.
604
+ * @param seed - Numeric seed for deterministic URL generation.
605
+ */
606
+ injectLinks(html: string, seed: number): string;
607
+ /** Return all registered trap paths. */
608
+ getTrapPaths(): readonly string[];
609
+ /** Return all currently flagged IPs and their cache entries. */
610
+ getFlaggedIps(): ReadonlyMap<string, Readonly<TrapCacheEntry>>;
611
+ }
612
+
613
+ /**
614
+ * @file src/middleware.ts
615
+ * @description Main ai-shield Express middleware factory.
616
+ *
617
+ * `createAiShield(options?)` returns an Express `RequestHandler` that:
618
+ *
619
+ * 1. Instantiates `BotDetector`, `PoisonEngine`, and `HoneypotManager`
620
+ * with the supplied options.
621
+ * 2. Optionally activates `TelemetryEmitter` when `apiKey` is provided.
622
+ * 3. Mounts honeypot trap routes (including the canonical
623
+ * `/api/internal/telemetry-sync` bait) BEFORE real app routes.
624
+ * 4. On each non-trap request:
625
+ * a. Checks the IP against the in-memory honeypot cache.
626
+ * b. Runs the multi-signal detector (UA patterns + header heuristics).
627
+ * c. If score >= scoreThreshold → serves poison (HTTP 200).
628
+ * d. Emits async batch telemetry event if apiKey is configured.
629
+ * e. Otherwise → calls `next()` transparently.
630
+ * 5. Attaches `req.aiShield` metadata for downstream handlers.
631
+ */
632
+
633
+ declare global {
634
+ namespace Express {
635
+ interface Request {
636
+ aiShield?: AiShieldRequestMeta;
637
+ }
638
+ }
639
+ }
640
+ /**
641
+ * Extended RequestHandler with attached telemetry subsystem.
642
+ */
643
+ interface AiShieldHandler extends RequestHandler {
644
+ telemetry?: TelemetryEmitter | undefined;
645
+ }
646
+ /**
647
+ * Create the ai-shield middleware stack.
648
+ *
649
+ * Returns a single Express `RequestHandler` that internally composes
650
+ * the honeypot router, bot detector, poison engine, and optional telemetry emitter.
651
+ *
652
+ * @param options - Optional configuration; all fields have safe defaults.
653
+ */
654
+ declare function createAiShield(options?: AiShieldOptions): AiShieldHandler;
655
+ declare const aiShieldMiddleware: typeof createAiShield;
656
+
657
+ export { type AiShieldOptions as A, BotCategory as B, CANONICAL_TRAP as C, DEFAULT_SIGNAL_WEIGHTS as D, type HoneypotRoute as H, IpTrapCache as I, type PoisonContentType as P, type ShieldOptions as S, TelemetryEmitter as T, type AiShieldRequestMeta as a, BotDetector as b, type BotFingerprint as c, Confidence as d, type DetectionResult as e, type DetectionSignal as f, HoneypotManager as g, PoisonEngine as h, type PoisonPayload as i, type SignalWeights as j, type TelemetryEvent as k, type TrapCacheEntry as l, aiShieldMiddleware as m, createAiShield as n, hashClientIp as o, type AiShieldHandler as p, syntheticTokenPublic as s };