/** * @module @arcis/node/middleware/bot-detection * Local-only bot detection using User-Agent and behavioral signals. * * Categorizes requests into bot types and allows/denies based on config. * No cloud calls — everything runs locally. * * @example * // Block automated tools, allow search engines * app.use(botProtection({ * allow: ['SEARCH_ENGINE', 'SOCIAL', 'MONITORING'], * deny: ['AUTOMATED', 'SCRAPER'], * })); */ import type { Request, Response, RequestHandler } from 'express'; export type BotCategory = 'SEARCH_ENGINE' | 'SOCIAL' | 'MONITORING' | 'AI_CRAWLER' | 'SCRAPER' | 'SECURITY_SCANNER' | 'AUTOMATED' | 'UNKNOWN' | 'HUMAN'; export interface BotDetectionResult { /** Whether the request appears to be from a bot */ isBot: boolean; /** Bot category */ category: BotCategory; /** Matched bot name (e.g. 'Googlebot', 'curl') or null */ name: string | null; /** Confidence score: 0-1 */ confidence: number; /** Behavioral signals detected */ signals: string[]; } export interface BotProtectionOptions { /** Categories to explicitly allow. Default: ['SEARCH_ENGINE', 'SOCIAL', 'MONITORING'] */ allow?: BotCategory[]; /** Categories to explicitly deny. Default: ['AUTOMATED'] */ deny?: BotCategory[]; /** Action for categories not in allow or deny. Default: 'allow' */ defaultAction?: 'allow' | 'deny'; /** HTTP status code for denied bots. Default: 403 */ statusCode?: number; /** Error message for denied bots */ message?: string; /** Enable behavioral signal detection. Default: true */ detectBehavior?: boolean; /** Custom handler called on detection (instead of default deny response) */ onDetected?: (req: Request, res: Response, result: BotDetectionResult) => void; } /** * Source data for the bot corpus — `packages/core/bot-patterns.json`, derived * from arcjet/well-known-bots (MIT) plus a supplementary list of browser * automation tools the upstream doesn't separately track. Regenerate via * `python packages/core/generate-bot-patterns.py` after upgrading the source. */ interface BotPatternData { id: string; name: string; category: string; patterns: string[]; forbidden: string[]; } /** * Merge cloud-fetched bot-corpus entries into the live pattern set (Phase C * cloud refresh). New ids are appended; existing ids are replaced in place so * a curated correction overrides the bundled entry. An entry with an * uncompilable pattern is skipped — this never throws into the refresh path * (fail-open). Process-global by design: the corpus is a singleton, same as the * bundled import. Returns the number of entries merged. */ export declare function mergeBotPatterns(entries: BotPatternData[]): number; /** Test hook — restore the bundled corpus, dropping any cloud-merged entries. */ export declare function _resetBotPatternsForTest(): void; /** * Detect what kind of bot (if any) is making the request. * * @param req - HTTP request object * @returns Detection result with category, name, confidence, and signals * * @example * const result = detectBot(req); * if (result.isBot && result.category === 'AUTOMATED') { * // Block automated tools * } */ export declare function detectBot(req: Request): BotDetectionResult; /** * Create Express middleware for bot protection. * * @example * // Block automated tools and scrapers * app.use(botProtection({ * allow: ['SEARCH_ENGINE', 'SOCIAL', 'MONITORING'], * deny: ['AUTOMATED', 'SCRAPER'], * })); * * @example * // Block everything except search engines * app.use(botProtection({ * allow: ['SEARCH_ENGINE'], * defaultAction: 'deny', * })); * * @example * // Custom handler * app.use(botProtection({ * deny: ['AUTOMATED'], * onDetected: (req, res, result) => { * console.log(`Bot blocked: ${result.name} (${result.category})`); * res.status(403).json({ error: 'Bots not allowed' }); * }, * })); */ export declare function botProtection(options?: BotProtectionOptions): RequestHandler; export {}; //# sourceMappingURL=bot-detection.d.ts.map