[{"data":1,"prerenderedAt":10749},["ShallowReactive",2],{"navLinks":3,"sidebar_docs_navigation_\u002Fblog\u002Flayered-bot-defense":64,"navigation":76,"navLinks_footer":790,"\u002Fblog\u002Flayered-bot-defense_page":803,"\u002Fblog\u002Flayered-bot-defense":6288},{"id":4,"extension":5,"links":6,"meta":61,"stem":62,"__hash__":63},"navigationMenu\u002Fnavigation.json","json",[7,52,57],{"nested":8,"label":9,"icon":10,"to":11,"children":12},true,"Docs","i-lucide-book-open","\u002Fdocs\u002Fgetting-started",[13,19,26,32,39,45],{"label":14,"icon":15,"to":11,"description":16,"github":17,"badge":18},"Getting Started","i-lucide-rocket","An introduction to help you understand the core components.","https:\u002F\u002Fgithub.com\u002FSergo706\u002Fdocshub","Start Here",{"label":20,"icon":21,"to":22,"description":23,"github":24,"badge":25},"Auth H3 Client","i-lucide-key-round","\u002Fdocs\u002Fauth-h3client","Seamlessly enforce OAuth 2.0 authentication and session management integrated directly as the client of the IAM module.","https:\u002F\u002Fgithub.com\u002FSergo706\u002Fauth-h3client","Core",{"label":27,"icon":28,"to":29,"description":30,"github":31,"badge":25},"IAM","i-lucide-shield-check","\u002Fdocs\u002Fiam","Identity and Access Management featuring granular roles, permissions, and security policies.","https:\u002F\u002Fgithub.com\u002FSergo706\u002Fauth",{"label":33,"icon":34,"to":35,"description":36,"github":37,"badge":38},"Bot Detection","i-lucide-cpu","\u002Fdocs\u002Fbot-detection","Advanced behavioral analysis and request fingerprinting to stop malicious automated traffic.","https:\u002F\u002Fgithub.com\u002FSergo706\u002Fbot-detector","Security",{"label":40,"icon":41,"to":42,"description":43,"github":44,"badge":38},"Shield Base","i-lucide-database-zap","\u002Fdocs\u002Fshield-base","CLI and programmatic toolkit for compiling offline-ready IP intelligence databases from BGP, GeoIP, Tor, FireHOL, and other public threat feeds.","https:\u002F\u002Fgithub.com\u002FSergo706\u002Fshield-base-cli",{"label":46,"icon":47,"to":48,"description":49,"github":50,"badge":51},"Utils","i-lucide-wrench","\u002Fdocs\u002Futils","A standard library of highly optimized helpers for formatting, validation, and core logic.","https:\u002F\u002Fgithub.com\u002FSergo706\u002Futils","Library",{"nested":53,"label":54,"icon":55,"to":56},false,"Blog","i-lucide-pen-line","\u002Fblog",{"nested":53,"label":58,"icon":59,"to":60},"Website","lucide:app-window-mac","https:\u002F\u002Friavzon.com",{},"navigation","gkaQ0xRGxSLrLyM3kttLe0oBwkrR1EBjlepF8LSbwF8",[65],{"title":54,"path":56,"stem":66,"children":67,"page":53},"blog",[68,72],{"title":69,"path":70,"stem":71},"IAM API Tokens with Auth H3 Client: Secure M2M Access in Nuxt and Nitro","\u002Fblog\u002Fiam-api-tokens-auth-h3client","blog\u002Fiam-api-tokens-auth-h3client",{"title":73,"path":74,"stem":75},"Layered Bot Defense: How Shield Base, Bot Detector, and the IAM Canary Cookie Work Together","\u002Fblog\u002Flayered-bot-defense","blog\u002Flayered-bot-defense",[77],{"title":9,"path":78,"stem":79,"children":80,"page":53},"\u002Fdocs","docs",[81,229,347,352,530,597],{"title":20,"path":22,"stem":82,"children":83},"docs\u002Fauth-h3client\u002Findex",[84,85,94,131,157,179,182,203,207],{"title":20,"path":22,"stem":82},{"title":14,"path":86,"stem":87,"children":88},"\u002Fdocs\u002Fauth-h3client\u002Fgetting-started","docs\u002Fauth-h3client\u002F00.getting-started\u002Findex",[89,90],{"title":14,"path":86,"stem":87},{"title":91,"path":92,"stem":93},"Nuxt Module","\u002Fdocs\u002Fauth-h3client\u002Fgetting-started\u002Fnuxt","docs\u002Fauth-h3client\u002F00.getting-started\u002F00.nuxt",{"title":95,"path":96,"stem":97,"children":98},"Essentials","\u002Fdocs\u002Fauth-h3client\u002Fessentials","docs\u002Fauth-h3client\u002F01.essentials\u002Findex",[99,100,104,108,112,116,120,123,127],{"title":95,"path":96,"stem":97},{"title":101,"path":102,"stem":103},"Session Management","\u002Fdocs\u002Fauth-h3client\u002Fessentials\u002Fsession","docs\u002Fauth-h3client\u002F01.essentials\u002F00.session",{"title":105,"path":106,"stem":107},"Route Protection","\u002Fdocs\u002Fauth-h3client\u002Fessentials\u002Froute-protection","docs\u002Fauth-h3client\u002F01.essentials\u002F01.route-protection",{"title":109,"path":110,"stem":111},"CSRF Protection","\u002Fdocs\u002Fauth-h3client\u002Fessentials\u002Fcsrf","docs\u002Fauth-h3client\u002F01.essentials\u002F02.csrf",{"title":113,"path":114,"stem":115},"Auth Flows","\u002Fdocs\u002Fauth-h3client\u002Fessentials\u002Fauth-flows","docs\u002Fauth-h3client\u002F01.essentials\u002F03.auth-flows",{"title":117,"path":118,"stem":119},"OAuth and OIDC","\u002Fdocs\u002Fauth-h3client\u002Fessentials\u002Foauth","docs\u002Fauth-h3client\u002F01.essentials\u002F04.oauth",{"title":33,"path":121,"stem":122},"\u002Fdocs\u002Fauth-h3client\u002Fessentials\u002Fbot-detection","docs\u002Fauth-h3client\u002F01.essentials\u002F05.bot-detection",{"title":124,"path":125,"stem":126},"Cookies","\u002Fdocs\u002Fauth-h3client\u002Fessentials\u002Fcookies","docs\u002Fauth-h3client\u002F01.essentials\u002F06.cookies",{"title":128,"path":129,"stem":130},"Logging","\u002Fdocs\u002Fauth-h3client\u002Fessentials\u002Flogging","docs\u002Fauth-h3client\u002F01.essentials\u002F07.logging",{"title":132,"path":133,"stem":134,"children":135},"MFA","\u002Fdocs\u002Fauth-h3client\u002Fmfa","docs\u002Fauth-h3client\u002F02.mfa\u002Findex",[136,137,141,145,149,153],{"title":132,"path":133,"stem":134},{"title":138,"path":139,"stem":140},"Built-in MFA","\u002Fdocs\u002Fauth-h3client\u002Fmfa\u002Fbuilt-in-flow","docs\u002Fauth-h3client\u002F02.mfa\u002F01.built-in-flow",{"title":142,"path":143,"stem":144},"Password Reset","\u002Fdocs\u002Fauth-h3client\u002Fmfa\u002Fpassword-reset","docs\u002Fauth-h3client\u002F02.mfa\u002F02.password-reset",{"title":146,"path":147,"stem":148},"Email Change","\u002Fdocs\u002Fauth-h3client\u002Fmfa\u002Femail-change","docs\u002Fauth-h3client\u002F02.mfa\u002F03.email-change",{"title":150,"path":151,"stem":152},"Custom MFA Flow","\u002Fdocs\u002Fauth-h3client\u002Fmfa\u002Fcustom-flow","docs\u002Fauth-h3client\u002F02.mfa\u002F04.custom-flow",{"title":154,"path":155,"stem":156},"Client-Side MFA","\u002Fdocs\u002Fauth-h3client\u002Fmfa\u002Fclient-side","docs\u002Fauth-h3client\u002F02.mfa\u002F05.client-side",{"title":158,"path":159,"stem":160,"children":161},"Client-side","\u002Fdocs\u002Fauth-h3client\u002Fclient","docs\u002Fauth-h3client\u002F03.client\u002Findex",[162,163,167,171,175],{"title":158,"path":159,"stem":160},{"title":164,"path":165,"stem":166},"useAuthData","\u002Fdocs\u002Fauth-h3client\u002Fclient\u002Fuse-auth-data","docs\u002Fauth-h3client\u002F03.client\u002F00.use-auth-data",{"title":168,"path":169,"stem":170},"useMagicLink","\u002Fdocs\u002Fauth-h3client\u002Fclient\u002Fuse-magic-link","docs\u002Fauth-h3client\u002F03.client\u002F01.use-magic-link",{"title":172,"path":173,"stem":174},"executeRequest","\u002Fdocs\u002Fauth-h3client\u002Fclient\u002Fexecute-request","docs\u002Fauth-h3client\u002F03.client\u002F02.execute-request",{"title":176,"path":177,"stem":178},"getCsrfToken","\u002Fdocs\u002Fauth-h3client\u002Fclient\u002Fget-csrf-token","docs\u002Fauth-h3client\u002F03.client\u002F03.get-csrf-token",{"title":38,"path":180,"stem":181},"\u002Fdocs\u002Fauth-h3client\u002Fsecurity","docs\u002Fauth-h3client\u002F04.security",{"title":183,"path":184,"stem":185,"children":186,"page":53},"Guides","\u002Fdocs\u002Fauth-h3client\u002Fguides","docs\u002Fauth-h3client\u002F05.guides",[187,191,195,199],{"title":188,"path":189,"stem":190},"H3 and Nitro Setup","\u002Fdocs\u002Fauth-h3client\u002Fguides\u002Fh3-nitro","docs\u002Fauth-h3client\u002F05.guides\u002F00.h3-nitro",{"title":192,"path":193,"stem":194},"HMAC Inter-service Auth","\u002Fdocs\u002Fauth-h3client\u002Fguides\u002Fhmac","docs\u002Fauth-h3client\u002F05.guides\u002Fhmac",{"title":196,"path":197,"stem":198},"Image Upload","\u002Fdocs\u002Fauth-h3client\u002Fguides\u002Fimage-upload","docs\u002Fauth-h3client\u002F05.guides\u002Fimage-upload",{"title":200,"path":201,"stem":202},"mTLS Configuration","\u002Fdocs\u002Fauth-h3client\u002Fguides\u002Fmtls","docs\u002Fauth-h3client\u002F05.guides\u002Fmtls",{"title":204,"path":205,"stem":206},"Configuration","\u002Fdocs\u002Fauth-h3client\u002Fconfiguration","docs\u002Fauth-h3client\u002F06.configuration",{"title":208,"path":209,"stem":210,"children":211},"API Reference","\u002Fdocs\u002Fauth-h3client\u002Fapi","docs\u002Fauth-h3client\u002F07.api\u002Findex",[212,213,217,221,225],{"title":208,"path":209,"stem":210},{"title":214,"path":215,"stem":216},"Routes Reference","\u002Fdocs\u002Fauth-h3client\u002Fapi\u002Fcontrollers","docs\u002Fauth-h3client\u002F07.api\u002F00.controllers",{"title":218,"path":219,"stem":220},"Middleware Reference","\u002Fdocs\u002Fauth-h3client\u002Fapi\u002Fmiddleware","docs\u002Fauth-h3client\u002F07.api\u002F01.middleware",{"title":222,"path":223,"stem":224},"Client-side Reference","\u002Fdocs\u002Fauth-h3client\u002Fapi\u002Fcomposables","docs\u002Fauth-h3client\u002F07.api\u002F02.composables",{"title":226,"path":227,"stem":228},"Utilities","\u002Fdocs\u002Fauth-h3client\u002Fapi\u002Futilities","docs\u002Fauth-h3client\u002F07.api\u002F03.utilities",{"title":230,"path":35,"stem":231,"children":232},"Bot Detector","docs\u002Fbot-detection\u002Findex",[233,234,237,241,245,264,338,341,344],{"title":230,"path":35,"stem":231},{"title":14,"path":235,"stem":236},"\u002Fdocs\u002Fbot-detection\u002Fgetting-started","docs\u002Fbot-detection\u002F00.getting-started",{"title":238,"path":239,"stem":240},"CLI","\u002Fdocs\u002Fbot-detection\u002Fcli","docs\u002Fbot-detection\u002F01.cli",{"title":242,"path":243,"stem":244},"Data Sources","\u002Fdocs\u002Fbot-detection\u002Fdata-sources","docs\u002Fbot-detection\u002F02.data-sources",{"title":183,"path":246,"stem":247,"children":248,"page":53},"\u002Fdocs\u002Fbot-detection\u002Fguides","docs\u002Fbot-detection\u002F03.guides",[249,253,257,260],{"title":250,"path":251,"stem":252},"Custom Checkers","\u002Fdocs\u002Fbot-detection\u002Fguides\u002Fcustom","docs\u002Fbot-detection\u002F03.guides\u002FCUSTOM",{"title":254,"path":255,"stem":256},"Scheduling Database Generation","\u002Fdocs\u002Fbot-detection\u002Fguides\u002Fgenerate","docs\u002Fbot-detection\u002F03.guides\u002FGENERATE",{"title":128,"path":258,"stem":259},"\u002Fdocs\u002Fbot-detection\u002Fguides\u002Flogging","docs\u002Fbot-detection\u002F03.guides\u002FLOGGING",{"title":261,"path":262,"stem":263},"Score Modes and Reputation Healing","\u002Fdocs\u002Fbot-detection\u002Fguides\u002Fscore","docs\u002Fbot-detection\u002F03.guides\u002FSCORE",{"title":265,"path":266,"stem":267,"children":268},"Checkers","\u002Fdocs\u002Fbot-detection\u002Fcheckers","docs\u002Fbot-detection\u002F04.checkers\u002Findex",[269,270,274,278,282,286,290,294,298,302,306,310,314,318,322,326,330,334],{"title":265,"path":266,"stem":267},{"title":271,"path":272,"stem":273},"IP Validation","\u002Fdocs\u002Fbot-detection\u002Fcheckers\u002Fip-validation","docs\u002Fbot-detection\u002F04.checkers\u002F01.ip-validation",{"title":275,"path":276,"stem":277},"Good \u002F Bad Bot Verification","\u002Fdocs\u002Fbot-detection\u002Fcheckers\u002Fgood-bots","docs\u002Fbot-detection\u002F04.checkers\u002F02.good-bots",{"title":279,"path":280,"stem":281},"Browser & Device Fingerprint","\u002Fdocs\u002Fbot-detection\u002Fcheckers\u002Fbrowser-device","docs\u002Fbot-detection\u002F04.checkers\u002F03.browser-device",{"title":283,"path":284,"stem":285},"Locale Map","\u002Fdocs\u002Fbot-detection\u002Fcheckers\u002Flocale-map","docs\u002Fbot-detection\u002F04.checkers\u002F04.locale-map",{"title":287,"path":288,"stem":289},"Known Threats","\u002Fdocs\u002Fbot-detection\u002Fcheckers\u002Fknown-threats","docs\u002Fbot-detection\u002F04.checkers\u002F05.known-threats",{"title":291,"path":292,"stem":293},"ASN Classification","\u002Fdocs\u002Fbot-detection\u002Fcheckers\u002Fasn-classification","docs\u002Fbot-detection\u002F04.checkers\u002F06.asn-classification",{"title":295,"path":296,"stem":297},"Tor Analysis","\u002Fdocs\u002Fbot-detection\u002Fcheckers\u002Ftor-analysis","docs\u002Fbot-detection\u002F04.checkers\u002F07.tor-analysis",{"title":299,"path":300,"stem":301},"Timezone Consistency","\u002Fdocs\u002Fbot-detection\u002Fcheckers\u002Ftimezone-consistency","docs\u002Fbot-detection\u002F04.checkers\u002F08.timezone-consistency",{"title":303,"path":304,"stem":305},"Honeypot","\u002Fdocs\u002Fbot-detection\u002Fcheckers\u002Fhoneypot","docs\u002Fbot-detection\u002F04.checkers\u002F09.honeypot",{"title":307,"path":308,"stem":309},"Known Bad IPs","\u002Fdocs\u002Fbot-detection\u002Fcheckers\u002Fknown-bad-ips","docs\u002Fbot-detection\u002F04.checkers\u002F10.known-bad-ips",{"title":311,"path":312,"stem":313},"Behavior Rate","\u002Fdocs\u002Fbot-detection\u002Fcheckers\u002Fbehavior-rate","docs\u002Fbot-detection\u002F04.checkers\u002F11.behavior-rate",{"title":315,"path":316,"stem":317},"Proxy \u002F ISP \u002F Cookie","\u002Fdocs\u002Fbot-detection\u002Fcheckers\u002Fproxy-isp-cookies","docs\u002Fbot-detection\u002F04.checkers\u002F12.proxy-isp-cookies",{"title":319,"path":320,"stem":321},"Session Coherence","\u002Fdocs\u002Fbot-detection\u002Fcheckers\u002Fsession-coherence","docs\u002Fbot-detection\u002F04.checkers\u002F13.session-coherence",{"title":323,"path":324,"stem":325},"Velocity Fingerprint","\u002Fdocs\u002Fbot-detection\u002Fcheckers\u002Fvelocity-fingerprint","docs\u002Fbot-detection\u002F04.checkers\u002F14.velocity-fingerprint",{"title":327,"path":328,"stem":329},"UA & Header Analysis","\u002Fdocs\u002Fbot-detection\u002Fcheckers\u002Fua-header","docs\u002Fbot-detection\u002F04.checkers\u002F15.ua-header",{"title":331,"path":332,"stem":333},"Geolocation","\u002Fdocs\u002Fbot-detection\u002Fcheckers\u002Fgeolocation","docs\u002Fbot-detection\u002F04.checkers\u002F16.geolocation",{"title":335,"path":336,"stem":337},"Known Bad User-Agents","\u002Fdocs\u002Fbot-detection\u002Fcheckers\u002Fknown-bad-ua","docs\u002Fbot-detection\u002F04.checkers\u002F17.known-bad-ua",{"title":38,"path":339,"stem":340},"\u002Fdocs\u002Fbot-detection\u002Fsecurity","docs\u002Fbot-detection\u002F04.security",{"title":208,"path":342,"stem":343},"\u002Fdocs\u002Fbot-detection\u002Fapi","docs\u002Fbot-detection\u002F05.api",{"title":204,"path":345,"stem":346},"\u002Fdocs\u002Fbot-detection\u002Fconfiguration","docs\u002Fbot-detection\u002F06.configuration",{"title":348,"path":11,"stem":349,"children":350},"Introduction","docs\u002Fgetting-started\u002Findex",[351],{"title":348,"path":11,"stem":349},{"title":27,"path":29,"stem":353,"children":354},"docs\u002Fiam\u002Findex",[355,356,359,494,497,513,516],{"title":27,"path":29,"stem":353},{"title":14,"path":357,"stem":358},"\u002Fdocs\u002Fiam\u002Fgetting-started","docs\u002Fiam\u002F00.getting-started",{"title":95,"path":360,"stem":361,"children":362},"\u002Fdocs\u002Fiam\u002Fessentials","docs\u002Fiam\u002F01.essentials\u002Findex",[363,364,368,372,376,380,384,388,392,396,400,404,407,411,415,419,423,426,430,434,437,441,444],{"title":95,"path":360,"stem":361},{"title":365,"path":366,"stem":367},"Tokens","\u002Fdocs\u002Fiam\u002Fessentials\u002Ftokens","docs\u002Fiam\u002F01.essentials\u002F00.tokens",{"title":369,"path":370,"stem":371},"Access Tokens","\u002Fdocs\u002Fiam\u002Fessentials\u002Faccess-tokens","docs\u002Fiam\u002F01.essentials\u002F01.access-tokens",{"title":373,"path":374,"stem":375},"Refresh Tokens","\u002Fdocs\u002Fiam\u002Fessentials\u002Frefresh-tokens","docs\u002Fiam\u002F01.essentials\u002F02.refresh-tokens",{"title":377,"path":378,"stem":379},"Anomaly Detection","\u002Fdocs\u002Fiam\u002Fessentials\u002Fanomalies","docs\u002Fiam\u002F01.essentials\u002F03.anomalies",{"title":381,"path":382,"stem":383},"Signup","\u002Fdocs\u002Fiam\u002Fessentials\u002Fsignup","docs\u002Fiam\u002F01.essentials\u002F04.signup",{"title":385,"path":386,"stem":387},"Login","\u002Fdocs\u002Fiam\u002Fessentials\u002Flogin","docs\u002Fiam\u002F01.essentials\u002F05.login",{"title":389,"path":390,"stem":391},"Logout","\u002Fdocs\u002Fiam\u002Fessentials\u002Flogout","docs\u002Fiam\u002F01.essentials\u002F06.logout",{"title":393,"path":394,"stem":395},"OAuth","\u002Fdocs\u002Fiam\u002Fessentials\u002Foauth","docs\u002Fiam\u002F01.essentials\u002F07.oauth",{"title":397,"path":398,"stem":399},"Magic Links","\u002Fdocs\u002Fiam\u002Fessentials\u002Fmagic-links","docs\u002Fiam\u002F01.essentials\u002F08.magic-links",{"title":401,"path":402,"stem":403},"Emails","\u002Fdocs\u002Fiam\u002Fessentials\u002Femails","docs\u002Fiam\u002F01.essentials\u002F09.emails",{"title":132,"path":405,"stem":406},"\u002Fdocs\u002Fiam\u002Fessentials\u002Fmfa","docs\u002Fiam\u002F01.essentials\u002F10.mfa",{"title":408,"path":409,"stem":410},"Fingerprinting","\u002Fdocs\u002Fiam\u002Fessentials\u002Ffingerprinting","docs\u002Fiam\u002F01.essentials\u002F11.fingerprinting",{"title":412,"path":413,"stem":414},"Backend for Frontend","\u002Fdocs\u002Fiam\u002Fessentials\u002Fbff","docs\u002Fiam\u002F01.essentials\u002F12.bff",{"title":416,"path":417,"stem":418},"HMAC Authentication","\u002Fdocs\u002Fiam\u002Fessentials\u002Fhmac","docs\u002Fiam\u002F01.essentials\u002F13.hmac",{"title":420,"path":421,"stem":422},"XSS Protection","\u002Fdocs\u002Fiam\u002Fessentials\u002Fxss","docs\u002Fiam\u002F01.essentials\u002F14.xss",{"title":128,"path":424,"stem":425},"\u002Fdocs\u002Fiam\u002Fessentials\u002Flogging","docs\u002Fiam\u002F01.essentials\u002F15.logging",{"title":427,"path":428,"stem":429},"Rate Limiting","\u002Fdocs\u002Fiam\u002Fessentials\u002Frate-limiting","docs\u002Fiam\u002F01.essentials\u002F16.rate-limiting",{"title":431,"path":432,"stem":433},"Database","\u002Fdocs\u002Fiam\u002Fessentials\u002Fdatabase","docs\u002Fiam\u002F01.essentials\u002F17.database",{"title":124,"path":435,"stem":436},"\u002Fdocs\u002Fiam\u002Fessentials\u002Fcookies","docs\u002Fiam\u002F01.essentials\u002F18.cookies",{"title":438,"path":439,"stem":440},"Service Startup","\u002Fdocs\u002Fiam\u002Fessentials\u002Fservice","docs\u002Fiam\u002F01.essentials\u002F19.service",{"title":142,"path":442,"stem":443},"\u002Fdocs\u002Fiam\u002Fessentials\u002Fpassword-reset","docs\u002Fiam\u002F01.essentials\u002F20.password-reset",{"title":445,"path":446,"stem":447,"children":448},"API Tokens","\u002Fdocs\u002Fiam\u002Fessentials\u002Fapi","docs\u002Fiam\u002F01.essentials\u002F21.api\u002Findex",[449,450,454,458,488,491],{"title":445,"path":446,"stem":447},{"title":451,"path":452,"stem":453},"Creating Tokens","\u002Fdocs\u002Fiam\u002Fessentials\u002Fapi\u002Fcreation","docs\u002Fiam\u002F01.essentials\u002F21.api\u002F00.creation",{"title":455,"path":456,"stem":457},"Verifying Tokens","\u002Fdocs\u002Fiam\u002Fessentials\u002Fapi\u002Fverification","docs\u002Fiam\u002F01.essentials\u002F21.api\u002F01.verification",{"title":459,"path":460,"stem":461,"children":462},"Manage Tokens","\u002Fdocs\u002Fiam\u002Fessentials\u002Fapi\u002Fmanagement","docs\u002Fiam\u002F01.essentials\u002F21.api\u002F02.management\u002Findex",[463,464,468,472,476,480,484],{"title":459,"path":460,"stem":461},{"title":465,"path":466,"stem":467},"Privileges","\u002Fdocs\u002Fiam\u002Fessentials\u002Fapi\u002Fmanagement\u002Fprivilege","docs\u002Fiam\u002F01.essentials\u002F21.api\u002F02.management\u002F00.privilege",{"title":469,"path":470,"stem":471},"Revocation","\u002Fdocs\u002Fiam\u002Fessentials\u002Fapi\u002Fmanagement\u002Frevocation","docs\u002Fiam\u002F01.essentials\u002F21.api\u002F02.management\u002F01.revocation",{"title":473,"path":474,"stem":475},"Rotation","\u002Fdocs\u002Fiam\u002Fessentials\u002Fapi\u002Fmanagement\u002Frotation","docs\u002Fiam\u002F01.essentials\u002F21.api\u002F02.management\u002F02.rotation",{"title":477,"path":478,"stem":479},"IP Restriction","\u002Fdocs\u002Fiam\u002Fessentials\u002Fapi\u002Fmanagement\u002Fip-updates","docs\u002Fiam\u002F01.essentials\u002F21.api\u002F02.management\u002F03.ip-updates",{"title":481,"path":482,"stem":483},"Metadata","\u002Fdocs\u002Fiam\u002Fessentials\u002Fapi\u002Fmanagement\u002Fmetadata","docs\u002Fiam\u002F01.essentials\u002F21.api\u002F02.management\u002F04.metadata",{"title":485,"path":486,"stem":487},"Token Listing","\u002Fdocs\u002Fiam\u002Fessentials\u002Fapi\u002Fmanagement\u002Flist","docs\u002Fiam\u002F01.essentials\u002F21.api\u002F02.management\u002F05.list",{"title":427,"path":489,"stem":490},"\u002Fdocs\u002Fiam\u002Fessentials\u002Fapi\u002Frate-limiting","docs\u002Fiam\u002F01.essentials\u002F21.api\u002F03.rate-limiting",{"title":38,"path":492,"stem":493},"\u002Fdocs\u002Fiam\u002Fessentials\u002Fapi\u002Fsecurity","docs\u002Fiam\u002F01.essentials\u002F21.api\u002F04.security",{"title":38,"path":495,"stem":496},"\u002Fdocs\u002Fiam\u002Fsecurity","docs\u002Fiam\u002F02.security",{"title":183,"path":498,"stem":499,"children":500,"page":53},"\u002Fdocs\u002Fiam\u002Fguides","docs\u002Fiam\u002F03.guides",[501,505,509],{"title":502,"path":503,"stem":504},"Deployment","\u002Fdocs\u002Fiam\u002Fguides\u002Fdeployment","docs\u002Fiam\u002F03.guides\u002Fdeployment",{"title":506,"path":507,"stem":508},"Operation Scripts","\u002Fdocs\u002Fiam\u002Fguides\u002Foperation-scripts","docs\u002Fiam\u002F03.guides\u002Foperation-scripts",{"title":510,"path":511,"stem":512},"Role-Based Access Control","\u002Fdocs\u002Fiam\u002Fguides\u002Frbac","docs\u002Fiam\u002F03.guides\u002Frbac",{"title":204,"path":514,"stem":515},"\u002Fdocs\u002Fiam\u002Fconfiguration","docs\u002Fiam\u002F04.configuration",{"title":517,"path":518,"stem":519,"children":520,"page":53},"Api","\u002Fdocs\u002Fiam\u002Fapi","docs\u002Fiam\u002F05.API",[521,524,527],{"title":208,"path":522,"stem":523},"\u002Fdocs\u002Fiam\u002Fapi\u002Fapi","docs\u002Fiam\u002F05.API\u002F00.api",{"title":218,"path":525,"stem":526},"\u002Fdocs\u002Fiam\u002Fapi\u002Fmiddlewares","docs\u002Fiam\u002F05.API\u002F02.middlewares",{"title":214,"path":528,"stem":529},"\u002Fdocs\u002Fiam\u002Fapi\u002Froutes","docs\u002Fiam\u002F05.API\u002F03.routes",{"title":40,"path":42,"stem":531,"children":532},"docs\u002Fshield-base\u002Findex",[533,534,537,541,582,586,590,594],{"title":40,"path":42,"stem":531},{"title":14,"path":535,"stem":536},"\u002Fdocs\u002Fshield-base\u002Fgetting-started","docs\u002Fshield-base\u002F00.getting-started",{"title":538,"path":539,"stem":540},"CLI Reference","\u002Fdocs\u002Fshield-base\u002Fcli","docs\u002Fshield-base\u002F01.cli",{"title":242,"path":542,"stem":543,"children":544},"\u002Fdocs\u002Fshield-base\u002Fdata-sources","docs\u002Fshield-base\u002F02.data-sources\u002Findex",[545,546,550,554,558,562,566,570,574,578],{"title":242,"path":542,"stem":543},{"title":547,"path":548,"stem":549},"BGP \u002F ASN","\u002Fdocs\u002Fshield-base\u002Fdata-sources\u002Fbgp","docs\u002Fshield-base\u002F02.data-sources\u002Fbgp",{"title":551,"path":552,"stem":553},"City Geolocation","\u002Fdocs\u002Fshield-base\u002Fdata-sources\u002Fcity","docs\u002Fshield-base\u002F02.data-sources\u002Fcity",{"title":555,"path":556,"stem":557},"Country Geolocation","\u002Fdocs\u002Fshield-base\u002Fdata-sources\u002Fcountry","docs\u002Fshield-base\u002F02.data-sources\u002Fcountry",{"title":559,"path":560,"stem":561},"Verified Crawlers","\u002Fdocs\u002Fshield-base\u002Fdata-sources\u002Fcrawlers","docs\u002Fshield-base\u002F02.data-sources\u002Fcrawlers",{"title":563,"path":564,"stem":565},"Disposable Emails","\u002Fdocs\u002Fshield-base\u002Fdata-sources\u002Femail","docs\u002Fshield-base\u002F02.data-sources\u002Femail",{"title":567,"path":568,"stem":569},"FireHOL Threat Intelligence","\u002Fdocs\u002Fshield-base\u002Fdata-sources\u002Ffirehol","docs\u002Fshield-base\u002F02.data-sources\u002Ffirehol",{"title":571,"path":572,"stem":573},"Proxy Detection","\u002Fdocs\u002Fshield-base\u002Fdata-sources\u002Fproxy","docs\u002Fshield-base\u002F02.data-sources\u002Fproxy",{"title":575,"path":576,"stem":577},"Tor Nodes","\u002Fdocs\u002Fshield-base\u002Fdata-sources\u002Ftor","docs\u002Fshield-base\u002F02.data-sources\u002Ftor",{"title":579,"path":580,"stem":581},"Suspicious User-Agents","\u002Fdocs\u002Fshield-base\u002Fdata-sources\u002Fuseragent","docs\u002Fshield-base\u002F02.data-sources\u002Fuseragent",{"title":583,"path":584,"stem":585},"Programmatic Usage","\u002Fdocs\u002Fshield-base\u002Fusage","docs\u002Fshield-base\u002F03.usage",{"title":587,"path":588,"stem":589},"Custom Data Sources","\u002Fdocs\u002Fshield-base\u002Fcustom-data-sources","docs\u002Fshield-base\u002F04.custom-data-sources",{"title":591,"path":592,"stem":593},"TypeScript Types","\u002Fdocs\u002Fshield-base\u002Ftypes","docs\u002Fshield-base\u002F05.types",{"title":208,"path":595,"stem":596},"\u002Fdocs\u002Fshield-base\u002Fapi","docs\u002Fshield-base\u002F06.api",{"title":226,"path":48,"stem":598,"children":599},"docs\u002Futils\u002Findex",[600,601,618,651,748],{"title":226,"path":48,"stem":598},{"title":602,"path":603,"stem":604,"children":605,"page":53},"Eslint","\u002Fdocs\u002Futils\u002Feslint","docs\u002Futils\u002Feslint",[606,610,614],{"title":607,"path":608,"stem":609},"React Config","\u002Fdocs\u002Futils\u002Feslint\u002Freact","docs\u002Futils\u002Feslint\u002Freact",{"title":611,"path":612,"stem":613},"TypeScript Config","\u002Fdocs\u002Futils\u002Feslint\u002Ftypescript","docs\u002Futils\u002Feslint\u002Ftypescript",{"title":615,"path":616,"stem":617},"Vue Config","\u002Fdocs\u002Futils\u002Feslint\u002Fvue","docs\u002Futils\u002Feslint\u002Fvue",{"title":619,"path":620,"stem":621,"children":622,"page":53},"Server","\u002Fdocs\u002Futils\u002Fserver","docs\u002Futils\u002Fserver",[623,627,631,635,639,643,647],{"title":624,"path":625,"stem":626},"Encryption","\u002Fdocs\u002Futils\u002Fserver\u002Fencryption","docs\u002Futils\u002Fserver\u002Fencryption",{"title":628,"path":629,"stem":630},"Path Resolver","\u002Fdocs\u002Futils\u002Fserver\u002Fpathresolver","docs\u002Futils\u002Fserver\u002FpathResolver",{"title":632,"path":633,"stem":634},"File Replacements","\u002Fdocs\u002Futils\u002Fserver\u002Freplace","docs\u002Futils\u002Fserver\u002Freplace",{"title":636,"path":637,"stem":638},"run","\u002Fdocs\u002Futils\u002Fserver\u002Frun","docs\u002Futils\u002Fserver\u002Frun",{"title":640,"path":641,"stem":642},"scheduleTask","\u002Fdocs\u002Futils\u002Fserver\u002Fscheduletask","docs\u002Futils\u002Fserver\u002FscheduleTask",{"title":644,"path":645,"stem":646},"spawnRun","\u002Fdocs\u002Futils\u002Fserver\u002Fspawnrun","docs\u002Futils\u002Fserver\u002FspawnRun",{"title":648,"path":649,"stem":650},"uploadCsv","\u002Fdocs\u002Futils\u002Fserver\u002Fuploadcsv","docs\u002Futils\u002Fserver\u002FuploadCsv",{"title":652,"path":653,"stem":654,"children":655,"page":53},"Shared","\u002Fdocs\u002Futils\u002Fshared","docs\u002Futils\u002Fshared",[656,660,664,668,672,676,680,684,688,692,696,700,704,708,712,716,720,724,728,732,736,740,744],{"title":657,"path":658,"stem":659},"BatchQueue","\u002Fdocs\u002Futils\u002Fshared\u002Fbatchqueue","docs\u002Futils\u002Fshared\u002FbatchQueue",{"title":661,"path":662,"stem":663},"capitalize","\u002Fdocs\u002Futils\u002Fshared\u002Fcapitalize","docs\u002Futils\u002Fshared\u002Fcapitalize",{"title":665,"path":666,"stem":667},"chunkProcess","\u002Fdocs\u002Futils\u002Fshared\u002Fchunkprocess","docs\u002Futils\u002Fshared\u002FchunkProcess",{"title":669,"path":670,"stem":671},"cleanObject","\u002Fdocs\u002Futils\u002Fshared\u002Fcleanobject","docs\u002Futils\u002Fshared\u002FcleanObject",{"title":673,"path":674,"stem":675},"createConfigManager","\u002Fdocs\u002Futils\u002Fshared\u002Fconfigurationdefiner","docs\u002Futils\u002Fshared\u002FconfigurationDefiner",{"title":677,"path":678,"stem":679},"debounce","\u002Fdocs\u002Futils\u002Fshared\u002Fdebounce","docs\u002Futils\u002Fshared\u002Fdebounce",{"title":681,"path":682,"stem":683},"ensureArray","\u002Fdocs\u002Futils\u002Fshared\u002Fensurearray","docs\u002Futils\u002Fshared\u002FensureArray",{"title":685,"path":686,"stem":687},"fetchWithRetry","\u002Fdocs\u002Futils\u002Fshared\u002Ffetchwithretry","docs\u002Futils\u002Fshared\u002FfetchWithRetry",{"title":689,"path":690,"stem":691},"filterEmptyValues","\u002Fdocs\u002Futils\u002Fshared\u002Ffilteremptyvalues","docs\u002Futils\u002Fshared\u002FfilterEmptyValues",{"title":693,"path":694,"stem":695},"findStringsInObject","\u002Fdocs\u002Futils\u002Fshared\u002Ffindobjectvalues","docs\u002Futils\u002Fshared\u002FfindObjectValues",{"title":697,"path":698,"stem":699},"fisherYatesShuffle","\u002Fdocs\u002Futils\u002Fshared\u002Ffisheryatesshuffle","docs\u002Futils\u002Fshared\u002FfisherYatesShuffle",{"title":701,"path":702,"stem":703},"getRandomImage","\u002Fdocs\u002Futils\u002Fshared\u002Fgetrandomimage","docs\u002Futils\u002Fshared\u002FgetRandomImage",{"title":705,"path":706,"stem":707},"isObjectHasValues","\u002Fdocs\u002Futils\u002Fshared\u002Fisobjecthasvalues","docs\u002Futils\u002Fshared\u002FisObjectHasValues",{"title":709,"path":710,"stem":711},"isAsyncOrPromise","\u002Fdocs\u002Futils\u002Fshared\u002Fispromise","docs\u002Futils\u002Fshared\u002FisPromise",{"title":713,"path":714,"stem":715},"MiniCache","\u002Fdocs\u002Futils\u002Fshared\u002Fminicache","docs\u002Futils\u002Fshared\u002FminiCache",{"title":717,"path":718,"stem":719},"parseCookies","\u002Fdocs\u002Futils\u002Fshared\u002Fparserawcookies","docs\u002Futils\u002Fshared\u002FparseRawCookies",{"title":721,"path":722,"stem":723},"safeAction","\u002Fdocs\u002Futils\u002Fshared\u002Fpromiselocker","docs\u002Futils\u002Fshared\u002FpromiseLocker",{"title":725,"path":726,"stem":727},"Random","\u002Fdocs\u002Futils\u002Fshared\u002Frandom","docs\u002Futils\u002Fshared\u002Frandom",{"title":729,"path":730,"stem":731},"range","\u002Fdocs\u002Futils\u002Fshared\u002Frange","docs\u002Futils\u002Fshared\u002Frange",{"title":733,"path":734,"stem":735},"rateLimiters","\u002Fdocs\u002Futils\u002Fshared\u002Fratelimiters","docs\u002Futils\u002Fshared\u002FrateLimiters",{"title":737,"path":738,"stem":739},"safeObjectMerge","\u002Fdocs\u002Futils\u002Fshared\u002Fsafemerge","docs\u002Futils\u002Fshared\u002FsafeMerge",{"title":741,"path":742,"stem":743},"textTruncation","\u002Fdocs\u002Futils\u002Fshared\u002Ftexttruncation","docs\u002Futils\u002Fshared\u002FtextTruncation",{"title":745,"path":746,"stem":747},"validateZodSchema","\u002Fdocs\u002Futils\u002Fshared\u002Fvalidatezodschema","docs\u002Futils\u002Fshared\u002FvalidateZodSchema",{"title":749,"path":750,"stem":751,"children":752},"Utility Types","\u002Fdocs\u002Futils\u002Ftypes","docs\u002Futils\u002Ftypes\u002Findex",[753,754,758,762,766,770,774,778,782,786],{"title":749,"path":750,"stem":751},{"title":755,"path":756,"stem":757},"Brand","\u002Fdocs\u002Futils\u002Ftypes\u002Fbrand","docs\u002Futils\u002Ftypes\u002FBrand",{"title":759,"path":760,"stem":761},"DeepPartial","\u002Fdocs\u002Futils\u002Ftypes\u002Fdeeppartial","docs\u002Futils\u002Ftypes\u002FDeepPartial",{"title":763,"path":764,"stem":765},"Merge","\u002Fdocs\u002Futils\u002Ftypes\u002Fmerge","docs\u002Futils\u002Ftypes\u002FMerge",{"title":767,"path":768,"stem":769},"NonNullable","\u002Fdocs\u002Futils\u002Ftypes\u002Fnonnullable","docs\u002Futils\u002Ftypes\u002FNonNullable",{"title":771,"path":772,"stem":773},"Prettify","\u002Fdocs\u002Futils\u002Ftypes\u002Fprettify","docs\u002Futils\u002Ftypes\u002FPrettify",{"title":775,"path":776,"stem":777},"PromiseType","\u002Fdocs\u002Futils\u002Ftypes\u002Fpromisetype","docs\u002Futils\u002Ftypes\u002FPromiseType",{"title":779,"path":780,"stem":781},"RequireKeys","\u002Fdocs\u002Futils\u002Ftypes\u002Frequirekeys","docs\u002Futils\u002Ftypes\u002FRequireKeys",{"title":783,"path":784,"stem":785},"StandardResponse","\u002Fdocs\u002Futils\u002Ftypes\u002Fstandardresponse","docs\u002Futils\u002Ftypes\u002FStandardResponse",{"title":787,"path":788,"stem":789},"ValueOf","\u002Fdocs\u002Futils\u002Ftypes\u002Fvalueof","docs\u002Futils\u002Ftypes\u002FValueOf",{"id":4,"extension":5,"links":791,"meta":802,"stem":62,"__hash__":63},[792,800,801],{"nested":8,"label":9,"icon":10,"to":11,"children":793},[794,795,796,797,798,799],{"label":14,"icon":15,"to":11,"description":16,"github":17,"badge":18},{"label":20,"icon":21,"to":22,"description":23,"github":24,"badge":25},{"label":27,"icon":28,"to":29,"description":30,"github":31,"badge":25},{"label":33,"icon":34,"to":35,"description":36,"github":37,"badge":38},{"label":40,"icon":41,"to":42,"description":43,"github":44,"badge":38},{"label":46,"icon":47,"to":48,"description":49,"github":50,"badge":51},{"nested":53,"label":54,"icon":55,"to":56},{"nested":53,"label":58,"icon":59,"to":60},{},{"id":804,"title":73,"author":805,"authorGithub":806,"authorGithubUserName":807,"authorImg":808,"body":809,"date":6276,"description":6277,"extension":6278,"featured":53,"icon":6279,"image":6280,"meta":6281,"navigation":8,"path":74,"rawbody":6282,"readingTime":6283,"seo":6284,"stem":75,"tags":6285,"__hash__":6287},"blog\u002Fblog\u002Flayered-bot-defense.md","Sergio","https:\u002F\u002Fgithub.com\u002FSergo706","Sergo706","https:\u002F\u002Fgithub.com\u002FSergo706.png",{"type":810,"value":811,"toc":6239},"minimark",[812,816,819,822,827,830,833,836,839,850,852,856,859,864,867,871,874,880,1001,1006,1009,1091,1096,1137,1141,1148,1312,1323,1329,1331,1335,1342,1346,1353,1357,1367,1374,1461,1468,1472,1478,1484,1501,1511,1592,1609,1615,1665,1674,1682,1746,1749,1758,1764,1778,1782,1785,1795,1801,1872,1877,1890,1932,1938,1944,1950,1953,1959,2007,2013,2022,2063,2065,2069,2078,2119,2122,2126,2132,2138,2142,2148,2153,2374,2379,2494,2497,2501,2504,2517,2526,2528,2532,2535,2546,2551,2584,2590,2593,2598,2616,2622,2624,2627,2630,3462,3468,3470,3474,3481,3488,3497,3694,3699,3703,3712,3880,3894,3898,3904,4345,4348,4432,4436,4454,4665,4994,4997,5001,5012,5114,5121,5125,5139,5682,5691,5693,5697,5707,5711,5718,5736,5767,5837,5849,5892,5896,5903,5907,5972,5979,6163,6167,6170,6202,6208,6210,6214,6217,6220,6223,6230,6235],[813,814,815],"p",{},"Most bot detection systems operate on a single layer: a rule list, a rate limiter, or a third-party API call. The problem with that model is that any single signal can be spoofed. A bot can rotate IPs, forge user-agent strings, and slow its request rate to look human. Defeating it requires combining signals from multiple independent layers so that evading one does not defeat the others.",[813,817,818],{},"The Riavzon stack addresses this with three coordinated components. Shield Base compiles IP intelligence from a dozen external sources into binary databases. Bot Detector runs those databases through a two-phase, 17-checker pipeline that scores every incoming request. The IAM canary cookie ties each browser session to a fingerprint that follows it through every subsequent request. This post walks through every layer in detail — how each one works, what data it uses, and what happens when a bot hits the stack.",[820,821],"hr",{},[823,824,826],"h2",{"id":825},"the-three-layers-at-a-glance","The Three Layers at a Glance",[813,828,829],{},"Before going deep on each component, it helps to understand how they relate to one another.",[813,831,832],{},"Shield Base is a build-time tool. You run it once to produce a set of binary database files, then run it again periodically to refresh them. It has no runtime presence — it just produces the files that the other layers consume.",[813,834,835],{},"Bot Detector is a runtime Express middleware. It reads the Shield Base databases at startup and holds them in memory. Every request passes through its pipeline, which scores the request across behavioral, fingerprint, and reputation dimensions. If the score reaches the ban threshold, the middleware short-circuits the request before it touches any application logic.",[813,837,838],{},"The canary cookie is a per-session identifier, issued on first contact and carried on every subsequent request. Bot Detector uses it to track session state across requests — storing timing patterns, path history, and reputation scores keyed on the cookie value. The IAM service uses the same cookie to bind authentication tokens to a specific visitor fingerprint, enabling anomaly detection during token rotation.",[840,841,846],"pre",{"className":842,"code":844,"language":845},[843],"language-text","Shield Base (build time)\n  └── Compiles MMDB + LMDB databases\n        └── Bot Detector (runtime middleware)\n              ├── Cheap phase: 10 synchronous checkers\n              ├── Heavy phase: 7 async checkers\n              └── Issues canary_id cookie on first request\n                    └── IAM service\n                          ├── Binds refresh tokens to canary fingerprint\n                          └── Flags anomalies during rotation\n","text",[847,848,844],"code",{"__ignoreMap":849},"",[820,851],{},[823,853,855],{"id":854},"shield-base-compiling-the-intelligence-layer","Shield Base: Compiling the Intelligence Layer",[813,857,858],{},"Shield Base is a CLI tool that downloads, processes, and compiles external threat intelligence into binary formats that Bot Detector can query in microseconds at runtime. It produces two kinds of output: MMDB files for IP-range lookups and LMDB files for hash-keyed pattern matching.",[860,861,863],"h3",{"id":862},"why-binary-databases","Why Binary Databases",[813,865,866],{},"The raw data that feeds bot detection is enormous. BGP routing tables, geolocation datasets, Tor node lists, FireHOL threat feeds, and user-agent pattern databases together contain hundreds of millions of entries. Querying them naively at runtime is not practical. MMDB (MaxMind DB) encodes IP ranges into a binary trie that resolves any IP to its metadata in a single file seek. LMDB (Lightning Memory-Mapped Database) is a memory-mapped key-value store that delivers zero-copy reads with no serialization overhead. Both formats are loaded once at startup and kept in memory for the lifetime of the process.",[860,868,870],{"id":869},"the-14-data-sources","The 14 Data Sources",[813,872,873],{},"Shield Base downloads and compiles 14 distinct data sources, each targeting a different threat signal.",[813,875,876],{},[877,878,879],"strong",{},"IP reputation and routing",[881,882,883,901],"table",{},[884,885,886],"thead",{},[887,888,889,892,895,898],"tr",{},[890,891,431],"th",{},[890,893,894],{},"Output",[890,896,897],{},"Source",[890,899,900],{},"What it contains",[902,903,904,921,937,953,969,985],"tbody",{},[887,905,906,910,915,918],{},[907,908,909],"td",{},"ASN routing",[907,911,912],{},[847,913,914],{},"asn.mmdb",[907,916,917],{},"bgp.tools",[907,919,920],{},"Autonomous system numbers, ISP classification, network visibility",[887,922,923,926,931,934],{},[907,924,925],{},"City geolocation",[907,927,928],{},[847,929,930],{},"city.mmdb",[907,932,933],{},"MaxMind Geofeed",[907,935,936],{},"IP-to-city mappings with coordinates, timezone, and subdivision",[887,938,939,942,947,950],{},[907,940,941],{},"Country\u002Fgeography",[907,943,944],{},[847,945,946],{},"country.mmdb",[907,948,949],{},"Sapics ip-location-db",[907,951,952],{},"IPv4-to-country with continent and subregion data",[887,954,955,958,963,966],{},[907,956,957],{},"Proxy detection",[907,959,960],{},[847,961,962],{},"proxy.mmdb",[907,964,965],{},"Custom proxy lists",[907,967,968],{},"Known VPN exit points and proxy server IPs",[887,970,971,974,979,982],{},[907,972,973],{},"Tor nodes",[907,975,976],{},[847,977,978],{},"tor.mmdb",[907,980,981],{},"Torproject Onionoo API",[907,983,984],{},"Active Tor relays classified by role: exit, guard, bad exit",[887,986,987,990,995,998],{},[907,988,989],{},"Verified crawlers",[907,991,992],{},[847,993,994],{},"goodBots.mmdb",[907,996,997],{},"Web crawler domain lists",[907,999,1000],{},"IP ranges belonging to legitimate search engines and SEO crawlers",[813,1002,1003],{},[877,1004,1005],{},"Threat intelligence (FireHOL)",[813,1007,1008],{},"FireHOL maintains multiple threat list tiers. Shield Base compiles all of them into separate MMDB files, which Bot Detector queries independently so that the scoring system can assign different penalty weights to each tier.",[881,1010,1011,1024],{},[884,1012,1013],{},[887,1014,1015,1018,1021],{},[890,1016,1017],{},"Level",[890,1019,1020],{},"File",[890,1022,1023],{},"What it tracks",[902,1025,1026,1039,1052,1065,1078],{},[887,1027,1028,1031,1036],{},[907,1029,1030],{},"L1",[907,1032,1033],{},[847,1034,1035],{},"firehol_l1.mmdb",[907,1037,1038],{},"Current attacks — minimum false positives, maximum severity",[887,1040,1041,1044,1049],{},[907,1042,1043],{},"L2",[907,1045,1046],{},[847,1047,1048],{},"firehol_l2.mmdb",[907,1050,1051],{},"Attacks observed in the last 48 hours, including dynamic IPs",[887,1053,1054,1057,1062],{},[907,1055,1056],{},"L3",[907,1058,1059],{},[847,1060,1061],{},"firehol_l3.mmdb",[907,1063,1064],{},"Attacks, spyware, and viruses tracked over the last 30 days",[887,1066,1067,1070,1075],{},[907,1068,1069],{},"L4",[907,1071,1072],{},[847,1073,1074],{},"firehol_l4.mmdb",[907,1076,1077],{},"Aggressive tracking with a higher false-positive rate",[887,1079,1080,1083,1088],{},[907,1081,1082],{},"Anonymous",[907,1084,1085],{},[847,1086,1087],{},"firehol_anonymous.mmdb",[907,1089,1090],{},"Tor exit nodes, I2P, VPNs, and other anonymity relays",[813,1092,1093],{},[877,1094,1095],{},"Pattern databases (LMDB)",[881,1097,1098,1109],{},[884,1099,1100],{},[887,1101,1102,1104,1107],{},[890,1103,431],{},[890,1105,1106],{},"Directory",[890,1108,900],{},[902,1110,1111,1124],{},[887,1112,1113,1116,1121],{},[907,1114,1115],{},"User-agent patterns",[907,1117,1118],{},[847,1119,1120],{},"useragent-db\u002Fuseragent.mdb",[907,1122,1123],{},"Known bot, scraper, and tool user-agent signatures with severity ratings",[887,1125,1126,1129,1134],{},[907,1127,1128],{},"Disposable emails",[907,1130,1131],{},[847,1132,1133],{},"email-db\u002Fdisposable-emails.mdb",[907,1135,1136],{},"Domain blocklist for temporary and disposable email providers",[860,1138,1140],{"id":1139},"running-shield-base","Running Shield Base",[813,1142,1143,1144,1147],{},"The CLI accepts flags for individual sources or bulk compilation. The ",[847,1145,1146],{},"--parallel"," flag compiles all sources concurrently, which is the standard approach for periodic refreshes.",[1149,1150,1151,1243],"code-group",{},[840,1152,1157],{"className":1153,"code":1154,"filename":1155,"language":1156,"meta":849,"style":849},"language-bash shiki shiki-themes light-plus light-plus dracula","# Compile all sources in parallel\npnpm shield-base --all --parallel\n\n# Compile specific sources\npnpm shield-base --bgp --geo --tor --l1 --l2\n\n# Compile only LMDB pattern databases\npnpm shield-base --useragent --email\n","pnpm","bash",[847,1158,1159,1168,1185,1191,1197,1219,1224,1230],{"__ignoreMap":849},[1160,1161,1164],"span",{"class":1162,"line":1163},"line",1,[1160,1165,1167],{"class":1166},"sghk6","# Compile all sources in parallel\n",[1160,1169,1171,1174,1178,1182],{"class":1162,"line":1170},2,[1160,1172,1155],{"class":1173},"sHOzp",[1160,1175,1177],{"class":1176},"sFB1V"," shield-base",[1160,1179,1181],{"class":1180},"sjR7W"," --all",[1160,1183,1184],{"class":1180}," --parallel\n",[1160,1186,1188],{"class":1162,"line":1187},3,[1160,1189,1190],{"emptyLinePlaceholder":8},"\n",[1160,1192,1194],{"class":1162,"line":1193},4,[1160,1195,1196],{"class":1166},"# Compile specific sources\n",[1160,1198,1200,1202,1204,1207,1210,1213,1216],{"class":1162,"line":1199},5,[1160,1201,1155],{"class":1173},[1160,1203,1177],{"class":1176},[1160,1205,1206],{"class":1180}," --bgp",[1160,1208,1209],{"class":1180}," --geo",[1160,1211,1212],{"class":1180}," --tor",[1160,1214,1215],{"class":1180}," --l1",[1160,1217,1218],{"class":1180}," --l2\n",[1160,1220,1222],{"class":1162,"line":1221},6,[1160,1223,1190],{"emptyLinePlaceholder":8},[1160,1225,1227],{"class":1162,"line":1226},7,[1160,1228,1229],{"class":1166},"# Compile only LMDB pattern databases\n",[1160,1231,1233,1235,1237,1240],{"class":1162,"line":1232},8,[1160,1234,1155],{"class":1173},[1160,1236,1177],{"class":1176},[1160,1238,1239],{"class":1180}," --useragent",[1160,1241,1242],{"class":1180}," --email\n",[840,1244,1247],{"className":1153,"code":1245,"filename":1246,"language":1156,"meta":849,"style":849},"# Compile all sources in parallel\nnpm run shield-base --all --parallel\n\n# Compile specific sources\nnpm run shield-base --bgp --geo --tor --l1 --l2\n\n# Compile only LMDB pattern databases\nnpm run shield-base --useragent --email\n","npm",[847,1248,1249,1253,1266,1270,1274,1292,1296,1300],{"__ignoreMap":849},[1160,1250,1251],{"class":1162,"line":1163},[1160,1252,1167],{"class":1166},[1160,1254,1255,1257,1260,1262,1264],{"class":1162,"line":1170},[1160,1256,1246],{"class":1173},[1160,1258,1259],{"class":1176}," run",[1160,1261,1177],{"class":1176},[1160,1263,1181],{"class":1180},[1160,1265,1184],{"class":1180},[1160,1267,1268],{"class":1162,"line":1187},[1160,1269,1190],{"emptyLinePlaceholder":8},[1160,1271,1272],{"class":1162,"line":1193},[1160,1273,1196],{"class":1166},[1160,1275,1276,1278,1280,1282,1284,1286,1288,1290],{"class":1162,"line":1199},[1160,1277,1246],{"class":1173},[1160,1279,1259],{"class":1176},[1160,1281,1177],{"class":1176},[1160,1283,1206],{"class":1180},[1160,1285,1209],{"class":1180},[1160,1287,1212],{"class":1180},[1160,1289,1215],{"class":1180},[1160,1291,1218],{"class":1180},[1160,1293,1294],{"class":1162,"line":1221},[1160,1295,1190],{"emptyLinePlaceholder":8},[1160,1297,1298],{"class":1162,"line":1226},[1160,1299,1229],{"class":1166},[1160,1301,1302,1304,1306,1308,1310],{"class":1162,"line":1232},[1160,1303,1246],{"class":1173},[1160,1305,1259],{"class":1176},[1160,1307,1177],{"class":1176},[1160,1309,1239],{"class":1180},[1160,1311,1242],{"class":1180},[813,1313,1314,1315,1318,1319,1322],{},"Internally, ",[847,1316,1317],{},"executeAll"," runs 10 compilation tasks in parallel. Each task downloads its source data, processes it into the intermediate format, and compiles it using either the ",[847,1320,1321],{},"mmdbctl"," binary (for MMDB) or the native LMDB Node.js bindings. The output files land in a configured output directory that Bot Detector reads from at startup.",[1324,1325,1326],"note",{},[813,1327,1328],{},"Shield Base requires a valid contact User-Agent for the BGP\u002FASN data fetch from bgp.tools. Configure this in your Shield Base settings before running the first compilation.",[820,1330],{},[823,1332,1334],{"id":1333},"bot-detector-the-two-phase-scoring-pipeline","Bot Detector: The Two-Phase Scoring Pipeline",[813,1336,1337,1338,1341],{},"Bot Detector is a middleware factory. You call ",[847,1339,1340],{},"configuration(config)"," once at startup to register your settings and mount the middleware on your Express router. From that point on, every request passes through the pipeline, accumulates a score, and either continues to the next handler or receives a ban response.",[860,1343,1345],{"id":1344},"loading-the-databases","Loading the Databases",[813,1347,1348,1349,1352],{},"The ",[847,1350,1351],{},"DataSources"," class loads all Shield Base outputs at initialization. It opens 11 MMDB readers (ASN, city, country, good bots, Tor, proxy, and all five FireHOL levels) and 1 LMDB reader (user-agent patterns). It also accepts optional banned and high-risk MMDB files for custom enforcement lists. All readers stay open and memory-resident for the lifetime of the process. There are no per-request file operations — every lookup is an in-memory binary search.",[860,1354,1356],{"id":1355},"scoring-mechanics","Scoring Mechanics",[813,1358,1359,1360,1363,1364,1366],{},"Every request starts with a score of zero. Checkers increment the score when they detect anomalies. The pipeline compares the running total against ",[847,1361,1362],{},"banScore"," (default: 100) after the cheap phase and again after the heavy phase. Reaching ",[847,1365,1362],{}," at any point ends the pipeline immediately and sends a ban response.",[813,1368,1369,1370,1373],{},"Between requests, a reputation healer decrements the stored score by ",[847,1371,1372],{},"restoredReputationPoints"," (default: 10) for every non-banned request. A visitor who accumulated a score of 35 on a suspicious-looking first request will recover to zero across three or four clean subsequent requests, assuming no new checkers fire.",[840,1375,1379],{"className":1376,"code":1377,"language":1378,"meta":849,"style":849},"language-ts shiki shiki-themes light-plus light-plus dracula","\u002F\u002F Default scoring configuration\nawait configuration({\n  banScore: 100,\n  maxScore: 100,\n  restoredReputationPoints: 10,\n  setNewComputedScore: false,\n  \u002F\u002F ...\n})\n","ts",[847,1380,1381,1386,1399,1416,1427,1439,1451,1456],{"__ignoreMap":849},[1160,1382,1383],{"class":1162,"line":1163},[1160,1384,1385],{"class":1166},"\u002F\u002F Default scoring configuration\n",[1160,1387,1388,1392,1395],{"class":1162,"line":1170},[1160,1389,1391],{"class":1390},"sZ328","await",[1160,1393,1394],{"class":1173}," configuration",[1160,1396,1398],{"class":1397},"sDd4n","({\n",[1160,1400,1401,1405,1409,1413],{"class":1162,"line":1187},[1160,1402,1404],{"class":1403},"sjsA6","  banScore",[1160,1406,1408],{"class":1407},"s34zl",":",[1160,1410,1412],{"class":1411},"spgvN"," 100",[1160,1414,1415],{"class":1397},",\n",[1160,1417,1418,1421,1423,1425],{"class":1162,"line":1193},[1160,1419,1420],{"class":1403},"  maxScore",[1160,1422,1408],{"class":1407},[1160,1424,1412],{"class":1411},[1160,1426,1415],{"class":1397},[1160,1428,1429,1432,1434,1437],{"class":1162,"line":1199},[1160,1430,1431],{"class":1403},"  restoredReputationPoints",[1160,1433,1408],{"class":1407},[1160,1435,1436],{"class":1411}," 10",[1160,1438,1415],{"class":1397},[1160,1440,1441,1444,1446,1449],{"class":1162,"line":1221},[1160,1442,1443],{"class":1403},"  setNewComputedScore",[1160,1445,1408],{"class":1407},[1160,1447,1448],{"class":1180}," false",[1160,1450,1415],{"class":1397},[1160,1452,1453],{"class":1162,"line":1226},[1160,1454,1455],{"class":1166},"  \u002F\u002F ...\n",[1160,1457,1458],{"class":1162,"line":1232},[1160,1459,1460],{"class":1397},"})\n",[813,1462,1463,1464,1467],{},"Setting ",[847,1465,1466],{},"setNewComputedScore: false"," (the recommended default) means the detector writes the computed score to the database only when no prior record exists. On subsequent requests, the reputation healer decrements the stored score without recomputing. This prevents a bot that varies its signals slightly between requests from oscillating between high and low scores — it accumulates a record and decays from it.",[860,1469,1471],{"id":1470},"phase-one-the-cheap-checkers","Phase One: The Cheap Checkers",[813,1473,1474,1475,1477],{},"The cheap phase runs 10 synchronous checks. These checks use only in-memory data — parsed request headers, pre-loaded database lookups, and cached session state. They run in microseconds. If the cumulative score reaches ",[847,1476,1362],{}," at any point in this phase, the pipeline stops immediately.",[813,1479,1480,1483],{},[877,1481,1482],{},"1. IP Validation"," — confirms the request carries a parseable, routable IP address. Malformed or missing IPs score 10 points. This catches raw tool invocations that do not set a legitimate source address.",[813,1485,1486,1489,1490,1492,1493,1496,1497,1500],{},[877,1487,1488],{},"2. Good and Bad Bot Verification"," — checks the request's IP against ",[847,1491,994],{},". If the IP belongs to a known crawler, the middleware performs a reverse DNS lookup to verify the IP actually belongs to the claimed crawler domain. A passing DNS check issues ",[847,1494,1495],{},"GOOD_BOT_IDENTIFIED"," and whitelists the request instantly — no further checks run. A failing DNS check (IP on the good-bot list but DNS does not verify) issues ",[847,1498,1499],{},"BAD_BOT_DETECTED"," at 100 points — an instant ban. This checker handles the common impersonation pattern where a bot claims a Google or Bing user-agent from an unrelated hosting IP.",[813,1502,1503,1506,1507,1510],{},[877,1504,1505],{},"3. Browser and Device Fingerprint"," — parses the ",[847,1508,1509],{},"User-Agent"," header and applies penalties for impossible or implausible combinations.",[881,1512,1513,1523],{},[884,1514,1515],{},[887,1516,1517,1520],{},[890,1518,1519],{},"Signal",[890,1521,1522],{},"Penalty",[902,1524,1525,1533,1540,1548,1556,1563,1570,1577,1584],{},[887,1526,1527,1530],{},[907,1528,1529],{},"CLI tool or HTTP library (curl, Python requests, etc.)",[907,1531,1532],{},"100",[887,1534,1535,1538],{},[907,1536,1537],{},"Internet Explorer",[907,1539,1532],{},[887,1541,1542,1545],{},[907,1543,1544],{},"Kali Linux OS",[907,1546,1547],{},"10",[887,1549,1550,1553],{},[907,1551,1552],{},"Impossible browser\u002FOS combination",[907,1554,1555],{},"30",[887,1557,1558,1561],{},[907,1559,1560],{},"Unknown browser type or name",[907,1562,1547],{},[887,1564,1565,1568],{},[907,1566,1567],{},"Desktop device without detectable OS",[907,1569,1547],{},[887,1571,1572,1575],{},[907,1573,1574],{},"Unknown device vendor",[907,1576,1547],{},[887,1578,1579,1582],{},[907,1580,1581],{},"Unknown browser version",[907,1583,1547],{},[887,1585,1586,1589],{},[907,1587,1588],{},"Unknown device model",[907,1590,1591],{},"5",[813,1593,1594,1597,1598,1601,1602,1605,1606,1608],{},[877,1595,1596],{},"4. Locale Map Verification"," — compares the ",[847,1599,1600],{},"Accept-Language"," header against the IP's geolocation country. A browser claiming ",[847,1603,1604],{},"fr-FR"," language from an IP geolocated to South Korea is suspicious. Missing or malformed ",[847,1607,1600],{}," headers score 20 points. A confirmed mismatch between language and geo scores an additional 20 points.",[813,1610,1611,1614],{},[877,1612,1613],{},"5. Known Threats (FireHOL)"," — queries all five FireHOL MMDB files against the request IP. Each tier scores independently, so an IP appearing on multiple lists accumulates points from each.",[881,1616,1617,1626],{},[884,1618,1619],{},[887,1620,1621,1624],{},[890,1622,1623],{},"FireHOL tier",[890,1625,1522],{},[902,1627,1628,1636,1644,1651,1658],{},[887,1629,1630,1633],{},[907,1631,1632],{},"Anonymity network (Tor, VPN, I2P)",[907,1634,1635],{},"20",[887,1637,1638,1641],{},[907,1639,1640],{},"L1 — critical current threats",[907,1642,1643],{},"40",[887,1645,1646,1649],{},[907,1647,1648],{},"L2 — attacks in last 48 hours",[907,1650,1555],{},[887,1652,1653,1656],{},[907,1654,1655],{},"L3 — attacks in last 30 days",[907,1657,1635],{},[887,1659,1660,1663],{},[907,1661,1662],{},"L4 — aggressive tracking",[907,1664,1547],{},[813,1666,1667,1670,1671,1673],{},[877,1668,1669],{},"6. ASN Classification"," — queries ",[847,1672,914],{}," to determine the Autonomous System the IP belongs to. Hosting and datacenter ASNs score 20 points. An ASN with unusually low visibility (few routes announced, below 15% of expected) scores an additional 10 points. The combination of hosting classification and low visibility scores a further 20 — this pattern is characteristic of freshly provisioned bot infrastructure.",[813,1675,1676,1670,1679,1681],{},[877,1677,1678],{},"7. Tor Node Analysis",[847,1680,978],{}," to classify the specific role of any Tor node. Different node types carry different penalties because they represent different risk profiles.",[881,1683,1684,1693],{},[884,1685,1686],{},[887,1687,1688,1691],{},[890,1689,1690],{},"Tor node type",[890,1692,1522],{},[902,1694,1695,1703,1710,1718,1725,1732,1739],{},[887,1696,1697,1700],{},[907,1698,1699],{},"Active running node",[907,1701,1702],{},"15",[887,1704,1705,1708],{},[907,1706,1707],{},"Exit node (base)",[907,1709,1635],{},[887,1711,1712,1715],{},[907,1713,1714],{},"Exit node (exit probability multiplier, up to +30)",[907,1716,1717],{},"dynamic",[887,1719,1720,1723],{},[907,1721,1722],{},"Web-capable exit node",[907,1724,1702],{},[887,1726,1727,1730],{},[907,1728,1729],{},"Guard node",[907,1731,1547],{},[887,1733,1734,1737],{},[907,1735,1736],{},"Bad exit (flagged by Tor directory)",[907,1738,1643],{},[887,1740,1741,1744],{},[907,1742,1743],{},"Obsolete version",[907,1745,1547],{},[813,1747,1748],{},"A high-probability exit node that is also flagged as a bad exit and running an obsolete version can accumulate 90 points from Tor analysis alone — enough to ban when combined with even minor signals from other checkers.",[813,1750,1751,1597,1754,1757],{},[877,1752,1753],{},"8. Timezone Consistency",[847,1755,1756],{},"Timezone"," request header against the timezone inferred from the IP's geolocation. A browser reporting a Central European timezone from an IP geolocated to Hong Kong scores 20 points.",[813,1759,1760,1763],{},[877,1761,1762],{},"9. Honeypot"," — checks the request path against a configurable list of trap URLs. Any request to a honeypot path scores an immediate ban. Legitimate users never visit URLs that are not linked anywhere in the application. Only crawlers following harvested or guessed paths hit them.",[813,1765,1766,1769,1770,1773,1774,1777],{},[877,1767,1768],{},"10. Known Bad IPs"," — queries optional ",[847,1771,1772],{},"banned.mmdb"," and ",[847,1775,1776],{},"highRisk.mmdb"," files you maintain independently. Previously banned IPs score an instant ban. High-risk IPs score 30 points. This checker enables you to carry forward enforcement decisions across restarts and import external blocklists.",[860,1779,1781],{"id":1780},"phase-two-the-heavy-checkers","Phase Two: The Heavy Checkers",[813,1783,1784],{},"The heavy phase runs only if the cheap phase did not trigger a ban. These seven checks require async operations — cache reads, timing calculations, database queries, and header analysis. They are deferred to the second phase because they are more expensive.",[813,1786,1787,1790,1791,1794],{},[877,1788,1789],{},"11. Behavior Rate Verification"," — counts requests from this ",[847,1792,1793],{},"canary_id"," within a sliding window (default: 60 seconds, threshold: 30 requests). Exceeding the threshold scores 60 points. Unlike a simple IP-based rate limiter, this checker tracks per-session request rates. A bot that uses many IPs but reuses the same session cookie still triggers it.",[813,1796,1797,1800],{},[877,1798,1799],{},"12. Proxy, ISP, and Cookie Verification"," — combines several signals into a single checker.",[881,1802,1803,1811],{},[884,1804,1805],{},[887,1806,1807,1809],{},[890,1808,1519],{},[890,1810,1522],{},[902,1812,1813,1824,1834,1842,1850,1858,1865],{},[887,1814,1815,1821],{},[907,1816,1817,1818,1820],{},"Missing ",[847,1819,1793],{}," cookie",[907,1822,1823],{},"80",[887,1825,1826,1832],{},[907,1827,1828,1829,1831],{},"Proxy detected (from ",[847,1830,962],{},")",[907,1833,1643],{},[887,1835,1836,1839],{},[907,1837,1838],{},"Multi-source proxy confirmation (2-3 sources)",[907,1840,1841],{},"+10",[887,1843,1844,1847],{},[907,1845,1846],{},"Multi-source proxy confirmation (4+ sources)",[907,1848,1849],{},"+20",[887,1851,1852,1855],{},[907,1853,1854],{},"Hosting provider detected",[907,1856,1857],{},"50",[887,1859,1860,1863],{},[907,1861,1862],{},"Unknown ISP",[907,1864,1547],{},[887,1866,1867,1870],{},[907,1868,1869],{},"Unknown ORG",[907,1871,1547],{},[813,1873,1348,1874,1876],{},[847,1875,1793],{}," cookie check is the single highest-penalty individual signal in the pipeline at 80 points. Any request that does not carry a cookie is one triggering event away from a ban. This matters because the cookie is set on the very first request — a missing cookie on a subsequent request means either the client is rejecting cookies (a strong bot signal) or the request is coming from a tool that does not preserve session state.",[813,1878,1879,1882,1883,1885,1886,1889],{},[877,1880,1881],{},"13. Session Coherence"," — uses the ",[847,1884,1793],{}," to retrieve the session's last known path from the session cache, then validates the incoming request's ",[847,1887,1888],{},"Referer"," header.",[881,1891,1892,1900],{},[884,1893,1894],{},[887,1895,1896,1898],{},[890,1897,1519],{},[890,1899,1522],{},[902,1901,1902,1914,1923],{},[887,1903,1904,1912],{},[907,1905,1817,1906,1908,1909,1831],{},[847,1907,1888],{}," on a same-origin request (",[847,1910,1911],{},"Sec-Fetch-Site: same-origin",[907,1913,1635],{},[887,1915,1916,1921],{},[907,1917,1918,1920],{},[847,1919,1888],{}," domain does not match the application domain",[907,1922,1555],{},[887,1924,1925,1930],{},[907,1926,1927,1929],{},[847,1928,1888],{}," path does not match the recorded last path",[907,1931,1547],{},[813,1933,1934,1935,1937],{},"Real browsers send a ",[847,1936,1888],{}," header when navigating within the same origin. Tools and scrapers that issue requests directly do not. A bot that correctly spoofs headers but does not correctly maintain session path history fails this check across multiple requests.",[813,1939,1940,1943],{},[877,1941,1942],{},"14. Velocity Fingerprinting"," — collects timestamps for the last 10 requests from this session (minimum 5 required to evaluate) and computes the coefficient of variation (CV) of the inter-request intervals. The CV measures the relative variability of a set of values — a CV near zero means all intervals are nearly identical, which is characteristic of programmatic request scheduling.",[840,1945,1948],{"className":1946,"code":1947,"language":845},[843],"CV = standard deviation \u002F mean\n\nCV \u003C 0.1 → timing too regular → penalty: 40\n",[847,1949,1947],{"__ignoreMap":849},[813,1951,1952],{},"Human browsing intervals are naturally irregular. Page load times, reading time, and click latency all vary. A bot that fires requests on a fixed timer — even a slow one — produces a CV far below the 0.1 threshold.",[813,1954,1955,1958],{},[877,1956,1957],{},"15. User-Agent and Header Analysis"," — extends the cheap-phase fingerprint check with deeper inspection.",[881,1960,1961,1969],{},[884,1962,1963],{},[887,1964,1965,1967],{},[890,1966,1519],{},[890,1968,1522],{},[902,1970,1971,1978,1985,1993,2000],{},[887,1972,1973,1976],{},[907,1974,1975],{},"Headless browser detected (Puppeteer, Selenium, Playwright, PhantomJS)",[907,1977,1532],{},[887,1979,1980,1983],{},[907,1981,1982],{},"User-agent shorter than 10 characters",[907,1984,1823],{},[887,1986,1987,1990],{},[907,1988,1989],{},"Header anomaly score too high",[907,1991,1992],{},"variable",[887,1994,1995,1998],{},[907,1996,1997],{},"Path traversal attempt detected",[907,1999,1992],{},[887,2001,2002,2005],{},[907,2003,2004],{},"XSS scripting attempt detected",[907,2006,1992],{},[813,2008,2009,2012],{},[877,2010,2011],{},"16. Geolocation Validation"," — penalizes missing geolocation data across nine dimensions: country, region, city, latitude\u002Flongitude, timezone, subregion, phone prefix, district, and continent. Each missing dimension scores 10 points. A request from an IP with no geolocation coverage can accumulate up to 90 points from this checker alone, making it trivially over the ban threshold when combined with any other signal. The checker also supports a configurable banned-country list.",[813,2014,2015,1670,2018,2021],{},[877,2016,2017],{},"17. Known Bad User-Agents",[847,2019,2020],{},"useragent.mdb"," against the full user-agent string. The LMDB database stores patterns compiled from community-maintained lists of bot and scraper signatures, each rated by severity.",[881,2023,2024,2033],{},[884,2025,2026],{},[887,2027,2028,2031],{},[890,2029,2030],{},"Severity",[890,2032,1522],{},[902,2034,2035,2042,2049,2056],{},[887,2036,2037,2040],{},[907,2038,2039],{},"Critical",[907,2041,1532],{},[887,2043,2044,2047],{},[907,2045,2046],{},"High",[907,2048,1823],{},[887,2050,2051,2054],{},[907,2052,2053],{},"Medium",[907,2055,1555],{},[887,2057,2058,2061],{},[907,2059,2060],{},"Low",[907,2062,1547],{},[820,2064],{},[823,2066,2068],{"id":2067},"the-canary-cookie-bridging-sessions","The Canary Cookie: Bridging Sessions",[813,2070,1348,2071,2073,2074,2077],{},[847,2072,1793],{}," cookie is issued by the ",[847,2075,2076],{},"canaryCookieChecker"," middleware on the very first request from any browser. Its value is a 64-character hex string generated from 32 cryptographically random bytes.",[840,2079,2081],{"className":1376,"code":2080,"language":1378,"meta":849,"style":849},"randomBytes(32).toString('hex')\n\u002F\u002F Example: \"a3f8e2c1d4b7a90f...\"  (64 hex characters)\n",[847,2082,2083,2114],{"__ignoreMap":849},[1160,2084,2085,2088,2091,2094,2097,2100,2102,2106,2109,2111],{"class":1162,"line":1163},[1160,2086,2087],{"class":1173},"randomBytes",[1160,2089,2090],{"class":1397},"(",[1160,2092,2093],{"class":1411},"32",[1160,2095,2096],{"class":1397},").",[1160,2098,2099],{"class":1173},"toString",[1160,2101,2090],{"class":1397},[1160,2103,2105],{"class":2104},"sFkSl","'",[1160,2107,2108],{"class":1176},"hex",[1160,2110,2105],{"class":2104},[1160,2112,2113],{"class":1397},")\n",[1160,2115,2116],{"class":1162,"line":1170},[1160,2117,2118],{"class":1166},"\u002F\u002F Example: \"a3f8e2c1d4b7a90f...\"  (64 hex characters)\n",[813,2120,2121],{},"The cookie itself is opaque — it carries no embedded data and cannot be decoded. All the meaningful state lives server-side, keyed on the cookie value.",[860,2123,2125],{"id":2124},"cookie-attributes","Cookie Attributes",[840,2127,2130],{"className":2128,"code":2129,"language":845},[843],"name:      canary_id\nhttpOnly:  true\nsameSite:  lax\nsecure:    true\npath:      \u002F\nmaxAge:    7,776,000,000 ms  (90 days)\n",[847,2131,2129],{"__ignoreMap":849},[813,2133,1348,2134,2137],{},[847,2135,2136],{},"httpOnly"," attribute prevents JavaScript from reading the cookie, blocking the class of attacks where a page script exfiltrates the cookie and reuses it from a different client. The 90-day maxAge matches the outer boundary for legitimate long-running sessions.",[860,2139,2141],{"id":2140},"what-the-server-stores","What the Server Stores",[813,2143,2144,2145,2147],{},"When Bot Detector issues a ",[847,2146,1793],{},", it begins building a persistent record keyed on that value. This record accumulates across every subsequent request.",[813,2149,2150],{},[877,2151,2152],{},"Visitor record (database, persistent):",[840,2154,2156],{"className":1376,"code":2155,"language":1378,"meta":849,"style":849},"{\n  visitorId: UUID,\n  cookie: canary_id,\n  userAgent: string,\n  ipAddress: string,\n  device_type: string,\n  browser: string,\n  is_bot: boolean,\n  first_seen: timestamp,\n  last_seen: timestamp,\n  request_count: number,\n  deviceVendor: string,\n  deviceModel: string,\n  browserType: string,\n  browserVersion: string,\n  os: string,\n  activity_score: number,\n  country: string,\n  region: string,\n  city: string,\n  timezone: string,\n  \u002F\u002F ...additional geolocation fields\n}\n",[847,2157,2158,2163,2174,2183,2193,2202,2211,2220,2230,2241,2251,2262,2272,2282,2292,2302,2312,2322,2332,2342,2352,2362,2368],{"__ignoreMap":849},[1160,2159,2160],{"class":1162,"line":1163},[1160,2161,2162],{"class":1397},"{\n",[1160,2164,2165,2168,2172],{"class":1162,"line":1170},[1160,2166,2167],{"class":1397},"  visitorId: ",[1160,2169,2171],{"class":2170},"s3JHE","UUID",[1160,2173,1415],{"class":1397},[1160,2175,2176,2179,2181],{"class":1162,"line":1187},[1160,2177,2178],{"class":1397},"  cookie: ",[1160,2180,1793],{"class":1403},[1160,2182,1415],{"class":1397},[1160,2184,2185,2188,2191],{"class":1162,"line":1193},[1160,2186,2187],{"class":1397},"  userAgent: ",[1160,2189,2190],{"class":1403},"string",[1160,2192,1415],{"class":1397},[1160,2194,2195,2198,2200],{"class":1162,"line":1199},[1160,2196,2197],{"class":1397},"  ipAddress: ",[1160,2199,2190],{"class":1403},[1160,2201,1415],{"class":1397},[1160,2203,2204,2207,2209],{"class":1162,"line":1221},[1160,2205,2206],{"class":1397},"  device_type: ",[1160,2208,2190],{"class":1403},[1160,2210,1415],{"class":1397},[1160,2212,2213,2216,2218],{"class":1162,"line":1226},[1160,2214,2215],{"class":1397},"  browser: ",[1160,2217,2190],{"class":1403},[1160,2219,1415],{"class":1397},[1160,2221,2222,2225,2228],{"class":1162,"line":1232},[1160,2223,2224],{"class":1397},"  is_bot: ",[1160,2226,2227],{"class":1403},"boolean",[1160,2229,1415],{"class":1397},[1160,2231,2233,2236,2239],{"class":1162,"line":2232},9,[1160,2234,2235],{"class":1397},"  first_seen: ",[1160,2237,2238],{"class":1403},"timestamp",[1160,2240,1415],{"class":1397},[1160,2242,2244,2247,2249],{"class":1162,"line":2243},10,[1160,2245,2246],{"class":1397},"  last_seen: ",[1160,2248,2238],{"class":1403},[1160,2250,1415],{"class":1397},[1160,2252,2254,2257,2260],{"class":1162,"line":2253},11,[1160,2255,2256],{"class":1397},"  request_count: ",[1160,2258,2259],{"class":1403},"number",[1160,2261,1415],{"class":1397},[1160,2263,2265,2268,2270],{"class":1162,"line":2264},12,[1160,2266,2267],{"class":1397},"  deviceVendor: ",[1160,2269,2190],{"class":1403},[1160,2271,1415],{"class":1397},[1160,2273,2275,2278,2280],{"class":1162,"line":2274},13,[1160,2276,2277],{"class":1397},"  deviceModel: ",[1160,2279,2190],{"class":1403},[1160,2281,1415],{"class":1397},[1160,2283,2285,2288,2290],{"class":1162,"line":2284},14,[1160,2286,2287],{"class":1397},"  browserType: ",[1160,2289,2190],{"class":1403},[1160,2291,1415],{"class":1397},[1160,2293,2295,2298,2300],{"class":1162,"line":2294},15,[1160,2296,2297],{"class":1397},"  browserVersion: ",[1160,2299,2190],{"class":1403},[1160,2301,1415],{"class":1397},[1160,2303,2305,2308,2310],{"class":1162,"line":2304},16,[1160,2306,2307],{"class":1397},"  os: ",[1160,2309,2190],{"class":1403},[1160,2311,1415],{"class":1397},[1160,2313,2315,2318,2320],{"class":1162,"line":2314},17,[1160,2316,2317],{"class":1397},"  activity_score: ",[1160,2319,2259],{"class":1403},[1160,2321,1415],{"class":1397},[1160,2323,2325,2328,2330],{"class":1162,"line":2324},18,[1160,2326,2327],{"class":1397},"  country: ",[1160,2329,2190],{"class":1403},[1160,2331,1415],{"class":1397},[1160,2333,2335,2338,2340],{"class":1162,"line":2334},19,[1160,2336,2337],{"class":1397},"  region: ",[1160,2339,2190],{"class":1403},[1160,2341,1415],{"class":1397},[1160,2343,2345,2348,2350],{"class":1162,"line":2344},20,[1160,2346,2347],{"class":1397},"  city: ",[1160,2349,2190],{"class":1403},[1160,2351,1415],{"class":1397},[1160,2353,2355,2358,2360],{"class":1162,"line":2354},21,[1160,2356,2357],{"class":1397},"  timezone: ",[1160,2359,2190],{"class":1403},[1160,2361,1415],{"class":1397},[1160,2363,2365],{"class":1162,"line":2364},22,[1160,2366,2367],{"class":1166},"  \u002F\u002F ...additional geolocation fields\n",[1160,2369,2371],{"class":1162,"line":2370},23,[1160,2372,2373],{"class":1397},"}\n",[813,2375,2376],{},[877,2377,2378],{},"In-memory caches (fast lookup per request):",[881,2380,2381,2394],{},[884,2382,2383],{},[887,2384,2385,2388,2391],{},[890,2386,2387],{},"Cache",[890,2389,2390],{},"Key",[890,2392,2393],{},"What it holds",[902,2395,2396,2413,2430,2447,2461,2478],{},[887,2397,2398,2403,2407],{},[907,2399,2400],{},[847,2401,2402],{},"visitorCache",[907,2404,2405],{},[847,2406,1793],{},[907,2408,2409,2412],{},[847,2410,2411],{},"{ banned, visitor_id }"," — fast ban lookup",[887,2414,2415,2420,2424],{},[907,2416,2417],{},[847,2418,2419],{},"sessionCache",[907,2421,2422],{},[847,2423,1793],{},[907,2425,2426,2429],{},[847,2427,2428],{},"{ lastPath }"," — session coherence tracking",[887,2431,2432,2437,2441],{},[907,2433,2434],{},[847,2435,2436],{},"rateCache",[907,2438,2439],{},[847,2440,1793],{},[907,2442,2443,2446],{},[847,2444,2445],{},"{ score, timestamp, request_count }"," — behavioral rate",[887,2448,2449,2454,2458],{},[907,2450,2451],{},[847,2452,2453],{},"timingCache",[907,2455,2456],{},[847,2457,1793],{},[907,2459,2460],{},"Array of last 10 request timestamps — velocity fingerprint",[887,2462,2463,2468,2472],{},[907,2464,2465],{},[847,2466,2467],{},"reputationCache",[907,2469,2470],{},[847,2471,1793],{},[907,2473,2474,2477],{},[847,2475,2476],{},"{ isBot, score }"," — reputation healer state",[887,2479,2480,2485,2488],{},[907,2481,2482],{},[847,2483,2484],{},"dnsCache",[907,2486,2487],{},"IP",[907,2489,2490,2493],{},[847,2491,2492],{},"{ ip, trustedBot }"," — verified crawler result",[813,2495,2496],{},"The split between the persistent database record and the in-memory caches is intentional. The database record survives restarts and is queryable for analytics. The in-memory caches are ephemeral but fast — they hold exactly the data the pipeline needs per request, without deserializing a full database row.",[860,2498,2500],{"id":2499},"the-canary-cookie-in-the-iam-service","The Canary Cookie in the IAM Service",[813,2502,2503],{},"The IAM service runs Bot Detector as part of its own middleware chain. Every request to the IAM service — login, logout, token rotation, MFA — passes through the same 17-checker pipeline before reaching any authentication logic.",[813,2505,2506,2507,2509,2510,2513,2514,2516],{},"When Bot Detector passes a request through, the IAM service reads the ",[847,2508,1793],{}," cookie and stores it alongside the refresh token family for that session. The ",[847,2511,2512],{},"strangeThings()"," anomaly detection function, which runs during every token rotation attempt, includes a ",[847,2515,1793],{}," binding check as one of its nine sequential verifications.",[813,2518,2519,2520,2522,2523,2525],{},"If the ",[847,2521,1793],{}," on a rotation request does not match the one recorded when the session was originally created, the anomaly detector triggers. Depending on the severity, it either sends an MFA challenge to the user's email or revokes the session entirely. This means an attacker who steals a valid refresh token but makes the rotation request from a different device — one with a different ",[847,2524,1793],{}," — cannot complete the rotation without also accessing the user's email.",[820,2527],{},[823,2529,2531],{"id":2530},"walking-through-a-bot-request","Walking Through a Bot Request",[813,2533,2534],{},"To make the pipeline concrete, here is what happens when a credential-stuffing bot attempts a login.",[813,2536,2537,2538,2541,2542,2545],{},"The bot sends a ",[847,2539,2540],{},"POST \u002Fauth\u002Fuser\u002Flogin"," request with a valid email and password combination. It uses a Python ",[847,2543,2544],{},"requests"," library with a spoofed user-agent string, from a residential proxy pool. It sends one request every 4 seconds on a fixed timer.",[813,2547,2548],{},[877,2549,2550],{},"Cheap phase results:",[2552,2553,2554,2558,2561,2572,2578,2581],"ul",{},[2555,2556,2557],"li",{},"IP Validation: passes (valid IPv4).",[2555,2559,2560],{},"Good\u002FBad Bot: IP is not on the good-bot list. No instant ban.",[2555,2562,2563,2564,2567,2568,2571],{},"Browser and Device Fingerprint: The user-agent parses as Chrome, but the library headers are subtly wrong — no ",[847,2565,2566],{},"sec-ch-ua"," header family, no ",[847,2569,2570],{},"sec-fetch-*"," headers. Unknown browser type: +10. Impossible header combination: +30. Running total: 40.",[2555,2573,2574,2575,2577],{},"Locale Map: The ",[847,2576,1600],{}," header is missing. +20. Running total: 60.",[2555,2579,2580],{},"Known Threats: The residential proxy IP happens to appear on the FireHOL L3 list (a 30-day tracked threat). +20. Running total: 80.",[2555,2582,2583],{},"ASN Classification: The proxy's ASN is classified as hosting with low visibility. +20 + +10. Running total exceeds 100.",[813,2585,2586,2589],{},[877,2587,2588],{},"The pipeline stops at the cheap phase."," The request receives a 403 response before the login handler runs. No database query for the user record. No password check. No rate limiter on the login endpoint needs to absorb the request.",[813,2591,2592],{},"Now consider a more sophisticated bot — one that uses a real browser, a real residential IP, and carefully spoofs all headers. The cheap phase may score only 10-20 points.",[813,2594,2595],{},[877,2596,2597],{},"Heavy phase results:",[2552,2599,2600,2603,2613],{},[2555,2601,2602],{},"Behavior Rate: The bot fires at exactly 4-second intervals. After 5 requests, the velocity fingerprint computes CV = 0.02. +40. Running total: 50-60.",[2555,2604,2605,2606,2609,2610,2612],{},"Session Coherence: The bot navigates directly to ",[847,2607,2608],{},"\u002Fauth\u002Fuser\u002Flogin"," without going through the home page first. The ",[847,2611,1888],{}," header is absent on what looks like same-origin navigation. +20. Running total: 70-80.",[2555,2614,2615],{},"User-Agent and Header Analysis: Header mismatch and lack of acceptable HTTP configurations indicate automated access. +60. Running total: 130+.",[813,2617,2618,2621],{},[877,2619,2620],{},"The pipeline stops at the heavy phase."," Even a well-configured bot that passes the cheap phase reveals itself through timing regularity, navigation patterns, and header analysis.",[820,2623],{},[823,2625,204],{"id":2626},"configuration",[813,2628,2629],{},"A realistic Bot Detector configuration that enables the full pipeline looks like this:",[840,2631,2633],{"className":1376,"code":2632,"language":1378,"meta":849,"style":849},"import { configuration } from 'bot-detector'\n\nawait configuration({\n  store: {\n    main: { driver: 'sqlite', name: '.\u002Fbot-detector.db' }\n  },\n\n  banScore: 100,\n  maxScore: 100,\n  restoredReputationPoints: 10,\n  setNewComputedScore: false,\n\n  whiteList: ['203.0.113.0\u002F24'],\n\n  checkers: {\n    enableIpChecks: { enable: true, penalties: 10 },\n\n    enableGoodBotsChecks: {\n      enable: true,\n      banUnlistedBots: true,\n      penalties: 100\n    },\n\n    enableBrowserAndDeviceChecks: { enable: true },\n\n    localeMapsCheck: { enable: true },\n\n    enableKnownThreatsDetections: {\n      enable: true,\n      penalties: {\n        anonymityNetwork: 20,\n        fireholL1: 40,\n        fireholL2: 30,\n        fireholL3: 20,\n        fireholL4: 10\n      }\n    },\n\n    enableAsnClassification: { enable: true },\n\n    enableTorAnalysis: { enable: true },\n\n    enableTimezoneConsistency: { enable: true },\n\n    honeypot: {\n      enable: true,\n      paths: ['\u002Fadmin', '\u002F.env', '\u002Fwp-login.php', '\u002Fxmlrpc.php']\n    },\n\n    enableKnownBadIpsCheck: { enable: true },\n\n    enableBehaviorRateCheck: {\n      enable: true,\n      behavioral_window: 60_000,\n      behavioral_threshold: 30,\n      penalties: 60\n    },\n\n    enableProxyIspCookiesChecks: { enable: true },\n\n    enableSessionCoherence: { enable: true },\n\n    enableVelocityFingerprint: {\n      enable: true,\n      cvThreshold: 0.1\n    },\n\n    enableUaAndHeaderChecks: { enable: true },\n\n    enableGeoChecks: {\n      enable: true,\n      bannedCountries: []\n    },\n\n    knownBadUserAgents: { enable: true }\n  }\n})\n",[847,2634,2635,2660,2664,2672,2682,2721,2726,2730,2740,2750,2760,2770,2774,2794,2798,2807,2836,2840,2849,2860,2871,2881,2886,2890,2908,2913,2931,2936,2946,2957,2966,2979,2992,3005,3017,3028,3034,3039,3044,3062,3067,3085,3090,3108,3113,3123,3134,3181,3186,3191,3209,3214,3224,3235,3248,3260,3270,3275,3280,3298,3303,3321,3326,3336,3347,3358,3363,3368,3386,3391,3401,3412,3423,3428,3433,3451,3457],{"__ignoreMap":849},[1160,2636,2637,2640,2643,2645,2648,2651,2654,2657],{"class":1162,"line":1163},[1160,2638,2639],{"class":1390},"import",[1160,2641,2642],{"class":1397}," { ",[1160,2644,2626],{"class":1403},[1160,2646,2647],{"class":1397}," } ",[1160,2649,2650],{"class":1390},"from",[1160,2652,2653],{"class":2104}," '",[1160,2655,2656],{"class":1176},"bot-detector",[1160,2658,2659],{"class":2104},"'\n",[1160,2661,2662],{"class":1162,"line":1170},[1160,2663,1190],{"emptyLinePlaceholder":8},[1160,2665,2666,2668,2670],{"class":1162,"line":1187},[1160,2667,1391],{"class":1390},[1160,2669,1394],{"class":1173},[1160,2671,1398],{"class":1397},[1160,2673,2674,2677,2679],{"class":1162,"line":1193},[1160,2675,2676],{"class":1403},"  store",[1160,2678,1408],{"class":1407},[1160,2680,2681],{"class":1397}," {\n",[1160,2683,2684,2687,2689,2691,2694,2696,2698,2701,2703,2706,2709,2711,2713,2716,2718],{"class":1162,"line":1199},[1160,2685,2686],{"class":1403},"    main",[1160,2688,1408],{"class":1407},[1160,2690,2642],{"class":1397},[1160,2692,2693],{"class":1403},"driver",[1160,2695,1408],{"class":1407},[1160,2697,2653],{"class":2104},[1160,2699,2700],{"class":1176},"sqlite",[1160,2702,2105],{"class":2104},[1160,2704,2705],{"class":1397},", ",[1160,2707,2708],{"class":1403},"name",[1160,2710,1408],{"class":1407},[1160,2712,2653],{"class":2104},[1160,2714,2715],{"class":1176},".\u002Fbot-detector.db",[1160,2717,2105],{"class":2104},[1160,2719,2720],{"class":1397}," }\n",[1160,2722,2723],{"class":1162,"line":1221},[1160,2724,2725],{"class":1397},"  },\n",[1160,2727,2728],{"class":1162,"line":1226},[1160,2729,1190],{"emptyLinePlaceholder":8},[1160,2731,2732,2734,2736,2738],{"class":1162,"line":1232},[1160,2733,1404],{"class":1403},[1160,2735,1408],{"class":1407},[1160,2737,1412],{"class":1411},[1160,2739,1415],{"class":1397},[1160,2741,2742,2744,2746,2748],{"class":1162,"line":2232},[1160,2743,1420],{"class":1403},[1160,2745,1408],{"class":1407},[1160,2747,1412],{"class":1411},[1160,2749,1415],{"class":1397},[1160,2751,2752,2754,2756,2758],{"class":1162,"line":2243},[1160,2753,1431],{"class":1403},[1160,2755,1408],{"class":1407},[1160,2757,1436],{"class":1411},[1160,2759,1415],{"class":1397},[1160,2761,2762,2764,2766,2768],{"class":1162,"line":2253},[1160,2763,1443],{"class":1403},[1160,2765,1408],{"class":1407},[1160,2767,1448],{"class":1180},[1160,2769,1415],{"class":1397},[1160,2771,2772],{"class":1162,"line":2264},[1160,2773,1190],{"emptyLinePlaceholder":8},[1160,2775,2776,2779,2781,2784,2786,2789,2791],{"class":1162,"line":2274},[1160,2777,2778],{"class":1403},"  whiteList",[1160,2780,1408],{"class":1407},[1160,2782,2783],{"class":1397}," [",[1160,2785,2105],{"class":2104},[1160,2787,2788],{"class":1176},"203.0.113.0\u002F24",[1160,2790,2105],{"class":2104},[1160,2792,2793],{"class":1397},"],\n",[1160,2795,2796],{"class":1162,"line":2284},[1160,2797,1190],{"emptyLinePlaceholder":8},[1160,2799,2800,2803,2805],{"class":1162,"line":2294},[1160,2801,2802],{"class":1403},"  checkers",[1160,2804,1408],{"class":1407},[1160,2806,2681],{"class":1397},[1160,2808,2809,2812,2814,2816,2819,2821,2824,2826,2829,2831,2833],{"class":1162,"line":2304},[1160,2810,2811],{"class":1403},"    enableIpChecks",[1160,2813,1408],{"class":1407},[1160,2815,2642],{"class":1397},[1160,2817,2818],{"class":1403},"enable",[1160,2820,1408],{"class":1407},[1160,2822,2823],{"class":1180}," true",[1160,2825,2705],{"class":1397},[1160,2827,2828],{"class":1403},"penalties",[1160,2830,1408],{"class":1407},[1160,2832,1436],{"class":1411},[1160,2834,2835],{"class":1397}," },\n",[1160,2837,2838],{"class":1162,"line":2314},[1160,2839,1190],{"emptyLinePlaceholder":8},[1160,2841,2842,2845,2847],{"class":1162,"line":2324},[1160,2843,2844],{"class":1403},"    enableGoodBotsChecks",[1160,2846,1408],{"class":1407},[1160,2848,2681],{"class":1397},[1160,2850,2851,2854,2856,2858],{"class":1162,"line":2334},[1160,2852,2853],{"class":1403},"      enable",[1160,2855,1408],{"class":1407},[1160,2857,2823],{"class":1180},[1160,2859,1415],{"class":1397},[1160,2861,2862,2865,2867,2869],{"class":1162,"line":2344},[1160,2863,2864],{"class":1403},"      banUnlistedBots",[1160,2866,1408],{"class":1407},[1160,2868,2823],{"class":1180},[1160,2870,1415],{"class":1397},[1160,2872,2873,2876,2878],{"class":1162,"line":2354},[1160,2874,2875],{"class":1403},"      penalties",[1160,2877,1408],{"class":1407},[1160,2879,2880],{"class":1411}," 100\n",[1160,2882,2883],{"class":1162,"line":2364},[1160,2884,2885],{"class":1397},"    },\n",[1160,2887,2888],{"class":1162,"line":2370},[1160,2889,1190],{"emptyLinePlaceholder":8},[1160,2891,2893,2896,2898,2900,2902,2904,2906],{"class":1162,"line":2892},24,[1160,2894,2895],{"class":1403},"    enableBrowserAndDeviceChecks",[1160,2897,1408],{"class":1407},[1160,2899,2642],{"class":1397},[1160,2901,2818],{"class":1403},[1160,2903,1408],{"class":1407},[1160,2905,2823],{"class":1180},[1160,2907,2835],{"class":1397},[1160,2909,2911],{"class":1162,"line":2910},25,[1160,2912,1190],{"emptyLinePlaceholder":8},[1160,2914,2916,2919,2921,2923,2925,2927,2929],{"class":1162,"line":2915},26,[1160,2917,2918],{"class":1403},"    localeMapsCheck",[1160,2920,1408],{"class":1407},[1160,2922,2642],{"class":1397},[1160,2924,2818],{"class":1403},[1160,2926,1408],{"class":1407},[1160,2928,2823],{"class":1180},[1160,2930,2835],{"class":1397},[1160,2932,2934],{"class":1162,"line":2933},27,[1160,2935,1190],{"emptyLinePlaceholder":8},[1160,2937,2939,2942,2944],{"class":1162,"line":2938},28,[1160,2940,2941],{"class":1403},"    enableKnownThreatsDetections",[1160,2943,1408],{"class":1407},[1160,2945,2681],{"class":1397},[1160,2947,2949,2951,2953,2955],{"class":1162,"line":2948},29,[1160,2950,2853],{"class":1403},[1160,2952,1408],{"class":1407},[1160,2954,2823],{"class":1180},[1160,2956,1415],{"class":1397},[1160,2958,2960,2962,2964],{"class":1162,"line":2959},30,[1160,2961,2875],{"class":1403},[1160,2963,1408],{"class":1407},[1160,2965,2681],{"class":1397},[1160,2967,2969,2972,2974,2977],{"class":1162,"line":2968},31,[1160,2970,2971],{"class":1403},"        anonymityNetwork",[1160,2973,1408],{"class":1407},[1160,2975,2976],{"class":1411}," 20",[1160,2978,1415],{"class":1397},[1160,2980,2982,2985,2987,2990],{"class":1162,"line":2981},32,[1160,2983,2984],{"class":1403},"        fireholL1",[1160,2986,1408],{"class":1407},[1160,2988,2989],{"class":1411}," 40",[1160,2991,1415],{"class":1397},[1160,2993,2995,2998,3000,3003],{"class":1162,"line":2994},33,[1160,2996,2997],{"class":1403},"        fireholL2",[1160,2999,1408],{"class":1407},[1160,3001,3002],{"class":1411}," 30",[1160,3004,1415],{"class":1397},[1160,3006,3008,3011,3013,3015],{"class":1162,"line":3007},34,[1160,3009,3010],{"class":1403},"        fireholL3",[1160,3012,1408],{"class":1407},[1160,3014,2976],{"class":1411},[1160,3016,1415],{"class":1397},[1160,3018,3020,3023,3025],{"class":1162,"line":3019},35,[1160,3021,3022],{"class":1403},"        fireholL4",[1160,3024,1408],{"class":1407},[1160,3026,3027],{"class":1411}," 10\n",[1160,3029,3031],{"class":1162,"line":3030},36,[1160,3032,3033],{"class":1397},"      }\n",[1160,3035,3037],{"class":1162,"line":3036},37,[1160,3038,2885],{"class":1397},[1160,3040,3042],{"class":1162,"line":3041},38,[1160,3043,1190],{"emptyLinePlaceholder":8},[1160,3045,3047,3050,3052,3054,3056,3058,3060],{"class":1162,"line":3046},39,[1160,3048,3049],{"class":1403},"    enableAsnClassification",[1160,3051,1408],{"class":1407},[1160,3053,2642],{"class":1397},[1160,3055,2818],{"class":1403},[1160,3057,1408],{"class":1407},[1160,3059,2823],{"class":1180},[1160,3061,2835],{"class":1397},[1160,3063,3065],{"class":1162,"line":3064},40,[1160,3066,1190],{"emptyLinePlaceholder":8},[1160,3068,3070,3073,3075,3077,3079,3081,3083],{"class":1162,"line":3069},41,[1160,3071,3072],{"class":1403},"    enableTorAnalysis",[1160,3074,1408],{"class":1407},[1160,3076,2642],{"class":1397},[1160,3078,2818],{"class":1403},[1160,3080,1408],{"class":1407},[1160,3082,2823],{"class":1180},[1160,3084,2835],{"class":1397},[1160,3086,3088],{"class":1162,"line":3087},42,[1160,3089,1190],{"emptyLinePlaceholder":8},[1160,3091,3093,3096,3098,3100,3102,3104,3106],{"class":1162,"line":3092},43,[1160,3094,3095],{"class":1403},"    enableTimezoneConsistency",[1160,3097,1408],{"class":1407},[1160,3099,2642],{"class":1397},[1160,3101,2818],{"class":1403},[1160,3103,1408],{"class":1407},[1160,3105,2823],{"class":1180},[1160,3107,2835],{"class":1397},[1160,3109,3111],{"class":1162,"line":3110},44,[1160,3112,1190],{"emptyLinePlaceholder":8},[1160,3114,3116,3119,3121],{"class":1162,"line":3115},45,[1160,3117,3118],{"class":1403},"    honeypot",[1160,3120,1408],{"class":1407},[1160,3122,2681],{"class":1397},[1160,3124,3126,3128,3130,3132],{"class":1162,"line":3125},46,[1160,3127,2853],{"class":1403},[1160,3129,1408],{"class":1407},[1160,3131,2823],{"class":1180},[1160,3133,1415],{"class":1397},[1160,3135,3137,3140,3142,3144,3146,3149,3151,3153,3155,3158,3160,3162,3164,3167,3169,3171,3173,3176,3178],{"class":1162,"line":3136},47,[1160,3138,3139],{"class":1403},"      paths",[1160,3141,1408],{"class":1407},[1160,3143,2783],{"class":1397},[1160,3145,2105],{"class":2104},[1160,3147,3148],{"class":1176},"\u002Fadmin",[1160,3150,2105],{"class":2104},[1160,3152,2705],{"class":1397},[1160,3154,2105],{"class":2104},[1160,3156,3157],{"class":1176},"\u002F.env",[1160,3159,2105],{"class":2104},[1160,3161,2705],{"class":1397},[1160,3163,2105],{"class":2104},[1160,3165,3166],{"class":1176},"\u002Fwp-login.php",[1160,3168,2105],{"class":2104},[1160,3170,2705],{"class":1397},[1160,3172,2105],{"class":2104},[1160,3174,3175],{"class":1176},"\u002Fxmlrpc.php",[1160,3177,2105],{"class":2104},[1160,3179,3180],{"class":1397},"]\n",[1160,3182,3184],{"class":1162,"line":3183},48,[1160,3185,2885],{"class":1397},[1160,3187,3189],{"class":1162,"line":3188},49,[1160,3190,1190],{"emptyLinePlaceholder":8},[1160,3192,3194,3197,3199,3201,3203,3205,3207],{"class":1162,"line":3193},50,[1160,3195,3196],{"class":1403},"    enableKnownBadIpsCheck",[1160,3198,1408],{"class":1407},[1160,3200,2642],{"class":1397},[1160,3202,2818],{"class":1403},[1160,3204,1408],{"class":1407},[1160,3206,2823],{"class":1180},[1160,3208,2835],{"class":1397},[1160,3210,3212],{"class":1162,"line":3211},51,[1160,3213,1190],{"emptyLinePlaceholder":8},[1160,3215,3217,3220,3222],{"class":1162,"line":3216},52,[1160,3218,3219],{"class":1403},"    enableBehaviorRateCheck",[1160,3221,1408],{"class":1407},[1160,3223,2681],{"class":1397},[1160,3225,3227,3229,3231,3233],{"class":1162,"line":3226},53,[1160,3228,2853],{"class":1403},[1160,3230,1408],{"class":1407},[1160,3232,2823],{"class":1180},[1160,3234,1415],{"class":1397},[1160,3236,3238,3241,3243,3246],{"class":1162,"line":3237},54,[1160,3239,3240],{"class":1403},"      behavioral_window",[1160,3242,1408],{"class":1407},[1160,3244,3245],{"class":1411}," 60_000",[1160,3247,1415],{"class":1397},[1160,3249,3251,3254,3256,3258],{"class":1162,"line":3250},55,[1160,3252,3253],{"class":1403},"      behavioral_threshold",[1160,3255,1408],{"class":1407},[1160,3257,3002],{"class":1411},[1160,3259,1415],{"class":1397},[1160,3261,3263,3265,3267],{"class":1162,"line":3262},56,[1160,3264,2875],{"class":1403},[1160,3266,1408],{"class":1407},[1160,3268,3269],{"class":1411}," 60\n",[1160,3271,3273],{"class":1162,"line":3272},57,[1160,3274,2885],{"class":1397},[1160,3276,3278],{"class":1162,"line":3277},58,[1160,3279,1190],{"emptyLinePlaceholder":8},[1160,3281,3283,3286,3288,3290,3292,3294,3296],{"class":1162,"line":3282},59,[1160,3284,3285],{"class":1403},"    enableProxyIspCookiesChecks",[1160,3287,1408],{"class":1407},[1160,3289,2642],{"class":1397},[1160,3291,2818],{"class":1403},[1160,3293,1408],{"class":1407},[1160,3295,2823],{"class":1180},[1160,3297,2835],{"class":1397},[1160,3299,3301],{"class":1162,"line":3300},60,[1160,3302,1190],{"emptyLinePlaceholder":8},[1160,3304,3306,3309,3311,3313,3315,3317,3319],{"class":1162,"line":3305},61,[1160,3307,3308],{"class":1403},"    enableSessionCoherence",[1160,3310,1408],{"class":1407},[1160,3312,2642],{"class":1397},[1160,3314,2818],{"class":1403},[1160,3316,1408],{"class":1407},[1160,3318,2823],{"class":1180},[1160,3320,2835],{"class":1397},[1160,3322,3324],{"class":1162,"line":3323},62,[1160,3325,1190],{"emptyLinePlaceholder":8},[1160,3327,3329,3332,3334],{"class":1162,"line":3328},63,[1160,3330,3331],{"class":1403},"    enableVelocityFingerprint",[1160,3333,1408],{"class":1407},[1160,3335,2681],{"class":1397},[1160,3337,3339,3341,3343,3345],{"class":1162,"line":3338},64,[1160,3340,2853],{"class":1403},[1160,3342,1408],{"class":1407},[1160,3344,2823],{"class":1180},[1160,3346,1415],{"class":1397},[1160,3348,3350,3353,3355],{"class":1162,"line":3349},65,[1160,3351,3352],{"class":1403},"      cvThreshold",[1160,3354,1408],{"class":1407},[1160,3356,3357],{"class":1411}," 0.1\n",[1160,3359,3361],{"class":1162,"line":3360},66,[1160,3362,2885],{"class":1397},[1160,3364,3366],{"class":1162,"line":3365},67,[1160,3367,1190],{"emptyLinePlaceholder":8},[1160,3369,3371,3374,3376,3378,3380,3382,3384],{"class":1162,"line":3370},68,[1160,3372,3373],{"class":1403},"    enableUaAndHeaderChecks",[1160,3375,1408],{"class":1407},[1160,3377,2642],{"class":1397},[1160,3379,2818],{"class":1403},[1160,3381,1408],{"class":1407},[1160,3383,2823],{"class":1180},[1160,3385,2835],{"class":1397},[1160,3387,3389],{"class":1162,"line":3388},69,[1160,3390,1190],{"emptyLinePlaceholder":8},[1160,3392,3394,3397,3399],{"class":1162,"line":3393},70,[1160,3395,3396],{"class":1403},"    enableGeoChecks",[1160,3398,1408],{"class":1407},[1160,3400,2681],{"class":1397},[1160,3402,3404,3406,3408,3410],{"class":1162,"line":3403},71,[1160,3405,2853],{"class":1403},[1160,3407,1408],{"class":1407},[1160,3409,2823],{"class":1180},[1160,3411,1415],{"class":1397},[1160,3413,3415,3418,3420],{"class":1162,"line":3414},72,[1160,3416,3417],{"class":1403},"      bannedCountries",[1160,3419,1408],{"class":1407},[1160,3421,3422],{"class":1397}," []\n",[1160,3424,3426],{"class":1162,"line":3425},73,[1160,3427,2885],{"class":1397},[1160,3429,3431],{"class":1162,"line":3430},74,[1160,3432,1190],{"emptyLinePlaceholder":8},[1160,3434,3436,3439,3441,3443,3445,3447,3449],{"class":1162,"line":3435},75,[1160,3437,3438],{"class":1403},"    knownBadUserAgents",[1160,3440,1408],{"class":1407},[1160,3442,2642],{"class":1397},[1160,3444,2818],{"class":1403},[1160,3446,1408],{"class":1407},[1160,3448,2823],{"class":1180},[1160,3450,2720],{"class":1397},[1160,3452,3454],{"class":1162,"line":3453},76,[1160,3455,3456],{"class":1397},"  }\n",[1160,3458,3460],{"class":1162,"line":3459},77,[1160,3461,1460],{"class":1397},[3463,3464,3465],"tip",{},[813,3466,3467],{},"Start with the cheap-phase checkers at conservative penalty values and raise them after observing traffic patterns. The FireHOL L4 level and ASN low-visibility penalties are the most likely to produce false positives on legitimate traffic from cloud-heavy regions.",[820,3469],{},[823,3471,3473],{"id":3472},"extending-the-pipeline-custom-checkers","Extending the Pipeline: Custom Checkers",[813,3475,3476,3477,3480],{},"Every built-in checker follows the same interface, and you can add your own with the exact same mechanism. The pipeline does not distinguish between built-in and custom checkers at runtime — they share the same scoring accumulation, the same short-circuit logic, and the same ",[847,3478,3479],{},"ValidationContext",".",[860,3482,1348,3484,3487],{"id":3483},"the-ibotchecker-interface",[847,3485,3486],{},"IBotChecker"," Interface",[813,3489,3490,3491,3493,3494,3496],{},"A checker is a class that implements ",[847,3492,3486],{},". It declares which phase it belongs to, a condition that enables or disables it, and a ",[847,3495,636],{}," method that returns a numeric score and an array of reason codes.",[840,3498,3500],{"className":1376,"code":3499,"language":1378,"meta":849,"style":849},"interface IBotChecker\u003CCode, TCustom = Record\u003Cstring, never>> {\n  name: string;\n  phase: 'cheap' | 'heavy';\n  isEnabled(config: BotDetectorConfig): boolean;\n  run(ctx: ValidationContext\u003CTCustom>, config: BotDetectorConfig):\n    | Promise\u003C{ score: number; reasons: Code[] }>\n    | { score: number; reasons: Code[] };\n}\n",[847,3501,3502,3543,3556,3582,3607,3640,3673,3690],{"__ignoreMap":849},[1160,3503,3504,3508,3512,3515,3519,3521,3524,3528,3531,3533,3535,3537,3540],{"class":1162,"line":1163},[1160,3505,3507],{"class":3506},"sl46w","interface",[1160,3509,3511],{"class":3510},"sFs1U"," IBotChecker",[1160,3513,3514],{"class":1397},"\u003C",[1160,3516,3518],{"class":3517},"sW-rI","Code",[1160,3520,2705],{"class":1397},[1160,3522,3523],{"class":3517},"TCustom",[1160,3525,3527],{"class":3526},"saOXh"," =",[1160,3529,3530],{"class":3517}," Record",[1160,3532,3514],{"class":1397},[1160,3534,2190],{"class":3510},[1160,3536,2705],{"class":1397},[1160,3538,3539],{"class":3510},"never",[1160,3541,3542],{"class":1397},">> {\n",[1160,3544,3545,3548,3550,3553],{"class":1162,"line":1170},[1160,3546,3547],{"class":1403},"  name",[1160,3549,1408],{"class":3526},[1160,3551,3552],{"class":3510}," string",[1160,3554,3555],{"class":1397},";\n",[1160,3557,3558,3561,3563,3565,3568,3570,3573,3575,3578,3580],{"class":1162,"line":1187},[1160,3559,3560],{"class":1403},"  phase",[1160,3562,1408],{"class":3526},[1160,3564,2653],{"class":2104},[1160,3566,3567],{"class":1176},"cheap",[1160,3569,2105],{"class":2104},[1160,3571,3572],{"class":3526}," |",[1160,3574,2653],{"class":2104},[1160,3576,3577],{"class":1176},"heavy",[1160,3579,2105],{"class":2104},[1160,3581,3555],{"class":1397},[1160,3583,3584,3587,3589,3593,3595,3598,3600,3602,3605],{"class":1162,"line":1193},[1160,3585,3586],{"class":1173},"  isEnabled",[1160,3588,2090],{"class":1397},[1160,3590,3592],{"class":3591},"sygFZ","config",[1160,3594,1408],{"class":3526},[1160,3596,3597],{"class":3510}," BotDetectorConfig",[1160,3599,1831],{"class":1397},[1160,3601,1408],{"class":3526},[1160,3603,3604],{"class":3510}," boolean",[1160,3606,3555],{"class":1397},[1160,3608,3609,3612,3614,3617,3619,3622,3624,3626,3629,3631,3633,3635,3637],{"class":1162,"line":1199},[1160,3610,3611],{"class":1173},"  run",[1160,3613,2090],{"class":1397},[1160,3615,3616],{"class":3591},"ctx",[1160,3618,1408],{"class":3526},[1160,3620,3621],{"class":3510}," ValidationContext",[1160,3623,3514],{"class":1397},[1160,3625,3523],{"class":3517},[1160,3627,3628],{"class":1397},">, ",[1160,3630,3592],{"class":3591},[1160,3632,1408],{"class":3526},[1160,3634,3597],{"class":3510},[1160,3636,1831],{"class":1397},[1160,3638,3639],{"class":3526},":\n",[1160,3641,3642,3645,3648,3651,3654,3656,3659,3662,3665,3667,3670],{"class":1162,"line":1221},[1160,3643,3644],{"class":3526},"    |",[1160,3646,3647],{"class":3510}," Promise",[1160,3649,3650],{"class":1397},"\u003C{ ",[1160,3652,3653],{"class":1403},"score",[1160,3655,1408],{"class":3526},[1160,3657,3658],{"class":3510}," number",[1160,3660,3661],{"class":1397},"; ",[1160,3663,3664],{"class":1403},"reasons",[1160,3666,1408],{"class":3526},[1160,3668,3669],{"class":3517}," Code",[1160,3671,3672],{"class":1397},"[] }>\n",[1160,3674,3675,3677,3680,3682,3685,3687],{"class":1162,"line":1226},[1160,3676,3644],{"class":3526},[1160,3678,3679],{"class":1397}," { score: ",[1160,3681,2259],{"class":1403},[1160,3683,3684],{"class":1397},"; reasons: ",[1160,3686,3518],{"class":1403},[1160,3688,3689],{"class":1397},"[] };\n",[1160,3691,3692],{"class":1162,"line":1232},[1160,3693,2373],{"class":1397},[813,3695,1348,3696,3698],{},[847,3697,636],{}," method can be synchronous or async. Phase assignment is the only routing decision you make — everything else is handled by the pipeline.",[860,3700,3702],{"id":3701},"what-the-pipeline-gives-you","What the Pipeline Gives You",[813,3704,3705,3706,3708,3709,3711],{},"Before your ",[847,3707,636],{}," method executes, the pipeline has already resolved every expensive lookup. All of this is available on ",[847,3710,3616],{}," at zero cost:",[881,3713,3714,3724],{},[884,3715,3716],{},[887,3717,3718,3721],{},[890,3719,3720],{},"Field",[890,3722,3723],{},"Contents",[902,3725,3726,3736,3746,3762,3772,3784,3806,3827,3840,3854,3867],{},[887,3727,3728,3733],{},[907,3729,3730],{},[847,3731,3732],{},"ctx.req",[907,3734,3735],{},"Full Express request (headers, path, cookies, method)",[887,3737,3738,3743],{},[907,3739,3740],{},[847,3741,3742],{},"ctx.ipAddress",[907,3744,3745],{},"Resolved client IP",[887,3747,3748,3753],{},[907,3749,3750],{},[847,3751,3752],{},"ctx.cookie",[907,3754,3755,3757,3758,3761],{},[847,3756,1793],{}," value, or ",[847,3759,3760],{},"undefined"," on first request",[887,3763,3764,3769],{},[907,3765,3766],{},[847,3767,3768],{},"ctx.geoData",[907,3770,3771],{},"Merged country, city, ASN, and proxy data",[887,3773,3774,3779],{},[907,3775,3776],{},[847,3777,3778],{},"ctx.tor",[907,3780,3781,3782],{},"Tor relay classification from ",[847,3783,978],{},[887,3785,3786,3791],{},[907,3787,3788],{},[847,3789,3790],{},"ctx.bgp",[907,3792,3793,3794,2705,3797,2705,3800,2705,3803],{},"ASN routing data: ",[847,3795,3796],{},"asn_id",[847,3798,3799],{},"asn_name",[847,3801,3802],{},"classification",[847,3804,3805],{},"hits",[887,3807,3808,3813],{},[907,3809,3810],{},[847,3811,3812],{},"ctx.threatLevel",[907,3814,3815,3816,3819,3820,3823,3824],{},"Highest FireHOL tier matched (",[847,3817,3818],{},"1","–",[847,3821,3822],{},"4","), or ",[847,3825,3826],{},"null",[887,3828,3829,3834],{},[907,3830,3831],{},[847,3832,3833],{},"ctx.anon",[907,3835,3836,3839],{},[847,3837,3838],{},"true"," if IP is in the anonymity network database",[887,3841,3842,3847],{},[907,3843,3844],{},[847,3845,3846],{},"ctx.parsedUA",[907,3848,3849,3850,3853],{},"Parsed user-agent: browser, OS, device, ",[847,3851,3852],{},"browserType",", bot flags",[887,3855,3856,3861],{},[907,3857,3858],{},[847,3859,3860],{},"ctx.proxy",[907,3862,3863,3866],{},[847,3864,3865],{},"{ isProxy, proxyType }"," from proxy MMDB",[887,3868,3869,3874],{},[907,3870,3871],{},[847,3872,3873],{},"ctx.custom",[907,3875,3876,3877],{},"Your own per-request data, populated by ",[847,3878,3879],{},"buildCustomContext",[813,3881,3882,3885,3886,3889,3890,3893],{},[847,3883,3884],{},"ctx.bgp.classification"," is worth highlighting. The value ",[847,3887,3888],{},"\"Content\""," means the ASN is classified as a hosting or CDN network. ",[847,3891,3892],{},"\"Eyeballs\""," means residential or business internet. This single field lets a custom checker apply completely different logic for datacenter traffic versus consumer traffic without any additional lookup.",[860,3895,3897],{"id":3896},"a-minimal-cheap-checker","A Minimal Cheap Checker",[813,3899,3900,3901,3903],{},"The example below penalises requests from a datacenter ASN that carry no ",[847,3902,1600],{}," header — a pattern common in automated clients that partially spoof browser headers but miss the locale details.",[840,3905,3908],{"className":1376,"code":3906,"filename":3907,"language":1378,"meta":849,"style":849},"import { CheckerRegistry } from '@riavzon\u002Fbot-detector';\nimport type { IBotChecker, ValidationContext, BotDetectorConfig } from '@riavzon\u002Fbot-detector';\n\ntype Code = 'DATACENTER_NO_LOCALE' | 'BAD_BOT_DETECTED';\n\nclass DatacenterLocaleChecker implements IBotChecker\u003CCode> {\n  name = 'DatacenterLocaleChecker';\n  phase = 'cheap' as const;\n\n  isEnabled(_config: BotDetectorConfig): boolean {\n    return true;\n  }\n\n  run(ctx: ValidationContext, _config: BotDetectorConfig) {\n    const reasons: Code[] = [];\n    let score = 0;\n\n    const isHosting = ctx.bgp.classification === 'Content';\n    const hasLocale = Boolean(ctx.req.get('Accept-Language'));\n\n    if (isHosting && !hasLocale) {\n      score += 25;\n      reasons.push('DATACENTER_NO_LOCALE');\n    }\n\n    return { score, reasons };\n  }\n}\n\nCheckerRegistry.register(new DatacenterLocaleChecker());\n","datacenter-locale-checker.ts",[847,3909,3910,3932,3964,3968,3994,3998,4019,4034,4054,4058,4079,4088,4092,4096,4119,4140,4155,4159,4192,4229,4233,4255,4268,4289,4294,4298,4313,4317,4321,4325],{"__ignoreMap":849},[1160,3911,3912,3914,3916,3919,3921,3923,3925,3928,3930],{"class":1162,"line":1163},[1160,3913,2639],{"class":1390},[1160,3915,2642],{"class":1397},[1160,3917,3918],{"class":1403},"CheckerRegistry",[1160,3920,2647],{"class":1397},[1160,3922,2650],{"class":1390},[1160,3924,2653],{"class":2104},[1160,3926,3927],{"class":1176},"@riavzon\u002Fbot-detector",[1160,3929,2105],{"class":2104},[1160,3931,3555],{"class":1397},[1160,3933,3934,3936,3939,3941,3943,3945,3947,3949,3952,3954,3956,3958,3960,3962],{"class":1162,"line":1170},[1160,3935,2639],{"class":1390},[1160,3937,3938],{"class":1390}," type",[1160,3940,2642],{"class":1397},[1160,3942,3486],{"class":1403},[1160,3944,2705],{"class":1397},[1160,3946,3479],{"class":1403},[1160,3948,2705],{"class":1397},[1160,3950,3951],{"class":1403},"BotDetectorConfig",[1160,3953,2647],{"class":1397},[1160,3955,2650],{"class":1390},[1160,3957,2653],{"class":2104},[1160,3959,3927],{"class":1176},[1160,3961,2105],{"class":2104},[1160,3963,3555],{"class":1397},[1160,3965,3966],{"class":1162,"line":1187},[1160,3967,1190],{"emptyLinePlaceholder":8},[1160,3969,3970,3973,3975,3977,3979,3982,3984,3986,3988,3990,3992],{"class":1162,"line":1193},[1160,3971,3972],{"class":3506},"type",[1160,3974,3669],{"class":3510},[1160,3976,3527],{"class":3526},[1160,3978,2653],{"class":2104},[1160,3980,3981],{"class":1176},"DATACENTER_NO_LOCALE",[1160,3983,2105],{"class":2104},[1160,3985,3572],{"class":3526},[1160,3987,2653],{"class":2104},[1160,3989,1499],{"class":1176},[1160,3991,2105],{"class":2104},[1160,3993,3555],{"class":1397},[1160,3995,3996],{"class":1162,"line":1199},[1160,3997,1190],{"emptyLinePlaceholder":8},[1160,3999,4000,4003,4007,4010,4012,4014,4016],{"class":1162,"line":1221},[1160,4001,4002],{"class":3506},"class",[1160,4004,4006],{"class":4005},"s5jk-"," DatacenterLocaleChecker",[1160,4008,4009],{"class":3506}," implements",[1160,4011,3511],{"class":3510},[1160,4013,3514],{"class":1397},[1160,4015,3518],{"class":3517},[1160,4017,4018],{"class":1397},"> {\n",[1160,4020,4021,4023,4025,4027,4030,4032],{"class":1162,"line":1226},[1160,4022,3547],{"class":1403},[1160,4024,3527],{"class":3526},[1160,4026,2653],{"class":2104},[1160,4028,4029],{"class":1176},"DatacenterLocaleChecker",[1160,4031,2105],{"class":2104},[1160,4033,3555],{"class":1397},[1160,4035,4036,4038,4040,4042,4044,4046,4049,4052],{"class":1162,"line":1232},[1160,4037,3560],{"class":1403},[1160,4039,3527],{"class":3526},[1160,4041,2653],{"class":2104},[1160,4043,3567],{"class":1176},[1160,4045,2105],{"class":2104},[1160,4047,4048],{"class":1390}," as",[1160,4050,4051],{"class":3506}," const",[1160,4053,3555],{"class":1397},[1160,4055,4056],{"class":1162,"line":2232},[1160,4057,1190],{"emptyLinePlaceholder":8},[1160,4059,4060,4062,4064,4067,4069,4071,4073,4075,4077],{"class":1162,"line":2243},[1160,4061,3586],{"class":1173},[1160,4063,2090],{"class":1397},[1160,4065,4066],{"class":3591},"_config",[1160,4068,1408],{"class":3526},[1160,4070,3597],{"class":3510},[1160,4072,1831],{"class":1397},[1160,4074,1408],{"class":3526},[1160,4076,3604],{"class":3510},[1160,4078,2681],{"class":1397},[1160,4080,4081,4084,4086],{"class":1162,"line":2253},[1160,4082,4083],{"class":1390},"    return",[1160,4085,2823],{"class":1180},[1160,4087,3555],{"class":1397},[1160,4089,4090],{"class":1162,"line":2264},[1160,4091,3456],{"class":1397},[1160,4093,4094],{"class":1162,"line":2274},[1160,4095,1190],{"emptyLinePlaceholder":8},[1160,4097,4098,4100,4102,4104,4106,4108,4110,4112,4114,4116],{"class":1162,"line":2284},[1160,4099,3611],{"class":1173},[1160,4101,2090],{"class":1397},[1160,4103,3616],{"class":3591},[1160,4105,1408],{"class":3526},[1160,4107,3621],{"class":3510},[1160,4109,2705],{"class":1397},[1160,4111,4066],{"class":3591},[1160,4113,1408],{"class":3526},[1160,4115,3597],{"class":3510},[1160,4117,4118],{"class":1397},") {\n",[1160,4120,4121,4124,4127,4129,4131,4134,4137],{"class":1162,"line":2294},[1160,4122,4123],{"class":3506},"    const",[1160,4125,4126],{"class":2170}," reasons",[1160,4128,1408],{"class":3526},[1160,4130,3669],{"class":3510},[1160,4132,4133],{"class":1397},"[] ",[1160,4135,4136],{"class":3526},"=",[1160,4138,4139],{"class":1397}," [];\n",[1160,4141,4142,4145,4148,4150,4153],{"class":1162,"line":2304},[1160,4143,4144],{"class":3506},"    let",[1160,4146,4147],{"class":1403}," score",[1160,4149,3527],{"class":3526},[1160,4151,4152],{"class":1411}," 0",[1160,4154,3555],{"class":1397},[1160,4156,4157],{"class":1162,"line":2314},[1160,4158,1190],{"emptyLinePlaceholder":8},[1160,4160,4161,4163,4166,4168,4171,4173,4176,4178,4180,4183,4185,4188,4190],{"class":1162,"line":2324},[1160,4162,4123],{"class":3506},[1160,4164,4165],{"class":2170}," isHosting",[1160,4167,3527],{"class":3526},[1160,4169,4170],{"class":1403}," ctx",[1160,4172,3480],{"class":1397},[1160,4174,4175],{"class":1403},"bgp",[1160,4177,3480],{"class":1397},[1160,4179,3802],{"class":1403},[1160,4181,4182],{"class":3526}," ===",[1160,4184,2653],{"class":2104},[1160,4186,4187],{"class":1176},"Content",[1160,4189,2105],{"class":2104},[1160,4191,3555],{"class":1397},[1160,4193,4194,4196,4199,4201,4204,4206,4208,4210,4213,4215,4218,4220,4222,4224,4226],{"class":1162,"line":2334},[1160,4195,4123],{"class":3506},[1160,4197,4198],{"class":2170}," hasLocale",[1160,4200,3527],{"class":3526},[1160,4202,4203],{"class":1173}," Boolean",[1160,4205,2090],{"class":1397},[1160,4207,3616],{"class":1403},[1160,4209,3480],{"class":1397},[1160,4211,4212],{"class":1403},"req",[1160,4214,3480],{"class":1397},[1160,4216,4217],{"class":1173},"get",[1160,4219,2090],{"class":1397},[1160,4221,2105],{"class":2104},[1160,4223,1600],{"class":1176},[1160,4225,2105],{"class":2104},[1160,4227,4228],{"class":1397},"));\n",[1160,4230,4231],{"class":1162,"line":2344},[1160,4232,1190],{"emptyLinePlaceholder":8},[1160,4234,4235,4238,4241,4244,4247,4250,4253],{"class":1162,"line":2354},[1160,4236,4237],{"class":1390},"    if",[1160,4239,4240],{"class":1397}," (",[1160,4242,4243],{"class":1403},"isHosting",[1160,4245,4246],{"class":3526}," &&",[1160,4248,4249],{"class":3526}," !",[1160,4251,4252],{"class":1403},"hasLocale",[1160,4254,4118],{"class":1397},[1160,4256,4257,4260,4263,4266],{"class":1162,"line":2364},[1160,4258,4259],{"class":1403},"      score",[1160,4261,4262],{"class":3526}," +=",[1160,4264,4265],{"class":1411}," 25",[1160,4267,3555],{"class":1397},[1160,4269,4270,4273,4275,4278,4280,4282,4284,4286],{"class":1162,"line":2370},[1160,4271,4272],{"class":1403},"      reasons",[1160,4274,3480],{"class":1397},[1160,4276,4277],{"class":1173},"push",[1160,4279,2090],{"class":1397},[1160,4281,2105],{"class":2104},[1160,4283,3981],{"class":1176},[1160,4285,2105],{"class":2104},[1160,4287,4288],{"class":1397},");\n",[1160,4290,4291],{"class":1162,"line":2892},[1160,4292,4293],{"class":1397},"    }\n",[1160,4295,4296],{"class":1162,"line":2910},[1160,4297,1190],{"emptyLinePlaceholder":8},[1160,4299,4300,4302,4304,4306,4308,4310],{"class":1162,"line":2915},[1160,4301,4083],{"class":1390},[1160,4303,2642],{"class":1397},[1160,4305,3653],{"class":1403},[1160,4307,2705],{"class":1397},[1160,4309,3664],{"class":1403},[1160,4311,4312],{"class":1397}," };\n",[1160,4314,4315],{"class":1162,"line":2933},[1160,4316,3456],{"class":1397},[1160,4318,4319],{"class":1162,"line":2938},[1160,4320,2373],{"class":1397},[1160,4322,4323],{"class":1162,"line":2948},[1160,4324,1190],{"emptyLinePlaceholder":8},[1160,4326,4327,4329,4331,4334,4336,4340,4342],{"class":1162,"line":2959},[1160,4328,3918],{"class":1403},[1160,4330,3480],{"class":1397},[1160,4332,4333],{"class":1173},"register",[1160,4335,2090],{"class":1397},[1160,4337,4339],{"class":4338},"sakC6","new",[1160,4341,4006],{"class":1173},[1160,4343,4344],{"class":1397},"());\n",[813,4346,4347],{},"Registration happens at module load time. A side-effect import in your server entry point is enough to activate the checker. Import order controls execution order within each phase.",[840,4349,4352],{"className":1376,"code":4350,"filename":4351,"language":1378,"meta":849,"style":849},"import { defineConfiguration, detectBots } from '@riavzon\u002Fbot-detector';\nimport '.\u002Fdatacenter-locale-checker.js'; \u002F\u002F registers on import\n\nawait defineConfiguration({ \u002F* ... *\u002F });\napp.use(detectBots());\n","server.ts",[847,4353,4354,4380,4396,4400,4416],{"__ignoreMap":849},[1160,4355,4356,4358,4360,4363,4365,4368,4370,4372,4374,4376,4378],{"class":1162,"line":1163},[1160,4357,2639],{"class":1390},[1160,4359,2642],{"class":1397},[1160,4361,4362],{"class":1403},"defineConfiguration",[1160,4364,2705],{"class":1397},[1160,4366,4367],{"class":1403},"detectBots",[1160,4369,2647],{"class":1397},[1160,4371,2650],{"class":1390},[1160,4373,2653],{"class":2104},[1160,4375,3927],{"class":1176},[1160,4377,2105],{"class":2104},[1160,4379,3555],{"class":1397},[1160,4381,4382,4384,4386,4389,4391,4393],{"class":1162,"line":1170},[1160,4383,2639],{"class":1390},[1160,4385,2653],{"class":2104},[1160,4387,4388],{"class":1176},".\u002Fdatacenter-locale-checker.js",[1160,4390,2105],{"class":2104},[1160,4392,3661],{"class":1397},[1160,4394,4395],{"class":1166},"\u002F\u002F registers on import\n",[1160,4397,4398],{"class":1162,"line":1187},[1160,4399,1190],{"emptyLinePlaceholder":8},[1160,4401,4402,4404,4407,4410,4413],{"class":1162,"line":1193},[1160,4403,1391],{"class":1390},[1160,4405,4406],{"class":1173}," defineConfiguration",[1160,4408,4409],{"class":1397},"({ ",[1160,4411,4412],{"class":1166},"\u002F* ... *\u002F",[1160,4414,4415],{"class":1397}," });\n",[1160,4417,4418,4421,4423,4426,4428,4430],{"class":1162,"line":1199},[1160,4419,4420],{"class":1403},"app",[1160,4422,3480],{"class":1397},[1160,4424,4425],{"class":1173},"use",[1160,4427,2090],{"class":1397},[1160,4429,4367],{"class":1173},[1160,4431,4344],{"class":1397},[860,4433,4435],{"id":4434},"passing-application-context-into-checkers","Passing Application Context Into Checkers",[813,4437,1348,4438,4440,4441,4443,4444,1773,4446,4448,4449,4451,4452,3480],{},[847,4439,3879],{}," function runs once per request before any checker executes. It receives the raw Express request and returns the ",[847,4442,3873],{}," object. Passing the generic type through to ",[847,4445,3486],{},[847,4447,3479],{}," gives full IntelliSense on ",[847,4450,3873],{}," inside ",[847,4453,636],{},[840,4455,4457],{"className":1376,"code":4456,"filename":4351,"language":1378,"meta":849,"style":849},"interface MyContext {\n  userId: string;\n  plan: 'free' | 'pro' | 'enterprise';\n  isInternal: boolean;\n}\n\napp.use(\n  detectBots\u003CMyContext>((req) => ({\n    userId:     req.user?.id   ?? 'anonymous',\n    plan:       req.user?.plan ?? 'free',\n    isInternal: req.ip === '127.0.0.1',\n  }))\n);\n",[847,4458,4459,4468,4479,4513,4524,4528,4532,4543,4567,4600,4630,4656,4661],{"__ignoreMap":849},[1160,4460,4461,4463,4466],{"class":1162,"line":1163},[1160,4462,3507],{"class":3506},[1160,4464,4465],{"class":3510}," MyContext",[1160,4467,2681],{"class":1397},[1160,4469,4470,4473,4475,4477],{"class":1162,"line":1170},[1160,4471,4472],{"class":1403},"  userId",[1160,4474,1408],{"class":3526},[1160,4476,3552],{"class":3510},[1160,4478,3555],{"class":1397},[1160,4480,4481,4484,4486,4488,4491,4493,4495,4497,4500,4502,4504,4506,4509,4511],{"class":1162,"line":1187},[1160,4482,4483],{"class":1403},"  plan",[1160,4485,1408],{"class":3526},[1160,4487,2653],{"class":2104},[1160,4489,4490],{"class":1176},"free",[1160,4492,2105],{"class":2104},[1160,4494,3572],{"class":3526},[1160,4496,2653],{"class":2104},[1160,4498,4499],{"class":1176},"pro",[1160,4501,2105],{"class":2104},[1160,4503,3572],{"class":3526},[1160,4505,2653],{"class":2104},[1160,4507,4508],{"class":1176},"enterprise",[1160,4510,2105],{"class":2104},[1160,4512,3555],{"class":1397},[1160,4514,4515,4518,4520,4522],{"class":1162,"line":1193},[1160,4516,4517],{"class":1403},"  isInternal",[1160,4519,1408],{"class":3526},[1160,4521,3604],{"class":3510},[1160,4523,3555],{"class":1397},[1160,4525,4526],{"class":1162,"line":1199},[1160,4527,2373],{"class":1397},[1160,4529,4530],{"class":1162,"line":1221},[1160,4531,1190],{"emptyLinePlaceholder":8},[1160,4533,4534,4536,4538,4540],{"class":1162,"line":1226},[1160,4535,4420],{"class":1403},[1160,4537,3480],{"class":1397},[1160,4539,4425],{"class":1173},[1160,4541,4542],{"class":1397},"(\n",[1160,4544,4545,4548,4550,4553,4556,4558,4561,4564],{"class":1162,"line":1232},[1160,4546,4547],{"class":1173},"  detectBots",[1160,4549,3514],{"class":1397},[1160,4551,4552],{"class":3517},"MyContext",[1160,4554,4555],{"class":1397},">((",[1160,4557,4212],{"class":3591},[1160,4559,4560],{"class":1397},") ",[1160,4562,4563],{"class":3506},"=>",[1160,4565,4566],{"class":1397}," ({\n",[1160,4568,4569,4572,4574,4577,4579,4582,4585,4588,4591,4593,4596,4598],{"class":1162,"line":2232},[1160,4570,4571],{"class":1403},"    userId",[1160,4573,1408],{"class":1407},[1160,4575,4576],{"class":1403},"     req",[1160,4578,3480],{"class":1397},[1160,4580,4581],{"class":1403},"user",[1160,4583,4584],{"class":1397},"?.",[1160,4586,4587],{"class":1403},"id",[1160,4589,4590],{"class":3526},"   ??",[1160,4592,2653],{"class":2104},[1160,4594,4595],{"class":1176},"anonymous",[1160,4597,2105],{"class":2104},[1160,4599,1415],{"class":1397},[1160,4601,4602,4605,4607,4610,4612,4614,4616,4619,4622,4624,4626,4628],{"class":1162,"line":2243},[1160,4603,4604],{"class":1403},"    plan",[1160,4606,1408],{"class":1407},[1160,4608,4609],{"class":1403},"       req",[1160,4611,3480],{"class":1397},[1160,4613,4581],{"class":1403},[1160,4615,4584],{"class":1397},[1160,4617,4618],{"class":1403},"plan",[1160,4620,4621],{"class":3526}," ??",[1160,4623,2653],{"class":2104},[1160,4625,4490],{"class":1176},[1160,4627,2105],{"class":2104},[1160,4629,1415],{"class":1397},[1160,4631,4632,4635,4637,4640,4642,4645,4647,4649,4652,4654],{"class":1162,"line":2253},[1160,4633,4634],{"class":1403},"    isInternal",[1160,4636,1408],{"class":1407},[1160,4638,4639],{"class":1403}," req",[1160,4641,3480],{"class":1397},[1160,4643,4644],{"class":1403},"ip",[1160,4646,4182],{"class":3526},[1160,4648,2653],{"class":2104},[1160,4650,4651],{"class":1176},"127.0.0.1",[1160,4653,2105],{"class":2104},[1160,4655,1415],{"class":1397},[1160,4657,4658],{"class":1162,"line":2264},[1160,4659,4660],{"class":1397},"  }))\n",[1160,4662,4663],{"class":1162,"line":2274},[1160,4664,4288],{"class":1397},[840,4666,4669],{"className":1376,"code":4667,"filename":4668,"language":1378,"meta":849,"style":849},"import type { IBotChecker, ValidationContext, BotDetectorConfig, BanReasonCode } from '@riavzon\u002Fbot-detector';\nimport type { MyContext } from '.\u002FmyContext.js';\n\nclass PlanAbuseChecker implements IBotChecker\u003CBanReasonCode, MyContext> {\n  name = 'PlanAbuseChecker';\n  phase = 'cheap' as const;\n\n  isEnabled(_config: BotDetectorConfig) { return true; }\n\n  run(ctx: ValidationContext\u003CMyContext>, _config: BotDetectorConfig) {\n    if (ctx.custom.isInternal) return { score: 0, reasons: [] };\n\n    if (ctx.custom.plan === 'free' && ctx.geoData.proxy) {\n      return { score: 20, reasons: ['PROXY_DETECTED'] };\n    }\n\n    return { score: 0, reasons: [] };\n  }\n}\n","plan-abuse-checker.ts",[847,4670,4671,4706,4729,4733,4754,4769,4787,4791,4814,4818,4844,4883,4887,4927,4958,4962,4966,4986,4990],{"__ignoreMap":849},[1160,4672,4673,4675,4677,4679,4681,4683,4685,4687,4689,4691,4694,4696,4698,4700,4702,4704],{"class":1162,"line":1163},[1160,4674,2639],{"class":1390},[1160,4676,3938],{"class":1390},[1160,4678,2642],{"class":1397},[1160,4680,3486],{"class":1403},[1160,4682,2705],{"class":1397},[1160,4684,3479],{"class":1403},[1160,4686,2705],{"class":1397},[1160,4688,3951],{"class":1403},[1160,4690,2705],{"class":1397},[1160,4692,4693],{"class":1403},"BanReasonCode",[1160,4695,2647],{"class":1397},[1160,4697,2650],{"class":1390},[1160,4699,2653],{"class":2104},[1160,4701,3927],{"class":1176},[1160,4703,2105],{"class":2104},[1160,4705,3555],{"class":1397},[1160,4707,4708,4710,4712,4714,4716,4718,4720,4722,4725,4727],{"class":1162,"line":1170},[1160,4709,2639],{"class":1390},[1160,4711,3938],{"class":1390},[1160,4713,2642],{"class":1397},[1160,4715,4552],{"class":1403},[1160,4717,2647],{"class":1397},[1160,4719,2650],{"class":1390},[1160,4721,2653],{"class":2104},[1160,4723,4724],{"class":1176},".\u002FmyContext.js",[1160,4726,2105],{"class":2104},[1160,4728,3555],{"class":1397},[1160,4730,4731],{"class":1162,"line":1187},[1160,4732,1190],{"emptyLinePlaceholder":8},[1160,4734,4735,4737,4740,4742,4744,4746,4748,4750,4752],{"class":1162,"line":1193},[1160,4736,4002],{"class":3506},[1160,4738,4739],{"class":4005}," PlanAbuseChecker",[1160,4741,4009],{"class":3506},[1160,4743,3511],{"class":3510},[1160,4745,3514],{"class":1397},[1160,4747,4693],{"class":3517},[1160,4749,2705],{"class":1397},[1160,4751,4552],{"class":3517},[1160,4753,4018],{"class":1397},[1160,4755,4756,4758,4760,4762,4765,4767],{"class":1162,"line":1199},[1160,4757,3547],{"class":1403},[1160,4759,3527],{"class":3526},[1160,4761,2653],{"class":2104},[1160,4763,4764],{"class":1176},"PlanAbuseChecker",[1160,4766,2105],{"class":2104},[1160,4768,3555],{"class":1397},[1160,4770,4771,4773,4775,4777,4779,4781,4783,4785],{"class":1162,"line":1221},[1160,4772,3560],{"class":1403},[1160,4774,3527],{"class":3526},[1160,4776,2653],{"class":2104},[1160,4778,3567],{"class":1176},[1160,4780,2105],{"class":2104},[1160,4782,4048],{"class":1390},[1160,4784,4051],{"class":3506},[1160,4786,3555],{"class":1397},[1160,4788,4789],{"class":1162,"line":1226},[1160,4790,1190],{"emptyLinePlaceholder":8},[1160,4792,4793,4795,4797,4799,4801,4803,4806,4809,4811],{"class":1162,"line":1232},[1160,4794,3586],{"class":1173},[1160,4796,2090],{"class":1397},[1160,4798,4066],{"class":3591},[1160,4800,1408],{"class":3526},[1160,4802,3597],{"class":3510},[1160,4804,4805],{"class":1397},") { ",[1160,4807,4808],{"class":1390},"return",[1160,4810,2823],{"class":1180},[1160,4812,4813],{"class":1397},"; }\n",[1160,4815,4816],{"class":1162,"line":2232},[1160,4817,1190],{"emptyLinePlaceholder":8},[1160,4819,4820,4822,4824,4826,4828,4830,4832,4834,4836,4838,4840,4842],{"class":1162,"line":2243},[1160,4821,3611],{"class":1173},[1160,4823,2090],{"class":1397},[1160,4825,3616],{"class":3591},[1160,4827,1408],{"class":3526},[1160,4829,3621],{"class":3510},[1160,4831,3514],{"class":1397},[1160,4833,4552],{"class":3517},[1160,4835,3628],{"class":1397},[1160,4837,4066],{"class":3591},[1160,4839,1408],{"class":3526},[1160,4841,3597],{"class":3510},[1160,4843,4118],{"class":1397},[1160,4845,4846,4848,4850,4852,4854,4857,4859,4862,4864,4866,4868,4870,4872,4874,4876,4878,4880],{"class":1162,"line":2253},[1160,4847,4237],{"class":1390},[1160,4849,4240],{"class":1397},[1160,4851,3616],{"class":1403},[1160,4853,3480],{"class":1397},[1160,4855,4856],{"class":1403},"custom",[1160,4858,3480],{"class":1397},[1160,4860,4861],{"class":1403},"isInternal",[1160,4863,4560],{"class":1397},[1160,4865,4808],{"class":1390},[1160,4867,2642],{"class":1397},[1160,4869,3653],{"class":1403},[1160,4871,1408],{"class":1407},[1160,4873,4152],{"class":1411},[1160,4875,2705],{"class":1397},[1160,4877,3664],{"class":1403},[1160,4879,1408],{"class":1407},[1160,4881,4882],{"class":1397}," [] };\n",[1160,4884,4885],{"class":1162,"line":2264},[1160,4886,1190],{"emptyLinePlaceholder":8},[1160,4888,4889,4891,4893,4895,4897,4899,4901,4903,4905,4907,4909,4911,4913,4915,4917,4920,4922,4925],{"class":1162,"line":2274},[1160,4890,4237],{"class":1390},[1160,4892,4240],{"class":1397},[1160,4894,3616],{"class":1403},[1160,4896,3480],{"class":1397},[1160,4898,4856],{"class":1403},[1160,4900,3480],{"class":1397},[1160,4902,4618],{"class":1403},[1160,4904,4182],{"class":3526},[1160,4906,2653],{"class":2104},[1160,4908,4490],{"class":1176},[1160,4910,2105],{"class":2104},[1160,4912,4246],{"class":3526},[1160,4914,4170],{"class":1403},[1160,4916,3480],{"class":1397},[1160,4918,4919],{"class":1403},"geoData",[1160,4921,3480],{"class":1397},[1160,4923,4924],{"class":1403},"proxy",[1160,4926,4118],{"class":1397},[1160,4928,4929,4932,4934,4936,4938,4940,4942,4944,4946,4948,4950,4953,4955],{"class":1162,"line":2284},[1160,4930,4931],{"class":1390},"      return",[1160,4933,2642],{"class":1397},[1160,4935,3653],{"class":1403},[1160,4937,1408],{"class":1407},[1160,4939,2976],{"class":1411},[1160,4941,2705],{"class":1397},[1160,4943,3664],{"class":1403},[1160,4945,1408],{"class":1407},[1160,4947,2783],{"class":1397},[1160,4949,2105],{"class":2104},[1160,4951,4952],{"class":1176},"PROXY_DETECTED",[1160,4954,2105],{"class":2104},[1160,4956,4957],{"class":1397},"] };\n",[1160,4959,4960],{"class":1162,"line":2294},[1160,4961,4293],{"class":1397},[1160,4963,4964],{"class":1162,"line":2304},[1160,4965,1190],{"emptyLinePlaceholder":8},[1160,4967,4968,4970,4972,4974,4976,4978,4980,4982,4984],{"class":1162,"line":2314},[1160,4969,4083],{"class":1390},[1160,4971,2642],{"class":1397},[1160,4973,3653],{"class":1403},[1160,4975,1408],{"class":1407},[1160,4977,4152],{"class":1411},[1160,4979,2705],{"class":1397},[1160,4981,3664],{"class":1403},[1160,4983,1408],{"class":1407},[1160,4985,4882],{"class":1397},[1160,4987,4988],{"class":1162,"line":2324},[1160,4989,3456],{"class":1397},[1160,4991,4992],{"class":1162,"line":2334},[1160,4993,2373],{"class":1397},[813,4995,4996],{},"This pattern lets you apply business logic — plan tier, user role, internal traffic bypass — inside the same scoring pipeline that handles IP reputation and behavioral analysis, without any special wiring.",[860,4998,5000],{"id":4999},"triggering-an-instant-ban","Triggering an Instant Ban",[813,5002,5003,5004,5007,5008,5011],{},"Returning ",[847,5005,5006],{},"'BAD_BOT_DETECTED'"," in the reasons array causes the pipeline to throw ",[847,5009,5010],{},"BadBotDetected"," immediately. No further checkers run, and the reputation healer does not execute. The visitor is banned without waiting for score accumulation.",[840,5013,5015],{"className":1376,"code":5014,"language":1378,"meta":849,"style":849},"run(ctx: ValidationContext, _config: BotDetectorConfig) {\n  if (isDefinitelyABot(ctx)) {\n    return { score: 0, reasons: ['BAD_BOT_DETECTED'] };\n  }\n  return { score: 0, reasons: [] };\n}\n",[847,5016,5017,5040,5057,5085,5089,5110],{"__ignoreMap":849},[1160,5018,5019,5021,5023,5025,5028,5030,5032,5034,5036,5038],{"class":1162,"line":1163},[1160,5020,636],{"class":1173},[1160,5022,2090],{"class":1397},[1160,5024,3616],{"class":1403},[1160,5026,5027],{"class":1397},": ",[1160,5029,3479],{"class":1403},[1160,5031,2705],{"class":1397},[1160,5033,4066],{"class":1403},[1160,5035,5027],{"class":1397},[1160,5037,3951],{"class":1403},[1160,5039,4118],{"class":1397},[1160,5041,5042,5045,5047,5050,5052,5054],{"class":1162,"line":1170},[1160,5043,5044],{"class":1390},"  if",[1160,5046,4240],{"class":1397},[1160,5048,5049],{"class":1173},"isDefinitelyABot",[1160,5051,2090],{"class":1397},[1160,5053,3616],{"class":1403},[1160,5055,5056],{"class":1397},")) {\n",[1160,5058,5059,5061,5063,5065,5067,5069,5071,5073,5075,5077,5079,5081,5083],{"class":1162,"line":1187},[1160,5060,4083],{"class":1390},[1160,5062,2642],{"class":1397},[1160,5064,3653],{"class":1403},[1160,5066,1408],{"class":1407},[1160,5068,4152],{"class":1411},[1160,5070,2705],{"class":1397},[1160,5072,3664],{"class":1403},[1160,5074,1408],{"class":1407},[1160,5076,2783],{"class":1397},[1160,5078,2105],{"class":2104},[1160,5080,1499],{"class":1176},[1160,5082,2105],{"class":2104},[1160,5084,4957],{"class":1397},[1160,5086,5087],{"class":1162,"line":1193},[1160,5088,3456],{"class":1397},[1160,5090,5091,5094,5096,5098,5100,5102,5104,5106,5108],{"class":1162,"line":1199},[1160,5092,5093],{"class":1390},"  return",[1160,5095,2642],{"class":1397},[1160,5097,3653],{"class":1403},[1160,5099,1408],{"class":1407},[1160,5101,4152],{"class":1411},[1160,5103,2705],{"class":1397},[1160,5105,3664],{"class":1403},[1160,5107,1408],{"class":1407},[1160,5109,4882],{"class":1397},[1160,5111,5112],{"class":1162,"line":1221},[1160,5113,2373],{"class":1397},[813,5115,5116,5117,5120],{},"The mirror is ",[847,5118,5119],{},"'GOOD_BOT_IDENTIFIED'",", which whitelists the request instantly. The built-in good-bot DNS verifier uses this same mechanism.",[860,5122,5124],{"id":5123},"heavy-checkers-and-the-built-in-storage","Heavy Checkers and the Built-In Storage",[813,5126,5127,5128,5131,5132,5134,5135,5138],{},"Checkers that require I\u002FO — database queries, external API calls, cache reads — declare ",[847,5129,5130],{},"phase: 'heavy'",". The heavy phase only runs when the cheap phase score stays below ",[847,5133,1362],{},". Call ",[847,5136,5137],{},"getStorage()"," to access the same storage instance Bot Detector uses internally, keeping all cache I\u002FO in one place.",[840,5140,5143],{"className":1376,"code":5141,"filename":5142,"language":1378,"meta":849,"style":849},"import { getStorage, CheckerRegistry } from '@riavzon\u002Fbot-detector';\nimport type { IBotChecker, ValidationContext, BotDetectorConfig } from '@riavzon\u002Fbot-detector';\n\nclass MyAsyncChecker implements IBotChecker\u003C'MY_REASON'> {\n  name = 'MyAsyncChecker';\n  phase = 'heavy' as const;\n\n  isEnabled(_config: BotDetectorConfig): boolean { return true; }\n\n  async run(ctx: ValidationContext, _config: BotDetectorConfig) {\n    if (!ctx.cookie) return { score: 0, reasons: [] };\n\n    const storage = getStorage();\n    const cacheKey = `custom:${ctx.cookie}`;\n\n    const cached = await storage.getItem\u003Cnumber>(cacheKey);\n    if (cached !== null) {\n      return { score: cached, reasons: cached > 0 ? ['MY_REASON' as const] : [] };\n    }\n\n    const result = await myDb.query('SELECT ...', [ctx.ipAddress]);\n    const score = result.isSuspicious ? 30 : 0;\n\n    await storage.setItem(cacheKey, score, { ttl: 300 });\n    return { score, reasons: score > 0 ? ['MY_REASON' as const] : [] };\n  }\n}\n\nCheckerRegistry.register(new MyAsyncChecker());\n","my-async-checker.ts",[847,5144,5145,5170,5200,5204,5226,5241,5259,5263,5289,5293,5318,5354,5358,5373,5403,5407,5438,5455,5502,5506,5510,5551,5577,5581,5614,5654,5658,5662,5666],{"__ignoreMap":849},[1160,5146,5147,5149,5151,5154,5156,5158,5160,5162,5164,5166,5168],{"class":1162,"line":1163},[1160,5148,2639],{"class":1390},[1160,5150,2642],{"class":1397},[1160,5152,5153],{"class":1403},"getStorage",[1160,5155,2705],{"class":1397},[1160,5157,3918],{"class":1403},[1160,5159,2647],{"class":1397},[1160,5161,2650],{"class":1390},[1160,5163,2653],{"class":2104},[1160,5165,3927],{"class":1176},[1160,5167,2105],{"class":2104},[1160,5169,3555],{"class":1397},[1160,5171,5172,5174,5176,5178,5180,5182,5184,5186,5188,5190,5192,5194,5196,5198],{"class":1162,"line":1170},[1160,5173,2639],{"class":1390},[1160,5175,3938],{"class":1390},[1160,5177,2642],{"class":1397},[1160,5179,3486],{"class":1403},[1160,5181,2705],{"class":1397},[1160,5183,3479],{"class":1403},[1160,5185,2705],{"class":1397},[1160,5187,3951],{"class":1403},[1160,5189,2647],{"class":1397},[1160,5191,2650],{"class":1390},[1160,5193,2653],{"class":2104},[1160,5195,3927],{"class":1176},[1160,5197,2105],{"class":2104},[1160,5199,3555],{"class":1397},[1160,5201,5202],{"class":1162,"line":1187},[1160,5203,1190],{"emptyLinePlaceholder":8},[1160,5205,5206,5208,5211,5213,5215,5217,5219,5222,5224],{"class":1162,"line":1193},[1160,5207,4002],{"class":3506},[1160,5209,5210],{"class":4005}," MyAsyncChecker",[1160,5212,4009],{"class":3506},[1160,5214,3511],{"class":3510},[1160,5216,3514],{"class":1397},[1160,5218,2105],{"class":2104},[1160,5220,5221],{"class":1176},"MY_REASON",[1160,5223,2105],{"class":2104},[1160,5225,4018],{"class":1397},[1160,5227,5228,5230,5232,5234,5237,5239],{"class":1162,"line":1199},[1160,5229,3547],{"class":1403},[1160,5231,3527],{"class":3526},[1160,5233,2653],{"class":2104},[1160,5235,5236],{"class":1176},"MyAsyncChecker",[1160,5238,2105],{"class":2104},[1160,5240,3555],{"class":1397},[1160,5242,5243,5245,5247,5249,5251,5253,5255,5257],{"class":1162,"line":1221},[1160,5244,3560],{"class":1403},[1160,5246,3527],{"class":3526},[1160,5248,2653],{"class":2104},[1160,5250,3577],{"class":1176},[1160,5252,2105],{"class":2104},[1160,5254,4048],{"class":1390},[1160,5256,4051],{"class":3506},[1160,5258,3555],{"class":1397},[1160,5260,5261],{"class":1162,"line":1226},[1160,5262,1190],{"emptyLinePlaceholder":8},[1160,5264,5265,5267,5269,5271,5273,5275,5277,5279,5281,5283,5285,5287],{"class":1162,"line":1232},[1160,5266,3586],{"class":1173},[1160,5268,2090],{"class":1397},[1160,5270,4066],{"class":3591},[1160,5272,1408],{"class":3526},[1160,5274,3597],{"class":3510},[1160,5276,1831],{"class":1397},[1160,5278,1408],{"class":3526},[1160,5280,3604],{"class":3510},[1160,5282,2642],{"class":1397},[1160,5284,4808],{"class":1390},[1160,5286,2823],{"class":1180},[1160,5288,4813],{"class":1397},[1160,5290,5291],{"class":1162,"line":2232},[1160,5292,1190],{"emptyLinePlaceholder":8},[1160,5294,5295,5298,5300,5302,5304,5306,5308,5310,5312,5314,5316],{"class":1162,"line":2243},[1160,5296,5297],{"class":3506},"  async",[1160,5299,1259],{"class":1173},[1160,5301,2090],{"class":1397},[1160,5303,3616],{"class":3591},[1160,5305,1408],{"class":3526},[1160,5307,3621],{"class":3510},[1160,5309,2705],{"class":1397},[1160,5311,4066],{"class":3591},[1160,5313,1408],{"class":3526},[1160,5315,3597],{"class":3510},[1160,5317,4118],{"class":1397},[1160,5319,5320,5322,5324,5327,5329,5331,5334,5336,5338,5340,5342,5344,5346,5348,5350,5352],{"class":1162,"line":2253},[1160,5321,4237],{"class":1390},[1160,5323,4240],{"class":1397},[1160,5325,5326],{"class":3526},"!",[1160,5328,3616],{"class":1403},[1160,5330,3480],{"class":1397},[1160,5332,5333],{"class":1403},"cookie",[1160,5335,4560],{"class":1397},[1160,5337,4808],{"class":1390},[1160,5339,2642],{"class":1397},[1160,5341,3653],{"class":1403},[1160,5343,1408],{"class":1407},[1160,5345,4152],{"class":1411},[1160,5347,2705],{"class":1397},[1160,5349,3664],{"class":1403},[1160,5351,1408],{"class":1407},[1160,5353,4882],{"class":1397},[1160,5355,5356],{"class":1162,"line":2264},[1160,5357,1190],{"emptyLinePlaceholder":8},[1160,5359,5360,5362,5365,5367,5370],{"class":1162,"line":2274},[1160,5361,4123],{"class":3506},[1160,5363,5364],{"class":2170}," storage",[1160,5366,3527],{"class":3526},[1160,5368,5369],{"class":1173}," getStorage",[1160,5371,5372],{"class":1397},"();\n",[1160,5374,5375,5377,5380,5382,5385,5388,5390,5393,5395,5398,5401],{"class":1162,"line":2284},[1160,5376,4123],{"class":3506},[1160,5378,5379],{"class":2170}," cacheKey",[1160,5381,3527],{"class":3526},[1160,5383,5384],{"class":1176}," `custom:",[1160,5386,5387],{"class":3506},"${",[1160,5389,3616],{"class":1403},[1160,5391,3480],{"class":5392},"s1lnM",[1160,5394,5333],{"class":1403},[1160,5396,5397],{"class":3506},"}",[1160,5399,5400],{"class":1176},"`",[1160,5402,3555],{"class":1397},[1160,5404,5405],{"class":1162,"line":2294},[1160,5406,1190],{"emptyLinePlaceholder":8},[1160,5408,5409,5411,5414,5416,5419,5421,5423,5426,5428,5430,5433,5436],{"class":1162,"line":2304},[1160,5410,4123],{"class":3506},[1160,5412,5413],{"class":2170}," cached",[1160,5415,3527],{"class":3526},[1160,5417,5418],{"class":1390}," await",[1160,5420,5364],{"class":1403},[1160,5422,3480],{"class":1397},[1160,5424,5425],{"class":1173},"getItem",[1160,5427,3514],{"class":1397},[1160,5429,2259],{"class":3510},[1160,5431,5432],{"class":1397},">(",[1160,5434,5435],{"class":1403},"cacheKey",[1160,5437,4288],{"class":1397},[1160,5439,5440,5442,5444,5447,5450,5453],{"class":1162,"line":2314},[1160,5441,4237],{"class":1390},[1160,5443,4240],{"class":1397},[1160,5445,5446],{"class":1403},"cached",[1160,5448,5449],{"class":3526}," !==",[1160,5451,5452],{"class":1180}," null",[1160,5454,4118],{"class":1397},[1160,5456,5457,5459,5461,5463,5465,5467,5469,5471,5473,5475,5478,5480,5483,5485,5487,5489,5491,5493,5495,5498,5500],{"class":1162,"line":2324},[1160,5458,4931],{"class":1390},[1160,5460,2642],{"class":1397},[1160,5462,3653],{"class":1403},[1160,5464,1408],{"class":1407},[1160,5466,5413],{"class":1403},[1160,5468,2705],{"class":1397},[1160,5470,3664],{"class":1403},[1160,5472,1408],{"class":1407},[1160,5474,5413],{"class":1403},[1160,5476,5477],{"class":3526}," >",[1160,5479,4152],{"class":1411},[1160,5481,5482],{"class":3526}," ?",[1160,5484,2783],{"class":1397},[1160,5486,2105],{"class":2104},[1160,5488,5221],{"class":1176},[1160,5490,2105],{"class":2104},[1160,5492,4048],{"class":1390},[1160,5494,4051],{"class":3506},[1160,5496,5497],{"class":1397},"] ",[1160,5499,1408],{"class":3526},[1160,5501,4882],{"class":1397},[1160,5503,5504],{"class":1162,"line":2334},[1160,5505,4293],{"class":1397},[1160,5507,5508],{"class":1162,"line":2344},[1160,5509,1190],{"emptyLinePlaceholder":8},[1160,5511,5512,5514,5517,5519,5521,5524,5526,5529,5531,5533,5536,5538,5541,5543,5545,5548],{"class":1162,"line":2354},[1160,5513,4123],{"class":3506},[1160,5515,5516],{"class":2170}," result",[1160,5518,3527],{"class":3526},[1160,5520,5418],{"class":1390},[1160,5522,5523],{"class":1403}," myDb",[1160,5525,3480],{"class":1397},[1160,5527,5528],{"class":1173},"query",[1160,5530,2090],{"class":1397},[1160,5532,2105],{"class":2104},[1160,5534,5535],{"class":1176},"SELECT ...",[1160,5537,2105],{"class":2104},[1160,5539,5540],{"class":1397},", [",[1160,5542,3616],{"class":1403},[1160,5544,3480],{"class":1397},[1160,5546,5547],{"class":1403},"ipAddress",[1160,5549,5550],{"class":1397},"]);\n",[1160,5552,5553,5555,5557,5559,5561,5563,5566,5568,5570,5573,5575],{"class":1162,"line":2364},[1160,5554,4123],{"class":3506},[1160,5556,4147],{"class":2170},[1160,5558,3527],{"class":3526},[1160,5560,5516],{"class":1403},[1160,5562,3480],{"class":1397},[1160,5564,5565],{"class":1403},"isSuspicious",[1160,5567,5482],{"class":3526},[1160,5569,3002],{"class":1411},[1160,5571,5572],{"class":3526}," :",[1160,5574,4152],{"class":1411},[1160,5576,3555],{"class":1397},[1160,5578,5579],{"class":1162,"line":2370},[1160,5580,1190],{"emptyLinePlaceholder":8},[1160,5582,5583,5586,5588,5590,5593,5595,5597,5599,5601,5604,5607,5609,5612],{"class":1162,"line":2892},[1160,5584,5585],{"class":1390},"    await",[1160,5587,5364],{"class":1403},[1160,5589,3480],{"class":1397},[1160,5591,5592],{"class":1173},"setItem",[1160,5594,2090],{"class":1397},[1160,5596,5435],{"class":1403},[1160,5598,2705],{"class":1397},[1160,5600,3653],{"class":1403},[1160,5602,5603],{"class":1397},", { ",[1160,5605,5606],{"class":1403},"ttl",[1160,5608,1408],{"class":1407},[1160,5610,5611],{"class":1411}," 300",[1160,5613,4415],{"class":1397},[1160,5615,5616,5618,5620,5622,5624,5626,5628,5630,5632,5634,5636,5638,5640,5642,5644,5646,5648,5650,5652],{"class":1162,"line":2910},[1160,5617,4083],{"class":1390},[1160,5619,2642],{"class":1397},[1160,5621,3653],{"class":1403},[1160,5623,2705],{"class":1397},[1160,5625,3664],{"class":1403},[1160,5627,1408],{"class":1407},[1160,5629,4147],{"class":1403},[1160,5631,5477],{"class":3526},[1160,5633,4152],{"class":1411},[1160,5635,5482],{"class":3526},[1160,5637,2783],{"class":1397},[1160,5639,2105],{"class":2104},[1160,5641,5221],{"class":1176},[1160,5643,2105],{"class":2104},[1160,5645,4048],{"class":1390},[1160,5647,4051],{"class":3506},[1160,5649,5497],{"class":1397},[1160,5651,1408],{"class":3526},[1160,5653,4882],{"class":1397},[1160,5655,5656],{"class":1162,"line":2915},[1160,5657,3456],{"class":1397},[1160,5659,5660],{"class":1162,"line":2933},[1160,5661,2373],{"class":1397},[1160,5663,5664],{"class":1162,"line":2938},[1160,5665,1190],{"emptyLinePlaceholder":8},[1160,5667,5668,5670,5672,5674,5676,5678,5680],{"class":1162,"line":2948},[1160,5669,3918],{"class":1403},[1160,5671,3480],{"class":1397},[1160,5673,4333],{"class":1173},[1160,5675,2090],{"class":1397},[1160,5677,4339],{"class":4338},[1160,5679,5210],{"class":1173},[1160,5681,4344],{"class":1397},[3463,5683,5684],{},[813,5685,5686,5687,5690],{},"Use a namespaced key prefix for your cache entries (for example ",[847,5688,5689],{},"custom:",") to avoid collisions with the built-in cache keys that share the same storage instance.",[820,5692],{},[823,5694,5696],{"id":5695},"automatic-threat-compilation-the-generator","Automatic Threat Compilation: The Generator",[813,5698,1348,5699,5701,5702,1773,5704,5706],{},[847,5700,307],{}," checker — checker 10 in the cheap phase — queries two optional MMDB files: ",[847,5703,1772],{},[847,5705,1776],{},". These files do not come from Shield Base. Bot Detector generates them itself from its own accumulated traffic history.",[860,5708,5710],{"id":5709},"what-gets-compiled","What Gets Compiled",[813,5712,5713,5714,5717],{},"Running ",[847,5715,5716],{},"bot-detector generate"," reads two tables from Bot Detector's database and compiles each into an MMDB file. Both compilations run in parallel.",[813,5719,5720,5724,5725,5728,5729,5732,5733,5735],{},[877,5721,5722],{},[847,5723,1772],{}," — every row in the ",[847,5726,5727],{},"banned"," table with a non-null ",[847,5730,5731],{},"ip_address"," gets compiled into this file. Each entry stores the IP, score, country, user-agent, and reason codes from the original ban event. On subsequent visits, the Known Bad IPs checker matches the IP in microseconds in the cheap phase and issues ",[847,5734,1499],{}," immediately — the full 17-checker pipeline never runs for a confirmed repeat offender.",[813,5737,5738,5724,5742,5745,5746,5749,5750,5753,5754,5757,5758,5760,5761,5753,5764,5766],{},[877,5739,5740],{},[847,5741,1776],{},[847,5743,5744],{},"visitors"," table where ",[847,5747,5748],{},"suspicious_activity_score"," is at or above ",[847,5751,5752],{},"generator.scoreThreshold"," (default ",[847,5755,5756],{},"70",") is compiled into this file. These are visitors who accumulated significant suspicion scores but were never pushed over ",[847,5759,1362],{},". On their next visit, they receive the ",[847,5762,5763],{},"highRiskPenalty",[847,5765,1555],{}," points) in the cheap phase, meaning far less effort from other checkers is needed to reach a ban.",[840,5768,5770],{"className":1376,"code":5769,"language":1378,"meta":849,"style":849},"generator: {\n  scoreThreshold: 70,   \u002F\u002F minimum score to include in highRisk.mmdb\n  deleteAfterBuild: false,  \u002F\u002F if true, removes compiled rows from DB after build\n  mmdbctlPath: 'mmdbctl',   \u002F\u002F path to mmdbctl binary\n  generateTypes: false,     \u002F\u002F emit TypeScript type definitions alongside MMDB files\n}\n",[847,5771,5772,5777,5790,5804,5820,5833],{"__ignoreMap":849},[1160,5773,5774],{"class":1162,"line":1163},[1160,5775,5776],{"class":1397},"generator: {\n",[1160,5778,5779,5782,5784,5787],{"class":1162,"line":1170},[1160,5780,5781],{"class":1397},"  scoreThreshold: ",[1160,5783,5756],{"class":1411},[1160,5785,5786],{"class":1397},",   ",[1160,5788,5789],{"class":1166},"\u002F\u002F minimum score to include in highRisk.mmdb\n",[1160,5791,5792,5795,5798,5801],{"class":1162,"line":1187},[1160,5793,5794],{"class":1397},"  deleteAfterBuild: ",[1160,5796,5797],{"class":1180},"false",[1160,5799,5800],{"class":1397},",  ",[1160,5802,5803],{"class":1166},"\u002F\u002F if true, removes compiled rows from DB after build\n",[1160,5805,5806,5809,5811,5813,5815,5817],{"class":1162,"line":1193},[1160,5807,5808],{"class":1397},"  mmdbctlPath: ",[1160,5810,2105],{"class":2104},[1160,5812,1321],{"class":1176},[1160,5814,2105],{"class":2104},[1160,5816,5786],{"class":1397},[1160,5818,5819],{"class":1166},"\u002F\u002F path to mmdbctl binary\n",[1160,5821,5822,5825,5827,5830],{"class":1162,"line":1199},[1160,5823,5824],{"class":1397},"  generateTypes: ",[1160,5826,5797],{"class":1180},[1160,5828,5829],{"class":1397},",     ",[1160,5831,5832],{"class":1166},"\u002F\u002F emit TypeScript type definitions alongside MMDB files\n",[1160,5834,5835],{"class":1162,"line":1221},[1160,5836,2373],{"class":1397},[813,5838,1348,5839,5842,5843,5845,5846,5848],{},[847,5840,5841],{},"scoreThreshold"," tradeoff is worth understanding. Lowering it to ",[847,5844,1643],{}," catches visitors with moderate suspicious history but risks false positives. Keeping it at ",[847,5847,5756],{}," or higher limits the file to visitors with strong behavioral evidence.",[881,5850,5851,5861],{},[884,5852,5853],{},[887,5854,5855,5858],{},[890,5856,5857],{},"Threshold",[890,5859,5860],{},"Effect",[902,5862,5863,5872,5882],{},[887,5864,5865,5869],{},[907,5866,5867],{},[847,5868,1643],{},[907,5870,5871],{},"Broader net — includes visitors with moderate accumulated scores",[887,5873,5874,5879],{},[907,5875,5876,5878],{},[847,5877,5756],{}," (default)",[907,5880,5881],{},"Balanced — strong suspicious history required",[887,5883,5884,5889],{},[907,5885,5886],{},[847,5887,5888],{},"90",[907,5890,5891],{},"Conservative — only the most suspicious non-banned visitors included",[860,5893,5895],{"id":5894},"hot-reload","Hot Reload",[813,5897,5898,5899,5902],{},"Both MMDB files are opened with ",[847,5900,5901],{},"watchForUpdates: true",". When a new file is written to disk after a generation run, the MMDB reader reloads it automatically within seconds — no application restart, no traffic interruption. You can run generation against a live service and the updated databases take effect immediately.",[860,5904,5906],{"id":5905},"running-generation","Running Generation",[1149,5908,5909,5927,5943,5957],{},[840,5910,5912],{"className":1153,"code":5911,"filename":1155,"language":1156,"meta":849,"style":849},"pnpm dlx @riavzon\u002Fbot-detector generate\n",[847,5913,5914],{"__ignoreMap":849},[1160,5915,5916,5918,5921,5924],{"class":1162,"line":1163},[1160,5917,1155],{"class":1173},[1160,5919,5920],{"class":1176}," dlx",[1160,5922,5923],{"class":1176}," @riavzon\u002Fbot-detector",[1160,5925,5926],{"class":1176}," generate\n",[840,5928,5931],{"className":1153,"code":5929,"filename":5930,"language":1156,"meta":849,"style":849},"yarn dlx @riavzon\u002Fbot-detector generate\n","yarn",[847,5932,5933],{"__ignoreMap":849},[1160,5934,5935,5937,5939,5941],{"class":1162,"line":1163},[1160,5936,5930],{"class":1173},[1160,5938,5920],{"class":1176},[1160,5940,5923],{"class":1176},[1160,5942,5926],{"class":1176},[840,5944,5946],{"className":1153,"code":5945,"filename":1246,"language":1156,"meta":849,"style":849},"npx @riavzon\u002Fbot-detector generate\n",[847,5947,5948],{"__ignoreMap":849},[1160,5949,5950,5953,5955],{"class":1162,"line":1163},[1160,5951,5952],{"class":1173},"npx",[1160,5954,5923],{"class":1176},[1160,5956,5926],{"class":1176},[840,5958,5961],{"className":1153,"code":5959,"filename":5960,"language":1156,"meta":849,"style":849},"bunx @riavzon\u002Fbot-detector generate\n","bun",[847,5962,5963],{"__ignoreMap":849},[1160,5964,5965,5968,5970],{"class":1162,"line":1163},[1160,5966,5967],{"class":1173},"bunx",[1160,5969,5923],{"class":1176},[1160,5971,5926],{"class":1176},[813,5973,5974,5975,5978],{},"For programmatic use — for example, triggering generation immediately after a bulk ban operation — call ",[847,5976,5977],{},"runGeneration()"," directly:",[840,5980,5983],{"className":1376,"code":5981,"filename":5982,"language":1378,"meta":849,"style":849},"import { updateBannedIP, runGeneration } from '@riavzon\u002Fbot-detector';\nimport type { BannedInfo } from '@riavzon\u002Fbot-detector';\n\nfor (const ip of badIps) {\n  const info: BannedInfo = { score: 100, reasons: ['PREVIOUSLY_BANNED_IP'] };\n  await updateBannedIP('', ip, 'us', '', info);\n}\n\n\u002F\u002F Compile updated MMDB files immediately so the next request from these IPs\n\u002F\u002F hits the cheap-phase known-bad-IPs check rather than the full pipeline.\nawait runGeneration();\n","admin-script.ts",[847,5984,5985,6011,6034,6038,6059,6099,6136,6140,6144,6149,6154],{"__ignoreMap":849},[1160,5986,5987,5989,5991,5994,5996,5999,6001,6003,6005,6007,6009],{"class":1162,"line":1163},[1160,5988,2639],{"class":1390},[1160,5990,2642],{"class":1397},[1160,5992,5993],{"class":1403},"updateBannedIP",[1160,5995,2705],{"class":1397},[1160,5997,5998],{"class":1403},"runGeneration",[1160,6000,2647],{"class":1397},[1160,6002,2650],{"class":1390},[1160,6004,2653],{"class":2104},[1160,6006,3927],{"class":1176},[1160,6008,2105],{"class":2104},[1160,6010,3555],{"class":1397},[1160,6012,6013,6015,6017,6019,6022,6024,6026,6028,6030,6032],{"class":1162,"line":1170},[1160,6014,2639],{"class":1390},[1160,6016,3938],{"class":1390},[1160,6018,2642],{"class":1397},[1160,6020,6021],{"class":1403},"BannedInfo",[1160,6023,2647],{"class":1397},[1160,6025,2650],{"class":1390},[1160,6027,2653],{"class":2104},[1160,6029,3927],{"class":1176},[1160,6031,2105],{"class":2104},[1160,6033,3555],{"class":1397},[1160,6035,6036],{"class":1162,"line":1187},[1160,6037,1190],{"emptyLinePlaceholder":8},[1160,6039,6040,6043,6045,6048,6051,6054,6057],{"class":1162,"line":1193},[1160,6041,6042],{"class":1390},"for",[1160,6044,4240],{"class":1397},[1160,6046,6047],{"class":3506},"const",[1160,6049,6050],{"class":2170}," ip",[1160,6052,6053],{"class":3506}," of",[1160,6055,6056],{"class":1403}," badIps",[1160,6058,4118],{"class":1397},[1160,6060,6061,6064,6067,6069,6072,6074,6076,6078,6080,6082,6084,6086,6088,6090,6092,6095,6097],{"class":1162,"line":1199},[1160,6062,6063],{"class":3506},"  const",[1160,6065,6066],{"class":2170}," info",[1160,6068,1408],{"class":3526},[1160,6070,6071],{"class":3510}," BannedInfo",[1160,6073,3527],{"class":3526},[1160,6075,2642],{"class":1397},[1160,6077,3653],{"class":1403},[1160,6079,1408],{"class":1407},[1160,6081,1412],{"class":1411},[1160,6083,2705],{"class":1397},[1160,6085,3664],{"class":1403},[1160,6087,1408],{"class":1407},[1160,6089,2783],{"class":1397},[1160,6091,2105],{"class":2104},[1160,6093,6094],{"class":1176},"PREVIOUSLY_BANNED_IP",[1160,6096,2105],{"class":2104},[1160,6098,4957],{"class":1397},[1160,6100,6101,6104,6107,6109,6112,6114,6116,6118,6120,6123,6125,6127,6129,6131,6134],{"class":1162,"line":1221},[1160,6102,6103],{"class":1390},"  await",[1160,6105,6106],{"class":1173}," updateBannedIP",[1160,6108,2090],{"class":1397},[1160,6110,6111],{"class":2104},"''",[1160,6113,2705],{"class":1397},[1160,6115,4644],{"class":1403},[1160,6117,2705],{"class":1397},[1160,6119,2105],{"class":2104},[1160,6121,6122],{"class":1176},"us",[1160,6124,2105],{"class":2104},[1160,6126,2705],{"class":1397},[1160,6128,6111],{"class":2104},[1160,6130,2705],{"class":1397},[1160,6132,6133],{"class":1403},"info",[1160,6135,4288],{"class":1397},[1160,6137,6138],{"class":1162,"line":1226},[1160,6139,2373],{"class":1397},[1160,6141,6142],{"class":1162,"line":1232},[1160,6143,1190],{"emptyLinePlaceholder":8},[1160,6145,6146],{"class":1162,"line":2232},[1160,6147,6148],{"class":1166},"\u002F\u002F Compile updated MMDB files immediately so the next request from these IPs\n",[1160,6150,6151],{"class":1162,"line":2243},[1160,6152,6153],{"class":1166},"\u002F\u002F hits the cheap-phase known-bad-IPs check rather than the full pipeline.\n",[1160,6155,6156,6158,6161],{"class":1162,"line":2253},[1160,6157,1391],{"class":1390},[1160,6159,6160],{"class":1173}," runGeneration",[1160,6162,5372],{"class":1397},[860,6164,6166],{"id":6165},"scheduling-generation","Scheduling Generation",[813,6168,6169],{},"The right generation frequency depends on traffic volume. A nightly run is a reasonable default. For higher-traffic applications where bans accumulate quickly, hourly generation keeps the banned MMDB current and prevents repeat offenders from absorbing pipeline capacity.",[840,6171,6176],{"className":6172,"code":6173,"filename":6174,"language":6175,"meta":849,"style":849},"language-cron shiki shiki-themes light-plus light-plus dracula","# Nightly at 2:00 AM\n0 2 * * * cd \u002Fapp && npx bot-detector generate >> \u002Fvar\u002Flog\u002Fbot-detector-generate.log 2>&1\n\n# Hourly for high-traffic deployments\n0 * * * * cd \u002Fapp && npx bot-detector generate >> \u002Fvar\u002Flog\u002Fbot-detector-generate.log 2>&1\n","crontab","cron",[847,6177,6178,6183,6188,6192,6197],{"__ignoreMap":849},[1160,6179,6180],{"class":1162,"line":1163},[1160,6181,6182],{},"# Nightly at 2:00 AM\n",[1160,6184,6185],{"class":1162,"line":1170},[1160,6186,6187],{},"0 2 * * * cd \u002Fapp && npx bot-detector generate >> \u002Fvar\u002Flog\u002Fbot-detector-generate.log 2>&1\n",[1160,6189,6190],{"class":1162,"line":1187},[1160,6191,1190],{"emptyLinePlaceholder":8},[1160,6193,6194],{"class":1162,"line":1193},[1160,6195,6196],{},"# Hourly for high-traffic deployments\n",[1160,6198,6199],{"class":1162,"line":1199},[1160,6200,6201],{},"0 * * * * cd \u002Fapp && npx bot-detector generate >> \u002Fvar\u002Flog\u002Fbot-detector-generate.log 2>&1\n",[813,6203,6204,6205,6207],{},"The generate command emits structured log lines including the entry count for each compiled database. Monitoring this output over time makes it easy to detect when ban volume spikes — a sudden increase in ",[847,6206,1772],{}," entries typically indicates a coordinated attack campaign starting.",[820,6209],{},[823,6211,6213],{"id":6212},"summary","Summary",[813,6215,6216],{},"Each of the three layers closes a gap that the others cannot. Shield Base provides static intelligence — historical threat reputation, network classification, and behavioral pattern databases — that no runtime analysis can replicate. Bot Detector performs dynamic behavioral analysis — velocity, session coherence, timing regularity — that static blocklists cannot catch. The canary cookie ties both together across sessions, making it impossible to reset accumulated behavioral signals simply by rotating IPs or changing headers.",[813,6218,6219],{},"A bot that evades Shield Base's IP reputation checks still faces 17 behavioral checkers. A bot that passes all 17 checkers on a single request still accumulates a session history that degrades its score over time. A bot that steals an authenticated session still cannot complete token rotation without matching the canary cookie fingerprint that was established on the original device.",[813,6221,6222],{},"The layered approach trades complexity for resilience. Each layer is effective in isolation. Together, they make the cost of a successful bot attack high enough that most attackers move on to easier targets.",[813,6224,6225,6226,6229],{},"Read the full ",[6227,6228,230],"a",{"href":35}," reference",[813,6231,6225,6232,6234],{},[6227,6233,40],{"href":42}," reference for database compilation options",[6236,6237,6238],"style",{},"html pre.shiki code .sghk6, html code.shiki .sghk6{--shiki-light:#008000;--shiki-default:#008000;--shiki-dark:#6272A4}html pre.shiki code .sHOzp, html code.shiki .sHOzp{--shiki-light:#795E26;--shiki-default:#795E26;--shiki-dark:#50FA7B}html pre.shiki code .sFB1V, html code.shiki .sFB1V{--shiki-light:#A31515;--shiki-default:#A31515;--shiki-dark:#F1FA8C}html pre.shiki code .sjR7W, html code.shiki .sjR7W{--shiki-light:#0000FF;--shiki-default:#0000FF;--shiki-dark:#BD93F9}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sZ328, html code.shiki .sZ328{--shiki-light:#AF00DB;--shiki-default:#AF00DB;--shiki-dark:#FF79C6}html pre.shiki code .sDd4n, html code.shiki .sDd4n{--shiki-light:#000000;--shiki-default:#000000;--shiki-dark:#F8F8F2}html pre.shiki code .sjsA6, html code.shiki .sjsA6{--shiki-light:#001080;--shiki-default:#001080;--shiki-dark:#F8F8F2}html pre.shiki code .s34zl, html code.shiki .s34zl{--shiki-light:#001080;--shiki-default:#001080;--shiki-dark:#FF79C6}html pre.shiki code .spgvN, html code.shiki .spgvN{--shiki-light:#098658;--shiki-default:#098658;--shiki-dark:#BD93F9}html pre.shiki code .sFkSl, html code.shiki .sFkSl{--shiki-light:#A31515;--shiki-default:#A31515;--shiki-dark:#E9F284}html pre.shiki code .s3JHE, html code.shiki .s3JHE{--shiki-light:#0070C1;--shiki-default:#0070C1;--shiki-dark:#F8F8F2}html pre.shiki code .sl46w, html code.shiki .sl46w{--shiki-light:#0000FF;--shiki-default:#0000FF;--shiki-dark:#FF79C6}html pre.shiki code .sFs1U, html code.shiki .sFs1U{--shiki-light:#267F99;--shiki-light-font-style:inherit;--shiki-default:#267F99;--shiki-default-font-style:inherit;--shiki-dark:#8BE9FD;--shiki-dark-font-style:italic}html pre.shiki code .sW-rI, html code.shiki .sW-rI{--shiki-light:#267F99;--shiki-light-font-style:inherit;--shiki-default:#267F99;--shiki-default-font-style:inherit;--shiki-dark:#FFB86C;--shiki-dark-font-style:italic}html pre.shiki code .saOXh, html code.shiki .saOXh{--shiki-light:#000000;--shiki-default:#000000;--shiki-dark:#FF79C6}html pre.shiki code .sygFZ, html code.shiki .sygFZ{--shiki-light:#001080;--shiki-light-font-style:inherit;--shiki-default:#001080;--shiki-default-font-style:inherit;--shiki-dark:#FFB86C;--shiki-dark-font-style:italic}html pre.shiki code .s5jk-, html code.shiki .s5jk-{--shiki-light:#267F99;--shiki-default:#267F99;--shiki-dark:#8BE9FD}html pre.shiki code .sakC6, html code.shiki .sakC6{--shiki-light:#0000FF;--shiki-light-font-weight:inherit;--shiki-default:#0000FF;--shiki-default-font-weight:inherit;--shiki-dark:#FF79C6;--shiki-dark-font-weight:bold}html pre.shiki code .s1lnM, html code.shiki .s1lnM{--shiki-light:#000000FF;--shiki-default:#000000FF;--shiki-dark:#F8F8F2}",{"title":849,"searchDepth":1170,"depth":1170,"links":6240},[6241,6242,6247,6253,6258,6259,6260,6269,6275],{"id":825,"depth":1170,"text":826},{"id":854,"depth":1170,"text":855,"children":6243},[6244,6245,6246],{"id":862,"depth":1187,"text":863},{"id":869,"depth":1187,"text":870},{"id":1139,"depth":1187,"text":1140},{"id":1333,"depth":1170,"text":1334,"children":6248},[6249,6250,6251,6252],{"id":1344,"depth":1187,"text":1345},{"id":1355,"depth":1187,"text":1356},{"id":1470,"depth":1187,"text":1471},{"id":1780,"depth":1187,"text":1781},{"id":2067,"depth":1170,"text":2068,"children":6254},[6255,6256,6257],{"id":2124,"depth":1187,"text":2125},{"id":2140,"depth":1187,"text":2141},{"id":2499,"depth":1187,"text":2500},{"id":2530,"depth":1170,"text":2531},{"id":2626,"depth":1170,"text":204},{"id":3472,"depth":1170,"text":3473,"children":6261},[6262,6264,6265,6266,6267,6268],{"id":3483,"depth":1187,"text":6263},"The IBotChecker Interface",{"id":3701,"depth":1187,"text":3702},{"id":3896,"depth":1187,"text":3897},{"id":4434,"depth":1187,"text":4435},{"id":4999,"depth":1187,"text":5000},{"id":5123,"depth":1187,"text":5124},{"id":5695,"depth":1170,"text":5696,"children":6270},[6271,6272,6273,6274],{"id":5709,"depth":1187,"text":5710},{"id":5894,"depth":1187,"text":5895},{"id":5905,"depth":1187,"text":5906},{"id":6165,"depth":1187,"text":6166},{"id":6212,"depth":1170,"text":6213},"2026-04-13","A complete walkthrough of the three-layer bot defense pipeline: from compiling IP intelligence databases with Shield Base, to running 17 checkers in two phases with Bot Detector, to fingerprinting sessions with the IAM canary cookie.","md",null,"https:\u002F\u002Fimages.unsplash.com\u002Fphoto-1555949963-aa79dcee981c?w=1200&q=80",{},"---\ntitle: \"Layered Bot Defense: How Shield Base, Bot Detector, and the IAM Canary Cookie Work Together\"\ndescription: \"A complete walkthrough of the three-layer bot defense pipeline: from compiling IP intelligence databases with Shield Base, to running 17 checkers in two phases with Bot Detector, to fingerprinting sessions with the IAM canary cookie.\"\ntags:\n  - Security\n  - Bot Detection\n  - Infrastructure\nimage: \"https:\u002F\u002Fimages.unsplash.com\u002Fphoto-1555949963-aa79dcee981c?w=1200&q=80\"\nauthor: \"Sergio\"\nauthorImg: \"https:\u002F\u002Fgithub.com\u002FSergo706.png\"\nauthorGithub: \"https:\u002F\u002Fgithub.com\u002FSergo706\"\nauthorGithubUserName: \"Sergo706\"\nfeatured: false\ndate: 2026-04-13\nreadingTime: \"18 min read\"\n---\n\nMost bot detection systems operate on a single layer: a rule list, a rate limiter, or a third-party API call. The problem with that model is that any single signal can be spoofed. A bot can rotate IPs, forge user-agent strings, and slow its request rate to look human. Defeating it requires combining signals from multiple independent layers so that evading one does not defeat the others.\n\nThe Riavzon stack addresses this with three coordinated components. Shield Base compiles IP intelligence from a dozen external sources into binary databases. Bot Detector runs those databases through a two-phase, 17-checker pipeline that scores every incoming request. The IAM canary cookie ties each browser session to a fingerprint that follows it through every subsequent request. This post walks through every layer in detail — how each one works, what data it uses, and what happens when a bot hits the stack.\n\n---\n\n## The Three Layers at a Glance\n\nBefore going deep on each component, it helps to understand how they relate to one another.\n\nShield Base is a build-time tool. You run it once to produce a set of binary database files, then run it again periodically to refresh them. It has no runtime presence — it just produces the files that the other layers consume.\n\nBot Detector is a runtime Express middleware. It reads the Shield Base databases at startup and holds them in memory. Every request passes through its pipeline, which scores the request across behavioral, fingerprint, and reputation dimensions. If the score reaches the ban threshold, the middleware short-circuits the request before it touches any application logic.\n\nThe canary cookie is a per-session identifier, issued on first contact and carried on every subsequent request. Bot Detector uses it to track session state across requests — storing timing patterns, path history, and reputation scores keyed on the cookie value. The IAM service uses the same cookie to bind authentication tokens to a specific visitor fingerprint, enabling anomaly detection during token rotation.\n\n```\nShield Base (build time)\n  └── Compiles MMDB + LMDB databases\n        └── Bot Detector (runtime middleware)\n              ├── Cheap phase: 10 synchronous checkers\n              ├── Heavy phase: 7 async checkers\n              └── Issues canary_id cookie on first request\n                    └── IAM service\n                          ├── Binds refresh tokens to canary fingerprint\n                          └── Flags anomalies during rotation\n```\n\n---\n\n## Shield Base: Compiling the Intelligence Layer\n\nShield Base is a CLI tool that downloads, processes, and compiles external threat intelligence into binary formats that Bot Detector can query in microseconds at runtime. It produces two kinds of output: MMDB files for IP-range lookups and LMDB files for hash-keyed pattern matching.\n\n### Why Binary Databases\n\nThe raw data that feeds bot detection is enormous. BGP routing tables, geolocation datasets, Tor node lists, FireHOL threat feeds, and user-agent pattern databases together contain hundreds of millions of entries. Querying them naively at runtime is not practical. MMDB (MaxMind DB) encodes IP ranges into a binary trie that resolves any IP to its metadata in a single file seek. LMDB (Lightning Memory-Mapped Database) is a memory-mapped key-value store that delivers zero-copy reads with no serialization overhead. Both formats are loaded once at startup and kept in memory for the lifetime of the process.\n\n### The 14 Data Sources\n\nShield Base downloads and compiles 14 distinct data sources, each targeting a different threat signal.\n\n**IP reputation and routing**\n\n| Database | Output | Source | What it contains |\n|---|---|---|---|\n| ASN routing | `asn.mmdb` | bgp.tools | Autonomous system numbers, ISP classification, network visibility |\n| City geolocation | `city.mmdb` | MaxMind Geofeed | IP-to-city mappings with coordinates, timezone, and subdivision |\n| Country\u002Fgeography | `country.mmdb` | Sapics ip-location-db | IPv4-to-country with continent and subregion data |\n| Proxy detection | `proxy.mmdb` | Custom proxy lists | Known VPN exit points and proxy server IPs |\n| Tor nodes | `tor.mmdb` | Torproject Onionoo API | Active Tor relays classified by role: exit, guard, bad exit |\n| Verified crawlers | `goodBots.mmdb` | Web crawler domain lists | IP ranges belonging to legitimate search engines and SEO crawlers |\n\n**Threat intelligence (FireHOL)**\n\nFireHOL maintains multiple threat list tiers. Shield Base compiles all of them into separate MMDB files, which Bot Detector queries independently so that the scoring system can assign different penalty weights to each tier.\n\n| Level | File | What it tracks |\n|---|---|---|\n| L1 | `firehol_l1.mmdb` | Current attacks — minimum false positives, maximum severity |\n| L2 | `firehol_l2.mmdb` | Attacks observed in the last 48 hours, including dynamic IPs |\n| L3 | `firehol_l3.mmdb` | Attacks, spyware, and viruses tracked over the last 30 days |\n| L4 | `firehol_l4.mmdb` | Aggressive tracking with a higher false-positive rate |\n| Anonymous | `firehol_anonymous.mmdb` | Tor exit nodes, I2P, VPNs, and other anonymity relays |\n\n**Pattern databases (LMDB)**\n\n| Database | Directory | What it contains |\n|---|---|---|\n| User-agent patterns | `useragent-db\u002Fuseragent.mdb` | Known bot, scraper, and tool user-agent signatures with severity ratings |\n| Disposable emails | `email-db\u002Fdisposable-emails.mdb` | Domain blocklist for temporary and disposable email providers |\n\n### Running Shield Base\n\nThe CLI accepts flags for individual sources or bulk compilation. The `--parallel` flag compiles all sources concurrently, which is the standard approach for periodic refreshes.\n\n::code-group\n\n```bash [pnpm]\n# Compile all sources in parallel\npnpm shield-base --all --parallel\n\n# Compile specific sources\npnpm shield-base --bgp --geo --tor --l1 --l2\n\n# Compile only LMDB pattern databases\npnpm shield-base --useragent --email\n```\n\n```bash [npm]\n# Compile all sources in parallel\nnpm run shield-base --all --parallel\n\n# Compile specific sources\nnpm run shield-base --bgp --geo --tor --l1 --l2\n\n# Compile only LMDB pattern databases\nnpm run shield-base --useragent --email\n```\n\n::\n\nInternally, `executeAll` runs 10 compilation tasks in parallel. Each task downloads its source data, processes it into the intermediate format, and compiles it using either the `mmdbctl` binary (for MMDB) or the native LMDB Node.js bindings. The output files land in a configured output directory that Bot Detector reads from at startup.\n\n::note\nShield Base requires a valid contact User-Agent for the BGP\u002FASN data fetch from bgp.tools. Configure this in your Shield Base settings before running the first compilation.\n::\n\n---\n\n## Bot Detector: The Two-Phase Scoring Pipeline\n\nBot Detector is a middleware factory. You call `configuration(config)` once at startup to register your settings and mount the middleware on your Express router. From that point on, every request passes through the pipeline, accumulates a score, and either continues to the next handler or receives a ban response.\n\n### Loading the Databases\n\nThe `DataSources` class loads all Shield Base outputs at initialization. It opens 11 MMDB readers (ASN, city, country, good bots, Tor, proxy, and all five FireHOL levels) and 1 LMDB reader (user-agent patterns). It also accepts optional banned and high-risk MMDB files for custom enforcement lists. All readers stay open and memory-resident for the lifetime of the process. There are no per-request file operations — every lookup is an in-memory binary search.\n\n### Scoring Mechanics\n\nEvery request starts with a score of zero. Checkers increment the score when they detect anomalies. The pipeline compares the running total against `banScore` (default: 100) after the cheap phase and again after the heavy phase. Reaching `banScore` at any point ends the pipeline immediately and sends a ban response.\n\nBetween requests, a reputation healer decrements the stored score by `restoredReputationPoints` (default: 10) for every non-banned request. A visitor who accumulated a score of 35 on a suspicious-looking first request will recover to zero across three or four clean subsequent requests, assuming no new checkers fire.\n\n```ts\n\u002F\u002F Default scoring configuration\nawait configuration({\n  banScore: 100,\n  maxScore: 100,\n  restoredReputationPoints: 10,\n  setNewComputedScore: false,\n  \u002F\u002F ...\n})\n```\n\nSetting `setNewComputedScore: false` (the recommended default) means the detector writes the computed score to the database only when no prior record exists. On subsequent requests, the reputation healer decrements the stored score without recomputing. This prevents a bot that varies its signals slightly between requests from oscillating between high and low scores — it accumulates a record and decays from it.\n\n### Phase One: The Cheap Checkers\n\nThe cheap phase runs 10 synchronous checks. These checks use only in-memory data — parsed request headers, pre-loaded database lookups, and cached session state. They run in microseconds. If the cumulative score reaches `banScore` at any point in this phase, the pipeline stops immediately.\n\n**1. IP Validation** — confirms the request carries a parseable, routable IP address. Malformed or missing IPs score 10 points. This catches raw tool invocations that do not set a legitimate source address.\n\n**2. Good and Bad Bot Verification** — checks the request's IP against `goodBots.mmdb`. If the IP belongs to a known crawler, the middleware performs a reverse DNS lookup to verify the IP actually belongs to the claimed crawler domain. A passing DNS check issues `GOOD_BOT_IDENTIFIED` and whitelists the request instantly — no further checks run. A failing DNS check (IP on the good-bot list but DNS does not verify) issues `BAD_BOT_DETECTED` at 100 points — an instant ban. This checker handles the common impersonation pattern where a bot claims a Google or Bing user-agent from an unrelated hosting IP.\n\n**3. Browser and Device Fingerprint** — parses the `User-Agent` header and applies penalties for impossible or implausible combinations.\n\n| Signal | Penalty |\n|---|---|\n| CLI tool or HTTP library (curl, Python requests, etc.) | 100 |\n| Internet Explorer | 100 |\n| Kali Linux OS | 10 |\n| Impossible browser\u002FOS combination | 30 |\n| Unknown browser type or name | 10 |\n| Desktop device without detectable OS | 10 |\n| Unknown device vendor | 10 |\n| Unknown browser version | 10 |\n| Unknown device model | 5 |\n\n**4. Locale Map Verification** — compares the `Accept-Language` header against the IP's geolocation country. A browser claiming `fr-FR` language from an IP geolocated to South Korea is suspicious. Missing or malformed `Accept-Language` headers score 20 points. A confirmed mismatch between language and geo scores an additional 20 points.\n\n**5. Known Threats (FireHOL)** — queries all five FireHOL MMDB files against the request IP. Each tier scores independently, so an IP appearing on multiple lists accumulates points from each.\n\n| FireHOL tier | Penalty |\n|---|---|\n| Anonymity network (Tor, VPN, I2P) | 20 |\n| L1 — critical current threats | 40 |\n| L2 — attacks in last 48 hours | 30 |\n| L3 — attacks in last 30 days | 20 |\n| L4 — aggressive tracking | 10 |\n\n**6. ASN Classification** — queries `asn.mmdb` to determine the Autonomous System the IP belongs to. Hosting and datacenter ASNs score 20 points. An ASN with unusually low visibility (few routes announced, below 15% of expected) scores an additional 10 points. The combination of hosting classification and low visibility scores a further 20 — this pattern is characteristic of freshly provisioned bot infrastructure.\n\n**7. Tor Node Analysis** — queries `tor.mmdb` to classify the specific role of any Tor node. Different node types carry different penalties because they represent different risk profiles.\n\n| Tor node type | Penalty |\n|---|---|\n| Active running node | 15 |\n| Exit node (base) | 20 |\n| Exit node (exit probability multiplier, up to +30) | dynamic |\n| Web-capable exit node | 15 |\n| Guard node | 10 |\n| Bad exit (flagged by Tor directory) | 40 |\n| Obsolete version | 10 |\n\nA high-probability exit node that is also flagged as a bad exit and running an obsolete version can accumulate 90 points from Tor analysis alone — enough to ban when combined with even minor signals from other checkers.\n\n**8. Timezone Consistency** — compares the `Timezone` request header against the timezone inferred from the IP's geolocation. A browser reporting a Central European timezone from an IP geolocated to Hong Kong scores 20 points.\n\n**9. Honeypot** — checks the request path against a configurable list of trap URLs. Any request to a honeypot path scores an immediate ban. Legitimate users never visit URLs that are not linked anywhere in the application. Only crawlers following harvested or guessed paths hit them.\n\n**10. Known Bad IPs** — queries optional `banned.mmdb` and `highRisk.mmdb` files you maintain independently. Previously banned IPs score an instant ban. High-risk IPs score 30 points. This checker enables you to carry forward enforcement decisions across restarts and import external blocklists.\n\n### Phase Two: The Heavy Checkers\n\nThe heavy phase runs only if the cheap phase did not trigger a ban. These seven checks require async operations — cache reads, timing calculations, database queries, and header analysis. They are deferred to the second phase because they are more expensive.\n\n**11. Behavior Rate Verification** — counts requests from this `canary_id` within a sliding window (default: 60 seconds, threshold: 30 requests). Exceeding the threshold scores 60 points. Unlike a simple IP-based rate limiter, this checker tracks per-session request rates. A bot that uses many IPs but reuses the same session cookie still triggers it.\n\n**12. Proxy, ISP, and Cookie Verification** — combines several signals into a single checker.\n\n| Signal | Penalty |\n|---|---|\n| Missing `canary_id` cookie | 80 |\n| Proxy detected (from `proxy.mmdb`) | 40 |\n| Multi-source proxy confirmation (2-3 sources) | +10 |\n| Multi-source proxy confirmation (4+ sources) | +20 |\n| Hosting provider detected | 50 |\n| Unknown ISP | 10 |\n| Unknown ORG | 10 |\n\nThe `canary_id` cookie check is the single highest-penalty individual signal in the pipeline at 80 points. Any request that does not carry a cookie is one triggering event away from a ban. This matters because the cookie is set on the very first request — a missing cookie on a subsequent request means either the client is rejecting cookies (a strong bot signal) or the request is coming from a tool that does not preserve session state.\n\n**13. Session Coherence** — uses the `canary_id` to retrieve the session's last known path from the session cache, then validates the incoming request's `Referer` header.\n\n| Signal | Penalty |\n|---|---|\n| Missing `Referer` on a same-origin request (`Sec-Fetch-Site: same-origin`) | 20 |\n| `Referer` domain does not match the application domain | 30 |\n| `Referer` path does not match the recorded last path | 10 |\n\nReal browsers send a `Referer` header when navigating within the same origin. Tools and scrapers that issue requests directly do not. A bot that correctly spoofs headers but does not correctly maintain session path history fails this check across multiple requests.\n\n**14. Velocity Fingerprinting** — collects timestamps for the last 10 requests from this session (minimum 5 required to evaluate) and computes the coefficient of variation (CV) of the inter-request intervals. The CV measures the relative variability of a set of values — a CV near zero means all intervals are nearly identical, which is characteristic of programmatic request scheduling.\n\n```\nCV = standard deviation \u002F mean\n\nCV \u003C 0.1 → timing too regular → penalty: 40\n```\n\nHuman browsing intervals are naturally irregular. Page load times, reading time, and click latency all vary. A bot that fires requests on a fixed timer — even a slow one — produces a CV far below the 0.1 threshold.\n\n**15. User-Agent and Header Analysis** — extends the cheap-phase fingerprint check with deeper inspection.\n\n| Signal | Penalty |\n|---|---|\n| Headless browser detected (Puppeteer, Selenium, Playwright, PhantomJS) | 100 |\n| User-agent shorter than 10 characters | 80 |\n| Header anomaly score too high | variable |\n| Path traversal attempt detected | variable |\n| XSS scripting attempt detected | variable |\n\n**16. Geolocation Validation** — penalizes missing geolocation data across nine dimensions: country, region, city, latitude\u002Flongitude, timezone, subregion, phone prefix, district, and continent. Each missing dimension scores 10 points. A request from an IP with no geolocation coverage can accumulate up to 90 points from this checker alone, making it trivially over the ban threshold when combined with any other signal. The checker also supports a configurable banned-country list.\n\n**17. Known Bad User-Agents** — queries `useragent.mdb` against the full user-agent string. The LMDB database stores patterns compiled from community-maintained lists of bot and scraper signatures, each rated by severity.\n\n| Severity | Penalty |\n|---|---|\n| Critical | 100 |\n| High | 80 |\n| Medium | 30 |\n| Low | 10 |\n\n---\n\n## The Canary Cookie: Bridging Sessions\n\nThe `canary_id` cookie is issued by the `canaryCookieChecker` middleware on the very first request from any browser. Its value is a 64-character hex string generated from 32 cryptographically random bytes.\n\n```ts\nrandomBytes(32).toString('hex')\n\u002F\u002F Example: \"a3f8e2c1d4b7a90f...\"  (64 hex characters)\n```\n\nThe cookie itself is opaque — it carries no embedded data and cannot be decoded. All the meaningful state lives server-side, keyed on the cookie value.\n\n### Cookie Attributes\n\n```\nname:      canary_id\nhttpOnly:  true\nsameSite:  lax\nsecure:    true\npath:      \u002F\nmaxAge:    7,776,000,000 ms  (90 days)\n```\n\nThe `httpOnly` attribute prevents JavaScript from reading the cookie, blocking the class of attacks where a page script exfiltrates the cookie and reuses it from a different client. The 90-day maxAge matches the outer boundary for legitimate long-running sessions.\n\n### What the Server Stores\n\nWhen Bot Detector issues a `canary_id`, it begins building a persistent record keyed on that value. This record accumulates across every subsequent request.\n\n**Visitor record (database, persistent):**\n\n```ts\n{\n  visitorId: UUID,\n  cookie: canary_id,\n  userAgent: string,\n  ipAddress: string,\n  device_type: string,\n  browser: string,\n  is_bot: boolean,\n  first_seen: timestamp,\n  last_seen: timestamp,\n  request_count: number,\n  deviceVendor: string,\n  deviceModel: string,\n  browserType: string,\n  browserVersion: string,\n  os: string,\n  activity_score: number,\n  country: string,\n  region: string,\n  city: string,\n  timezone: string,\n  \u002F\u002F ...additional geolocation fields\n}\n```\n\n**In-memory caches (fast lookup per request):**\n\n| Cache | Key | What it holds |\n|---|---|---|\n| `visitorCache` | `canary_id` | `{ banned, visitor_id }` — fast ban lookup |\n| `sessionCache` | `canary_id` | `{ lastPath }` — session coherence tracking |\n| `rateCache` | `canary_id` | `{ score, timestamp, request_count }` — behavioral rate |\n| `timingCache` | `canary_id` | Array of last 10 request timestamps — velocity fingerprint |\n| `reputationCache` | `canary_id` | `{ isBot, score }` — reputation healer state |\n| `dnsCache` | IP | `{ ip, trustedBot }` — verified crawler result |\n\nThe split between the persistent database record and the in-memory caches is intentional. The database record survives restarts and is queryable for analytics. The in-memory caches are ephemeral but fast — they hold exactly the data the pipeline needs per request, without deserializing a full database row.\n\n### The Canary Cookie in the IAM Service\n\nThe IAM service runs Bot Detector as part of its own middleware chain. Every request to the IAM service — login, logout, token rotation, MFA — passes through the same 17-checker pipeline before reaching any authentication logic.\n\nWhen Bot Detector passes a request through, the IAM service reads the `canary_id` cookie and stores it alongside the refresh token family for that session. The `strangeThings()` anomaly detection function, which runs during every token rotation attempt, includes a `canary_id` binding check as one of its nine sequential verifications.\n\nIf the `canary_id` on a rotation request does not match the one recorded when the session was originally created, the anomaly detector triggers. Depending on the severity, it either sends an MFA challenge to the user's email or revokes the session entirely. This means an attacker who steals a valid refresh token but makes the rotation request from a different device — one with a different `canary_id` — cannot complete the rotation without also accessing the user's email.\n\n---\n\n## Walking Through a Bot Request\n\nTo make the pipeline concrete, here is what happens when a credential-stuffing bot attempts a login.\n\nThe bot sends a `POST \u002Fauth\u002Fuser\u002Flogin` request with a valid email and password combination. It uses a Python `requests` library with a spoofed user-agent string, from a residential proxy pool. It sends one request every 4 seconds on a fixed timer.\n\n**Cheap phase results:**\n\n- IP Validation: passes (valid IPv4).\n- Good\u002FBad Bot: IP is not on the good-bot list. No instant ban.\n- Browser and Device Fingerprint: The user-agent parses as Chrome, but the library headers are subtly wrong — no `sec-ch-ua` header family, no `sec-fetch-*` headers. Unknown browser type: +10. Impossible header combination: +30. Running total: 40.\n- Locale Map: The `Accept-Language` header is missing. +20. Running total: 60.\n- Known Threats: The residential proxy IP happens to appear on the FireHOL L3 list (a 30-day tracked threat). +20. Running total: 80.\n- ASN Classification: The proxy's ASN is classified as hosting with low visibility. +20 + +10. Running total exceeds 100.\n\n**The pipeline stops at the cheap phase.** The request receives a 403 response before the login handler runs. No database query for the user record. No password check. No rate limiter on the login endpoint needs to absorb the request.\n\nNow consider a more sophisticated bot — one that uses a real browser, a real residential IP, and carefully spoofs all headers. The cheap phase may score only 10-20 points.\n\n**Heavy phase results:**\n\n- Behavior Rate: The bot fires at exactly 4-second intervals. After 5 requests, the velocity fingerprint computes CV = 0.02. +40. Running total: 50-60.\n- Session Coherence: The bot navigates directly to `\u002Fauth\u002Fuser\u002Flogin` without going through the home page first. The `Referer` header is absent on what looks like same-origin navigation. +20. Running total: 70-80.\n- User-Agent and Header Analysis: Header mismatch and lack of acceptable HTTP configurations indicate automated access. +60. Running total: 130+.\n\n**The pipeline stops at the heavy phase.** Even a well-configured bot that passes the cheap phase reveals itself through timing regularity, navigation patterns, and header analysis.\n\n---\n\n## Configuration\n\nA realistic Bot Detector configuration that enables the full pipeline looks like this:\n\n```ts\nimport { configuration } from 'bot-detector'\n\nawait configuration({\n  store: {\n    main: { driver: 'sqlite', name: '.\u002Fbot-detector.db' }\n  },\n\n  banScore: 100,\n  maxScore: 100,\n  restoredReputationPoints: 10,\n  setNewComputedScore: false,\n\n  whiteList: ['203.0.113.0\u002F24'],\n\n  checkers: {\n    enableIpChecks: { enable: true, penalties: 10 },\n\n    enableGoodBotsChecks: {\n      enable: true,\n      banUnlistedBots: true,\n      penalties: 100\n    },\n\n    enableBrowserAndDeviceChecks: { enable: true },\n\n    localeMapsCheck: { enable: true },\n\n    enableKnownThreatsDetections: {\n      enable: true,\n      penalties: {\n        anonymityNetwork: 20,\n        fireholL1: 40,\n        fireholL2: 30,\n        fireholL3: 20,\n        fireholL4: 10\n      }\n    },\n\n    enableAsnClassification: { enable: true },\n\n    enableTorAnalysis: { enable: true },\n\n    enableTimezoneConsistency: { enable: true },\n\n    honeypot: {\n      enable: true,\n      paths: ['\u002Fadmin', '\u002F.env', '\u002Fwp-login.php', '\u002Fxmlrpc.php']\n    },\n\n    enableKnownBadIpsCheck: { enable: true },\n\n    enableBehaviorRateCheck: {\n      enable: true,\n      behavioral_window: 60_000,\n      behavioral_threshold: 30,\n      penalties: 60\n    },\n\n    enableProxyIspCookiesChecks: { enable: true },\n\n    enableSessionCoherence: { enable: true },\n\n    enableVelocityFingerprint: {\n      enable: true,\n      cvThreshold: 0.1\n    },\n\n    enableUaAndHeaderChecks: { enable: true },\n\n    enableGeoChecks: {\n      enable: true,\n      bannedCountries: []\n    },\n\n    knownBadUserAgents: { enable: true }\n  }\n})\n```\n\n::tip\nStart with the cheap-phase checkers at conservative penalty values and raise them after observing traffic patterns. The FireHOL L4 level and ASN low-visibility penalties are the most likely to produce false positives on legitimate traffic from cloud-heavy regions.\n::\n\n---\n\n## Extending the Pipeline: Custom Checkers\n\nEvery built-in checker follows the same interface, and you can add your own with the exact same mechanism. The pipeline does not distinguish between built-in and custom checkers at runtime — they share the same scoring accumulation, the same short-circuit logic, and the same `ValidationContext`.\n\n### The `IBotChecker` Interface\n\nA checker is a class that implements `IBotChecker`. It declares which phase it belongs to, a condition that enables or disables it, and a `run` method that returns a numeric score and an array of reason codes.\n\n```ts\ninterface IBotChecker\u003CCode, TCustom = Record\u003Cstring, never>> {\n  name: string;\n  phase: 'cheap' | 'heavy';\n  isEnabled(config: BotDetectorConfig): boolean;\n  run(ctx: ValidationContext\u003CTCustom>, config: BotDetectorConfig):\n    | Promise\u003C{ score: number; reasons: Code[] }>\n    | { score: number; reasons: Code[] };\n}\n```\n\nThe `run` method can be synchronous or async. Phase assignment is the only routing decision you make — everything else is handled by the pipeline.\n\n### What the Pipeline Gives You\n\nBefore your `run` method executes, the pipeline has already resolved every expensive lookup. All of this is available on `ctx` at zero cost:\n\n| Field | Contents |\n|---|---|\n| `ctx.req` | Full Express request (headers, path, cookies, method) |\n| `ctx.ipAddress` | Resolved client IP |\n| `ctx.cookie` | `canary_id` value, or `undefined` on first request |\n| `ctx.geoData` | Merged country, city, ASN, and proxy data |\n| `ctx.tor` | Tor relay classification from `tor.mmdb` |\n| `ctx.bgp` | ASN routing data: `asn_id`, `asn_name`, `classification`, `hits` |\n| `ctx.threatLevel` | Highest FireHOL tier matched (`1`–`4`), or `null` |\n| `ctx.anon` | `true` if IP is in the anonymity network database |\n| `ctx.parsedUA` | Parsed user-agent: browser, OS, device, `browserType`, bot flags |\n| `ctx.proxy` | `{ isProxy, proxyType }` from proxy MMDB |\n| `ctx.custom` | Your own per-request data, populated by `buildCustomContext` |\n\n`ctx.bgp.classification` is worth highlighting. The value `\"Content\"` means the ASN is classified as a hosting or CDN network. `\"Eyeballs\"` means residential or business internet. This single field lets a custom checker apply completely different logic for datacenter traffic versus consumer traffic without any additional lookup.\n\n### A Minimal Cheap Checker\n\nThe example below penalises requests from a datacenter ASN that carry no `Accept-Language` header — a pattern common in automated clients that partially spoof browser headers but miss the locale details.\n\n```ts [datacenter-locale-checker.ts]\nimport { CheckerRegistry } from '@riavzon\u002Fbot-detector';\nimport type { IBotChecker, ValidationContext, BotDetectorConfig } from '@riavzon\u002Fbot-detector';\n\ntype Code = 'DATACENTER_NO_LOCALE' | 'BAD_BOT_DETECTED';\n\nclass DatacenterLocaleChecker implements IBotChecker\u003CCode> {\n  name = 'DatacenterLocaleChecker';\n  phase = 'cheap' as const;\n\n  isEnabled(_config: BotDetectorConfig): boolean {\n    return true;\n  }\n\n  run(ctx: ValidationContext, _config: BotDetectorConfig) {\n    const reasons: Code[] = [];\n    let score = 0;\n\n    const isHosting = ctx.bgp.classification === 'Content';\n    const hasLocale = Boolean(ctx.req.get('Accept-Language'));\n\n    if (isHosting && !hasLocale) {\n      score += 25;\n      reasons.push('DATACENTER_NO_LOCALE');\n    }\n\n    return { score, reasons };\n  }\n}\n\nCheckerRegistry.register(new DatacenterLocaleChecker());\n```\n\nRegistration happens at module load time. A side-effect import in your server entry point is enough to activate the checker. Import order controls execution order within each phase.\n\n```ts [server.ts]\nimport { defineConfiguration, detectBots } from '@riavzon\u002Fbot-detector';\nimport '.\u002Fdatacenter-locale-checker.js'; \u002F\u002F registers on import\n\nawait defineConfiguration({ \u002F* ... *\u002F });\napp.use(detectBots());\n```\n\n### Passing Application Context Into Checkers\n\nThe `buildCustomContext` function runs once per request before any checker executes. It receives the raw Express request and returns the `ctx.custom` object. Passing the generic type through to `IBotChecker` and `ValidationContext` gives full IntelliSense on `ctx.custom` inside `run`.\n\n```ts [server.ts]\ninterface MyContext {\n  userId: string;\n  plan: 'free' | 'pro' | 'enterprise';\n  isInternal: boolean;\n}\n\napp.use(\n  detectBots\u003CMyContext>((req) => ({\n    userId:     req.user?.id   ?? 'anonymous',\n    plan:       req.user?.plan ?? 'free',\n    isInternal: req.ip === '127.0.0.1',\n  }))\n);\n```\n\n```ts [plan-abuse-checker.ts]\nimport type { IBotChecker, ValidationContext, BotDetectorConfig, BanReasonCode } from '@riavzon\u002Fbot-detector';\nimport type { MyContext } from '.\u002FmyContext.js';\n\nclass PlanAbuseChecker implements IBotChecker\u003CBanReasonCode, MyContext> {\n  name = 'PlanAbuseChecker';\n  phase = 'cheap' as const;\n\n  isEnabled(_config: BotDetectorConfig) { return true; }\n\n  run(ctx: ValidationContext\u003CMyContext>, _config: BotDetectorConfig) {\n    if (ctx.custom.isInternal) return { score: 0, reasons: [] };\n\n    if (ctx.custom.plan === 'free' && ctx.geoData.proxy) {\n      return { score: 20, reasons: ['PROXY_DETECTED'] };\n    }\n\n    return { score: 0, reasons: [] };\n  }\n}\n```\n\nThis pattern lets you apply business logic — plan tier, user role, internal traffic bypass — inside the same scoring pipeline that handles IP reputation and behavioral analysis, without any special wiring.\n\n### Triggering an Instant Ban\n\nReturning `'BAD_BOT_DETECTED'` in the reasons array causes the pipeline to throw `BadBotDetected` immediately. No further checkers run, and the reputation healer does not execute. The visitor is banned without waiting for score accumulation.\n\n```ts\nrun(ctx: ValidationContext, _config: BotDetectorConfig) {\n  if (isDefinitelyABot(ctx)) {\n    return { score: 0, reasons: ['BAD_BOT_DETECTED'] };\n  }\n  return { score: 0, reasons: [] };\n}\n```\n\nThe mirror is `'GOOD_BOT_IDENTIFIED'`, which whitelists the request instantly. The built-in good-bot DNS verifier uses this same mechanism.\n\n### Heavy Checkers and the Built-In Storage\n\nCheckers that require I\u002FO — database queries, external API calls, cache reads — declare `phase: 'heavy'`. The heavy phase only runs when the cheap phase score stays below `banScore`. Call `getStorage()` to access the same storage instance Bot Detector uses internally, keeping all cache I\u002FO in one place.\n\n```ts [my-async-checker.ts]\nimport { getStorage, CheckerRegistry } from '@riavzon\u002Fbot-detector';\nimport type { IBotChecker, ValidationContext, BotDetectorConfig } from '@riavzon\u002Fbot-detector';\n\nclass MyAsyncChecker implements IBotChecker\u003C'MY_REASON'> {\n  name = 'MyAsyncChecker';\n  phase = 'heavy' as const;\n\n  isEnabled(_config: BotDetectorConfig): boolean { return true; }\n\n  async run(ctx: ValidationContext, _config: BotDetectorConfig) {\n    if (!ctx.cookie) return { score: 0, reasons: [] };\n\n    const storage = getStorage();\n    const cacheKey = `custom:${ctx.cookie}`;\n\n    const cached = await storage.getItem\u003Cnumber>(cacheKey);\n    if (cached !== null) {\n      return { score: cached, reasons: cached > 0 ? ['MY_REASON' as const] : [] };\n    }\n\n    const result = await myDb.query('SELECT ...', [ctx.ipAddress]);\n    const score = result.isSuspicious ? 30 : 0;\n\n    await storage.setItem(cacheKey, score, { ttl: 300 });\n    return { score, reasons: score > 0 ? ['MY_REASON' as const] : [] };\n  }\n}\n\nCheckerRegistry.register(new MyAsyncChecker());\n```\n\n::tip\nUse a namespaced key prefix for your cache entries (for example `custom:`) to avoid collisions with the built-in cache keys that share the same storage instance.\n::\n\n---\n\n## Automatic Threat Compilation: The Generator\n\nThe `Known Bad IPs` checker — checker 10 in the cheap phase — queries two optional MMDB files: `banned.mmdb` and `highRisk.mmdb`. These files do not come from Shield Base. Bot Detector generates them itself from its own accumulated traffic history.\n\n### What Gets Compiled\n\nRunning `bot-detector generate` reads two tables from Bot Detector's database and compiles each into an MMDB file. Both compilations run in parallel.\n\n**`banned.mmdb`** — every row in the `banned` table with a non-null `ip_address` gets compiled into this file. Each entry stores the IP, score, country, user-agent, and reason codes from the original ban event. On subsequent visits, the Known Bad IPs checker matches the IP in microseconds in the cheap phase and issues `BAD_BOT_DETECTED` immediately — the full 17-checker pipeline never runs for a confirmed repeat offender.\n\n**`highRisk.mmdb`** — every row in the `visitors` table where `suspicious_activity_score` is at or above `generator.scoreThreshold` (default `70`) is compiled into this file. These are visitors who accumulated significant suspicion scores but were never pushed over `banScore`. On their next visit, they receive the `highRiskPenalty` (default `30` points) in the cheap phase, meaning far less effort from other checkers is needed to reach a ban.\n\n```ts\ngenerator: {\n  scoreThreshold: 70,   \u002F\u002F minimum score to include in highRisk.mmdb\n  deleteAfterBuild: false,  \u002F\u002F if true, removes compiled rows from DB after build\n  mmdbctlPath: 'mmdbctl',   \u002F\u002F path to mmdbctl binary\n  generateTypes: false,     \u002F\u002F emit TypeScript type definitions alongside MMDB files\n}\n```\n\nThe `scoreThreshold` tradeoff is worth understanding. Lowering it to `40` catches visitors with moderate suspicious history but risks false positives. Keeping it at `70` or higher limits the file to visitors with strong behavioral evidence.\n\n| Threshold | Effect |\n|---|---|\n| `40` | Broader net — includes visitors with moderate accumulated scores |\n| `70` (default) | Balanced — strong suspicious history required |\n| `90` | Conservative — only the most suspicious non-banned visitors included |\n\n### Hot Reload\n\nBoth MMDB files are opened with `watchForUpdates: true`. When a new file is written to disk after a generation run, the MMDB reader reloads it automatically within seconds — no application restart, no traffic interruption. You can run generation against a live service and the updated databases take effect immediately.\n\n### Running Generation\n\n::code-group\n\n```bash [pnpm]\npnpm dlx @riavzon\u002Fbot-detector generate\n```\n\n```bash [yarn]\nyarn dlx @riavzon\u002Fbot-detector generate\n```\n\n```bash [npm]\nnpx @riavzon\u002Fbot-detector generate\n```\n\n```bash [bun]\nbunx @riavzon\u002Fbot-detector generate\n```\n\n::\n\nFor programmatic use — for example, triggering generation immediately after a bulk ban operation — call `runGeneration()` directly:\n\n```ts [admin-script.ts]\nimport { updateBannedIP, runGeneration } from '@riavzon\u002Fbot-detector';\nimport type { BannedInfo } from '@riavzon\u002Fbot-detector';\n\nfor (const ip of badIps) {\n  const info: BannedInfo = { score: 100, reasons: ['PREVIOUSLY_BANNED_IP'] };\n  await updateBannedIP('', ip, 'us', '', info);\n}\n\n\u002F\u002F Compile updated MMDB files immediately so the next request from these IPs\n\u002F\u002F hits the cheap-phase known-bad-IPs check rather than the full pipeline.\nawait runGeneration();\n```\n\n### Scheduling Generation\n\nThe right generation frequency depends on traffic volume. A nightly run is a reasonable default. For higher-traffic applications where bans accumulate quickly, hourly generation keeps the banned MMDB current and prevents repeat offenders from absorbing pipeline capacity.\n\n```cron [crontab]\n# Nightly at 2:00 AM\n0 2 * * * cd \u002Fapp && npx bot-detector generate >> \u002Fvar\u002Flog\u002Fbot-detector-generate.log 2>&1\n\n# Hourly for high-traffic deployments\n0 * * * * cd \u002Fapp && npx bot-detector generate >> \u002Fvar\u002Flog\u002Fbot-detector-generate.log 2>&1\n```\n\nThe generate command emits structured log lines including the entry count for each compiled database. Monitoring this output over time makes it easy to detect when ban volume spikes — a sudden increase in `banned.mmdb` entries typically indicates a coordinated attack campaign starting.\n\n---\n\n## Summary\n\nEach of the three layers closes a gap that the others cannot. Shield Base provides static intelligence — historical threat reputation, network classification, and behavioral pattern databases — that no runtime analysis can replicate. Bot Detector performs dynamic behavioral analysis — velocity, session coherence, timing regularity — that static blocklists cannot catch. The canary cookie ties both together across sessions, making it impossible to reset accumulated behavioral signals simply by rotating IPs or changing headers.\n\nA bot that evades Shield Base's IP reputation checks still faces 17 behavioral checkers. A bot that passes all 17 checkers on a single request still accumulates a session history that degrades its score over time. A bot that steals an authenticated session still cannot complete token rotation without matching the canary cookie fingerprint that was established on the original device.\n\nThe layered approach trades complexity for resilience. Each layer is effective in isolation. Together, they make the cost of a successful bot attack high enough that most attackers move on to easier targets.\n\n\nRead the full [Bot Detector](\u002Fdocs\u002Fbot-detection) reference\n\n\n\nRead the full [Shield Base](\u002Fdocs\u002Fshield-base) reference for database compilation options\n\n","18 min read",{"title":73,"description":6277},[38,33,6286],"Infrastructure","qrRrFdD_8vsWWEP0DFOijKzFT1C836gEzCUNyAsDjhk",{"id":804,"title":73,"author":805,"authorGithub":806,"authorGithubUserName":807,"authorImg":808,"body":6289,"date":6276,"description":6277,"extension":6278,"featured":53,"icon":6279,"image":6280,"meta":10746,"navigation":8,"path":74,"rawbody":6282,"readingTime":6283,"seo":10747,"stem":75,"tags":10748,"__hash__":6287},{"type":810,"value":6290,"toc":10710},[6291,6293,6295,6297,6299,6301,6303,6305,6307,6312,6314,6316,6318,6320,6322,6324,6326,6330,6418,6422,6424,6488,6492,6526,6528,6532,6660,6666,6670,6672,6674,6678,6680,6684,6686,6692,6696,6760,6764,6766,6770,6774,6784,6790,6856,6866,6870,6912,6918,6924,6978,6980,6986,6990,6998,7000,7002,7008,7012,7070,7074,7082,7120,7124,7128,7133,7135,7139,7181,7185,7191,7227,7229,7231,7237,7267,7269,7271,7276,7280,7282,7286,7290,7466,7470,7564,7566,7568,7570,7578,7584,7586,7588,7590,7596,7600,7620,7624,7626,7630,7642,7646,7648,7650,7652,8356,8360,8362,8364,8368,8372,8378,8532,8536,8538,8544,8672,8680,8682,8686,9072,9074,9146,9148,9162,9342,9654,9656,9658,9664,9760,9764,9766,9774,10274,10280,10282,10284,10292,10294,10298,10310,10330,10386,10394,10430,10432,10436,10438,10492,10496,10658,10660,10662,10686,10690,10692,10694,10696,10698,10700,10704,10708],[813,6292,815],{},[813,6294,818],{},[820,6296],{},[823,6298,826],{"id":825},[813,6300,829],{},[813,6302,832],{},[813,6304,835],{},[813,6306,838],{},[840,6308,6310],{"className":6309,"code":844,"language":845},[843],[847,6311,844],{"__ignoreMap":849},[820,6313],{},[823,6315,855],{"id":854},[813,6317,858],{},[860,6319,863],{"id":862},[813,6321,866],{},[860,6323,870],{"id":869},[813,6325,873],{},[813,6327,6328],{},[877,6329,879],{},[881,6331,6332,6344],{},[884,6333,6334],{},[887,6335,6336,6338,6340,6342],{},[890,6337,431],{},[890,6339,894],{},[890,6341,897],{},[890,6343,900],{},[902,6345,6346,6358,6370,6382,6394,6406],{},[887,6347,6348,6350,6354,6356],{},[907,6349,909],{},[907,6351,6352],{},[847,6353,914],{},[907,6355,917],{},[907,6357,920],{},[887,6359,6360,6362,6366,6368],{},[907,6361,925],{},[907,6363,6364],{},[847,6365,930],{},[907,6367,933],{},[907,6369,936],{},[887,6371,6372,6374,6378,6380],{},[907,6373,941],{},[907,6375,6376],{},[847,6377,946],{},[907,6379,949],{},[907,6381,952],{},[887,6383,6384,6386,6390,6392],{},[907,6385,957],{},[907,6387,6388],{},[847,6389,962],{},[907,6391,965],{},[907,6393,968],{},[887,6395,6396,6398,6402,6404],{},[907,6397,973],{},[907,6399,6400],{},[847,6401,978],{},[907,6403,981],{},[907,6405,984],{},[887,6407,6408,6410,6414,6416],{},[907,6409,989],{},[907,6411,6412],{},[847,6413,994],{},[907,6415,997],{},[907,6417,1000],{},[813,6419,6420],{},[877,6421,1005],{},[813,6423,1008],{},[881,6425,6426,6436],{},[884,6427,6428],{},[887,6429,6430,6432,6434],{},[890,6431,1017],{},[890,6433,1020],{},[890,6435,1023],{},[902,6437,6438,6448,6458,6468,6478],{},[887,6439,6440,6442,6446],{},[907,6441,1030],{},[907,6443,6444],{},[847,6445,1035],{},[907,6447,1038],{},[887,6449,6450,6452,6456],{},[907,6451,1043],{},[907,6453,6454],{},[847,6455,1048],{},[907,6457,1051],{},[887,6459,6460,6462,6466],{},[907,6461,1056],{},[907,6463,6464],{},[847,6465,1061],{},[907,6467,1064],{},[887,6469,6470,6472,6476],{},[907,6471,1069],{},[907,6473,6474],{},[847,6475,1074],{},[907,6477,1077],{},[887,6479,6480,6482,6486],{},[907,6481,1082],{},[907,6483,6484],{},[847,6485,1087],{},[907,6487,1090],{},[813,6489,6490],{},[877,6491,1095],{},[881,6493,6494,6504],{},[884,6495,6496],{},[887,6497,6498,6500,6502],{},[890,6499,431],{},[890,6501,1106],{},[890,6503,900],{},[902,6505,6506,6516],{},[887,6507,6508,6510,6514],{},[907,6509,1115],{},[907,6511,6512],{},[847,6513,1120],{},[907,6515,1123],{},[887,6517,6518,6520,6524],{},[907,6519,1128],{},[907,6521,6522],{},[847,6523,1133],{},[907,6525,1136],{},[860,6527,1140],{"id":1139},[813,6529,1143,6530,1147],{},[847,6531,1146],{},[1149,6533,6534,6594],{},[840,6535,6536],{"className":1153,"code":1154,"filename":1155,"language":1156,"meta":849,"style":849},[847,6537,6538,6542,6552,6556,6560,6576,6580,6584],{"__ignoreMap":849},[1160,6539,6540],{"class":1162,"line":1163},[1160,6541,1167],{"class":1166},[1160,6543,6544,6546,6548,6550],{"class":1162,"line":1170},[1160,6545,1155],{"class":1173},[1160,6547,1177],{"class":1176},[1160,6549,1181],{"class":1180},[1160,6551,1184],{"class":1180},[1160,6553,6554],{"class":1162,"line":1187},[1160,6555,1190],{"emptyLinePlaceholder":8},[1160,6557,6558],{"class":1162,"line":1193},[1160,6559,1196],{"class":1166},[1160,6561,6562,6564,6566,6568,6570,6572,6574],{"class":1162,"line":1199},[1160,6563,1155],{"class":1173},[1160,6565,1177],{"class":1176},[1160,6567,1206],{"class":1180},[1160,6569,1209],{"class":1180},[1160,6571,1212],{"class":1180},[1160,6573,1215],{"class":1180},[1160,6575,1218],{"class":1180},[1160,6577,6578],{"class":1162,"line":1221},[1160,6579,1190],{"emptyLinePlaceholder":8},[1160,6581,6582],{"class":1162,"line":1226},[1160,6583,1229],{"class":1166},[1160,6585,6586,6588,6590,6592],{"class":1162,"line":1232},[1160,6587,1155],{"class":1173},[1160,6589,1177],{"class":1176},[1160,6591,1239],{"class":1180},[1160,6593,1242],{"class":1180},[840,6595,6596],{"className":1153,"code":1245,"filename":1246,"language":1156,"meta":849,"style":849},[847,6597,6598,6602,6614,6618,6622,6640,6644,6648],{"__ignoreMap":849},[1160,6599,6600],{"class":1162,"line":1163},[1160,6601,1167],{"class":1166},[1160,6603,6604,6606,6608,6610,6612],{"class":1162,"line":1170},[1160,6605,1246],{"class":1173},[1160,6607,1259],{"class":1176},[1160,6609,1177],{"class":1176},[1160,6611,1181],{"class":1180},[1160,6613,1184],{"class":1180},[1160,6615,6616],{"class":1162,"line":1187},[1160,6617,1190],{"emptyLinePlaceholder":8},[1160,6619,6620],{"class":1162,"line":1193},[1160,6621,1196],{"class":1166},[1160,6623,6624,6626,6628,6630,6632,6634,6636,6638],{"class":1162,"line":1199},[1160,6625,1246],{"class":1173},[1160,6627,1259],{"class":1176},[1160,6629,1177],{"class":1176},[1160,6631,1206],{"class":1180},[1160,6633,1209],{"class":1180},[1160,6635,1212],{"class":1180},[1160,6637,1215],{"class":1180},[1160,6639,1218],{"class":1180},[1160,6641,6642],{"class":1162,"line":1221},[1160,6643,1190],{"emptyLinePlaceholder":8},[1160,6645,6646],{"class":1162,"line":1226},[1160,6647,1229],{"class":1166},[1160,6649,6650,6652,6654,6656,6658],{"class":1162,"line":1232},[1160,6651,1246],{"class":1173},[1160,6653,1259],{"class":1176},[1160,6655,1177],{"class":1176},[1160,6657,1239],{"class":1180},[1160,6659,1242],{"class":1180},[813,6661,1314,6662,1318,6664,1322],{},[847,6663,1317],{},[847,6665,1321],{},[1324,6667,6668],{},[813,6669,1328],{},[820,6671],{},[823,6673,1334],{"id":1333},[813,6675,1337,6676,1341],{},[847,6677,1340],{},[860,6679,1345],{"id":1344},[813,6681,1348,6682,1352],{},[847,6683,1351],{},[860,6685,1356],{"id":1355},[813,6687,1359,6688,1363,6690,1366],{},[847,6689,1362],{},[847,6691,1362],{},[813,6693,1369,6694,1373],{},[847,6695,1372],{},[840,6697,6698],{"className":1376,"code":1377,"language":1378,"meta":849,"style":849},[847,6699,6700,6704,6712,6722,6732,6742,6752,6756],{"__ignoreMap":849},[1160,6701,6702],{"class":1162,"line":1163},[1160,6703,1385],{"class":1166},[1160,6705,6706,6708,6710],{"class":1162,"line":1170},[1160,6707,1391],{"class":1390},[1160,6709,1394],{"class":1173},[1160,6711,1398],{"class":1397},[1160,6713,6714,6716,6718,6720],{"class":1162,"line":1187},[1160,6715,1404],{"class":1403},[1160,6717,1408],{"class":1407},[1160,6719,1412],{"class":1411},[1160,6721,1415],{"class":1397},[1160,6723,6724,6726,6728,6730],{"class":1162,"line":1193},[1160,6725,1420],{"class":1403},[1160,6727,1408],{"class":1407},[1160,6729,1412],{"class":1411},[1160,6731,1415],{"class":1397},[1160,6733,6734,6736,6738,6740],{"class":1162,"line":1199},[1160,6735,1431],{"class":1403},[1160,6737,1408],{"class":1407},[1160,6739,1436],{"class":1411},[1160,6741,1415],{"class":1397},[1160,6743,6744,6746,6748,6750],{"class":1162,"line":1221},[1160,6745,1443],{"class":1403},[1160,6747,1408],{"class":1407},[1160,6749,1448],{"class":1180},[1160,6751,1415],{"class":1397},[1160,6753,6754],{"class":1162,"line":1226},[1160,6755,1455],{"class":1166},[1160,6757,6758],{"class":1162,"line":1232},[1160,6759,1460],{"class":1397},[813,6761,1463,6762,1467],{},[847,6763,1466],{},[860,6765,1471],{"id":1470},[813,6767,1474,6768,1477],{},[847,6769,1362],{},[813,6771,6772,1483],{},[877,6773,1482],{},[813,6775,6776,1489,6778,1492,6780,1496,6782,1500],{},[877,6777,1488],{},[847,6779,994],{},[847,6781,1495],{},[847,6783,1499],{},[813,6785,6786,1506,6788,1510],{},[877,6787,1505],{},[847,6789,1509],{},[881,6791,6792,6800],{},[884,6793,6794],{},[887,6795,6796,6798],{},[890,6797,1519],{},[890,6799,1522],{},[902,6801,6802,6808,6814,6820,6826,6832,6838,6844,6850],{},[887,6803,6804,6806],{},[907,6805,1529],{},[907,6807,1532],{},[887,6809,6810,6812],{},[907,6811,1537],{},[907,6813,1532],{},[887,6815,6816,6818],{},[907,6817,1544],{},[907,6819,1547],{},[887,6821,6822,6824],{},[907,6823,1552],{},[907,6825,1555],{},[887,6827,6828,6830],{},[907,6829,1560],{},[907,6831,1547],{},[887,6833,6834,6836],{},[907,6835,1567],{},[907,6837,1547],{},[887,6839,6840,6842],{},[907,6841,1574],{},[907,6843,1547],{},[887,6845,6846,6848],{},[907,6847,1581],{},[907,6849,1547],{},[887,6851,6852,6854],{},[907,6853,1588],{},[907,6855,1591],{},[813,6857,6858,1597,6860,1601,6862,1605,6864,1608],{},[877,6859,1596],{},[847,6861,1600],{},[847,6863,1604],{},[847,6865,1600],{},[813,6867,6868,1614],{},[877,6869,1613],{},[881,6871,6872,6880],{},[884,6873,6874],{},[887,6875,6876,6878],{},[890,6877,1623],{},[890,6879,1522],{},[902,6881,6882,6888,6894,6900,6906],{},[887,6883,6884,6886],{},[907,6885,1632],{},[907,6887,1635],{},[887,6889,6890,6892],{},[907,6891,1640],{},[907,6893,1643],{},[887,6895,6896,6898],{},[907,6897,1648],{},[907,6899,1555],{},[887,6901,6902,6904],{},[907,6903,1655],{},[907,6905,1635],{},[887,6907,6908,6910],{},[907,6909,1662],{},[907,6911,1547],{},[813,6913,6914,1670,6916,1673],{},[877,6915,1669],{},[847,6917,914],{},[813,6919,6920,1670,6922,1681],{},[877,6921,1678],{},[847,6923,978],{},[881,6925,6926,6934],{},[884,6927,6928],{},[887,6929,6930,6932],{},[890,6931,1690],{},[890,6933,1522],{},[902,6935,6936,6942,6948,6954,6960,6966,6972],{},[887,6937,6938,6940],{},[907,6939,1699],{},[907,6941,1702],{},[887,6943,6944,6946],{},[907,6945,1707],{},[907,6947,1635],{},[887,6949,6950,6952],{},[907,6951,1714],{},[907,6953,1717],{},[887,6955,6956,6958],{},[907,6957,1722],{},[907,6959,1702],{},[887,6961,6962,6964],{},[907,6963,1729],{},[907,6965,1547],{},[887,6967,6968,6970],{},[907,6969,1736],{},[907,6971,1643],{},[887,6973,6974,6976],{},[907,6975,1743],{},[907,6977,1547],{},[813,6979,1748],{},[813,6981,6982,1597,6984,1757],{},[877,6983,1753],{},[847,6985,1756],{},[813,6987,6988,1763],{},[877,6989,1762],{},[813,6991,6992,1769,6994,1773,6996,1777],{},[877,6993,1768],{},[847,6995,1772],{},[847,6997,1776],{},[860,6999,1781],{"id":1780},[813,7001,1784],{},[813,7003,7004,1790,7006,1794],{},[877,7005,1789],{},[847,7007,1793],{},[813,7009,7010,1800],{},[877,7011,1799],{},[881,7013,7014,7022],{},[884,7015,7016],{},[887,7017,7018,7020],{},[890,7019,1519],{},[890,7021,1522],{},[902,7023,7024,7032,7040,7046,7052,7058,7064],{},[887,7025,7026,7030],{},[907,7027,1817,7028,1820],{},[847,7029,1793],{},[907,7031,1823],{},[887,7033,7034,7038],{},[907,7035,1828,7036,1831],{},[847,7037,962],{},[907,7039,1643],{},[887,7041,7042,7044],{},[907,7043,1838],{},[907,7045,1841],{},[887,7047,7048,7050],{},[907,7049,1846],{},[907,7051,1849],{},[887,7053,7054,7056],{},[907,7055,1854],{},[907,7057,1857],{},[887,7059,7060,7062],{},[907,7061,1862],{},[907,7063,1547],{},[887,7065,7066,7068],{},[907,7067,1869],{},[907,7069,1547],{},[813,7071,1348,7072,1876],{},[847,7073,1793],{},[813,7075,7076,1882,7078,1885,7080,1889],{},[877,7077,1881],{},[847,7079,1793],{},[847,7081,1888],{},[881,7083,7084,7092],{},[884,7085,7086],{},[887,7087,7088,7090],{},[890,7089,1519],{},[890,7091,1522],{},[902,7093,7094,7104,7112],{},[887,7095,7096,7102],{},[907,7097,1817,7098,1908,7100,1831],{},[847,7099,1888],{},[847,7101,1911],{},[907,7103,1635],{},[887,7105,7106,7110],{},[907,7107,7108,1920],{},[847,7109,1888],{},[907,7111,1555],{},[887,7113,7114,7118],{},[907,7115,7116,1929],{},[847,7117,1888],{},[907,7119,1547],{},[813,7121,1934,7122,1937],{},[847,7123,1888],{},[813,7125,7126,1943],{},[877,7127,1942],{},[840,7129,7131],{"className":7130,"code":1947,"language":845},[843],[847,7132,1947],{"__ignoreMap":849},[813,7134,1952],{},[813,7136,7137,1958],{},[877,7138,1957],{},[881,7140,7141,7149],{},[884,7142,7143],{},[887,7144,7145,7147],{},[890,7146,1519],{},[890,7148,1522],{},[902,7150,7151,7157,7163,7169,7175],{},[887,7152,7153,7155],{},[907,7154,1975],{},[907,7156,1532],{},[887,7158,7159,7161],{},[907,7160,1982],{},[907,7162,1823],{},[887,7164,7165,7167],{},[907,7166,1989],{},[907,7168,1992],{},[887,7170,7171,7173],{},[907,7172,1997],{},[907,7174,1992],{},[887,7176,7177,7179],{},[907,7178,2004],{},[907,7180,1992],{},[813,7182,7183,2012],{},[877,7184,2011],{},[813,7186,7187,1670,7189,2021],{},[877,7188,2017],{},[847,7190,2020],{},[881,7192,7193,7201],{},[884,7194,7195],{},[887,7196,7197,7199],{},[890,7198,2030],{},[890,7200,1522],{},[902,7202,7203,7209,7215,7221],{},[887,7204,7205,7207],{},[907,7206,2039],{},[907,7208,1532],{},[887,7210,7211,7213],{},[907,7212,2046],{},[907,7214,1823],{},[887,7216,7217,7219],{},[907,7218,2053],{},[907,7220,1555],{},[887,7222,7223,7225],{},[907,7224,2060],{},[907,7226,1547],{},[820,7228],{},[823,7230,2068],{"id":2067},[813,7232,1348,7233,2073,7235,2077],{},[847,7234,1793],{},[847,7236,2076],{},[840,7238,7239],{"className":1376,"code":2080,"language":1378,"meta":849,"style":849},[847,7240,7241,7263],{"__ignoreMap":849},[1160,7242,7243,7245,7247,7249,7251,7253,7255,7257,7259,7261],{"class":1162,"line":1163},[1160,7244,2087],{"class":1173},[1160,7246,2090],{"class":1397},[1160,7248,2093],{"class":1411},[1160,7250,2096],{"class":1397},[1160,7252,2099],{"class":1173},[1160,7254,2090],{"class":1397},[1160,7256,2105],{"class":2104},[1160,7258,2108],{"class":1176},[1160,7260,2105],{"class":2104},[1160,7262,2113],{"class":1397},[1160,7264,7265],{"class":1162,"line":1170},[1160,7266,2118],{"class":1166},[813,7268,2121],{},[860,7270,2125],{"id":2124},[840,7272,7274],{"className":7273,"code":2129,"language":845},[843],[847,7275,2129],{"__ignoreMap":849},[813,7277,1348,7278,2137],{},[847,7279,2136],{},[860,7281,2141],{"id":2140},[813,7283,2144,7284,2147],{},[847,7285,1793],{},[813,7287,7288],{},[877,7289,2152],{},[840,7291,7292],{"className":1376,"code":2155,"language":1378,"meta":849,"style":849},[847,7293,7294,7298,7306,7314,7322,7330,7338,7346,7354,7362,7370,7378,7386,7394,7402,7410,7418,7426,7434,7442,7450,7458,7462],{"__ignoreMap":849},[1160,7295,7296],{"class":1162,"line":1163},[1160,7297,2162],{"class":1397},[1160,7299,7300,7302,7304],{"class":1162,"line":1170},[1160,7301,2167],{"class":1397},[1160,7303,2171],{"class":2170},[1160,7305,1415],{"class":1397},[1160,7307,7308,7310,7312],{"class":1162,"line":1187},[1160,7309,2178],{"class":1397},[1160,7311,1793],{"class":1403},[1160,7313,1415],{"class":1397},[1160,7315,7316,7318,7320],{"class":1162,"line":1193},[1160,7317,2187],{"class":1397},[1160,7319,2190],{"class":1403},[1160,7321,1415],{"class":1397},[1160,7323,7324,7326,7328],{"class":1162,"line":1199},[1160,7325,2197],{"class":1397},[1160,7327,2190],{"class":1403},[1160,7329,1415],{"class":1397},[1160,7331,7332,7334,7336],{"class":1162,"line":1221},[1160,7333,2206],{"class":1397},[1160,7335,2190],{"class":1403},[1160,7337,1415],{"class":1397},[1160,7339,7340,7342,7344],{"class":1162,"line":1226},[1160,7341,2215],{"class":1397},[1160,7343,2190],{"class":1403},[1160,7345,1415],{"class":1397},[1160,7347,7348,7350,7352],{"class":1162,"line":1232},[1160,7349,2224],{"class":1397},[1160,7351,2227],{"class":1403},[1160,7353,1415],{"class":1397},[1160,7355,7356,7358,7360],{"class":1162,"line":2232},[1160,7357,2235],{"class":1397},[1160,7359,2238],{"class":1403},[1160,7361,1415],{"class":1397},[1160,7363,7364,7366,7368],{"class":1162,"line":2243},[1160,7365,2246],{"class":1397},[1160,7367,2238],{"class":1403},[1160,7369,1415],{"class":1397},[1160,7371,7372,7374,7376],{"class":1162,"line":2253},[1160,7373,2256],{"class":1397},[1160,7375,2259],{"class":1403},[1160,7377,1415],{"class":1397},[1160,7379,7380,7382,7384],{"class":1162,"line":2264},[1160,7381,2267],{"class":1397},[1160,7383,2190],{"class":1403},[1160,7385,1415],{"class":1397},[1160,7387,7388,7390,7392],{"class":1162,"line":2274},[1160,7389,2277],{"class":1397},[1160,7391,2190],{"class":1403},[1160,7393,1415],{"class":1397},[1160,7395,7396,7398,7400],{"class":1162,"line":2284},[1160,7397,2287],{"class":1397},[1160,7399,2190],{"class":1403},[1160,7401,1415],{"class":1397},[1160,7403,7404,7406,7408],{"class":1162,"line":2294},[1160,7405,2297],{"class":1397},[1160,7407,2190],{"class":1403},[1160,7409,1415],{"class":1397},[1160,7411,7412,7414,7416],{"class":1162,"line":2304},[1160,7413,2307],{"class":1397},[1160,7415,2190],{"class":1403},[1160,7417,1415],{"class":1397},[1160,7419,7420,7422,7424],{"class":1162,"line":2314},[1160,7421,2317],{"class":1397},[1160,7423,2259],{"class":1403},[1160,7425,1415],{"class":1397},[1160,7427,7428,7430,7432],{"class":1162,"line":2324},[1160,7429,2327],{"class":1397},[1160,7431,2190],{"class":1403},[1160,7433,1415],{"class":1397},[1160,7435,7436,7438,7440],{"class":1162,"line":2334},[1160,7437,2337],{"class":1397},[1160,7439,2190],{"class":1403},[1160,7441,1415],{"class":1397},[1160,7443,7444,7446,7448],{"class":1162,"line":2344},[1160,7445,2347],{"class":1397},[1160,7447,2190],{"class":1403},[1160,7449,1415],{"class":1397},[1160,7451,7452,7454,7456],{"class":1162,"line":2354},[1160,7453,2357],{"class":1397},[1160,7455,2190],{"class":1403},[1160,7457,1415],{"class":1397},[1160,7459,7460],{"class":1162,"line":2364},[1160,7461,2367],{"class":1166},[1160,7463,7464],{"class":1162,"line":2370},[1160,7465,2373],{"class":1397},[813,7467,7468],{},[877,7469,2378],{},[881,7471,7472,7482],{},[884,7473,7474],{},[887,7475,7476,7478,7480],{},[890,7477,2387],{},[890,7479,2390],{},[890,7481,2393],{},[902,7483,7484,7498,7512,7526,7538,7552],{},[887,7485,7486,7490,7494],{},[907,7487,7488],{},[847,7489,2402],{},[907,7491,7492],{},[847,7493,1793],{},[907,7495,7496,2412],{},[847,7497,2411],{},[887,7499,7500,7504,7508],{},[907,7501,7502],{},[847,7503,2419],{},[907,7505,7506],{},[847,7507,1793],{},[907,7509,7510,2429],{},[847,7511,2428],{},[887,7513,7514,7518,7522],{},[907,7515,7516],{},[847,7517,2436],{},[907,7519,7520],{},[847,7521,1793],{},[907,7523,7524,2446],{},[847,7525,2445],{},[887,7527,7528,7532,7536],{},[907,7529,7530],{},[847,7531,2453],{},[907,7533,7534],{},[847,7535,1793],{},[907,7537,2460],{},[887,7539,7540,7544,7548],{},[907,7541,7542],{},[847,7543,2467],{},[907,7545,7546],{},[847,7547,1793],{},[907,7549,7550,2477],{},[847,7551,2476],{},[887,7553,7554,7558,7560],{},[907,7555,7556],{},[847,7557,2484],{},[907,7559,2487],{},[907,7561,7562,2493],{},[847,7563,2492],{},[813,7565,2496],{},[860,7567,2500],{"id":2499},[813,7569,2503],{},[813,7571,2506,7572,2509,7574,2513,7576,2516],{},[847,7573,1793],{},[847,7575,2512],{},[847,7577,1793],{},[813,7579,2519,7580,2522,7582,2525],{},[847,7581,1793],{},[847,7583,1793],{},[820,7585],{},[823,7587,2531],{"id":2530},[813,7589,2534],{},[813,7591,2537,7592,2541,7594,2545],{},[847,7593,2540],{},[847,7595,2544],{},[813,7597,7598],{},[877,7599,2550],{},[2552,7601,7602,7604,7606,7612,7616,7618],{},[2555,7603,2557],{},[2555,7605,2560],{},[2555,7607,2563,7608,2567,7610,2571],{},[847,7609,2566],{},[847,7611,2570],{},[2555,7613,2574,7614,2577],{},[847,7615,1600],{},[2555,7617,2580],{},[2555,7619,2583],{},[813,7621,7622,2589],{},[877,7623,2588],{},[813,7625,2592],{},[813,7627,7628],{},[877,7629,2597],{},[2552,7631,7632,7634,7640],{},[2555,7633,2602],{},[2555,7635,2605,7636,2609,7638,2612],{},[847,7637,2608],{},[847,7639,1888],{},[2555,7641,2615],{},[813,7643,7644,2621],{},[877,7645,2620],{},[820,7647],{},[823,7649,204],{"id":2626},[813,7651,2629],{},[840,7653,7654],{"className":1376,"code":2632,"language":1378,"meta":849,"style":849},[847,7655,7656,7674,7678,7686,7694,7726,7730,7734,7744,7754,7764,7774,7778,7794,7798,7806,7830,7834,7842,7852,7862,7870,7874,7878,7894,7898,7914,7918,7926,7936,7944,7954,7964,7974,7984,7992,7996,8000,8004,8020,8024,8040,8044,8060,8064,8072,8082,8122,8126,8130,8146,8150,8158,8168,8178,8188,8196,8200,8204,8220,8224,8240,8244,8252,8262,8270,8274,8278,8294,8298,8306,8316,8324,8328,8332,8348,8352],{"__ignoreMap":849},[1160,7657,7658,7660,7662,7664,7666,7668,7670,7672],{"class":1162,"line":1163},[1160,7659,2639],{"class":1390},[1160,7661,2642],{"class":1397},[1160,7663,2626],{"class":1403},[1160,7665,2647],{"class":1397},[1160,7667,2650],{"class":1390},[1160,7669,2653],{"class":2104},[1160,7671,2656],{"class":1176},[1160,7673,2659],{"class":2104},[1160,7675,7676],{"class":1162,"line":1170},[1160,7677,1190],{"emptyLinePlaceholder":8},[1160,7679,7680,7682,7684],{"class":1162,"line":1187},[1160,7681,1391],{"class":1390},[1160,7683,1394],{"class":1173},[1160,7685,1398],{"class":1397},[1160,7687,7688,7690,7692],{"class":1162,"line":1193},[1160,7689,2676],{"class":1403},[1160,7691,1408],{"class":1407},[1160,7693,2681],{"class":1397},[1160,7695,7696,7698,7700,7702,7704,7706,7708,7710,7712,7714,7716,7718,7720,7722,7724],{"class":1162,"line":1199},[1160,7697,2686],{"class":1403},[1160,7699,1408],{"class":1407},[1160,7701,2642],{"class":1397},[1160,7703,2693],{"class":1403},[1160,7705,1408],{"class":1407},[1160,7707,2653],{"class":2104},[1160,7709,2700],{"class":1176},[1160,7711,2105],{"class":2104},[1160,7713,2705],{"class":1397},[1160,7715,2708],{"class":1403},[1160,7717,1408],{"class":1407},[1160,7719,2653],{"class":2104},[1160,7721,2715],{"class":1176},[1160,7723,2105],{"class":2104},[1160,7725,2720],{"class":1397},[1160,7727,7728],{"class":1162,"line":1221},[1160,7729,2725],{"class":1397},[1160,7731,7732],{"class":1162,"line":1226},[1160,7733,1190],{"emptyLinePlaceholder":8},[1160,7735,7736,7738,7740,7742],{"class":1162,"line":1232},[1160,7737,1404],{"class":1403},[1160,7739,1408],{"class":1407},[1160,7741,1412],{"class":1411},[1160,7743,1415],{"class":1397},[1160,7745,7746,7748,7750,7752],{"class":1162,"line":2232},[1160,7747,1420],{"class":1403},[1160,7749,1408],{"class":1407},[1160,7751,1412],{"class":1411},[1160,7753,1415],{"class":1397},[1160,7755,7756,7758,7760,7762],{"class":1162,"line":2243},[1160,7757,1431],{"class":1403},[1160,7759,1408],{"class":1407},[1160,7761,1436],{"class":1411},[1160,7763,1415],{"class":1397},[1160,7765,7766,7768,7770,7772],{"class":1162,"line":2253},[1160,7767,1443],{"class":1403},[1160,7769,1408],{"class":1407},[1160,7771,1448],{"class":1180},[1160,7773,1415],{"class":1397},[1160,7775,7776],{"class":1162,"line":2264},[1160,7777,1190],{"emptyLinePlaceholder":8},[1160,7779,7780,7782,7784,7786,7788,7790,7792],{"class":1162,"line":2274},[1160,7781,2778],{"class":1403},[1160,7783,1408],{"class":1407},[1160,7785,2783],{"class":1397},[1160,7787,2105],{"class":2104},[1160,7789,2788],{"class":1176},[1160,7791,2105],{"class":2104},[1160,7793,2793],{"class":1397},[1160,7795,7796],{"class":1162,"line":2284},[1160,7797,1190],{"emptyLinePlaceholder":8},[1160,7799,7800,7802,7804],{"class":1162,"line":2294},[1160,7801,2802],{"class":1403},[1160,7803,1408],{"class":1407},[1160,7805,2681],{"class":1397},[1160,7807,7808,7810,7812,7814,7816,7818,7820,7822,7824,7826,7828],{"class":1162,"line":2304},[1160,7809,2811],{"class":1403},[1160,7811,1408],{"class":1407},[1160,7813,2642],{"class":1397},[1160,7815,2818],{"class":1403},[1160,7817,1408],{"class":1407},[1160,7819,2823],{"class":1180},[1160,7821,2705],{"class":1397},[1160,7823,2828],{"class":1403},[1160,7825,1408],{"class":1407},[1160,7827,1436],{"class":1411},[1160,7829,2835],{"class":1397},[1160,7831,7832],{"class":1162,"line":2314},[1160,7833,1190],{"emptyLinePlaceholder":8},[1160,7835,7836,7838,7840],{"class":1162,"line":2324},[1160,7837,2844],{"class":1403},[1160,7839,1408],{"class":1407},[1160,7841,2681],{"class":1397},[1160,7843,7844,7846,7848,7850],{"class":1162,"line":2334},[1160,7845,2853],{"class":1403},[1160,7847,1408],{"class":1407},[1160,7849,2823],{"class":1180},[1160,7851,1415],{"class":1397},[1160,7853,7854,7856,7858,7860],{"class":1162,"line":2344},[1160,7855,2864],{"class":1403},[1160,7857,1408],{"class":1407},[1160,7859,2823],{"class":1180},[1160,7861,1415],{"class":1397},[1160,7863,7864,7866,7868],{"class":1162,"line":2354},[1160,7865,2875],{"class":1403},[1160,7867,1408],{"class":1407},[1160,7869,2880],{"class":1411},[1160,7871,7872],{"class":1162,"line":2364},[1160,7873,2885],{"class":1397},[1160,7875,7876],{"class":1162,"line":2370},[1160,7877,1190],{"emptyLinePlaceholder":8},[1160,7879,7880,7882,7884,7886,7888,7890,7892],{"class":1162,"line":2892},[1160,7881,2895],{"class":1403},[1160,7883,1408],{"class":1407},[1160,7885,2642],{"class":1397},[1160,7887,2818],{"class":1403},[1160,7889,1408],{"class":1407},[1160,7891,2823],{"class":1180},[1160,7893,2835],{"class":1397},[1160,7895,7896],{"class":1162,"line":2910},[1160,7897,1190],{"emptyLinePlaceholder":8},[1160,7899,7900,7902,7904,7906,7908,7910,7912],{"class":1162,"line":2915},[1160,7901,2918],{"class":1403},[1160,7903,1408],{"class":1407},[1160,7905,2642],{"class":1397},[1160,7907,2818],{"class":1403},[1160,7909,1408],{"class":1407},[1160,7911,2823],{"class":1180},[1160,7913,2835],{"class":1397},[1160,7915,7916],{"class":1162,"line":2933},[1160,7917,1190],{"emptyLinePlaceholder":8},[1160,7919,7920,7922,7924],{"class":1162,"line":2938},[1160,7921,2941],{"class":1403},[1160,7923,1408],{"class":1407},[1160,7925,2681],{"class":1397},[1160,7927,7928,7930,7932,7934],{"class":1162,"line":2948},[1160,7929,2853],{"class":1403},[1160,7931,1408],{"class":1407},[1160,7933,2823],{"class":1180},[1160,7935,1415],{"class":1397},[1160,7937,7938,7940,7942],{"class":1162,"line":2959},[1160,7939,2875],{"class":1403},[1160,7941,1408],{"class":1407},[1160,7943,2681],{"class":1397},[1160,7945,7946,7948,7950,7952],{"class":1162,"line":2968},[1160,7947,2971],{"class":1403},[1160,7949,1408],{"class":1407},[1160,7951,2976],{"class":1411},[1160,7953,1415],{"class":1397},[1160,7955,7956,7958,7960,7962],{"class":1162,"line":2981},[1160,7957,2984],{"class":1403},[1160,7959,1408],{"class":1407},[1160,7961,2989],{"class":1411},[1160,7963,1415],{"class":1397},[1160,7965,7966,7968,7970,7972],{"class":1162,"line":2994},[1160,7967,2997],{"class":1403},[1160,7969,1408],{"class":1407},[1160,7971,3002],{"class":1411},[1160,7973,1415],{"class":1397},[1160,7975,7976,7978,7980,7982],{"class":1162,"line":3007},[1160,7977,3010],{"class":1403},[1160,7979,1408],{"class":1407},[1160,7981,2976],{"class":1411},[1160,7983,1415],{"class":1397},[1160,7985,7986,7988,7990],{"class":1162,"line":3019},[1160,7987,3022],{"class":1403},[1160,7989,1408],{"class":1407},[1160,7991,3027],{"class":1411},[1160,7993,7994],{"class":1162,"line":3030},[1160,7995,3033],{"class":1397},[1160,7997,7998],{"class":1162,"line":3036},[1160,7999,2885],{"class":1397},[1160,8001,8002],{"class":1162,"line":3041},[1160,8003,1190],{"emptyLinePlaceholder":8},[1160,8005,8006,8008,8010,8012,8014,8016,8018],{"class":1162,"line":3046},[1160,8007,3049],{"class":1403},[1160,8009,1408],{"class":1407},[1160,8011,2642],{"class":1397},[1160,8013,2818],{"class":1403},[1160,8015,1408],{"class":1407},[1160,8017,2823],{"class":1180},[1160,8019,2835],{"class":1397},[1160,8021,8022],{"class":1162,"line":3064},[1160,8023,1190],{"emptyLinePlaceholder":8},[1160,8025,8026,8028,8030,8032,8034,8036,8038],{"class":1162,"line":3069},[1160,8027,3072],{"class":1403},[1160,8029,1408],{"class":1407},[1160,8031,2642],{"class":1397},[1160,8033,2818],{"class":1403},[1160,8035,1408],{"class":1407},[1160,8037,2823],{"class":1180},[1160,8039,2835],{"class":1397},[1160,8041,8042],{"class":1162,"line":3087},[1160,8043,1190],{"emptyLinePlaceholder":8},[1160,8045,8046,8048,8050,8052,8054,8056,8058],{"class":1162,"line":3092},[1160,8047,3095],{"class":1403},[1160,8049,1408],{"class":1407},[1160,8051,2642],{"class":1397},[1160,8053,2818],{"class":1403},[1160,8055,1408],{"class":1407},[1160,8057,2823],{"class":1180},[1160,8059,2835],{"class":1397},[1160,8061,8062],{"class":1162,"line":3110},[1160,8063,1190],{"emptyLinePlaceholder":8},[1160,8065,8066,8068,8070],{"class":1162,"line":3115},[1160,8067,3118],{"class":1403},[1160,8069,1408],{"class":1407},[1160,8071,2681],{"class":1397},[1160,8073,8074,8076,8078,8080],{"class":1162,"line":3125},[1160,8075,2853],{"class":1403},[1160,8077,1408],{"class":1407},[1160,8079,2823],{"class":1180},[1160,8081,1415],{"class":1397},[1160,8083,8084,8086,8088,8090,8092,8094,8096,8098,8100,8102,8104,8106,8108,8110,8112,8114,8116,8118,8120],{"class":1162,"line":3136},[1160,8085,3139],{"class":1403},[1160,8087,1408],{"class":1407},[1160,8089,2783],{"class":1397},[1160,8091,2105],{"class":2104},[1160,8093,3148],{"class":1176},[1160,8095,2105],{"class":2104},[1160,8097,2705],{"class":1397},[1160,8099,2105],{"class":2104},[1160,8101,3157],{"class":1176},[1160,8103,2105],{"class":2104},[1160,8105,2705],{"class":1397},[1160,8107,2105],{"class":2104},[1160,8109,3166],{"class":1176},[1160,8111,2105],{"class":2104},[1160,8113,2705],{"class":1397},[1160,8115,2105],{"class":2104},[1160,8117,3175],{"class":1176},[1160,8119,2105],{"class":2104},[1160,8121,3180],{"class":1397},[1160,8123,8124],{"class":1162,"line":3183},[1160,8125,2885],{"class":1397},[1160,8127,8128],{"class":1162,"line":3188},[1160,8129,1190],{"emptyLinePlaceholder":8},[1160,8131,8132,8134,8136,8138,8140,8142,8144],{"class":1162,"line":3193},[1160,8133,3196],{"class":1403},[1160,8135,1408],{"class":1407},[1160,8137,2642],{"class":1397},[1160,8139,2818],{"class":1403},[1160,8141,1408],{"class":1407},[1160,8143,2823],{"class":1180},[1160,8145,2835],{"class":1397},[1160,8147,8148],{"class":1162,"line":3211},[1160,8149,1190],{"emptyLinePlaceholder":8},[1160,8151,8152,8154,8156],{"class":1162,"line":3216},[1160,8153,3219],{"class":1403},[1160,8155,1408],{"class":1407},[1160,8157,2681],{"class":1397},[1160,8159,8160,8162,8164,8166],{"class":1162,"line":3226},[1160,8161,2853],{"class":1403},[1160,8163,1408],{"class":1407},[1160,8165,2823],{"class":1180},[1160,8167,1415],{"class":1397},[1160,8169,8170,8172,8174,8176],{"class":1162,"line":3237},[1160,8171,3240],{"class":1403},[1160,8173,1408],{"class":1407},[1160,8175,3245],{"class":1411},[1160,8177,1415],{"class":1397},[1160,8179,8180,8182,8184,8186],{"class":1162,"line":3250},[1160,8181,3253],{"class":1403},[1160,8183,1408],{"class":1407},[1160,8185,3002],{"class":1411},[1160,8187,1415],{"class":1397},[1160,8189,8190,8192,8194],{"class":1162,"line":3262},[1160,8191,2875],{"class":1403},[1160,8193,1408],{"class":1407},[1160,8195,3269],{"class":1411},[1160,8197,8198],{"class":1162,"line":3272},[1160,8199,2885],{"class":1397},[1160,8201,8202],{"class":1162,"line":3277},[1160,8203,1190],{"emptyLinePlaceholder":8},[1160,8205,8206,8208,8210,8212,8214,8216,8218],{"class":1162,"line":3282},[1160,8207,3285],{"class":1403},[1160,8209,1408],{"class":1407},[1160,8211,2642],{"class":1397},[1160,8213,2818],{"class":1403},[1160,8215,1408],{"class":1407},[1160,8217,2823],{"class":1180},[1160,8219,2835],{"class":1397},[1160,8221,8222],{"class":1162,"line":3300},[1160,8223,1190],{"emptyLinePlaceholder":8},[1160,8225,8226,8228,8230,8232,8234,8236,8238],{"class":1162,"line":3305},[1160,8227,3308],{"class":1403},[1160,8229,1408],{"class":1407},[1160,8231,2642],{"class":1397},[1160,8233,2818],{"class":1403},[1160,8235,1408],{"class":1407},[1160,8237,2823],{"class":1180},[1160,8239,2835],{"class":1397},[1160,8241,8242],{"class":1162,"line":3323},[1160,8243,1190],{"emptyLinePlaceholder":8},[1160,8245,8246,8248,8250],{"class":1162,"line":3328},[1160,8247,3331],{"class":1403},[1160,8249,1408],{"class":1407},[1160,8251,2681],{"class":1397},[1160,8253,8254,8256,8258,8260],{"class":1162,"line":3338},[1160,8255,2853],{"class":1403},[1160,8257,1408],{"class":1407},[1160,8259,2823],{"class":1180},[1160,8261,1415],{"class":1397},[1160,8263,8264,8266,8268],{"class":1162,"line":3349},[1160,8265,3352],{"class":1403},[1160,8267,1408],{"class":1407},[1160,8269,3357],{"class":1411},[1160,8271,8272],{"class":1162,"line":3360},[1160,8273,2885],{"class":1397},[1160,8275,8276],{"class":1162,"line":3365},[1160,8277,1190],{"emptyLinePlaceholder":8},[1160,8279,8280,8282,8284,8286,8288,8290,8292],{"class":1162,"line":3370},[1160,8281,3373],{"class":1403},[1160,8283,1408],{"class":1407},[1160,8285,2642],{"class":1397},[1160,8287,2818],{"class":1403},[1160,8289,1408],{"class":1407},[1160,8291,2823],{"class":1180},[1160,8293,2835],{"class":1397},[1160,8295,8296],{"class":1162,"line":3388},[1160,8297,1190],{"emptyLinePlaceholder":8},[1160,8299,8300,8302,8304],{"class":1162,"line":3393},[1160,8301,3396],{"class":1403},[1160,8303,1408],{"class":1407},[1160,8305,2681],{"class":1397},[1160,8307,8308,8310,8312,8314],{"class":1162,"line":3403},[1160,8309,2853],{"class":1403},[1160,8311,1408],{"class":1407},[1160,8313,2823],{"class":1180},[1160,8315,1415],{"class":1397},[1160,8317,8318,8320,8322],{"class":1162,"line":3414},[1160,8319,3417],{"class":1403},[1160,8321,1408],{"class":1407},[1160,8323,3422],{"class":1397},[1160,8325,8326],{"class":1162,"line":3425},[1160,8327,2885],{"class":1397},[1160,8329,8330],{"class":1162,"line":3430},[1160,8331,1190],{"emptyLinePlaceholder":8},[1160,8333,8334,8336,8338,8340,8342,8344,8346],{"class":1162,"line":3435},[1160,8335,3438],{"class":1403},[1160,8337,1408],{"class":1407},[1160,8339,2642],{"class":1397},[1160,8341,2818],{"class":1403},[1160,8343,1408],{"class":1407},[1160,8345,2823],{"class":1180},[1160,8347,2720],{"class":1397},[1160,8349,8350],{"class":1162,"line":3453},[1160,8351,3456],{"class":1397},[1160,8353,8354],{"class":1162,"line":3459},[1160,8355,1460],{"class":1397},[3463,8357,8358],{},[813,8359,3467],{},[820,8361],{},[823,8363,3473],{"id":3472},[813,8365,3476,8366,3480],{},[847,8367,3479],{},[860,8369,1348,8370,3487],{"id":3483},[847,8371,3486],{},[813,8373,3490,8374,3493,8376,3496],{},[847,8375,3486],{},[847,8377,636],{},[840,8379,8380],{"className":1376,"code":3499,"language":1378,"meta":849,"style":849},[847,8381,8382,8410,8420,8442,8462,8490,8514,8528],{"__ignoreMap":849},[1160,8383,8384,8386,8388,8390,8392,8394,8396,8398,8400,8402,8404,8406,8408],{"class":1162,"line":1163},[1160,8385,3507],{"class":3506},[1160,8387,3511],{"class":3510},[1160,8389,3514],{"class":1397},[1160,8391,3518],{"class":3517},[1160,8393,2705],{"class":1397},[1160,8395,3523],{"class":3517},[1160,8397,3527],{"class":3526},[1160,8399,3530],{"class":3517},[1160,8401,3514],{"class":1397},[1160,8403,2190],{"class":3510},[1160,8405,2705],{"class":1397},[1160,8407,3539],{"class":3510},[1160,8409,3542],{"class":1397},[1160,8411,8412,8414,8416,8418],{"class":1162,"line":1170},[1160,8413,3547],{"class":1403},[1160,8415,1408],{"class":3526},[1160,8417,3552],{"class":3510},[1160,8419,3555],{"class":1397},[1160,8421,8422,8424,8426,8428,8430,8432,8434,8436,8438,8440],{"class":1162,"line":1187},[1160,8423,3560],{"class":1403},[1160,8425,1408],{"class":3526},[1160,8427,2653],{"class":2104},[1160,8429,3567],{"class":1176},[1160,8431,2105],{"class":2104},[1160,8433,3572],{"class":3526},[1160,8435,2653],{"class":2104},[1160,8437,3577],{"class":1176},[1160,8439,2105],{"class":2104},[1160,8441,3555],{"class":1397},[1160,8443,8444,8446,8448,8450,8452,8454,8456,8458,8460],{"class":1162,"line":1193},[1160,8445,3586],{"class":1173},[1160,8447,2090],{"class":1397},[1160,8449,3592],{"class":3591},[1160,8451,1408],{"class":3526},[1160,8453,3597],{"class":3510},[1160,8455,1831],{"class":1397},[1160,8457,1408],{"class":3526},[1160,8459,3604],{"class":3510},[1160,8461,3555],{"class":1397},[1160,8463,8464,8466,8468,8470,8472,8474,8476,8478,8480,8482,8484,8486,8488],{"class":1162,"line":1199},[1160,8465,3611],{"class":1173},[1160,8467,2090],{"class":1397},[1160,8469,3616],{"class":3591},[1160,8471,1408],{"class":3526},[1160,8473,3621],{"class":3510},[1160,8475,3514],{"class":1397},[1160,8477,3523],{"class":3517},[1160,8479,3628],{"class":1397},[1160,8481,3592],{"class":3591},[1160,8483,1408],{"class":3526},[1160,8485,3597],{"class":3510},[1160,8487,1831],{"class":1397},[1160,8489,3639],{"class":3526},[1160,8491,8492,8494,8496,8498,8500,8502,8504,8506,8508,8510,8512],{"class":1162,"line":1221},[1160,8493,3644],{"class":3526},[1160,8495,3647],{"class":3510},[1160,8497,3650],{"class":1397},[1160,8499,3653],{"class":1403},[1160,8501,1408],{"class":3526},[1160,8503,3658],{"class":3510},[1160,8505,3661],{"class":1397},[1160,8507,3664],{"class":1403},[1160,8509,1408],{"class":3526},[1160,8511,3669],{"class":3517},[1160,8513,3672],{"class":1397},[1160,8515,8516,8518,8520,8522,8524,8526],{"class":1162,"line":1226},[1160,8517,3644],{"class":3526},[1160,8519,3679],{"class":1397},[1160,8521,2259],{"class":1403},[1160,8523,3684],{"class":1397},[1160,8525,3518],{"class":1403},[1160,8527,3689],{"class":1397},[1160,8529,8530],{"class":1162,"line":1232},[1160,8531,2373],{"class":1397},[813,8533,1348,8534,3698],{},[847,8535,636],{},[860,8537,3702],{"id":3701},[813,8539,3705,8540,3708,8542,3711],{},[847,8541,636],{},[847,8543,3616],{},[881,8545,8546,8554],{},[884,8547,8548],{},[887,8549,8550,8552],{},[890,8551,3720],{},[890,8553,3723],{},[902,8555,8556,8564,8572,8584,8592,8602,8618,8632,8642,8652,8662],{},[887,8557,8558,8562],{},[907,8559,8560],{},[847,8561,3732],{},[907,8563,3735],{},[887,8565,8566,8570],{},[907,8567,8568],{},[847,8569,3742],{},[907,8571,3745],{},[887,8573,8574,8578],{},[907,8575,8576],{},[847,8577,3752],{},[907,8579,8580,3757,8582,3761],{},[847,8581,1793],{},[847,8583,3760],{},[887,8585,8586,8590],{},[907,8587,8588],{},[847,8589,3768],{},[907,8591,3771],{},[887,8593,8594,8598],{},[907,8595,8596],{},[847,8597,3778],{},[907,8599,3781,8600],{},[847,8601,978],{},[887,8603,8604,8608],{},[907,8605,8606],{},[847,8607,3790],{},[907,8609,3793,8610,2705,8612,2705,8614,2705,8616],{},[847,8611,3796],{},[847,8613,3799],{},[847,8615,3802],{},[847,8617,3805],{},[887,8619,8620,8624],{},[907,8621,8622],{},[847,8623,3812],{},[907,8625,3815,8626,3819,8628,3823,8630],{},[847,8627,3818],{},[847,8629,3822],{},[847,8631,3826],{},[887,8633,8634,8638],{},[907,8635,8636],{},[847,8637,3833],{},[907,8639,8640,3839],{},[847,8641,3838],{},[887,8643,8644,8648],{},[907,8645,8646],{},[847,8647,3846],{},[907,8649,3849,8650,3853],{},[847,8651,3852],{},[887,8653,8654,8658],{},[907,8655,8656],{},[847,8657,3860],{},[907,8659,8660,3866],{},[847,8661,3865],{},[887,8663,8664,8668],{},[907,8665,8666],{},[847,8667,3873],{},[907,8669,3876,8670],{},[847,8671,3879],{},[813,8673,8674,3885,8676,3889,8678,3893],{},[847,8675,3884],{},[847,8677,3888],{},[847,8679,3892],{},[860,8681,3897],{"id":3896},[813,8683,3900,8684,3903],{},[847,8685,1600],{},[840,8687,8688],{"className":1376,"code":3906,"filename":3907,"language":1378,"meta":849,"style":849},[847,8689,8690,8710,8740,8744,8768,8772,8788,8802,8820,8824,8844,8852,8856,8860,8882,8898,8910,8914,8942,8974,8978,8994,9004,9022,9026,9030,9044,9048,9052,9056],{"__ignoreMap":849},[1160,8691,8692,8694,8696,8698,8700,8702,8704,8706,8708],{"class":1162,"line":1163},[1160,8693,2639],{"class":1390},[1160,8695,2642],{"class":1397},[1160,8697,3918],{"class":1403},[1160,8699,2647],{"class":1397},[1160,8701,2650],{"class":1390},[1160,8703,2653],{"class":2104},[1160,8705,3927],{"class":1176},[1160,8707,2105],{"class":2104},[1160,8709,3555],{"class":1397},[1160,8711,8712,8714,8716,8718,8720,8722,8724,8726,8728,8730,8732,8734,8736,8738],{"class":1162,"line":1170},[1160,8713,2639],{"class":1390},[1160,8715,3938],{"class":1390},[1160,8717,2642],{"class":1397},[1160,8719,3486],{"class":1403},[1160,8721,2705],{"class":1397},[1160,8723,3479],{"class":1403},[1160,8725,2705],{"class":1397},[1160,8727,3951],{"class":1403},[1160,8729,2647],{"class":1397},[1160,8731,2650],{"class":1390},[1160,8733,2653],{"class":2104},[1160,8735,3927],{"class":1176},[1160,8737,2105],{"class":2104},[1160,8739,3555],{"class":1397},[1160,8741,8742],{"class":1162,"line":1187},[1160,8743,1190],{"emptyLinePlaceholder":8},[1160,8745,8746,8748,8750,8752,8754,8756,8758,8760,8762,8764,8766],{"class":1162,"line":1193},[1160,8747,3972],{"class":3506},[1160,8749,3669],{"class":3510},[1160,8751,3527],{"class":3526},[1160,8753,2653],{"class":2104},[1160,8755,3981],{"class":1176},[1160,8757,2105],{"class":2104},[1160,8759,3572],{"class":3526},[1160,8761,2653],{"class":2104},[1160,8763,1499],{"class":1176},[1160,8765,2105],{"class":2104},[1160,8767,3555],{"class":1397},[1160,8769,8770],{"class":1162,"line":1199},[1160,8771,1190],{"emptyLinePlaceholder":8},[1160,8773,8774,8776,8778,8780,8782,8784,8786],{"class":1162,"line":1221},[1160,8775,4002],{"class":3506},[1160,8777,4006],{"class":4005},[1160,8779,4009],{"class":3506},[1160,8781,3511],{"class":3510},[1160,8783,3514],{"class":1397},[1160,8785,3518],{"class":3517},[1160,8787,4018],{"class":1397},[1160,8789,8790,8792,8794,8796,8798,8800],{"class":1162,"line":1226},[1160,8791,3547],{"class":1403},[1160,8793,3527],{"class":3526},[1160,8795,2653],{"class":2104},[1160,8797,4029],{"class":1176},[1160,8799,2105],{"class":2104},[1160,8801,3555],{"class":1397},[1160,8803,8804,8806,8808,8810,8812,8814,8816,8818],{"class":1162,"line":1232},[1160,8805,3560],{"class":1403},[1160,8807,3527],{"class":3526},[1160,8809,2653],{"class":2104},[1160,8811,3567],{"class":1176},[1160,8813,2105],{"class":2104},[1160,8815,4048],{"class":1390},[1160,8817,4051],{"class":3506},[1160,8819,3555],{"class":1397},[1160,8821,8822],{"class":1162,"line":2232},[1160,8823,1190],{"emptyLinePlaceholder":8},[1160,8825,8826,8828,8830,8832,8834,8836,8838,8840,8842],{"class":1162,"line":2243},[1160,8827,3586],{"class":1173},[1160,8829,2090],{"class":1397},[1160,8831,4066],{"class":3591},[1160,8833,1408],{"class":3526},[1160,8835,3597],{"class":3510},[1160,8837,1831],{"class":1397},[1160,8839,1408],{"class":3526},[1160,8841,3604],{"class":3510},[1160,8843,2681],{"class":1397},[1160,8845,8846,8848,8850],{"class":1162,"line":2253},[1160,8847,4083],{"class":1390},[1160,8849,2823],{"class":1180},[1160,8851,3555],{"class":1397},[1160,8853,8854],{"class":1162,"line":2264},[1160,8855,3456],{"class":1397},[1160,8857,8858],{"class":1162,"line":2274},[1160,8859,1190],{"emptyLinePlaceholder":8},[1160,8861,8862,8864,8866,8868,8870,8872,8874,8876,8878,8880],{"class":1162,"line":2284},[1160,8863,3611],{"class":1173},[1160,8865,2090],{"class":1397},[1160,8867,3616],{"class":3591},[1160,8869,1408],{"class":3526},[1160,8871,3621],{"class":3510},[1160,8873,2705],{"class":1397},[1160,8875,4066],{"class":3591},[1160,8877,1408],{"class":3526},[1160,8879,3597],{"class":3510},[1160,8881,4118],{"class":1397},[1160,8883,8884,8886,8888,8890,8892,8894,8896],{"class":1162,"line":2294},[1160,8885,4123],{"class":3506},[1160,8887,4126],{"class":2170},[1160,8889,1408],{"class":3526},[1160,8891,3669],{"class":3510},[1160,8893,4133],{"class":1397},[1160,8895,4136],{"class":3526},[1160,8897,4139],{"class":1397},[1160,8899,8900,8902,8904,8906,8908],{"class":1162,"line":2304},[1160,8901,4144],{"class":3506},[1160,8903,4147],{"class":1403},[1160,8905,3527],{"class":3526},[1160,8907,4152],{"class":1411},[1160,8909,3555],{"class":1397},[1160,8911,8912],{"class":1162,"line":2314},[1160,8913,1190],{"emptyLinePlaceholder":8},[1160,8915,8916,8918,8920,8922,8924,8926,8928,8930,8932,8934,8936,8938,8940],{"class":1162,"line":2324},[1160,8917,4123],{"class":3506},[1160,8919,4165],{"class":2170},[1160,8921,3527],{"class":3526},[1160,8923,4170],{"class":1403},[1160,8925,3480],{"class":1397},[1160,8927,4175],{"class":1403},[1160,8929,3480],{"class":1397},[1160,8931,3802],{"class":1403},[1160,8933,4182],{"class":3526},[1160,8935,2653],{"class":2104},[1160,8937,4187],{"class":1176},[1160,8939,2105],{"class":2104},[1160,8941,3555],{"class":1397},[1160,8943,8944,8946,8948,8950,8952,8954,8956,8958,8960,8962,8964,8966,8968,8970,8972],{"class":1162,"line":2334},[1160,8945,4123],{"class":3506},[1160,8947,4198],{"class":2170},[1160,8949,3527],{"class":3526},[1160,8951,4203],{"class":1173},[1160,8953,2090],{"class":1397},[1160,8955,3616],{"class":1403},[1160,8957,3480],{"class":1397},[1160,8959,4212],{"class":1403},[1160,8961,3480],{"class":1397},[1160,8963,4217],{"class":1173},[1160,8965,2090],{"class":1397},[1160,8967,2105],{"class":2104},[1160,8969,1600],{"class":1176},[1160,8971,2105],{"class":2104},[1160,8973,4228],{"class":1397},[1160,8975,8976],{"class":1162,"line":2344},[1160,8977,1190],{"emptyLinePlaceholder":8},[1160,8979,8980,8982,8984,8986,8988,8990,8992],{"class":1162,"line":2354},[1160,8981,4237],{"class":1390},[1160,8983,4240],{"class":1397},[1160,8985,4243],{"class":1403},[1160,8987,4246],{"class":3526},[1160,8989,4249],{"class":3526},[1160,8991,4252],{"class":1403},[1160,8993,4118],{"class":1397},[1160,8995,8996,8998,9000,9002],{"class":1162,"line":2364},[1160,8997,4259],{"class":1403},[1160,8999,4262],{"class":3526},[1160,9001,4265],{"class":1411},[1160,9003,3555],{"class":1397},[1160,9005,9006,9008,9010,9012,9014,9016,9018,9020],{"class":1162,"line":2370},[1160,9007,4272],{"class":1403},[1160,9009,3480],{"class":1397},[1160,9011,4277],{"class":1173},[1160,9013,2090],{"class":1397},[1160,9015,2105],{"class":2104},[1160,9017,3981],{"class":1176},[1160,9019,2105],{"class":2104},[1160,9021,4288],{"class":1397},[1160,9023,9024],{"class":1162,"line":2892},[1160,9025,4293],{"class":1397},[1160,9027,9028],{"class":1162,"line":2910},[1160,9029,1190],{"emptyLinePlaceholder":8},[1160,9031,9032,9034,9036,9038,9040,9042],{"class":1162,"line":2915},[1160,9033,4083],{"class":1390},[1160,9035,2642],{"class":1397},[1160,9037,3653],{"class":1403},[1160,9039,2705],{"class":1397},[1160,9041,3664],{"class":1403},[1160,9043,4312],{"class":1397},[1160,9045,9046],{"class":1162,"line":2933},[1160,9047,3456],{"class":1397},[1160,9049,9050],{"class":1162,"line":2938},[1160,9051,2373],{"class":1397},[1160,9053,9054],{"class":1162,"line":2948},[1160,9055,1190],{"emptyLinePlaceholder":8},[1160,9057,9058,9060,9062,9064,9066,9068,9070],{"class":1162,"line":2959},[1160,9059,3918],{"class":1403},[1160,9061,3480],{"class":1397},[1160,9063,4333],{"class":1173},[1160,9065,2090],{"class":1397},[1160,9067,4339],{"class":4338},[1160,9069,4006],{"class":1173},[1160,9071,4344],{"class":1397},[813,9073,4347],{},[840,9075,9076],{"className":1376,"code":4350,"filename":4351,"language":1378,"meta":849,"style":849},[847,9077,9078,9102,9116,9120,9132],{"__ignoreMap":849},[1160,9079,9080,9082,9084,9086,9088,9090,9092,9094,9096,9098,9100],{"class":1162,"line":1163},[1160,9081,2639],{"class":1390},[1160,9083,2642],{"class":1397},[1160,9085,4362],{"class":1403},[1160,9087,2705],{"class":1397},[1160,9089,4367],{"class":1403},[1160,9091,2647],{"class":1397},[1160,9093,2650],{"class":1390},[1160,9095,2653],{"class":2104},[1160,9097,3927],{"class":1176},[1160,9099,2105],{"class":2104},[1160,9101,3555],{"class":1397},[1160,9103,9104,9106,9108,9110,9112,9114],{"class":1162,"line":1170},[1160,9105,2639],{"class":1390},[1160,9107,2653],{"class":2104},[1160,9109,4388],{"class":1176},[1160,9111,2105],{"class":2104},[1160,9113,3661],{"class":1397},[1160,9115,4395],{"class":1166},[1160,9117,9118],{"class":1162,"line":1187},[1160,9119,1190],{"emptyLinePlaceholder":8},[1160,9121,9122,9124,9126,9128,9130],{"class":1162,"line":1193},[1160,9123,1391],{"class":1390},[1160,9125,4406],{"class":1173},[1160,9127,4409],{"class":1397},[1160,9129,4412],{"class":1166},[1160,9131,4415],{"class":1397},[1160,9133,9134,9136,9138,9140,9142,9144],{"class":1162,"line":1199},[1160,9135,4420],{"class":1403},[1160,9137,3480],{"class":1397},[1160,9139,4425],{"class":1173},[1160,9141,2090],{"class":1397},[1160,9143,4367],{"class":1173},[1160,9145,4344],{"class":1397},[860,9147,4435],{"id":4434},[813,9149,1348,9150,4440,9152,4443,9154,1773,9156,4448,9158,4451,9160,3480],{},[847,9151,3879],{},[847,9153,3873],{},[847,9155,3486],{},[847,9157,3479],{},[847,9159,3873],{},[847,9161,636],{},[840,9163,9164],{"className":1376,"code":4456,"filename":4351,"language":1378,"meta":849,"style":849},[847,9165,9166,9174,9184,9214,9224,9228,9232,9242,9260,9286,9312,9334,9338],{"__ignoreMap":849},[1160,9167,9168,9170,9172],{"class":1162,"line":1163},[1160,9169,3507],{"class":3506},[1160,9171,4465],{"class":3510},[1160,9173,2681],{"class":1397},[1160,9175,9176,9178,9180,9182],{"class":1162,"line":1170},[1160,9177,4472],{"class":1403},[1160,9179,1408],{"class":3526},[1160,9181,3552],{"class":3510},[1160,9183,3555],{"class":1397},[1160,9185,9186,9188,9190,9192,9194,9196,9198,9200,9202,9204,9206,9208,9210,9212],{"class":1162,"line":1187},[1160,9187,4483],{"class":1403},[1160,9189,1408],{"class":3526},[1160,9191,2653],{"class":2104},[1160,9193,4490],{"class":1176},[1160,9195,2105],{"class":2104},[1160,9197,3572],{"class":3526},[1160,9199,2653],{"class":2104},[1160,9201,4499],{"class":1176},[1160,9203,2105],{"class":2104},[1160,9205,3572],{"class":3526},[1160,9207,2653],{"class":2104},[1160,9209,4508],{"class":1176},[1160,9211,2105],{"class":2104},[1160,9213,3555],{"class":1397},[1160,9215,9216,9218,9220,9222],{"class":1162,"line":1193},[1160,9217,4517],{"class":1403},[1160,9219,1408],{"class":3526},[1160,9221,3604],{"class":3510},[1160,9223,3555],{"class":1397},[1160,9225,9226],{"class":1162,"line":1199},[1160,9227,2373],{"class":1397},[1160,9229,9230],{"class":1162,"line":1221},[1160,9231,1190],{"emptyLinePlaceholder":8},[1160,9233,9234,9236,9238,9240],{"class":1162,"line":1226},[1160,9235,4420],{"class":1403},[1160,9237,3480],{"class":1397},[1160,9239,4425],{"class":1173},[1160,9241,4542],{"class":1397},[1160,9243,9244,9246,9248,9250,9252,9254,9256,9258],{"class":1162,"line":1232},[1160,9245,4547],{"class":1173},[1160,9247,3514],{"class":1397},[1160,9249,4552],{"class":3517},[1160,9251,4555],{"class":1397},[1160,9253,4212],{"class":3591},[1160,9255,4560],{"class":1397},[1160,9257,4563],{"class":3506},[1160,9259,4566],{"class":1397},[1160,9261,9262,9264,9266,9268,9270,9272,9274,9276,9278,9280,9282,9284],{"class":1162,"line":2232},[1160,9263,4571],{"class":1403},[1160,9265,1408],{"class":1407},[1160,9267,4576],{"class":1403},[1160,9269,3480],{"class":1397},[1160,9271,4581],{"class":1403},[1160,9273,4584],{"class":1397},[1160,9275,4587],{"class":1403},[1160,9277,4590],{"class":3526},[1160,9279,2653],{"class":2104},[1160,9281,4595],{"class":1176},[1160,9283,2105],{"class":2104},[1160,9285,1415],{"class":1397},[1160,9287,9288,9290,9292,9294,9296,9298,9300,9302,9304,9306,9308,9310],{"class":1162,"line":2243},[1160,9289,4604],{"class":1403},[1160,9291,1408],{"class":1407},[1160,9293,4609],{"class":1403},[1160,9295,3480],{"class":1397},[1160,9297,4581],{"class":1403},[1160,9299,4584],{"class":1397},[1160,9301,4618],{"class":1403},[1160,9303,4621],{"class":3526},[1160,9305,2653],{"class":2104},[1160,9307,4490],{"class":1176},[1160,9309,2105],{"class":2104},[1160,9311,1415],{"class":1397},[1160,9313,9314,9316,9318,9320,9322,9324,9326,9328,9330,9332],{"class":1162,"line":2253},[1160,9315,4634],{"class":1403},[1160,9317,1408],{"class":1407},[1160,9319,4639],{"class":1403},[1160,9321,3480],{"class":1397},[1160,9323,4644],{"class":1403},[1160,9325,4182],{"class":3526},[1160,9327,2653],{"class":2104},[1160,9329,4651],{"class":1176},[1160,9331,2105],{"class":2104},[1160,9333,1415],{"class":1397},[1160,9335,9336],{"class":1162,"line":2264},[1160,9337,4660],{"class":1397},[1160,9339,9340],{"class":1162,"line":2274},[1160,9341,4288],{"class":1397},[840,9343,9344],{"className":1376,"code":4667,"filename":4668,"language":1378,"meta":849,"style":849},[847,9345,9346,9380,9402,9406,9426,9440,9458,9462,9482,9486,9512,9548,9552,9590,9618,9622,9626,9646,9650],{"__ignoreMap":849},[1160,9347,9348,9350,9352,9354,9356,9358,9360,9362,9364,9366,9368,9370,9372,9374,9376,9378],{"class":1162,"line":1163},[1160,9349,2639],{"class":1390},[1160,9351,3938],{"class":1390},[1160,9353,2642],{"class":1397},[1160,9355,3486],{"class":1403},[1160,9357,2705],{"class":1397},[1160,9359,3479],{"class":1403},[1160,9361,2705],{"class":1397},[1160,9363,3951],{"class":1403},[1160,9365,2705],{"class":1397},[1160,9367,4693],{"class":1403},[1160,9369,2647],{"class":1397},[1160,9371,2650],{"class":1390},[1160,9373,2653],{"class":2104},[1160,9375,3927],{"class":1176},[1160,9377,2105],{"class":2104},[1160,9379,3555],{"class":1397},[1160,9381,9382,9384,9386,9388,9390,9392,9394,9396,9398,9400],{"class":1162,"line":1170},[1160,9383,2639],{"class":1390},[1160,9385,3938],{"class":1390},[1160,9387,2642],{"class":1397},[1160,9389,4552],{"class":1403},[1160,9391,2647],{"class":1397},[1160,9393,2650],{"class":1390},[1160,9395,2653],{"class":2104},[1160,9397,4724],{"class":1176},[1160,9399,2105],{"class":2104},[1160,9401,3555],{"class":1397},[1160,9403,9404],{"class":1162,"line":1187},[1160,9405,1190],{"emptyLinePlaceholder":8},[1160,9407,9408,9410,9412,9414,9416,9418,9420,9422,9424],{"class":1162,"line":1193},[1160,9409,4002],{"class":3506},[1160,9411,4739],{"class":4005},[1160,9413,4009],{"class":3506},[1160,9415,3511],{"class":3510},[1160,9417,3514],{"class":1397},[1160,9419,4693],{"class":3517},[1160,9421,2705],{"class":1397},[1160,9423,4552],{"class":3517},[1160,9425,4018],{"class":1397},[1160,9427,9428,9430,9432,9434,9436,9438],{"class":1162,"line":1199},[1160,9429,3547],{"class":1403},[1160,9431,3527],{"class":3526},[1160,9433,2653],{"class":2104},[1160,9435,4764],{"class":1176},[1160,9437,2105],{"class":2104},[1160,9439,3555],{"class":1397},[1160,9441,9442,9444,9446,9448,9450,9452,9454,9456],{"class":1162,"line":1221},[1160,9443,3560],{"class":1403},[1160,9445,3527],{"class":3526},[1160,9447,2653],{"class":2104},[1160,9449,3567],{"class":1176},[1160,9451,2105],{"class":2104},[1160,9453,4048],{"class":1390},[1160,9455,4051],{"class":3506},[1160,9457,3555],{"class":1397},[1160,9459,9460],{"class":1162,"line":1226},[1160,9461,1190],{"emptyLinePlaceholder":8},[1160,9463,9464,9466,9468,9470,9472,9474,9476,9478,9480],{"class":1162,"line":1232},[1160,9465,3586],{"class":1173},[1160,9467,2090],{"class":1397},[1160,9469,4066],{"class":3591},[1160,9471,1408],{"class":3526},[1160,9473,3597],{"class":3510},[1160,9475,4805],{"class":1397},[1160,9477,4808],{"class":1390},[1160,9479,2823],{"class":1180},[1160,9481,4813],{"class":1397},[1160,9483,9484],{"class":1162,"line":2232},[1160,9485,1190],{"emptyLinePlaceholder":8},[1160,9487,9488,9490,9492,9494,9496,9498,9500,9502,9504,9506,9508,9510],{"class":1162,"line":2243},[1160,9489,3611],{"class":1173},[1160,9491,2090],{"class":1397},[1160,9493,3616],{"class":3591},[1160,9495,1408],{"class":3526},[1160,9497,3621],{"class":3510},[1160,9499,3514],{"class":1397},[1160,9501,4552],{"class":3517},[1160,9503,3628],{"class":1397},[1160,9505,4066],{"class":3591},[1160,9507,1408],{"class":3526},[1160,9509,3597],{"class":3510},[1160,9511,4118],{"class":1397},[1160,9513,9514,9516,9518,9520,9522,9524,9526,9528,9530,9532,9534,9536,9538,9540,9542,9544,9546],{"class":1162,"line":2253},[1160,9515,4237],{"class":1390},[1160,9517,4240],{"class":1397},[1160,9519,3616],{"class":1403},[1160,9521,3480],{"class":1397},[1160,9523,4856],{"class":1403},[1160,9525,3480],{"class":1397},[1160,9527,4861],{"class":1403},[1160,9529,4560],{"class":1397},[1160,9531,4808],{"class":1390},[1160,9533,2642],{"class":1397},[1160,9535,3653],{"class":1403},[1160,9537,1408],{"class":1407},[1160,9539,4152],{"class":1411},[1160,9541,2705],{"class":1397},[1160,9543,3664],{"class":1403},[1160,9545,1408],{"class":1407},[1160,9547,4882],{"class":1397},[1160,9549,9550],{"class":1162,"line":2264},[1160,9551,1190],{"emptyLinePlaceholder":8},[1160,9553,9554,9556,9558,9560,9562,9564,9566,9568,9570,9572,9574,9576,9578,9580,9582,9584,9586,9588],{"class":1162,"line":2274},[1160,9555,4237],{"class":1390},[1160,9557,4240],{"class":1397},[1160,9559,3616],{"class":1403},[1160,9561,3480],{"class":1397},[1160,9563,4856],{"class":1403},[1160,9565,3480],{"class":1397},[1160,9567,4618],{"class":1403},[1160,9569,4182],{"class":3526},[1160,9571,2653],{"class":2104},[1160,9573,4490],{"class":1176},[1160,9575,2105],{"class":2104},[1160,9577,4246],{"class":3526},[1160,9579,4170],{"class":1403},[1160,9581,3480],{"class":1397},[1160,9583,4919],{"class":1403},[1160,9585,3480],{"class":1397},[1160,9587,4924],{"class":1403},[1160,9589,4118],{"class":1397},[1160,9591,9592,9594,9596,9598,9600,9602,9604,9606,9608,9610,9612,9614,9616],{"class":1162,"line":2284},[1160,9593,4931],{"class":1390},[1160,9595,2642],{"class":1397},[1160,9597,3653],{"class":1403},[1160,9599,1408],{"class":1407},[1160,9601,2976],{"class":1411},[1160,9603,2705],{"class":1397},[1160,9605,3664],{"class":1403},[1160,9607,1408],{"class":1407},[1160,9609,2783],{"class":1397},[1160,9611,2105],{"class":2104},[1160,9613,4952],{"class":1176},[1160,9615,2105],{"class":2104},[1160,9617,4957],{"class":1397},[1160,9619,9620],{"class":1162,"line":2294},[1160,9621,4293],{"class":1397},[1160,9623,9624],{"class":1162,"line":2304},[1160,9625,1190],{"emptyLinePlaceholder":8},[1160,9627,9628,9630,9632,9634,9636,9638,9640,9642,9644],{"class":1162,"line":2314},[1160,9629,4083],{"class":1390},[1160,9631,2642],{"class":1397},[1160,9633,3653],{"class":1403},[1160,9635,1408],{"class":1407},[1160,9637,4152],{"class":1411},[1160,9639,2705],{"class":1397},[1160,9641,3664],{"class":1403},[1160,9643,1408],{"class":1407},[1160,9645,4882],{"class":1397},[1160,9647,9648],{"class":1162,"line":2324},[1160,9649,3456],{"class":1397},[1160,9651,9652],{"class":1162,"line":2334},[1160,9653,2373],{"class":1397},[813,9655,4996],{},[860,9657,5000],{"id":4999},[813,9659,5003,9660,5007,9662,5011],{},[847,9661,5006],{},[847,9663,5010],{},[840,9665,9666],{"className":1376,"code":5014,"language":1378,"meta":849,"style":849},[847,9667,9668,9690,9704,9732,9736,9756],{"__ignoreMap":849},[1160,9669,9670,9672,9674,9676,9678,9680,9682,9684,9686,9688],{"class":1162,"line":1163},[1160,9671,636],{"class":1173},[1160,9673,2090],{"class":1397},[1160,9675,3616],{"class":1403},[1160,9677,5027],{"class":1397},[1160,9679,3479],{"class":1403},[1160,9681,2705],{"class":1397},[1160,9683,4066],{"class":1403},[1160,9685,5027],{"class":1397},[1160,9687,3951],{"class":1403},[1160,9689,4118],{"class":1397},[1160,9691,9692,9694,9696,9698,9700,9702],{"class":1162,"line":1170},[1160,9693,5044],{"class":1390},[1160,9695,4240],{"class":1397},[1160,9697,5049],{"class":1173},[1160,9699,2090],{"class":1397},[1160,9701,3616],{"class":1403},[1160,9703,5056],{"class":1397},[1160,9705,9706,9708,9710,9712,9714,9716,9718,9720,9722,9724,9726,9728,9730],{"class":1162,"line":1187},[1160,9707,4083],{"class":1390},[1160,9709,2642],{"class":1397},[1160,9711,3653],{"class":1403},[1160,9713,1408],{"class":1407},[1160,9715,4152],{"class":1411},[1160,9717,2705],{"class":1397},[1160,9719,3664],{"class":1403},[1160,9721,1408],{"class":1407},[1160,9723,2783],{"class":1397},[1160,9725,2105],{"class":2104},[1160,9727,1499],{"class":1176},[1160,9729,2105],{"class":2104},[1160,9731,4957],{"class":1397},[1160,9733,9734],{"class":1162,"line":1193},[1160,9735,3456],{"class":1397},[1160,9737,9738,9740,9742,9744,9746,9748,9750,9752,9754],{"class":1162,"line":1199},[1160,9739,5093],{"class":1390},[1160,9741,2642],{"class":1397},[1160,9743,3653],{"class":1403},[1160,9745,1408],{"class":1407},[1160,9747,4152],{"class":1411},[1160,9749,2705],{"class":1397},[1160,9751,3664],{"class":1403},[1160,9753,1408],{"class":1407},[1160,9755,4882],{"class":1397},[1160,9757,9758],{"class":1162,"line":1221},[1160,9759,2373],{"class":1397},[813,9761,5116,9762,5120],{},[847,9763,5119],{},[860,9765,5124],{"id":5123},[813,9767,5127,9768,5131,9770,5134,9772,5138],{},[847,9769,5130],{},[847,9771,1362],{},[847,9773,5137],{},[840,9775,9776],{"className":1376,"code":5141,"filename":5142,"language":1378,"meta":849,"style":849},[847,9777,9778,9802,9832,9836,9856,9870,9888,9892,9918,9922,9946,9980,9984,9996,10020,10024,10050,10064,10108,10112,10116,10150,10174,10178,10206,10246,10250,10254,10258],{"__ignoreMap":849},[1160,9779,9780,9782,9784,9786,9788,9790,9792,9794,9796,9798,9800],{"class":1162,"line":1163},[1160,9781,2639],{"class":1390},[1160,9783,2642],{"class":1397},[1160,9785,5153],{"class":1403},[1160,9787,2705],{"class":1397},[1160,9789,3918],{"class":1403},[1160,9791,2647],{"class":1397},[1160,9793,2650],{"class":1390},[1160,9795,2653],{"class":2104},[1160,9797,3927],{"class":1176},[1160,9799,2105],{"class":2104},[1160,9801,3555],{"class":1397},[1160,9803,9804,9806,9808,9810,9812,9814,9816,9818,9820,9822,9824,9826,9828,9830],{"class":1162,"line":1170},[1160,9805,2639],{"class":1390},[1160,9807,3938],{"class":1390},[1160,9809,2642],{"class":1397},[1160,9811,3486],{"class":1403},[1160,9813,2705],{"class":1397},[1160,9815,3479],{"class":1403},[1160,9817,2705],{"class":1397},[1160,9819,3951],{"class":1403},[1160,9821,2647],{"class":1397},[1160,9823,2650],{"class":1390},[1160,9825,2653],{"class":2104},[1160,9827,3927],{"class":1176},[1160,9829,2105],{"class":2104},[1160,9831,3555],{"class":1397},[1160,9833,9834],{"class":1162,"line":1187},[1160,9835,1190],{"emptyLinePlaceholder":8},[1160,9837,9838,9840,9842,9844,9846,9848,9850,9852,9854],{"class":1162,"line":1193},[1160,9839,4002],{"class":3506},[1160,9841,5210],{"class":4005},[1160,9843,4009],{"class":3506},[1160,9845,3511],{"class":3510},[1160,9847,3514],{"class":1397},[1160,9849,2105],{"class":2104},[1160,9851,5221],{"class":1176},[1160,9853,2105],{"class":2104},[1160,9855,4018],{"class":1397},[1160,9857,9858,9860,9862,9864,9866,9868],{"class":1162,"line":1199},[1160,9859,3547],{"class":1403},[1160,9861,3527],{"class":3526},[1160,9863,2653],{"class":2104},[1160,9865,5236],{"class":1176},[1160,9867,2105],{"class":2104},[1160,9869,3555],{"class":1397},[1160,9871,9872,9874,9876,9878,9880,9882,9884,9886],{"class":1162,"line":1221},[1160,9873,3560],{"class":1403},[1160,9875,3527],{"class":3526},[1160,9877,2653],{"class":2104},[1160,9879,3577],{"class":1176},[1160,9881,2105],{"class":2104},[1160,9883,4048],{"class":1390},[1160,9885,4051],{"class":3506},[1160,9887,3555],{"class":1397},[1160,9889,9890],{"class":1162,"line":1226},[1160,9891,1190],{"emptyLinePlaceholder":8},[1160,9893,9894,9896,9898,9900,9902,9904,9906,9908,9910,9912,9914,9916],{"class":1162,"line":1232},[1160,9895,3586],{"class":1173},[1160,9897,2090],{"class":1397},[1160,9899,4066],{"class":3591},[1160,9901,1408],{"class":3526},[1160,9903,3597],{"class":3510},[1160,9905,1831],{"class":1397},[1160,9907,1408],{"class":3526},[1160,9909,3604],{"class":3510},[1160,9911,2642],{"class":1397},[1160,9913,4808],{"class":1390},[1160,9915,2823],{"class":1180},[1160,9917,4813],{"class":1397},[1160,9919,9920],{"class":1162,"line":2232},[1160,9921,1190],{"emptyLinePlaceholder":8},[1160,9923,9924,9926,9928,9930,9932,9934,9936,9938,9940,9942,9944],{"class":1162,"line":2243},[1160,9925,5297],{"class":3506},[1160,9927,1259],{"class":1173},[1160,9929,2090],{"class":1397},[1160,9931,3616],{"class":3591},[1160,9933,1408],{"class":3526},[1160,9935,3621],{"class":3510},[1160,9937,2705],{"class":1397},[1160,9939,4066],{"class":3591},[1160,9941,1408],{"class":3526},[1160,9943,3597],{"class":3510},[1160,9945,4118],{"class":1397},[1160,9947,9948,9950,9952,9954,9956,9958,9960,9962,9964,9966,9968,9970,9972,9974,9976,9978],{"class":1162,"line":2253},[1160,9949,4237],{"class":1390},[1160,9951,4240],{"class":1397},[1160,9953,5326],{"class":3526},[1160,9955,3616],{"class":1403},[1160,9957,3480],{"class":1397},[1160,9959,5333],{"class":1403},[1160,9961,4560],{"class":1397},[1160,9963,4808],{"class":1390},[1160,9965,2642],{"class":1397},[1160,9967,3653],{"class":1403},[1160,9969,1408],{"class":1407},[1160,9971,4152],{"class":1411},[1160,9973,2705],{"class":1397},[1160,9975,3664],{"class":1403},[1160,9977,1408],{"class":1407},[1160,9979,4882],{"class":1397},[1160,9981,9982],{"class":1162,"line":2264},[1160,9983,1190],{"emptyLinePlaceholder":8},[1160,9985,9986,9988,9990,9992,9994],{"class":1162,"line":2274},[1160,9987,4123],{"class":3506},[1160,9989,5364],{"class":2170},[1160,9991,3527],{"class":3526},[1160,9993,5369],{"class":1173},[1160,9995,5372],{"class":1397},[1160,9997,9998,10000,10002,10004,10006,10008,10010,10012,10014,10016,10018],{"class":1162,"line":2284},[1160,9999,4123],{"class":3506},[1160,10001,5379],{"class":2170},[1160,10003,3527],{"class":3526},[1160,10005,5384],{"class":1176},[1160,10007,5387],{"class":3506},[1160,10009,3616],{"class":1403},[1160,10011,3480],{"class":5392},[1160,10013,5333],{"class":1403},[1160,10015,5397],{"class":3506},[1160,10017,5400],{"class":1176},[1160,10019,3555],{"class":1397},[1160,10021,10022],{"class":1162,"line":2294},[1160,10023,1190],{"emptyLinePlaceholder":8},[1160,10025,10026,10028,10030,10032,10034,10036,10038,10040,10042,10044,10046,10048],{"class":1162,"line":2304},[1160,10027,4123],{"class":3506},[1160,10029,5413],{"class":2170},[1160,10031,3527],{"class":3526},[1160,10033,5418],{"class":1390},[1160,10035,5364],{"class":1403},[1160,10037,3480],{"class":1397},[1160,10039,5425],{"class":1173},[1160,10041,3514],{"class":1397},[1160,10043,2259],{"class":3510},[1160,10045,5432],{"class":1397},[1160,10047,5435],{"class":1403},[1160,10049,4288],{"class":1397},[1160,10051,10052,10054,10056,10058,10060,10062],{"class":1162,"line":2314},[1160,10053,4237],{"class":1390},[1160,10055,4240],{"class":1397},[1160,10057,5446],{"class":1403},[1160,10059,5449],{"class":3526},[1160,10061,5452],{"class":1180},[1160,10063,4118],{"class":1397},[1160,10065,10066,10068,10070,10072,10074,10076,10078,10080,10082,10084,10086,10088,10090,10092,10094,10096,10098,10100,10102,10104,10106],{"class":1162,"line":2324},[1160,10067,4931],{"class":1390},[1160,10069,2642],{"class":1397},[1160,10071,3653],{"class":1403},[1160,10073,1408],{"class":1407},[1160,10075,5413],{"class":1403},[1160,10077,2705],{"class":1397},[1160,10079,3664],{"class":1403},[1160,10081,1408],{"class":1407},[1160,10083,5413],{"class":1403},[1160,10085,5477],{"class":3526},[1160,10087,4152],{"class":1411},[1160,10089,5482],{"class":3526},[1160,10091,2783],{"class":1397},[1160,10093,2105],{"class":2104},[1160,10095,5221],{"class":1176},[1160,10097,2105],{"class":2104},[1160,10099,4048],{"class":1390},[1160,10101,4051],{"class":3506},[1160,10103,5497],{"class":1397},[1160,10105,1408],{"class":3526},[1160,10107,4882],{"class":1397},[1160,10109,10110],{"class":1162,"line":2334},[1160,10111,4293],{"class":1397},[1160,10113,10114],{"class":1162,"line":2344},[1160,10115,1190],{"emptyLinePlaceholder":8},[1160,10117,10118,10120,10122,10124,10126,10128,10130,10132,10134,10136,10138,10140,10142,10144,10146,10148],{"class":1162,"line":2354},[1160,10119,4123],{"class":3506},[1160,10121,5516],{"class":2170},[1160,10123,3527],{"class":3526},[1160,10125,5418],{"class":1390},[1160,10127,5523],{"class":1403},[1160,10129,3480],{"class":1397},[1160,10131,5528],{"class":1173},[1160,10133,2090],{"class":1397},[1160,10135,2105],{"class":2104},[1160,10137,5535],{"class":1176},[1160,10139,2105],{"class":2104},[1160,10141,5540],{"class":1397},[1160,10143,3616],{"class":1403},[1160,10145,3480],{"class":1397},[1160,10147,5547],{"class":1403},[1160,10149,5550],{"class":1397},[1160,10151,10152,10154,10156,10158,10160,10162,10164,10166,10168,10170,10172],{"class":1162,"line":2364},[1160,10153,4123],{"class":3506},[1160,10155,4147],{"class":2170},[1160,10157,3527],{"class":3526},[1160,10159,5516],{"class":1403},[1160,10161,3480],{"class":1397},[1160,10163,5565],{"class":1403},[1160,10165,5482],{"class":3526},[1160,10167,3002],{"class":1411},[1160,10169,5572],{"class":3526},[1160,10171,4152],{"class":1411},[1160,10173,3555],{"class":1397},[1160,10175,10176],{"class":1162,"line":2370},[1160,10177,1190],{"emptyLinePlaceholder":8},[1160,10179,10180,10182,10184,10186,10188,10190,10192,10194,10196,10198,10200,10202,10204],{"class":1162,"line":2892},[1160,10181,5585],{"class":1390},[1160,10183,5364],{"class":1403},[1160,10185,3480],{"class":1397},[1160,10187,5592],{"class":1173},[1160,10189,2090],{"class":1397},[1160,10191,5435],{"class":1403},[1160,10193,2705],{"class":1397},[1160,10195,3653],{"class":1403},[1160,10197,5603],{"class":1397},[1160,10199,5606],{"class":1403},[1160,10201,1408],{"class":1407},[1160,10203,5611],{"class":1411},[1160,10205,4415],{"class":1397},[1160,10207,10208,10210,10212,10214,10216,10218,10220,10222,10224,10226,10228,10230,10232,10234,10236,10238,10240,10242,10244],{"class":1162,"line":2910},[1160,10209,4083],{"class":1390},[1160,10211,2642],{"class":1397},[1160,10213,3653],{"class":1403},[1160,10215,2705],{"class":1397},[1160,10217,3664],{"class":1403},[1160,10219,1408],{"class":1407},[1160,10221,4147],{"class":1403},[1160,10223,5477],{"class":3526},[1160,10225,4152],{"class":1411},[1160,10227,5482],{"class":3526},[1160,10229,2783],{"class":1397},[1160,10231,2105],{"class":2104},[1160,10233,5221],{"class":1176},[1160,10235,2105],{"class":2104},[1160,10237,4048],{"class":1390},[1160,10239,4051],{"class":3506},[1160,10241,5497],{"class":1397},[1160,10243,1408],{"class":3526},[1160,10245,4882],{"class":1397},[1160,10247,10248],{"class":1162,"line":2915},[1160,10249,3456],{"class":1397},[1160,10251,10252],{"class":1162,"line":2933},[1160,10253,2373],{"class":1397},[1160,10255,10256],{"class":1162,"line":2938},[1160,10257,1190],{"emptyLinePlaceholder":8},[1160,10259,10260,10262,10264,10266,10268,10270,10272],{"class":1162,"line":2948},[1160,10261,3918],{"class":1403},[1160,10263,3480],{"class":1397},[1160,10265,4333],{"class":1173},[1160,10267,2090],{"class":1397},[1160,10269,4339],{"class":4338},[1160,10271,5210],{"class":1173},[1160,10273,4344],{"class":1397},[3463,10275,10276],{},[813,10277,5686,10278,5690],{},[847,10279,5689],{},[820,10281],{},[823,10283,5696],{"id":5695},[813,10285,1348,10286,5701,10288,1773,10290,5706],{},[847,10287,307],{},[847,10289,1772],{},[847,10291,1776],{},[860,10293,5710],{"id":5709},[813,10295,5713,10296,5717],{},[847,10297,5716],{},[813,10299,10300,5724,10304,5728,10306,5732,10308,5735],{},[877,10301,10302],{},[847,10303,1772],{},[847,10305,5727],{},[847,10307,5731],{},[847,10309,1499],{},[813,10311,10312,5724,10316,5745,10318,5749,10320,5753,10322,5757,10324,5760,10326,5753,10328,5766],{},[877,10313,10314],{},[847,10315,1776],{},[847,10317,5744],{},[847,10319,5748],{},[847,10321,5752],{},[847,10323,5756],{},[847,10325,1362],{},[847,10327,5763],{},[847,10329,1555],{},[840,10331,10332],{"className":1376,"code":5769,"language":1378,"meta":849,"style":849},[847,10333,10334,10338,10348,10358,10372,10382],{"__ignoreMap":849},[1160,10335,10336],{"class":1162,"line":1163},[1160,10337,5776],{"class":1397},[1160,10339,10340,10342,10344,10346],{"class":1162,"line":1170},[1160,10341,5781],{"class":1397},[1160,10343,5756],{"class":1411},[1160,10345,5786],{"class":1397},[1160,10347,5789],{"class":1166},[1160,10349,10350,10352,10354,10356],{"class":1162,"line":1187},[1160,10351,5794],{"class":1397},[1160,10353,5797],{"class":1180},[1160,10355,5800],{"class":1397},[1160,10357,5803],{"class":1166},[1160,10359,10360,10362,10364,10366,10368,10370],{"class":1162,"line":1193},[1160,10361,5808],{"class":1397},[1160,10363,2105],{"class":2104},[1160,10365,1321],{"class":1176},[1160,10367,2105],{"class":2104},[1160,10369,5786],{"class":1397},[1160,10371,5819],{"class":1166},[1160,10373,10374,10376,10378,10380],{"class":1162,"line":1199},[1160,10375,5824],{"class":1397},[1160,10377,5797],{"class":1180},[1160,10379,5829],{"class":1397},[1160,10381,5832],{"class":1166},[1160,10383,10384],{"class":1162,"line":1221},[1160,10385,2373],{"class":1397},[813,10387,1348,10388,5842,10390,5845,10392,5848],{},[847,10389,5841],{},[847,10391,1643],{},[847,10393,5756],{},[881,10395,10396,10404],{},[884,10397,10398],{},[887,10399,10400,10402],{},[890,10401,5857],{},[890,10403,5860],{},[902,10405,10406,10414,10422],{},[887,10407,10408,10412],{},[907,10409,10410],{},[847,10411,1643],{},[907,10413,5871],{},[887,10415,10416,10420],{},[907,10417,10418,5878],{},[847,10419,5756],{},[907,10421,5881],{},[887,10423,10424,10428],{},[907,10425,10426],{},[847,10427,5888],{},[907,10429,5891],{},[860,10431,5895],{"id":5894},[813,10433,5898,10434,5902],{},[847,10435,5901],{},[860,10437,5906],{"id":5905},[1149,10439,10440,10454,10468,10480],{},[840,10441,10442],{"className":1153,"code":5911,"filename":1155,"language":1156,"meta":849,"style":849},[847,10443,10444],{"__ignoreMap":849},[1160,10445,10446,10448,10450,10452],{"class":1162,"line":1163},[1160,10447,1155],{"class":1173},[1160,10449,5920],{"class":1176},[1160,10451,5923],{"class":1176},[1160,10453,5926],{"class":1176},[840,10455,10456],{"className":1153,"code":5929,"filename":5930,"language":1156,"meta":849,"style":849},[847,10457,10458],{"__ignoreMap":849},[1160,10459,10460,10462,10464,10466],{"class":1162,"line":1163},[1160,10461,5930],{"class":1173},[1160,10463,5920],{"class":1176},[1160,10465,5923],{"class":1176},[1160,10467,5926],{"class":1176},[840,10469,10470],{"className":1153,"code":5945,"filename":1246,"language":1156,"meta":849,"style":849},[847,10471,10472],{"__ignoreMap":849},[1160,10473,10474,10476,10478],{"class":1162,"line":1163},[1160,10475,5952],{"class":1173},[1160,10477,5923],{"class":1176},[1160,10479,5926],{"class":1176},[840,10481,10482],{"className":1153,"code":5959,"filename":5960,"language":1156,"meta":849,"style":849},[847,10483,10484],{"__ignoreMap":849},[1160,10485,10486,10488,10490],{"class":1162,"line":1163},[1160,10487,5967],{"class":1173},[1160,10489,5923],{"class":1176},[1160,10491,5926],{"class":1176},[813,10493,5974,10494,5978],{},[847,10495,5977],{},[840,10497,10498],{"className":1376,"code":5981,"filename":5982,"language":1378,"meta":849,"style":849},[847,10499,10500,10524,10546,10550,10566,10602,10634,10638,10642,10646,10650],{"__ignoreMap":849},[1160,10501,10502,10504,10506,10508,10510,10512,10514,10516,10518,10520,10522],{"class":1162,"line":1163},[1160,10503,2639],{"class":1390},[1160,10505,2642],{"class":1397},[1160,10507,5993],{"class":1403},[1160,10509,2705],{"class":1397},[1160,10511,5998],{"class":1403},[1160,10513,2647],{"class":1397},[1160,10515,2650],{"class":1390},[1160,10517,2653],{"class":2104},[1160,10519,3927],{"class":1176},[1160,10521,2105],{"class":2104},[1160,10523,3555],{"class":1397},[1160,10525,10526,10528,10530,10532,10534,10536,10538,10540,10542,10544],{"class":1162,"line":1170},[1160,10527,2639],{"class":1390},[1160,10529,3938],{"class":1390},[1160,10531,2642],{"class":1397},[1160,10533,6021],{"class":1403},[1160,10535,2647],{"class":1397},[1160,10537,2650],{"class":1390},[1160,10539,2653],{"class":2104},[1160,10541,3927],{"class":1176},[1160,10543,2105],{"class":2104},[1160,10545,3555],{"class":1397},[1160,10547,10548],{"class":1162,"line":1187},[1160,10549,1190],{"emptyLinePlaceholder":8},[1160,10551,10552,10554,10556,10558,10560,10562,10564],{"class":1162,"line":1193},[1160,10553,6042],{"class":1390},[1160,10555,4240],{"class":1397},[1160,10557,6047],{"class":3506},[1160,10559,6050],{"class":2170},[1160,10561,6053],{"class":3506},[1160,10563,6056],{"class":1403},[1160,10565,4118],{"class":1397},[1160,10567,10568,10570,10572,10574,10576,10578,10580,10582,10584,10586,10588,10590,10592,10594,10596,10598,10600],{"class":1162,"line":1199},[1160,10569,6063],{"class":3506},[1160,10571,6066],{"class":2170},[1160,10573,1408],{"class":3526},[1160,10575,6071],{"class":3510},[1160,10577,3527],{"class":3526},[1160,10579,2642],{"class":1397},[1160,10581,3653],{"class":1403},[1160,10583,1408],{"class":1407},[1160,10585,1412],{"class":1411},[1160,10587,2705],{"class":1397},[1160,10589,3664],{"class":1403},[1160,10591,1408],{"class":1407},[1160,10593,2783],{"class":1397},[1160,10595,2105],{"class":2104},[1160,10597,6094],{"class":1176},[1160,10599,2105],{"class":2104},[1160,10601,4957],{"class":1397},[1160,10603,10604,10606,10608,10610,10612,10614,10616,10618,10620,10622,10624,10626,10628,10630,10632],{"class":1162,"line":1221},[1160,10605,6103],{"class":1390},[1160,10607,6106],{"class":1173},[1160,10609,2090],{"class":1397},[1160,10611,6111],{"class":2104},[1160,10613,2705],{"class":1397},[1160,10615,4644],{"class":1403},[1160,10617,2705],{"class":1397},[1160,10619,2105],{"class":2104},[1160,10621,6122],{"class":1176},[1160,10623,2105],{"class":2104},[1160,10625,2705],{"class":1397},[1160,10627,6111],{"class":2104},[1160,10629,2705],{"class":1397},[1160,10631,6133],{"class":1403},[1160,10633,4288],{"class":1397},[1160,10635,10636],{"class":1162,"line":1226},[1160,10637,2373],{"class":1397},[1160,10639,10640],{"class":1162,"line":1232},[1160,10641,1190],{"emptyLinePlaceholder":8},[1160,10643,10644],{"class":1162,"line":2232},[1160,10645,6148],{"class":1166},[1160,10647,10648],{"class":1162,"line":2243},[1160,10649,6153],{"class":1166},[1160,10651,10652,10654,10656],{"class":1162,"line":2253},[1160,10653,1391],{"class":1390},[1160,10655,6160],{"class":1173},[1160,10657,5372],{"class":1397},[860,10659,6166],{"id":6165},[813,10661,6169],{},[840,10663,10664],{"className":6172,"code":6173,"filename":6174,"language":6175,"meta":849,"style":849},[847,10665,10666,10670,10674,10678,10682],{"__ignoreMap":849},[1160,10667,10668],{"class":1162,"line":1163},[1160,10669,6182],{},[1160,10671,10672],{"class":1162,"line":1170},[1160,10673,6187],{},[1160,10675,10676],{"class":1162,"line":1187},[1160,10677,1190],{"emptyLinePlaceholder":8},[1160,10679,10680],{"class":1162,"line":1193},[1160,10681,6196],{},[1160,10683,10684],{"class":1162,"line":1199},[1160,10685,6201],{},[813,10687,6204,10688,6207],{},[847,10689,1772],{},[820,10691],{},[823,10693,6213],{"id":6212},[813,10695,6216],{},[813,10697,6219],{},[813,10699,6222],{},[813,10701,6225,10702,6229],{},[6227,10703,230],{"href":35},[813,10705,6225,10706,6234],{},[6227,10707,40],{"href":42},[6236,10709,6238],{},{"title":849,"searchDepth":1170,"depth":1170,"links":10711},[10712,10713,10718,10724,10729,10730,10731,10739,10745],{"id":825,"depth":1170,"text":826},{"id":854,"depth":1170,"text":855,"children":10714},[10715,10716,10717],{"id":862,"depth":1187,"text":863},{"id":869,"depth":1187,"text":870},{"id":1139,"depth":1187,"text":1140},{"id":1333,"depth":1170,"text":1334,"children":10719},[10720,10721,10722,10723],{"id":1344,"depth":1187,"text":1345},{"id":1355,"depth":1187,"text":1356},{"id":1470,"depth":1187,"text":1471},{"id":1780,"depth":1187,"text":1781},{"id":2067,"depth":1170,"text":2068,"children":10725},[10726,10727,10728],{"id":2124,"depth":1187,"text":2125},{"id":2140,"depth":1187,"text":2141},{"id":2499,"depth":1187,"text":2500},{"id":2530,"depth":1170,"text":2531},{"id":2626,"depth":1170,"text":204},{"id":3472,"depth":1170,"text":3473,"children":10732},[10733,10734,10735,10736,10737,10738],{"id":3483,"depth":1187,"text":6263},{"id":3701,"depth":1187,"text":3702},{"id":3896,"depth":1187,"text":3897},{"id":4434,"depth":1187,"text":4435},{"id":4999,"depth":1187,"text":5000},{"id":5123,"depth":1187,"text":5124},{"id":5695,"depth":1170,"text":5696,"children":10740},[10741,10742,10743,10744],{"id":5709,"depth":1187,"text":5710},{"id":5894,"depth":1187,"text":5895},{"id":5905,"depth":1187,"text":5906},{"id":6165,"depth":1187,"text":6166},{"id":6212,"depth":1170,"text":6213},{},{"title":73,"description":6277},[38,33,6286],1780436274137]