[{"data":1,"prerenderedAt":2633},["ShallowReactive",2],{"navLinks":3,"sidebar_docs_navigation_\u002Fdocs\u002Fbot-detection":64,"navigation":191,"navLinks_footer":816,"\u002Fdocs\u002Fbot-detection\u002Fsecurity_page":829,"\u002Fdocs\u002Fbot-detection\u002Fsecurity_surround":1877,"\u002Fdocs\u002Fbot-detection\u002Fsecurity":1880},{"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":9,"path":66,"stem":67,"children":68,"page":53},"\u002Fdocs","docs",[69],{"title":70,"path":35,"stem":71,"children":72},"Bot Detector","docs\u002Fbot-detection\u002Findex",[73,74,77,81,85,106,180,183,187],{"title":70,"path":35,"stem":71},{"title":14,"path":75,"stem":76},"\u002Fdocs\u002Fbot-detection\u002Fgetting-started","docs\u002Fbot-detection\u002F00.getting-started",{"title":78,"path":79,"stem":80},"CLI","\u002Fdocs\u002Fbot-detection\u002Fcli","docs\u002Fbot-detection\u002F01.cli",{"title":82,"path":83,"stem":84},"Data Sources","\u002Fdocs\u002Fbot-detection\u002Fdata-sources","docs\u002Fbot-detection\u002F02.data-sources",{"title":86,"path":87,"stem":88,"children":89,"page":53},"Guides","\u002Fdocs\u002Fbot-detection\u002Fguides","docs\u002Fbot-detection\u002F03.guides",[90,94,98,102],{"title":91,"path":92,"stem":93},"Custom Checkers","\u002Fdocs\u002Fbot-detection\u002Fguides\u002Fcustom","docs\u002Fbot-detection\u002F03.guides\u002FCUSTOM",{"title":95,"path":96,"stem":97},"Scheduling Database Generation","\u002Fdocs\u002Fbot-detection\u002Fguides\u002Fgenerate","docs\u002Fbot-detection\u002F03.guides\u002FGENERATE",{"title":99,"path":100,"stem":101},"Logging","\u002Fdocs\u002Fbot-detection\u002Fguides\u002Flogging","docs\u002Fbot-detection\u002F03.guides\u002FLOGGING",{"title":103,"path":104,"stem":105},"Score Modes and Reputation Healing","\u002Fdocs\u002Fbot-detection\u002Fguides\u002Fscore","docs\u002Fbot-detection\u002F03.guides\u002FSCORE",{"title":107,"path":108,"stem":109,"children":110},"Checkers","\u002Fdocs\u002Fbot-detection\u002Fcheckers","docs\u002Fbot-detection\u002F04.checkers\u002Findex",[111,112,116,120,124,128,132,136,140,144,148,152,156,160,164,168,172,176],{"title":107,"path":108,"stem":109},{"title":113,"path":114,"stem":115},"IP Validation","\u002Fdocs\u002Fbot-detection\u002Fcheckers\u002Fip-validation","docs\u002Fbot-detection\u002F04.checkers\u002F01.ip-validation",{"title":117,"path":118,"stem":119},"Good \u002F Bad Bot Verification","\u002Fdocs\u002Fbot-detection\u002Fcheckers\u002Fgood-bots","docs\u002Fbot-detection\u002F04.checkers\u002F02.good-bots",{"title":121,"path":122,"stem":123},"Browser & Device Fingerprint","\u002Fdocs\u002Fbot-detection\u002Fcheckers\u002Fbrowser-device","docs\u002Fbot-detection\u002F04.checkers\u002F03.browser-device",{"title":125,"path":126,"stem":127},"Locale Map","\u002Fdocs\u002Fbot-detection\u002Fcheckers\u002Flocale-map","docs\u002Fbot-detection\u002F04.checkers\u002F04.locale-map",{"title":129,"path":130,"stem":131},"Known Threats","\u002Fdocs\u002Fbot-detection\u002Fcheckers\u002Fknown-threats","docs\u002Fbot-detection\u002F04.checkers\u002F05.known-threats",{"title":133,"path":134,"stem":135},"ASN Classification","\u002Fdocs\u002Fbot-detection\u002Fcheckers\u002Fasn-classification","docs\u002Fbot-detection\u002F04.checkers\u002F06.asn-classification",{"title":137,"path":138,"stem":139},"Tor Analysis","\u002Fdocs\u002Fbot-detection\u002Fcheckers\u002Ftor-analysis","docs\u002Fbot-detection\u002F04.checkers\u002F07.tor-analysis",{"title":141,"path":142,"stem":143},"Timezone Consistency","\u002Fdocs\u002Fbot-detection\u002Fcheckers\u002Ftimezone-consistency","docs\u002Fbot-detection\u002F04.checkers\u002F08.timezone-consistency",{"title":145,"path":146,"stem":147},"Honeypot","\u002Fdocs\u002Fbot-detection\u002Fcheckers\u002Fhoneypot","docs\u002Fbot-detection\u002F04.checkers\u002F09.honeypot",{"title":149,"path":150,"stem":151},"Known Bad IPs","\u002Fdocs\u002Fbot-detection\u002Fcheckers\u002Fknown-bad-ips","docs\u002Fbot-detection\u002F04.checkers\u002F10.known-bad-ips",{"title":153,"path":154,"stem":155},"Behavior Rate","\u002Fdocs\u002Fbot-detection\u002Fcheckers\u002Fbehavior-rate","docs\u002Fbot-detection\u002F04.checkers\u002F11.behavior-rate",{"title":157,"path":158,"stem":159},"Proxy \u002F ISP \u002F Cookie","\u002Fdocs\u002Fbot-detection\u002Fcheckers\u002Fproxy-isp-cookies","docs\u002Fbot-detection\u002F04.checkers\u002F12.proxy-isp-cookies",{"title":161,"path":162,"stem":163},"Session Coherence","\u002Fdocs\u002Fbot-detection\u002Fcheckers\u002Fsession-coherence","docs\u002Fbot-detection\u002F04.checkers\u002F13.session-coherence",{"title":165,"path":166,"stem":167},"Velocity Fingerprint","\u002Fdocs\u002Fbot-detection\u002Fcheckers\u002Fvelocity-fingerprint","docs\u002Fbot-detection\u002F04.checkers\u002F14.velocity-fingerprint",{"title":169,"path":170,"stem":171},"UA & Header Analysis","\u002Fdocs\u002Fbot-detection\u002Fcheckers\u002Fua-header","docs\u002Fbot-detection\u002F04.checkers\u002F15.ua-header",{"title":173,"path":174,"stem":175},"Geolocation","\u002Fdocs\u002Fbot-detection\u002Fcheckers\u002Fgeolocation","docs\u002Fbot-detection\u002F04.checkers\u002F16.geolocation",{"title":177,"path":178,"stem":179},"Known Bad User-Agents","\u002Fdocs\u002Fbot-detection\u002Fcheckers\u002Fknown-bad-ua","docs\u002Fbot-detection\u002F04.checkers\u002F17.known-bad-ua",{"title":38,"path":181,"stem":182},"\u002Fdocs\u002Fbot-detection\u002Fsecurity","docs\u002Fbot-detection\u002F04.security",{"title":184,"path":185,"stem":186},"API Reference","\u002Fdocs\u002Fbot-detection\u002Fapi","docs\u002Fbot-detection\u002F05.api",{"title":188,"path":189,"stem":190},"Configuration","\u002Fdocs\u002Fbot-detection\u002Fconfiguration","docs\u002Fbot-detection\u002F06.configuration",[192],{"title":9,"path":66,"stem":67,"children":193,"page":53},[194,338,373,378,556,623],{"title":20,"path":22,"stem":195,"children":196},"docs\u002Fauth-h3client\u002Findex",[197,198,207,243,269,291,294,314,317],{"title":20,"path":22,"stem":195},{"title":14,"path":199,"stem":200,"children":201},"\u002Fdocs\u002Fauth-h3client\u002Fgetting-started","docs\u002Fauth-h3client\u002F00.getting-started\u002Findex",[202,203],{"title":14,"path":199,"stem":200},{"title":204,"path":205,"stem":206},"Nuxt Module","\u002Fdocs\u002Fauth-h3client\u002Fgetting-started\u002Fnuxt","docs\u002Fauth-h3client\u002F00.getting-started\u002F00.nuxt",{"title":208,"path":209,"stem":210,"children":211},"Essentials","\u002Fdocs\u002Fauth-h3client\u002Fessentials","docs\u002Fauth-h3client\u002F01.essentials\u002Findex",[212,213,217,221,225,229,233,236,240],{"title":208,"path":209,"stem":210},{"title":214,"path":215,"stem":216},"Session Management","\u002Fdocs\u002Fauth-h3client\u002Fessentials\u002Fsession","docs\u002Fauth-h3client\u002F01.essentials\u002F00.session",{"title":218,"path":219,"stem":220},"Route Protection","\u002Fdocs\u002Fauth-h3client\u002Fessentials\u002Froute-protection","docs\u002Fauth-h3client\u002F01.essentials\u002F01.route-protection",{"title":222,"path":223,"stem":224},"CSRF Protection","\u002Fdocs\u002Fauth-h3client\u002Fessentials\u002Fcsrf","docs\u002Fauth-h3client\u002F01.essentials\u002F02.csrf",{"title":226,"path":227,"stem":228},"Auth Flows","\u002Fdocs\u002Fauth-h3client\u002Fessentials\u002Fauth-flows","docs\u002Fauth-h3client\u002F01.essentials\u002F03.auth-flows",{"title":230,"path":231,"stem":232},"OAuth and OIDC","\u002Fdocs\u002Fauth-h3client\u002Fessentials\u002Foauth","docs\u002Fauth-h3client\u002F01.essentials\u002F04.oauth",{"title":33,"path":234,"stem":235},"\u002Fdocs\u002Fauth-h3client\u002Fessentials\u002Fbot-detection","docs\u002Fauth-h3client\u002F01.essentials\u002F05.bot-detection",{"title":237,"path":238,"stem":239},"Cookies","\u002Fdocs\u002Fauth-h3client\u002Fessentials\u002Fcookies","docs\u002Fauth-h3client\u002F01.essentials\u002F06.cookies",{"title":99,"path":241,"stem":242},"\u002Fdocs\u002Fauth-h3client\u002Fessentials\u002Flogging","docs\u002Fauth-h3client\u002F01.essentials\u002F07.logging",{"title":244,"path":245,"stem":246,"children":247},"MFA","\u002Fdocs\u002Fauth-h3client\u002Fmfa","docs\u002Fauth-h3client\u002F02.mfa\u002Findex",[248,249,253,257,261,265],{"title":244,"path":245,"stem":246},{"title":250,"path":251,"stem":252},"Built-in MFA","\u002Fdocs\u002Fauth-h3client\u002Fmfa\u002Fbuilt-in-flow","docs\u002Fauth-h3client\u002F02.mfa\u002F01.built-in-flow",{"title":254,"path":255,"stem":256},"Password Reset","\u002Fdocs\u002Fauth-h3client\u002Fmfa\u002Fpassword-reset","docs\u002Fauth-h3client\u002F02.mfa\u002F02.password-reset",{"title":258,"path":259,"stem":260},"Email Change","\u002Fdocs\u002Fauth-h3client\u002Fmfa\u002Femail-change","docs\u002Fauth-h3client\u002F02.mfa\u002F03.email-change",{"title":262,"path":263,"stem":264},"Custom MFA Flow","\u002Fdocs\u002Fauth-h3client\u002Fmfa\u002Fcustom-flow","docs\u002Fauth-h3client\u002F02.mfa\u002F04.custom-flow",{"title":266,"path":267,"stem":268},"Client-Side MFA","\u002Fdocs\u002Fauth-h3client\u002Fmfa\u002Fclient-side","docs\u002Fauth-h3client\u002F02.mfa\u002F05.client-side",{"title":270,"path":271,"stem":272,"children":273},"Client-side","\u002Fdocs\u002Fauth-h3client\u002Fclient","docs\u002Fauth-h3client\u002F03.client\u002Findex",[274,275,279,283,287],{"title":270,"path":271,"stem":272},{"title":276,"path":277,"stem":278},"useAuthData","\u002Fdocs\u002Fauth-h3client\u002Fclient\u002Fuse-auth-data","docs\u002Fauth-h3client\u002F03.client\u002F00.use-auth-data",{"title":280,"path":281,"stem":282},"useMagicLink","\u002Fdocs\u002Fauth-h3client\u002Fclient\u002Fuse-magic-link","docs\u002Fauth-h3client\u002F03.client\u002F01.use-magic-link",{"title":284,"path":285,"stem":286},"executeRequest","\u002Fdocs\u002Fauth-h3client\u002Fclient\u002Fexecute-request","docs\u002Fauth-h3client\u002F03.client\u002F02.execute-request",{"title":288,"path":289,"stem":290},"getCsrfToken","\u002Fdocs\u002Fauth-h3client\u002Fclient\u002Fget-csrf-token","docs\u002Fauth-h3client\u002F03.client\u002F03.get-csrf-token",{"title":38,"path":292,"stem":293},"\u002Fdocs\u002Fauth-h3client\u002Fsecurity","docs\u002Fauth-h3client\u002F04.security",{"title":86,"path":295,"stem":296,"children":297,"page":53},"\u002Fdocs\u002Fauth-h3client\u002Fguides","docs\u002Fauth-h3client\u002F05.guides",[298,302,306,310],{"title":299,"path":300,"stem":301},"H3 and Nitro Setup","\u002Fdocs\u002Fauth-h3client\u002Fguides\u002Fh3-nitro","docs\u002Fauth-h3client\u002F05.guides\u002F00.h3-nitro",{"title":303,"path":304,"stem":305},"HMAC Inter-service Auth","\u002Fdocs\u002Fauth-h3client\u002Fguides\u002Fhmac","docs\u002Fauth-h3client\u002F05.guides\u002Fhmac",{"title":307,"path":308,"stem":309},"Image Upload","\u002Fdocs\u002Fauth-h3client\u002Fguides\u002Fimage-upload","docs\u002Fauth-h3client\u002F05.guides\u002Fimage-upload",{"title":311,"path":312,"stem":313},"mTLS Configuration","\u002Fdocs\u002Fauth-h3client\u002Fguides\u002Fmtls","docs\u002Fauth-h3client\u002F05.guides\u002Fmtls",{"title":188,"path":315,"stem":316},"\u002Fdocs\u002Fauth-h3client\u002Fconfiguration","docs\u002Fauth-h3client\u002F06.configuration",{"title":184,"path":318,"stem":319,"children":320},"\u002Fdocs\u002Fauth-h3client\u002Fapi","docs\u002Fauth-h3client\u002F07.api\u002Findex",[321,322,326,330,334],{"title":184,"path":318,"stem":319},{"title":323,"path":324,"stem":325},"Routes Reference","\u002Fdocs\u002Fauth-h3client\u002Fapi\u002Fcontrollers","docs\u002Fauth-h3client\u002F07.api\u002F00.controllers",{"title":327,"path":328,"stem":329},"Middleware Reference","\u002Fdocs\u002Fauth-h3client\u002Fapi\u002Fmiddleware","docs\u002Fauth-h3client\u002F07.api\u002F01.middleware",{"title":331,"path":332,"stem":333},"Client-side Reference","\u002Fdocs\u002Fauth-h3client\u002Fapi\u002Fcomposables","docs\u002Fauth-h3client\u002F07.api\u002F02.composables",{"title":335,"path":336,"stem":337},"Utilities","\u002Fdocs\u002Fauth-h3client\u002Fapi\u002Futilities","docs\u002Fauth-h3client\u002F07.api\u002F03.utilities",{"title":70,"path":35,"stem":71,"children":339},[340,341,342,343,344,350,370,371,372],{"title":70,"path":35,"stem":71},{"title":14,"path":75,"stem":76},{"title":78,"path":79,"stem":80},{"title":82,"path":83,"stem":84},{"title":86,"path":87,"stem":88,"children":345,"page":53},[346,347,348,349],{"title":91,"path":92,"stem":93},{"title":95,"path":96,"stem":97},{"title":99,"path":100,"stem":101},{"title":103,"path":104,"stem":105},{"title":107,"path":108,"stem":109,"children":351},[352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369],{"title":107,"path":108,"stem":109},{"title":113,"path":114,"stem":115},{"title":117,"path":118,"stem":119},{"title":121,"path":122,"stem":123},{"title":125,"path":126,"stem":127},{"title":129,"path":130,"stem":131},{"title":133,"path":134,"stem":135},{"title":137,"path":138,"stem":139},{"title":141,"path":142,"stem":143},{"title":145,"path":146,"stem":147},{"title":149,"path":150,"stem":151},{"title":153,"path":154,"stem":155},{"title":157,"path":158,"stem":159},{"title":161,"path":162,"stem":163},{"title":165,"path":166,"stem":167},{"title":169,"path":170,"stem":171},{"title":173,"path":174,"stem":175},{"title":177,"path":178,"stem":179},{"title":38,"path":181,"stem":182},{"title":184,"path":185,"stem":186},{"title":188,"path":189,"stem":190},{"title":374,"path":11,"stem":375,"children":376},"Introduction","docs\u002Fgetting-started\u002Findex",[377],{"title":374,"path":11,"stem":375},{"title":27,"path":29,"stem":379,"children":380},"docs\u002Fiam\u002Findex",[381,382,385,520,523,539,542],{"title":27,"path":29,"stem":379},{"title":14,"path":383,"stem":384},"\u002Fdocs\u002Fiam\u002Fgetting-started","docs\u002Fiam\u002F00.getting-started",{"title":208,"path":386,"stem":387,"children":388},"\u002Fdocs\u002Fiam\u002Fessentials","docs\u002Fiam\u002F01.essentials\u002Findex",[389,390,394,398,402,406,410,414,418,422,426,430,433,437,441,445,449,452,456,460,463,467,470],{"title":208,"path":386,"stem":387},{"title":391,"path":392,"stem":393},"Tokens","\u002Fdocs\u002Fiam\u002Fessentials\u002Ftokens","docs\u002Fiam\u002F01.essentials\u002F00.tokens",{"title":395,"path":396,"stem":397},"Access Tokens","\u002Fdocs\u002Fiam\u002Fessentials\u002Faccess-tokens","docs\u002Fiam\u002F01.essentials\u002F01.access-tokens",{"title":399,"path":400,"stem":401},"Refresh Tokens","\u002Fdocs\u002Fiam\u002Fessentials\u002Frefresh-tokens","docs\u002Fiam\u002F01.essentials\u002F02.refresh-tokens",{"title":403,"path":404,"stem":405},"Anomaly Detection","\u002Fdocs\u002Fiam\u002Fessentials\u002Fanomalies","docs\u002Fiam\u002F01.essentials\u002F03.anomalies",{"title":407,"path":408,"stem":409},"Signup","\u002Fdocs\u002Fiam\u002Fessentials\u002Fsignup","docs\u002Fiam\u002F01.essentials\u002F04.signup",{"title":411,"path":412,"stem":413},"Login","\u002Fdocs\u002Fiam\u002Fessentials\u002Flogin","docs\u002Fiam\u002F01.essentials\u002F05.login",{"title":415,"path":416,"stem":417},"Logout","\u002Fdocs\u002Fiam\u002Fessentials\u002Flogout","docs\u002Fiam\u002F01.essentials\u002F06.logout",{"title":419,"path":420,"stem":421},"OAuth","\u002Fdocs\u002Fiam\u002Fessentials\u002Foauth","docs\u002Fiam\u002F01.essentials\u002F07.oauth",{"title":423,"path":424,"stem":425},"Magic Links","\u002Fdocs\u002Fiam\u002Fessentials\u002Fmagic-links","docs\u002Fiam\u002F01.essentials\u002F08.magic-links",{"title":427,"path":428,"stem":429},"Emails","\u002Fdocs\u002Fiam\u002Fessentials\u002Femails","docs\u002Fiam\u002F01.essentials\u002F09.emails",{"title":244,"path":431,"stem":432},"\u002Fdocs\u002Fiam\u002Fessentials\u002Fmfa","docs\u002Fiam\u002F01.essentials\u002F10.mfa",{"title":434,"path":435,"stem":436},"Fingerprinting","\u002Fdocs\u002Fiam\u002Fessentials\u002Ffingerprinting","docs\u002Fiam\u002F01.essentials\u002F11.fingerprinting",{"title":438,"path":439,"stem":440},"Backend for Frontend","\u002Fdocs\u002Fiam\u002Fessentials\u002Fbff","docs\u002Fiam\u002F01.essentials\u002F12.bff",{"title":442,"path":443,"stem":444},"HMAC Authentication","\u002Fdocs\u002Fiam\u002Fessentials\u002Fhmac","docs\u002Fiam\u002F01.essentials\u002F13.hmac",{"title":446,"path":447,"stem":448},"XSS Protection","\u002Fdocs\u002Fiam\u002Fessentials\u002Fxss","docs\u002Fiam\u002F01.essentials\u002F14.xss",{"title":99,"path":450,"stem":451},"\u002Fdocs\u002Fiam\u002Fessentials\u002Flogging","docs\u002Fiam\u002F01.essentials\u002F15.logging",{"title":453,"path":454,"stem":455},"Rate Limiting","\u002Fdocs\u002Fiam\u002Fessentials\u002Frate-limiting","docs\u002Fiam\u002F01.essentials\u002F16.rate-limiting",{"title":457,"path":458,"stem":459},"Database","\u002Fdocs\u002Fiam\u002Fessentials\u002Fdatabase","docs\u002Fiam\u002F01.essentials\u002F17.database",{"title":237,"path":461,"stem":462},"\u002Fdocs\u002Fiam\u002Fessentials\u002Fcookies","docs\u002Fiam\u002F01.essentials\u002F18.cookies",{"title":464,"path":465,"stem":466},"Service Startup","\u002Fdocs\u002Fiam\u002Fessentials\u002Fservice","docs\u002Fiam\u002F01.essentials\u002F19.service",{"title":254,"path":468,"stem":469},"\u002Fdocs\u002Fiam\u002Fessentials\u002Fpassword-reset","docs\u002Fiam\u002F01.essentials\u002F20.password-reset",{"title":471,"path":472,"stem":473,"children":474},"API Tokens","\u002Fdocs\u002Fiam\u002Fessentials\u002Fapi","docs\u002Fiam\u002F01.essentials\u002F21.api\u002Findex",[475,476,480,484,514,517],{"title":471,"path":472,"stem":473},{"title":477,"path":478,"stem":479},"Creating Tokens","\u002Fdocs\u002Fiam\u002Fessentials\u002Fapi\u002Fcreation","docs\u002Fiam\u002F01.essentials\u002F21.api\u002F00.creation",{"title":481,"path":482,"stem":483},"Verifying Tokens","\u002Fdocs\u002Fiam\u002Fessentials\u002Fapi\u002Fverification","docs\u002Fiam\u002F01.essentials\u002F21.api\u002F01.verification",{"title":485,"path":486,"stem":487,"children":488},"Manage Tokens","\u002Fdocs\u002Fiam\u002Fessentials\u002Fapi\u002Fmanagement","docs\u002Fiam\u002F01.essentials\u002F21.api\u002F02.management\u002Findex",[489,490,494,498,502,506,510],{"title":485,"path":486,"stem":487},{"title":491,"path":492,"stem":493},"Privileges","\u002Fdocs\u002Fiam\u002Fessentials\u002Fapi\u002Fmanagement\u002Fprivilege","docs\u002Fiam\u002F01.essentials\u002F21.api\u002F02.management\u002F00.privilege",{"title":495,"path":496,"stem":497},"Revocation","\u002Fdocs\u002Fiam\u002Fessentials\u002Fapi\u002Fmanagement\u002Frevocation","docs\u002Fiam\u002F01.essentials\u002F21.api\u002F02.management\u002F01.revocation",{"title":499,"path":500,"stem":501},"Rotation","\u002Fdocs\u002Fiam\u002Fessentials\u002Fapi\u002Fmanagement\u002Frotation","docs\u002Fiam\u002F01.essentials\u002F21.api\u002F02.management\u002F02.rotation",{"title":503,"path":504,"stem":505},"IP Restriction","\u002Fdocs\u002Fiam\u002Fessentials\u002Fapi\u002Fmanagement\u002Fip-updates","docs\u002Fiam\u002F01.essentials\u002F21.api\u002F02.management\u002F03.ip-updates",{"title":507,"path":508,"stem":509},"Metadata","\u002Fdocs\u002Fiam\u002Fessentials\u002Fapi\u002Fmanagement\u002Fmetadata","docs\u002Fiam\u002F01.essentials\u002F21.api\u002F02.management\u002F04.metadata",{"title":511,"path":512,"stem":513},"Token Listing","\u002Fdocs\u002Fiam\u002Fessentials\u002Fapi\u002Fmanagement\u002Flist","docs\u002Fiam\u002F01.essentials\u002F21.api\u002F02.management\u002F05.list",{"title":453,"path":515,"stem":516},"\u002Fdocs\u002Fiam\u002Fessentials\u002Fapi\u002Frate-limiting","docs\u002Fiam\u002F01.essentials\u002F21.api\u002F03.rate-limiting",{"title":38,"path":518,"stem":519},"\u002Fdocs\u002Fiam\u002Fessentials\u002Fapi\u002Fsecurity","docs\u002Fiam\u002F01.essentials\u002F21.api\u002F04.security",{"title":38,"path":521,"stem":522},"\u002Fdocs\u002Fiam\u002Fsecurity","docs\u002Fiam\u002F02.security",{"title":86,"path":524,"stem":525,"children":526,"page":53},"\u002Fdocs\u002Fiam\u002Fguides","docs\u002Fiam\u002F03.guides",[527,531,535],{"title":528,"path":529,"stem":530},"Deployment","\u002Fdocs\u002Fiam\u002Fguides\u002Fdeployment","docs\u002Fiam\u002F03.guides\u002Fdeployment",{"title":532,"path":533,"stem":534},"Operation Scripts","\u002Fdocs\u002Fiam\u002Fguides\u002Foperation-scripts","docs\u002Fiam\u002F03.guides\u002Foperation-scripts",{"title":536,"path":537,"stem":538},"Role-Based Access Control","\u002Fdocs\u002Fiam\u002Fguides\u002Frbac","docs\u002Fiam\u002F03.guides\u002Frbac",{"title":188,"path":540,"stem":541},"\u002Fdocs\u002Fiam\u002Fconfiguration","docs\u002Fiam\u002F04.configuration",{"title":543,"path":544,"stem":545,"children":546,"page":53},"Api","\u002Fdocs\u002Fiam\u002Fapi","docs\u002Fiam\u002F05.API",[547,550,553],{"title":184,"path":548,"stem":549},"\u002Fdocs\u002Fiam\u002Fapi\u002Fapi","docs\u002Fiam\u002F05.API\u002F00.api",{"title":327,"path":551,"stem":552},"\u002Fdocs\u002Fiam\u002Fapi\u002Fmiddlewares","docs\u002Fiam\u002F05.API\u002F02.middlewares",{"title":323,"path":554,"stem":555},"\u002Fdocs\u002Fiam\u002Fapi\u002Froutes","docs\u002Fiam\u002F05.API\u002F03.routes",{"title":40,"path":42,"stem":557,"children":558},"docs\u002Fshield-base\u002Findex",[559,560,563,567,608,612,616,620],{"title":40,"path":42,"stem":557},{"title":14,"path":561,"stem":562},"\u002Fdocs\u002Fshield-base\u002Fgetting-started","docs\u002Fshield-base\u002F00.getting-started",{"title":564,"path":565,"stem":566},"CLI Reference","\u002Fdocs\u002Fshield-base\u002Fcli","docs\u002Fshield-base\u002F01.cli",{"title":82,"path":568,"stem":569,"children":570},"\u002Fdocs\u002Fshield-base\u002Fdata-sources","docs\u002Fshield-base\u002F02.data-sources\u002Findex",[571,572,576,580,584,588,592,596,600,604],{"title":82,"path":568,"stem":569},{"title":573,"path":574,"stem":575},"BGP \u002F ASN","\u002Fdocs\u002Fshield-base\u002Fdata-sources\u002Fbgp","docs\u002Fshield-base\u002F02.data-sources\u002Fbgp",{"title":577,"path":578,"stem":579},"City Geolocation","\u002Fdocs\u002Fshield-base\u002Fdata-sources\u002Fcity","docs\u002Fshield-base\u002F02.data-sources\u002Fcity",{"title":581,"path":582,"stem":583},"Country Geolocation","\u002Fdocs\u002Fshield-base\u002Fdata-sources\u002Fcountry","docs\u002Fshield-base\u002F02.data-sources\u002Fcountry",{"title":585,"path":586,"stem":587},"Verified Crawlers","\u002Fdocs\u002Fshield-base\u002Fdata-sources\u002Fcrawlers","docs\u002Fshield-base\u002F02.data-sources\u002Fcrawlers",{"title":589,"path":590,"stem":591},"Disposable Emails","\u002Fdocs\u002Fshield-base\u002Fdata-sources\u002Femail","docs\u002Fshield-base\u002F02.data-sources\u002Femail",{"title":593,"path":594,"stem":595},"FireHOL Threat Intelligence","\u002Fdocs\u002Fshield-base\u002Fdata-sources\u002Ffirehol","docs\u002Fshield-base\u002F02.data-sources\u002Ffirehol",{"title":597,"path":598,"stem":599},"Proxy Detection","\u002Fdocs\u002Fshield-base\u002Fdata-sources\u002Fproxy","docs\u002Fshield-base\u002F02.data-sources\u002Fproxy",{"title":601,"path":602,"stem":603},"Tor Nodes","\u002Fdocs\u002Fshield-base\u002Fdata-sources\u002Ftor","docs\u002Fshield-base\u002F02.data-sources\u002Ftor",{"title":605,"path":606,"stem":607},"Suspicious User-Agents","\u002Fdocs\u002Fshield-base\u002Fdata-sources\u002Fuseragent","docs\u002Fshield-base\u002F02.data-sources\u002Fuseragent",{"title":609,"path":610,"stem":611},"Programmatic Usage","\u002Fdocs\u002Fshield-base\u002Fusage","docs\u002Fshield-base\u002F03.usage",{"title":613,"path":614,"stem":615},"Custom Data Sources","\u002Fdocs\u002Fshield-base\u002Fcustom-data-sources","docs\u002Fshield-base\u002F04.custom-data-sources",{"title":617,"path":618,"stem":619},"TypeScript Types","\u002Fdocs\u002Fshield-base\u002Ftypes","docs\u002Fshield-base\u002F05.types",{"title":184,"path":621,"stem":622},"\u002Fdocs\u002Fshield-base\u002Fapi","docs\u002Fshield-base\u002F06.api",{"title":335,"path":48,"stem":624,"children":625},"docs\u002Futils\u002Findex",[626,627,644,677,774],{"title":335,"path":48,"stem":624},{"title":628,"path":629,"stem":630,"children":631,"page":53},"Eslint","\u002Fdocs\u002Futils\u002Feslint","docs\u002Futils\u002Feslint",[632,636,640],{"title":633,"path":634,"stem":635},"React Config","\u002Fdocs\u002Futils\u002Feslint\u002Freact","docs\u002Futils\u002Feslint\u002Freact",{"title":637,"path":638,"stem":639},"TypeScript Config","\u002Fdocs\u002Futils\u002Feslint\u002Ftypescript","docs\u002Futils\u002Feslint\u002Ftypescript",{"title":641,"path":642,"stem":643},"Vue Config","\u002Fdocs\u002Futils\u002Feslint\u002Fvue","docs\u002Futils\u002Feslint\u002Fvue",{"title":645,"path":646,"stem":647,"children":648,"page":53},"Server","\u002Fdocs\u002Futils\u002Fserver","docs\u002Futils\u002Fserver",[649,653,657,661,665,669,673],{"title":650,"path":651,"stem":652},"Encryption","\u002Fdocs\u002Futils\u002Fserver\u002Fencryption","docs\u002Futils\u002Fserver\u002Fencryption",{"title":654,"path":655,"stem":656},"Path Resolver","\u002Fdocs\u002Futils\u002Fserver\u002Fpathresolver","docs\u002Futils\u002Fserver\u002FpathResolver",{"title":658,"path":659,"stem":660},"File Replacements","\u002Fdocs\u002Futils\u002Fserver\u002Freplace","docs\u002Futils\u002Fserver\u002Freplace",{"title":662,"path":663,"stem":664},"run","\u002Fdocs\u002Futils\u002Fserver\u002Frun","docs\u002Futils\u002Fserver\u002Frun",{"title":666,"path":667,"stem":668},"scheduleTask","\u002Fdocs\u002Futils\u002Fserver\u002Fscheduletask","docs\u002Futils\u002Fserver\u002FscheduleTask",{"title":670,"path":671,"stem":672},"spawnRun","\u002Fdocs\u002Futils\u002Fserver\u002Fspawnrun","docs\u002Futils\u002Fserver\u002FspawnRun",{"title":674,"path":675,"stem":676},"uploadCsv","\u002Fdocs\u002Futils\u002Fserver\u002Fuploadcsv","docs\u002Futils\u002Fserver\u002FuploadCsv",{"title":678,"path":679,"stem":680,"children":681,"page":53},"Shared","\u002Fdocs\u002Futils\u002Fshared","docs\u002Futils\u002Fshared",[682,686,690,694,698,702,706,710,714,718,722,726,730,734,738,742,746,750,754,758,762,766,770],{"title":683,"path":684,"stem":685},"BatchQueue","\u002Fdocs\u002Futils\u002Fshared\u002Fbatchqueue","docs\u002Futils\u002Fshared\u002FbatchQueue",{"title":687,"path":688,"stem":689},"capitalize","\u002Fdocs\u002Futils\u002Fshared\u002Fcapitalize","docs\u002Futils\u002Fshared\u002Fcapitalize",{"title":691,"path":692,"stem":693},"chunkProcess","\u002Fdocs\u002Futils\u002Fshared\u002Fchunkprocess","docs\u002Futils\u002Fshared\u002FchunkProcess",{"title":695,"path":696,"stem":697},"cleanObject","\u002Fdocs\u002Futils\u002Fshared\u002Fcleanobject","docs\u002Futils\u002Fshared\u002FcleanObject",{"title":699,"path":700,"stem":701},"createConfigManager","\u002Fdocs\u002Futils\u002Fshared\u002Fconfigurationdefiner","docs\u002Futils\u002Fshared\u002FconfigurationDefiner",{"title":703,"path":704,"stem":705},"debounce","\u002Fdocs\u002Futils\u002Fshared\u002Fdebounce","docs\u002Futils\u002Fshared\u002Fdebounce",{"title":707,"path":708,"stem":709},"ensureArray","\u002Fdocs\u002Futils\u002Fshared\u002Fensurearray","docs\u002Futils\u002Fshared\u002FensureArray",{"title":711,"path":712,"stem":713},"fetchWithRetry","\u002Fdocs\u002Futils\u002Fshared\u002Ffetchwithretry","docs\u002Futils\u002Fshared\u002FfetchWithRetry",{"title":715,"path":716,"stem":717},"filterEmptyValues","\u002Fdocs\u002Futils\u002Fshared\u002Ffilteremptyvalues","docs\u002Futils\u002Fshared\u002FfilterEmptyValues",{"title":719,"path":720,"stem":721},"findStringsInObject","\u002Fdocs\u002Futils\u002Fshared\u002Ffindobjectvalues","docs\u002Futils\u002Fshared\u002FfindObjectValues",{"title":723,"path":724,"stem":725},"fisherYatesShuffle","\u002Fdocs\u002Futils\u002Fshared\u002Ffisheryatesshuffle","docs\u002Futils\u002Fshared\u002FfisherYatesShuffle",{"title":727,"path":728,"stem":729},"getRandomImage","\u002Fdocs\u002Futils\u002Fshared\u002Fgetrandomimage","docs\u002Futils\u002Fshared\u002FgetRandomImage",{"title":731,"path":732,"stem":733},"isObjectHasValues","\u002Fdocs\u002Futils\u002Fshared\u002Fisobjecthasvalues","docs\u002Futils\u002Fshared\u002FisObjectHasValues",{"title":735,"path":736,"stem":737},"isAsyncOrPromise","\u002Fdocs\u002Futils\u002Fshared\u002Fispromise","docs\u002Futils\u002Fshared\u002FisPromise",{"title":739,"path":740,"stem":741},"MiniCache","\u002Fdocs\u002Futils\u002Fshared\u002Fminicache","docs\u002Futils\u002Fshared\u002FminiCache",{"title":743,"path":744,"stem":745},"parseCookies","\u002Fdocs\u002Futils\u002Fshared\u002Fparserawcookies","docs\u002Futils\u002Fshared\u002FparseRawCookies",{"title":747,"path":748,"stem":749},"safeAction","\u002Fdocs\u002Futils\u002Fshared\u002Fpromiselocker","docs\u002Futils\u002Fshared\u002FpromiseLocker",{"title":751,"path":752,"stem":753},"Random","\u002Fdocs\u002Futils\u002Fshared\u002Frandom","docs\u002Futils\u002Fshared\u002Frandom",{"title":755,"path":756,"stem":757},"range","\u002Fdocs\u002Futils\u002Fshared\u002Frange","docs\u002Futils\u002Fshared\u002Frange",{"title":759,"path":760,"stem":761},"rateLimiters","\u002Fdocs\u002Futils\u002Fshared\u002Fratelimiters","docs\u002Futils\u002Fshared\u002FrateLimiters",{"title":763,"path":764,"stem":765},"safeObjectMerge","\u002Fdocs\u002Futils\u002Fshared\u002Fsafemerge","docs\u002Futils\u002Fshared\u002FsafeMerge",{"title":767,"path":768,"stem":769},"textTruncation","\u002Fdocs\u002Futils\u002Fshared\u002Ftexttruncation","docs\u002Futils\u002Fshared\u002FtextTruncation",{"title":771,"path":772,"stem":773},"validateZodSchema","\u002Fdocs\u002Futils\u002Fshared\u002Fvalidatezodschema","docs\u002Futils\u002Fshared\u002FvalidateZodSchema",{"title":775,"path":776,"stem":777,"children":778},"Utility Types","\u002Fdocs\u002Futils\u002Ftypes","docs\u002Futils\u002Ftypes\u002Findex",[779,780,784,788,792,796,800,804,808,812],{"title":775,"path":776,"stem":777},{"title":781,"path":782,"stem":783},"Brand","\u002Fdocs\u002Futils\u002Ftypes\u002Fbrand","docs\u002Futils\u002Ftypes\u002FBrand",{"title":785,"path":786,"stem":787},"DeepPartial","\u002Fdocs\u002Futils\u002Ftypes\u002Fdeeppartial","docs\u002Futils\u002Ftypes\u002FDeepPartial",{"title":789,"path":790,"stem":791},"Merge","\u002Fdocs\u002Futils\u002Ftypes\u002Fmerge","docs\u002Futils\u002Ftypes\u002FMerge",{"title":793,"path":794,"stem":795},"NonNullable","\u002Fdocs\u002Futils\u002Ftypes\u002Fnonnullable","docs\u002Futils\u002Ftypes\u002FNonNullable",{"title":797,"path":798,"stem":799},"Prettify","\u002Fdocs\u002Futils\u002Ftypes\u002Fprettify","docs\u002Futils\u002Ftypes\u002FPrettify",{"title":801,"path":802,"stem":803},"PromiseType","\u002Fdocs\u002Futils\u002Ftypes\u002Fpromisetype","docs\u002Futils\u002Ftypes\u002FPromiseType",{"title":805,"path":806,"stem":807},"RequireKeys","\u002Fdocs\u002Futils\u002Ftypes\u002Frequirekeys","docs\u002Futils\u002Ftypes\u002FRequireKeys",{"title":809,"path":810,"stem":811},"StandardResponse","\u002Fdocs\u002Futils\u002Ftypes\u002Fstandardresponse","docs\u002Futils\u002Ftypes\u002FStandardResponse",{"title":813,"path":814,"stem":815},"ValueOf","\u002Fdocs\u002Futils\u002Ftypes\u002Fvalueof","docs\u002Futils\u002Ftypes\u002FValueOf",{"id":4,"extension":5,"links":817,"meta":828,"stem":62,"__hash__":63},[818,826,827],{"nested":8,"label":9,"icon":10,"to":11,"children":819},[820,821,822,823,824,825],{"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":830,"title":38,"body":831,"description":1869,"extension":1870,"icon":1871,"meta":1872,"module":1873,"navigation":8,"path":181,"rawbody":1874,"seo":1875,"stem":182,"__hash__":1876},"docs\u002Fdocs\u002Fbot-detection\u002F04.security.md",{"type":832,"value":833,"toc":1857},"minimark",[834,843,846,851,861,871,874,890,899,901,905,917,923,929,932,934,938,967,980,994,1102,1104,1108,1111,1126,1136,1138,1142,1152,1241,1264,1373,1375,1379,1407,1412,1422,1460,1463,1476,1480,1501,1694,1696,1700,1720,1734,1853],[835,836,837,838,842],"p",{},"The middleware evaluates every incoming request through a cumulative scoring model. Checkers contribute penalty points, and when the total reaches the ",[839,840,841],"code",{},"banScore"," threshold, the request is rejected. This page explains how that model works end to end.",[844,845],"hr",{},[847,848,850],"h2",{"id":849},"the-scoring-system","The Scoring System",[835,852,853,854,856,857,860],{},"Every checker returns a numeric score and a list of reason codes. The middleware accumulates these scores across all checkers that run for a given request. When the running total reaches or exceeds ",[839,855,841],{},", the pipeline stops immediately and the request receives a ",[839,858,859],{},"403 Forbidden"," response. No further checkers run.",[835,862,863,864,867,868,870],{},"The score is bounded by ",[839,865,866],{},"maxScore",". If multiple checkers fire on the same request and their combined total would exceed ",[839,869,866],{},", the surplus is discarded. This prevents extreme outliers from producing scores that are impossible to reason about.",[835,872,873],{},"Two reason codes skip the scoring check entirely:",[875,876,877,884],"ul",{},[878,879,880,883],"li",{},[839,881,882],{},"BAD_BOT_DETECTED",": the pipeline stops and bans the visitor instantly, regardless of the current score.",[878,885,886,889],{},[839,887,888],{},"GOOD_BOT_IDENTIFIED",": the pipeline stops and allows the visitor through instantly.",[835,891,892,893,895,896,898],{},"The honeypot checker uses ",[839,894,882],{}," for any request to a configured trap path. The good-bot DNS verifier uses ",[839,897,888],{}," for verified legitimate crawlers.",[844,900],{},[847,902,904],{"id":903},"detection-phases","Detection Phases",[835,906,907,908,912,913,916],{},"The pipeline splits into two sequential phases: ",[909,910,911],"strong",{},"cheap"," and ",[909,914,915],{},"heavy",".",[835,918,919,920,922],{},"The cheap phase runs on every request. It consists of synchronous checks that read pre-loaded MMDB data (geo, ASN, Tor, threat feeds, proxy lists) and inspect headers without any I\u002FO. If the score from cheap checkers reaches ",[839,921,841],{},", the heavy phase is skipped entirely.",[835,924,925,926,928],{},"The heavy phase only runs when the cheap phase score stays below ",[839,927,841],{},". It includes rate tracking, proxy confirmation, session coherence checks, and User-Agent pattern matching against the LMDB database. These operations may query the cache or the database.",[835,930,931],{},"This two-phase design means that obvious bots, those that trigger high-confidence cheap signals, are rejected without any database I\u002FO at all.",[844,933],{},[847,935,937],{"id":936},"the-canary-cookie","The Canary Cookie",[835,939,940,941,944,945,948,949,951,952,955,956,955,959,962,963,966],{},"When ",[839,942,943],{},"detectBots()"," runs for a visitor with no existing ",[839,946,947],{},"canary_id"," cookie, the middleware treats this as a first visit and sets a new ",[839,950,947],{}," cookie with ",[839,953,954],{},"HttpOnly: true",", ",[839,957,958],{},"SameSite: \"lax\"",[839,960,961],{},"Secure: true",", and a 90-day ",[839,964,965],{},"maxAge",". This cookie acts as a persistent visitor identifier across requests.",[835,968,969,970,973,974,977,978,916],{},"The canary cookie serves two roles. First, it allows the rate tracking and velocity fingerprint checkers to correlate requests from the same browser session over time. Second, its absence on a non-first request is itself a signal: bots that do not persist cookies between requests are caught by ",[839,971,972],{},"enableProxyIspCookiesChecks",", which applies an ",[839,975,976],{},"80","-point penalty by default for a missing ",[839,979,947],{},[835,981,982,983,986,987,990,991,993],{},"The middleware reads ",[839,984,985],{},"req.cookies.canary_id",", which requires ",[839,988,989],{},"cookie-parser"," to be mounted before ",[839,992,943],{}," in the Express middleware stack.",[995,996,1002],"pre",{"className":997,"code":998,"filename":999,"language":1000,"meta":1001,"style":1001},"language-ts shiki shiki-themes light-plus light-plus dracula","import cookieParser from 'cookie-parser';\nimport { detectBots } from '@riavzon\u002Fbot-detector';\n\napp.use(cookieParser());\napp.use(detectBots());\n","server.ts","ts","",[839,1003,1004,1034,1060,1066,1087],{"__ignoreMap":1001},[1005,1006,1009,1013,1017,1020,1024,1027,1030],"span",{"class":1007,"line":1008},"line",1,[1005,1010,1012],{"class":1011},"sZ328","import",[1005,1014,1016],{"class":1015},"sjsA6"," cookieParser",[1005,1018,1019],{"class":1011}," from",[1005,1021,1023],{"class":1022},"sFkSl"," '",[1005,1025,989],{"class":1026},"sFB1V",[1005,1028,1029],{"class":1022},"'",[1005,1031,1033],{"class":1032},"sDd4n",";\n",[1005,1035,1037,1039,1042,1045,1048,1051,1053,1056,1058],{"class":1007,"line":1036},2,[1005,1038,1012],{"class":1011},[1005,1040,1041],{"class":1032}," { ",[1005,1043,1044],{"class":1015},"detectBots",[1005,1046,1047],{"class":1032}," } ",[1005,1049,1050],{"class":1011},"from",[1005,1052,1023],{"class":1022},[1005,1054,1055],{"class":1026},"@riavzon\u002Fbot-detector",[1005,1057,1029],{"class":1022},[1005,1059,1033],{"class":1032},[1005,1061,1063],{"class":1007,"line":1062},3,[1005,1064,1065],{"emptyLinePlaceholder":8},"\n",[1005,1067,1069,1072,1074,1078,1081,1084],{"class":1007,"line":1068},4,[1005,1070,1071],{"class":1015},"app",[1005,1073,916],{"class":1032},[1005,1075,1077],{"class":1076},"sHOzp","use",[1005,1079,1080],{"class":1032},"(",[1005,1082,1083],{"class":1076},"cookieParser",[1005,1085,1086],{"class":1032},"());\n",[1005,1088,1090,1092,1094,1096,1098,1100],{"class":1007,"line":1089},5,[1005,1091,1071],{"class":1015},[1005,1093,916],{"class":1032},[1005,1095,1077],{"class":1076},[1005,1097,1080],{"class":1032},[1005,1099,1044],{"class":1076},[1005,1101,1086],{"class":1032},[844,1103],{},[847,1105,1107],{"id":1106},"reputation-healing","Reputation Healing",[835,1109,1110],{},"A visitor's risk score persists in the database between requests. This is intentional, a bot that probes your server once should still carry that history on the next visit. But legitimate visitors can occasionally trigger one or two checkers due to unusual network conditions (VPN, shared IP, unusual browser configuration). Reputation healing gives them a path back to a clean score.",[835,1112,1113,1114,1117,1118,1121,1122,1125],{},"After every request that does not result in a ban, the middleware decrements the visitor's stored score by ",[839,1115,1116],{},"restoredReputationPoints"," (default ",[839,1119,1120],{},"10","). A visitor with an accumulated score of ",[839,1123,1124],{},"40"," recovers to zero after four consecutive clean requests.",[835,1127,1128,1129,1131,1132,1135],{},"Set ",[839,1130,1116],{}," to ",[839,1133,1134],{},"0"," to disable healing entirely. Increase it to speed up recovery for environments where false positives are more likely.",[844,1137],{},[847,1139,1141],{"id":1140},"reading-detection-results","Reading Detection Results",[835,1143,1144,1145,1147,1148,1151],{},"After ",[839,1146,943],{}," runs for a non-banned request, it sets ",[839,1149,1150],{},"req.botDetection"," on the request object. You can read this in any subsequent middleware or route handler.",[995,1153,1155],{"className":997,"code":1154,"language":1000,"meta":1001,"style":1001},"interface BotDetectionResult {\n  success: boolean;  \u002F\u002F always true when this field is present\n  banned: boolean;   \u002F\u002F always false when this field is present; banned requests return early\n  time: string;      \u002F\u002F ISO 8601 timestamp of when detection completed\n  ipAddress: string; \u002F\u002F resolved client IP address\n}\n",[839,1156,1157,1170,1189,1204,1220,1235],{"__ignoreMap":1001},[1005,1158,1159,1163,1167],{"class":1007,"line":1008},[1005,1160,1162],{"class":1161},"sl46w","interface",[1005,1164,1166],{"class":1165},"sFs1U"," BotDetectionResult",[1005,1168,1169],{"class":1032}," {\n",[1005,1171,1172,1175,1179,1182,1185],{"class":1007,"line":1036},[1005,1173,1174],{"class":1015},"  success",[1005,1176,1178],{"class":1177},"saOXh",":",[1005,1180,1181],{"class":1165}," boolean",[1005,1183,1184],{"class":1032},";  ",[1005,1186,1188],{"class":1187},"sghk6","\u002F\u002F always true when this field is present\n",[1005,1190,1191,1194,1196,1198,1201],{"class":1007,"line":1062},[1005,1192,1193],{"class":1015},"  banned",[1005,1195,1178],{"class":1177},[1005,1197,1181],{"class":1165},[1005,1199,1200],{"class":1032},";   ",[1005,1202,1203],{"class":1187},"\u002F\u002F always false when this field is present; banned requests return early\n",[1005,1205,1206,1209,1211,1214,1217],{"class":1007,"line":1068},[1005,1207,1208],{"class":1015},"  time",[1005,1210,1178],{"class":1177},[1005,1212,1213],{"class":1165}," string",[1005,1215,1216],{"class":1032},";      ",[1005,1218,1219],{"class":1187},"\u002F\u002F ISO 8601 timestamp of when detection completed\n",[1005,1221,1222,1225,1227,1229,1232],{"class":1007,"line":1089},[1005,1223,1224],{"class":1015},"  ipAddress",[1005,1226,1178],{"class":1177},[1005,1228,1213],{"class":1165},[1005,1230,1231],{"class":1032},"; ",[1005,1233,1234],{"class":1187},"\u002F\u002F resolved client IP address\n",[1005,1236,1238],{"class":1007,"line":1237},6,[1005,1239,1240],{"class":1032},"}\n",[1242,1243,1244],"note",{},[835,1245,1246,1248,1249,1252,1253,1256,1257,1260,1261,1263],{},[839,1247,1150],{}," is only set when detection completes without banning. Banned requests receive the ",[839,1250,1251],{},"403"," and return before this field is written, so it is never accessible in a handler that runs after a banned request. If ",[839,1254,1255],{},"checksTimeRateControl.checkEveryRequest"," is ",[839,1258,1259],{},"false"," and the visitor is already cached, the pipeline is skipped entirely and ",[839,1262,1150],{}," is also not set for that request.",[995,1265,1267],{"className":997,"code":1266,"filename":999,"language":1000,"meta":1001,"style":1001},"app.get('\u002Fapi\u002Fdata', (req, res) => {\n  const { time, ipAddress } = req.botDetection;\n  \u002F\u002F req.botDetection is present: visitor passed detection\n  res.json({ ok: true });\n});\n",[839,1268,1269,1307,1338,1343,1368],{"__ignoreMap":1001},[1005,1270,1271,1273,1275,1278,1280,1282,1285,1287,1290,1294,1296,1299,1302,1305],{"class":1007,"line":1008},[1005,1272,1071],{"class":1015},[1005,1274,916],{"class":1032},[1005,1276,1277],{"class":1076},"get",[1005,1279,1080],{"class":1032},[1005,1281,1029],{"class":1022},[1005,1283,1284],{"class":1026},"\u002Fapi\u002Fdata",[1005,1286,1029],{"class":1022},[1005,1288,1289],{"class":1032},", (",[1005,1291,1293],{"class":1292},"sygFZ","req",[1005,1295,955],{"class":1032},[1005,1297,1298],{"class":1292},"res",[1005,1300,1301],{"class":1032},") ",[1005,1303,1304],{"class":1161},"=>",[1005,1306,1169],{"class":1032},[1005,1308,1309,1312,1314,1318,1320,1323,1325,1328,1331,1333,1336],{"class":1007,"line":1036},[1005,1310,1311],{"class":1161},"  const",[1005,1313,1041],{"class":1032},[1005,1315,1317],{"class":1316},"s3JHE","time",[1005,1319,955],{"class":1032},[1005,1321,1322],{"class":1316},"ipAddress",[1005,1324,1047],{"class":1032},[1005,1326,1327],{"class":1177},"=",[1005,1329,1330],{"class":1015}," req",[1005,1332,916],{"class":1032},[1005,1334,1335],{"class":1015},"botDetection",[1005,1337,1033],{"class":1032},[1005,1339,1340],{"class":1007,"line":1062},[1005,1341,1342],{"class":1187},"  \u002F\u002F req.botDetection is present: visitor passed detection\n",[1005,1344,1345,1348,1350,1352,1355,1358,1361,1365],{"class":1007,"line":1068},[1005,1346,1347],{"class":1015},"  res",[1005,1349,916],{"class":1032},[1005,1351,5],{"class":1076},[1005,1353,1354],{"class":1032},"({ ",[1005,1356,1357],{"class":1015},"ok",[1005,1359,1178],{"class":1360},"s34zl",[1005,1362,1364],{"class":1363},"sjR7W"," true",[1005,1366,1367],{"class":1032}," });\n",[1005,1369,1370],{"class":1007,"line":1089},[1005,1371,1372],{"class":1032},"});\n",[844,1374],{},[847,1376,1378],{"id":1377},"ban-enforcement","Ban Enforcement",[835,1380,1381,1382,1384,1385,1388,1389,1391,1392,1394,1395,1398,1399,1402,1403,1406],{},"When a request reaches ",[839,1383,841],{},", the middleware records the ban in the ",[839,1386,1387],{},"banned"," table and responds with ",[839,1390,1251],{},". The visitor's ",[839,1393,947],{}," and IP address are both stored. On all future requests, the ",[839,1396,1397],{},"enableKnownBadIpsCheck"," checker reads ",[839,1400,1401],{},"banned.mmdb"," (compiled by ",[839,1404,1405],{},"bot-detector generate",") and catches the IP in the cheap phase without re-running the full pipeline.",[1408,1409,1411],"h3",{"id":1410},"firewall-level-bans","Firewall-Level Bans",[835,1413,1414,1415,1418,1419,1421],{},"Setting ",[839,1416,1417],{},"punishmentType.enableFireWallBan: true"," adds an OS-level block on top of the application-level ",[839,1420,1251],{},". When a visitor is banned, the middleware issues:",[995,1423,1427],{"className":1424,"code":1425,"language":1426,"meta":1001,"style":1001},"language-sh shiki shiki-themes light-plus light-plus dracula","sudo ufw insert 1 deny from \u003Cip>\n","sh",[839,1428,1429],{"__ignoreMap":1001},[1005,1430,1431,1434,1437,1440,1444,1447,1449,1452,1455,1457],{"class":1007,"line":1008},[1005,1432,1433],{"class":1076},"sudo",[1005,1435,1436],{"class":1026}," ufw",[1005,1438,1439],{"class":1026}," insert",[1005,1441,1443],{"class":1442},"spgvN"," 1",[1005,1445,1446],{"class":1026}," deny",[1005,1448,1019],{"class":1026},[1005,1450,1451],{"class":1177}," \u003C",[1005,1453,1454],{"class":1026},"i",[1005,1456,835],{"class":1032},[1005,1458,1459],{"class":1177},">\n",[835,1461,1462],{},"This blocks the IP at the network layer so subsequent connections from that address never reach the Node.js process. The firewall rule is permanent and survives application restarts.",[1464,1465,1466],"warning",{},[835,1467,1468,1469,1472,1473,1475],{},"Firewall bans require a Linux environment with ",[839,1470,1471],{},"ufw"," installed and passwordless ",[839,1474,1433],{}," access for the Node.js process user. This setting has no effect on macOS or Windows and should not be enabled on those platforms.",[1408,1477,1479],{"id":1478},"updating-records-manually","Updating Records Manually",[835,1481,1482,1485,1486,1488,1489,1492,1493,1496,1497,1500],{},[839,1483,1484],{},"updateBannedIP"," upserts a row directly into the ",[839,1487,1387],{}," table. ",[839,1490,1491],{},"updateIsBot"," flips the ",[839,1494,1495],{},"is_bot"," flag on a ",[839,1498,1499],{},"visitors"," row by canary cookie.",[995,1502,1504],{"className":997,"code":1503,"language":1000,"meta":1001,"style":1001},"import { updateBannedIP, updateIsBot } from '@riavzon\u002Fbot-detector';\nimport type { BannedInfo } from '@riavzon\u002Fbot-detector';\n\n\u002F\u002F upsert a ban record\nconst info: BannedInfo = { score: 100, reasons: ['PREVIOUSLY_BANNED_IP'] };\nawait updateBannedIP('canary-cookie-value', '1.2.3.4', 'us', 'Mozilla\u002F5.0 ...', info);\n\n\u002F\u002F flip the is_bot flag on a visitor record\nawait updateIsBot(false, 'canary-cookie-value');\n",[839,1505,1506,1530,1554,1558,1563,1609,1661,1666,1672],{"__ignoreMap":1001},[1005,1507,1508,1510,1512,1514,1516,1518,1520,1522,1524,1526,1528],{"class":1007,"line":1008},[1005,1509,1012],{"class":1011},[1005,1511,1041],{"class":1032},[1005,1513,1484],{"class":1015},[1005,1515,955],{"class":1032},[1005,1517,1491],{"class":1015},[1005,1519,1047],{"class":1032},[1005,1521,1050],{"class":1011},[1005,1523,1023],{"class":1022},[1005,1525,1055],{"class":1026},[1005,1527,1029],{"class":1022},[1005,1529,1033],{"class":1032},[1005,1531,1532,1534,1537,1539,1542,1544,1546,1548,1550,1552],{"class":1007,"line":1036},[1005,1533,1012],{"class":1011},[1005,1535,1536],{"class":1011}," type",[1005,1538,1041],{"class":1032},[1005,1540,1541],{"class":1015},"BannedInfo",[1005,1543,1047],{"class":1032},[1005,1545,1050],{"class":1011},[1005,1547,1023],{"class":1022},[1005,1549,1055],{"class":1026},[1005,1551,1029],{"class":1022},[1005,1553,1033],{"class":1032},[1005,1555,1556],{"class":1007,"line":1062},[1005,1557,1065],{"emptyLinePlaceholder":8},[1005,1559,1560],{"class":1007,"line":1068},[1005,1561,1562],{"class":1187},"\u002F\u002F upsert a ban record\n",[1005,1564,1565,1568,1571,1573,1576,1579,1581,1584,1586,1589,1591,1594,1596,1599,1601,1604,1606],{"class":1007,"line":1089},[1005,1566,1567],{"class":1161},"const",[1005,1569,1570],{"class":1316}," info",[1005,1572,1178],{"class":1177},[1005,1574,1575],{"class":1165}," BannedInfo",[1005,1577,1578],{"class":1177}," =",[1005,1580,1041],{"class":1032},[1005,1582,1583],{"class":1015},"score",[1005,1585,1178],{"class":1360},[1005,1587,1588],{"class":1442}," 100",[1005,1590,955],{"class":1032},[1005,1592,1593],{"class":1015},"reasons",[1005,1595,1178],{"class":1360},[1005,1597,1598],{"class":1032}," [",[1005,1600,1029],{"class":1022},[1005,1602,1603],{"class":1026},"PREVIOUSLY_BANNED_IP",[1005,1605,1029],{"class":1022},[1005,1607,1608],{"class":1032},"] };\n",[1005,1610,1611,1614,1617,1619,1621,1624,1626,1628,1630,1633,1635,1637,1639,1642,1644,1646,1648,1651,1653,1655,1658],{"class":1007,"line":1237},[1005,1612,1613],{"class":1011},"await",[1005,1615,1616],{"class":1076}," updateBannedIP",[1005,1618,1080],{"class":1032},[1005,1620,1029],{"class":1022},[1005,1622,1623],{"class":1026},"canary-cookie-value",[1005,1625,1029],{"class":1022},[1005,1627,955],{"class":1032},[1005,1629,1029],{"class":1022},[1005,1631,1632],{"class":1026},"1.2.3.4",[1005,1634,1029],{"class":1022},[1005,1636,955],{"class":1032},[1005,1638,1029],{"class":1022},[1005,1640,1641],{"class":1026},"us",[1005,1643,1029],{"class":1022},[1005,1645,955],{"class":1032},[1005,1647,1029],{"class":1022},[1005,1649,1650],{"class":1026},"Mozilla\u002F5.0 ...",[1005,1652,1029],{"class":1022},[1005,1654,955],{"class":1032},[1005,1656,1657],{"class":1015},"info",[1005,1659,1660],{"class":1032},");\n",[1005,1662,1664],{"class":1007,"line":1663},7,[1005,1665,1065],{"emptyLinePlaceholder":8},[1005,1667,1669],{"class":1007,"line":1668},8,[1005,1670,1671],{"class":1187},"\u002F\u002F flip the is_bot flag on a visitor record\n",[1005,1673,1675,1677,1680,1682,1684,1686,1688,1690,1692],{"class":1007,"line":1674},9,[1005,1676,1613],{"class":1011},[1005,1678,1679],{"class":1076}," updateIsBot",[1005,1681,1080],{"class":1032},[1005,1683,1259],{"class":1363},[1005,1685,955],{"class":1032},[1005,1687,1029],{"class":1022},[1005,1689,1623],{"class":1026},[1005,1691,1029],{"class":1022},[1005,1693,1660],{"class":1032},[844,1695],{},[847,1697,1699],{"id":1698},"persistent-visitor-records","Persistent Visitor Records",[835,1701,1702,1703,1705,1706,1708,1709,1711,1712,1714,1715,912,1717,916],{},"The middleware writes a record to the ",[839,1704,1499],{}," table for every unique ",[839,1707,947],{}," it encounters. Each row stores the visitor's IP, accumulated score, reason codes, first-seen and last-seen timestamps, and an ",[839,1710,1495],{}," flag. These records are the source data for ",[839,1713,1405],{},", which compiles ",[839,1716,1401],{},[839,1718,1719],{},"highRisk.mmdb",[1721,1722,1723],"tip",{},[835,1724,1725,1726,1729,1730,1733],{},"Use ",[839,1727,1728],{},"warmUp()"," at server startup to prime the database connection pool before the first real request arrives. It runs a set of parallel ",[839,1731,1732],{},"SELECT 1"," queries to open connections and a dummy visitor lookup to warm the query plan cache.",[995,1735,1737],{"className":997,"code":1736,"filename":999,"language":1000,"meta":1001,"style":1001},"import { defineConfiguration, warmUp, detectBots } from '@riavzon\u002Fbot-detector';\n\nawait defineConfiguration({ store: { main: { driver: 'sqlite', name: '.\u002Fbot-detector.db' } } });\nawait warmUp();\n\napp.use(detectBots());\n",[839,1738,1739,1769,1773,1825,1835,1839],{"__ignoreMap":1001},[1005,1740,1741,1743,1745,1748,1750,1753,1755,1757,1759,1761,1763,1765,1767],{"class":1007,"line":1008},[1005,1742,1012],{"class":1011},[1005,1744,1041],{"class":1032},[1005,1746,1747],{"class":1015},"defineConfiguration",[1005,1749,955],{"class":1032},[1005,1751,1752],{"class":1015},"warmUp",[1005,1754,955],{"class":1032},[1005,1756,1044],{"class":1015},[1005,1758,1047],{"class":1032},[1005,1760,1050],{"class":1011},[1005,1762,1023],{"class":1022},[1005,1764,1055],{"class":1026},[1005,1766,1029],{"class":1022},[1005,1768,1033],{"class":1032},[1005,1770,1771],{"class":1007,"line":1036},[1005,1772,1065],{"emptyLinePlaceholder":8},[1005,1774,1775,1777,1780,1782,1785,1787,1789,1792,1794,1796,1799,1801,1803,1806,1808,1810,1813,1815,1817,1820,1822],{"class":1007,"line":1062},[1005,1776,1613],{"class":1011},[1005,1778,1779],{"class":1076}," defineConfiguration",[1005,1781,1354],{"class":1032},[1005,1783,1784],{"class":1015},"store",[1005,1786,1178],{"class":1360},[1005,1788,1041],{"class":1032},[1005,1790,1791],{"class":1015},"main",[1005,1793,1178],{"class":1360},[1005,1795,1041],{"class":1032},[1005,1797,1798],{"class":1015},"driver",[1005,1800,1178],{"class":1360},[1005,1802,1023],{"class":1022},[1005,1804,1805],{"class":1026},"sqlite",[1005,1807,1029],{"class":1022},[1005,1809,955],{"class":1032},[1005,1811,1812],{"class":1015},"name",[1005,1814,1178],{"class":1360},[1005,1816,1023],{"class":1022},[1005,1818,1819],{"class":1026},".\u002Fbot-detector.db",[1005,1821,1029],{"class":1022},[1005,1823,1824],{"class":1032}," } } });\n",[1005,1826,1827,1829,1832],{"class":1007,"line":1068},[1005,1828,1613],{"class":1011},[1005,1830,1831],{"class":1076}," warmUp",[1005,1833,1834],{"class":1032},"();\n",[1005,1836,1837],{"class":1007,"line":1089},[1005,1838,1065],{"emptyLinePlaceholder":8},[1005,1840,1841,1843,1845,1847,1849,1851],{"class":1007,"line":1237},[1005,1842,1071],{"class":1015},[1005,1844,916],{"class":1032},[1005,1846,1077],{"class":1076},[1005,1848,1080],{"class":1032},[1005,1850,1044],{"class":1076},[1005,1852,1086],{"class":1032},[1854,1855,1856],"style",{},"html pre.shiki code .sZ328, html code.shiki .sZ328{--shiki-light:#AF00DB;--shiki-default:#AF00DB;--shiki-dark:#FF79C6}html pre.shiki code .sjsA6, html code.shiki .sjsA6{--shiki-light:#001080;--shiki-default:#001080;--shiki-dark:#F8F8F2}html pre.shiki code .sFkSl, html code.shiki .sFkSl{--shiki-light:#A31515;--shiki-default:#A31515;--shiki-dark:#E9F284}html pre.shiki code .sFB1V, html code.shiki .sFB1V{--shiki-light:#A31515;--shiki-default:#A31515;--shiki-dark:#F1FA8C}html pre.shiki code .sDd4n, html code.shiki .sDd4n{--shiki-light:#000000;--shiki-default:#000000;--shiki-dark:#F8F8F2}html pre.shiki code .sHOzp, html code.shiki .sHOzp{--shiki-light:#795E26;--shiki-default:#795E26;--shiki-dark:#50FA7B}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 .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 .saOXh, html code.shiki .saOXh{--shiki-light:#000000;--shiki-default:#000000;--shiki-dark:#FF79C6}html pre.shiki code .sghk6, html code.shiki .sghk6{--shiki-light:#008000;--shiki-default:#008000;--shiki-dark:#6272A4}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 .s3JHE, html code.shiki .s3JHE{--shiki-light:#0070C1;--shiki-default:#0070C1;--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 .sjR7W, html code.shiki .sjR7W{--shiki-light:#0000FF;--shiki-default:#0000FF;--shiki-dark:#BD93F9}html pre.shiki code .spgvN, html code.shiki .spgvN{--shiki-light:#098658;--shiki-default:#098658;--shiki-dark:#BD93F9}",{"title":1001,"searchDepth":1036,"depth":1036,"links":1858},[1859,1860,1861,1862,1863,1864,1868],{"id":849,"depth":1036,"text":850},{"id":903,"depth":1036,"text":904},{"id":936,"depth":1036,"text":937},{"id":1106,"depth":1036,"text":1107},{"id":1140,"depth":1036,"text":1141},{"id":1377,"depth":1036,"text":1378,"children":1865},[1866,1867],{"id":1410,"depth":1062,"text":1411},{"id":1478,"depth":1062,"text":1479},{"id":1698,"depth":1036,"text":1699},"The scoring system, penalty configuration, the canary cookie mechanism, and persistent bans.","md","i-lucide-lock",{},null,"---\ntitle: Security\ndescription: The scoring system, penalty configuration, the canary cookie mechanism, and persistent bans.\nicon: i-lucide-lock\n---\n\nThe middleware evaluates every incoming request through a cumulative scoring model. Checkers contribute penalty points, and when the total reaches the `banScore` threshold, the request is rejected. This page explains how that model works end to end.\n\n---\n\n## The Scoring System\n\nEvery checker returns a numeric score and a list of reason codes. The middleware accumulates these scores across all checkers that run for a given request. When the running total reaches or exceeds `banScore`, the pipeline stops immediately and the request receives a `403 Forbidden` response. No further checkers run.\n\nThe score is bounded by `maxScore`. If multiple checkers fire on the same request and their combined total would exceed `maxScore`, the surplus is discarded. This prevents extreme outliers from producing scores that are impossible to reason about.\n\nTwo reason codes skip the scoring check entirely:\n\n- `BAD_BOT_DETECTED`: the pipeline stops and bans the visitor instantly, regardless of the current score.\n- `GOOD_BOT_IDENTIFIED`: the pipeline stops and allows the visitor through instantly.\n\nThe honeypot checker uses `BAD_BOT_DETECTED` for any request to a configured trap path. The good-bot DNS verifier uses `GOOD_BOT_IDENTIFIED` for verified legitimate crawlers.\n\n---\n\n## Detection Phases\n\nThe pipeline splits into two sequential phases: **cheap** and **heavy**.\n\nThe cheap phase runs on every request. It consists of synchronous checks that read pre-loaded MMDB data (geo, ASN, Tor, threat feeds, proxy lists) and inspect headers without any I\u002FO. If the score from cheap checkers reaches `banScore`, the heavy phase is skipped entirely.\n\nThe heavy phase only runs when the cheap phase score stays below `banScore`. It includes rate tracking, proxy confirmation, session coherence checks, and User-Agent pattern matching against the LMDB database. These operations may query the cache or the database.\n\nThis two-phase design means that obvious bots, those that trigger high-confidence cheap signals, are rejected without any database I\u002FO at all.\n\n---\n\n## The Canary Cookie\n\nWhen `detectBots()` runs for a visitor with no existing `canary_id` cookie, the middleware treats this as a first visit and sets a new `canary_id` cookie with `HttpOnly: true`, `SameSite: \"lax\"`, `Secure: true`, and a 90-day `maxAge`. This cookie acts as a persistent visitor identifier across requests.\n\nThe canary cookie serves two roles. First, it allows the rate tracking and velocity fingerprint checkers to correlate requests from the same browser session over time. Second, its absence on a non-first request is itself a signal: bots that do not persist cookies between requests are caught by `enableProxyIspCookiesChecks`, which applies an `80`-point penalty by default for a missing `canary_id`.\n\nThe middleware reads `req.cookies.canary_id`, which requires `cookie-parser` to be mounted before `detectBots()` in the Express middleware stack.\n\n```ts [server.ts]\nimport cookieParser from 'cookie-parser';\nimport { detectBots } from '@riavzon\u002Fbot-detector';\n\napp.use(cookieParser());\napp.use(detectBots());\n```\n\n---\n\n## Reputation Healing\n\nA visitor's risk score persists in the database between requests. This is intentional, a bot that probes your server once should still carry that history on the next visit. But legitimate visitors can occasionally trigger one or two checkers due to unusual network conditions (VPN, shared IP, unusual browser configuration). Reputation healing gives them a path back to a clean score.\n\nAfter every request that does not result in a ban, the middleware decrements the visitor's stored score by `restoredReputationPoints` (default `10`). A visitor with an accumulated score of `40` recovers to zero after four consecutive clean requests.\n\nSet `restoredReputationPoints` to `0` to disable healing entirely. Increase it to speed up recovery for environments where false positives are more likely.\n\n---\n\n## Reading Detection Results\n\nAfter `detectBots()` runs for a non-banned request, it sets `req.botDetection` on the request object. You can read this in any subsequent middleware or route handler.\n\n```ts\ninterface BotDetectionResult {\n  success: boolean;  \u002F\u002F always true when this field is present\n  banned: boolean;   \u002F\u002F always false when this field is present; banned requests return early\n  time: string;      \u002F\u002F ISO 8601 timestamp of when detection completed\n  ipAddress: string; \u002F\u002F resolved client IP address\n}\n```\n\n::note\n`req.botDetection` is only set when detection completes without banning. Banned requests receive the `403` and return before this field is written, so it is never accessible in a handler that runs after a banned request. If `checksTimeRateControl.checkEveryRequest` is `false` and the visitor is already cached, the pipeline is skipped entirely and `req.botDetection` is also not set for that request.\n::\n\n```ts [server.ts]\napp.get('\u002Fapi\u002Fdata', (req, res) => {\n  const { time, ipAddress } = req.botDetection;\n  \u002F\u002F req.botDetection is present: visitor passed detection\n  res.json({ ok: true });\n});\n```\n\n---\n\n## Ban Enforcement\n\nWhen a request reaches `banScore`, the middleware records the ban in the `banned` table and responds with `403`. The visitor's `canary_id` and IP address are both stored. On all future requests, the `enableKnownBadIpsCheck` checker reads `banned.mmdb` (compiled by `bot-detector generate`) and catches the IP in the cheap phase without re-running the full pipeline.\n\n### Firewall-Level Bans\n\nSetting `punishmentType.enableFireWallBan: true` adds an OS-level block on top of the application-level `403`. When a visitor is banned, the middleware issues:\n\n```sh\nsudo ufw insert 1 deny from \u003Cip>\n```\n\nThis blocks the IP at the network layer so subsequent connections from that address never reach the Node.js process. The firewall rule is permanent and survives application restarts.\n\n::warning\nFirewall bans require a Linux environment with `ufw` installed and passwordless `sudo` access for the Node.js process user. This setting has no effect on macOS or Windows and should not be enabled on those platforms.\n::\n\n### Updating Records Manually\n\n`updateBannedIP` upserts a row directly into the `banned` table. `updateIsBot` flips the `is_bot` flag on a `visitors` row by canary cookie.\n\n```ts\nimport { updateBannedIP, updateIsBot } from '@riavzon\u002Fbot-detector';\nimport type { BannedInfo } from '@riavzon\u002Fbot-detector';\n\n\u002F\u002F upsert a ban record\nconst info: BannedInfo = { score: 100, reasons: ['PREVIOUSLY_BANNED_IP'] };\nawait updateBannedIP('canary-cookie-value', '1.2.3.4', 'us', 'Mozilla\u002F5.0 ...', info);\n\n\u002F\u002F flip the is_bot flag on a visitor record\nawait updateIsBot(false, 'canary-cookie-value');\n```\n\n---\n\n## Persistent Visitor Records\n\nThe middleware writes a record to the `visitors` table for every unique `canary_id` it encounters. Each row stores the visitor's IP, accumulated score, reason codes, first-seen and last-seen timestamps, and an `is_bot` flag. These records are the source data for `bot-detector generate`, which compiles `banned.mmdb` and `highRisk.mmdb`.\n\n::tip\nUse `warmUp()` at server startup to prime the database connection pool before the first real request arrives. It runs a set of parallel `SELECT 1` queries to open connections and a dummy visitor lookup to warm the query plan cache.\n::\n\n```ts [server.ts]\nimport { defineConfiguration, warmUp, detectBots } from '@riavzon\u002Fbot-detector';\n\nawait defineConfiguration({ store: { main: { driver: 'sqlite', name: '.\u002Fbot-detector.db' } } });\nawait warmUp();\n\napp.use(detectBots());\n```\n",{"title":38,"description":1869},"ODmdzDBCtO_UzEQ9DCYGBxDbNi5nICQYYI4cQj_4KgM",[1878,1879],{"title":177,"path":178,"stem":179,"children":-1},{"title":184,"path":185,"stem":186,"children":-1},{"id":830,"title":38,"body":1881,"description":1869,"extension":1870,"icon":1871,"meta":2631,"module":1873,"navigation":8,"path":181,"rawbody":1874,"seo":2632,"stem":182,"__hash__":1876},{"type":832,"value":1882,"toc":2619},[1883,1887,1889,1891,1897,1903,1905,1915,1921,1923,1925,1931,1935,1939,1941,1943,1945,1961,1969,1977,2049,2051,2053,2055,2063,2069,2071,2073,2079,2143,2157,2241,2243,2245,2261,2263,2269,2295,2297,2305,2307,2319,2485,2487,2489,2503,2511,2617],[835,1884,837,1885,842],{},[839,1886,841],{},[844,1888],{},[847,1890,850],{"id":849},[835,1892,853,1893,856,1895,860],{},[839,1894,841],{},[839,1896,859],{},[835,1898,863,1899,867,1901,870],{},[839,1900,866],{},[839,1902,866],{},[835,1904,873],{},[875,1906,1907,1911],{},[878,1908,1909,883],{},[839,1910,882],{},[878,1912,1913,889],{},[839,1914,888],{},[835,1916,892,1917,895,1919,898],{},[839,1918,882],{},[839,1920,888],{},[844,1922],{},[847,1924,904],{"id":903},[835,1926,907,1927,912,1929,916],{},[909,1928,911],{},[909,1930,915],{},[835,1932,919,1933,922],{},[839,1934,841],{},[835,1936,925,1937,928],{},[839,1938,841],{},[835,1940,931],{},[844,1942],{},[847,1944,937],{"id":936},[835,1946,940,1947,944,1949,948,1951,951,1953,955,1955,955,1957,962,1959,966],{},[839,1948,943],{},[839,1950,947],{},[839,1952,947],{},[839,1954,954],{},[839,1956,958],{},[839,1958,961],{},[839,1960,965],{},[835,1962,969,1963,973,1965,977,1967,916],{},[839,1964,972],{},[839,1966,976],{},[839,1968,947],{},[835,1970,982,1971,986,1973,990,1975,993],{},[839,1972,985],{},[839,1974,989],{},[839,1976,943],{},[995,1978,1979],{"className":997,"code":998,"filename":999,"language":1000,"meta":1001,"style":1001},[839,1980,1981,1997,2017,2021,2035],{"__ignoreMap":1001},[1005,1982,1983,1985,1987,1989,1991,1993,1995],{"class":1007,"line":1008},[1005,1984,1012],{"class":1011},[1005,1986,1016],{"class":1015},[1005,1988,1019],{"class":1011},[1005,1990,1023],{"class":1022},[1005,1992,989],{"class":1026},[1005,1994,1029],{"class":1022},[1005,1996,1033],{"class":1032},[1005,1998,1999,2001,2003,2005,2007,2009,2011,2013,2015],{"class":1007,"line":1036},[1005,2000,1012],{"class":1011},[1005,2002,1041],{"class":1032},[1005,2004,1044],{"class":1015},[1005,2006,1047],{"class":1032},[1005,2008,1050],{"class":1011},[1005,2010,1023],{"class":1022},[1005,2012,1055],{"class":1026},[1005,2014,1029],{"class":1022},[1005,2016,1033],{"class":1032},[1005,2018,2019],{"class":1007,"line":1062},[1005,2020,1065],{"emptyLinePlaceholder":8},[1005,2022,2023,2025,2027,2029,2031,2033],{"class":1007,"line":1068},[1005,2024,1071],{"class":1015},[1005,2026,916],{"class":1032},[1005,2028,1077],{"class":1076},[1005,2030,1080],{"class":1032},[1005,2032,1083],{"class":1076},[1005,2034,1086],{"class":1032},[1005,2036,2037,2039,2041,2043,2045,2047],{"class":1007,"line":1089},[1005,2038,1071],{"class":1015},[1005,2040,916],{"class":1032},[1005,2042,1077],{"class":1076},[1005,2044,1080],{"class":1032},[1005,2046,1044],{"class":1076},[1005,2048,1086],{"class":1032},[844,2050],{},[847,2052,1107],{"id":1106},[835,2054,1110],{},[835,2056,1113,2057,1117,2059,1121,2061,1125],{},[839,2058,1116],{},[839,2060,1120],{},[839,2062,1124],{},[835,2064,1128,2065,1131,2067,1135],{},[839,2066,1116],{},[839,2068,1134],{},[844,2070],{},[847,2072,1141],{"id":1140},[835,2074,1144,2075,1147,2077,1151],{},[839,2076,943],{},[839,2078,1150],{},[995,2080,2081],{"className":997,"code":1154,"language":1000,"meta":1001,"style":1001},[839,2082,2083,2091,2103,2115,2127,2139],{"__ignoreMap":1001},[1005,2084,2085,2087,2089],{"class":1007,"line":1008},[1005,2086,1162],{"class":1161},[1005,2088,1166],{"class":1165},[1005,2090,1169],{"class":1032},[1005,2092,2093,2095,2097,2099,2101],{"class":1007,"line":1036},[1005,2094,1174],{"class":1015},[1005,2096,1178],{"class":1177},[1005,2098,1181],{"class":1165},[1005,2100,1184],{"class":1032},[1005,2102,1188],{"class":1187},[1005,2104,2105,2107,2109,2111,2113],{"class":1007,"line":1062},[1005,2106,1193],{"class":1015},[1005,2108,1178],{"class":1177},[1005,2110,1181],{"class":1165},[1005,2112,1200],{"class":1032},[1005,2114,1203],{"class":1187},[1005,2116,2117,2119,2121,2123,2125],{"class":1007,"line":1068},[1005,2118,1208],{"class":1015},[1005,2120,1178],{"class":1177},[1005,2122,1213],{"class":1165},[1005,2124,1216],{"class":1032},[1005,2126,1219],{"class":1187},[1005,2128,2129,2131,2133,2135,2137],{"class":1007,"line":1089},[1005,2130,1224],{"class":1015},[1005,2132,1178],{"class":1177},[1005,2134,1213],{"class":1165},[1005,2136,1231],{"class":1032},[1005,2138,1234],{"class":1187},[1005,2140,2141],{"class":1007,"line":1237},[1005,2142,1240],{"class":1032},[1242,2144,2145],{},[835,2146,2147,1248,2149,1252,2151,1256,2153,1260,2155,1263],{},[839,2148,1150],{},[839,2150,1251],{},[839,2152,1255],{},[839,2154,1259],{},[839,2156,1150],{},[995,2158,2159],{"className":997,"code":1266,"filename":999,"language":1000,"meta":1001,"style":1001},[839,2160,2161,2191,2215,2219,2237],{"__ignoreMap":1001},[1005,2162,2163,2165,2167,2169,2171,2173,2175,2177,2179,2181,2183,2185,2187,2189],{"class":1007,"line":1008},[1005,2164,1071],{"class":1015},[1005,2166,916],{"class":1032},[1005,2168,1277],{"class":1076},[1005,2170,1080],{"class":1032},[1005,2172,1029],{"class":1022},[1005,2174,1284],{"class":1026},[1005,2176,1029],{"class":1022},[1005,2178,1289],{"class":1032},[1005,2180,1293],{"class":1292},[1005,2182,955],{"class":1032},[1005,2184,1298],{"class":1292},[1005,2186,1301],{"class":1032},[1005,2188,1304],{"class":1161},[1005,2190,1169],{"class":1032},[1005,2192,2193,2195,2197,2199,2201,2203,2205,2207,2209,2211,2213],{"class":1007,"line":1036},[1005,2194,1311],{"class":1161},[1005,2196,1041],{"class":1032},[1005,2198,1317],{"class":1316},[1005,2200,955],{"class":1032},[1005,2202,1322],{"class":1316},[1005,2204,1047],{"class":1032},[1005,2206,1327],{"class":1177},[1005,2208,1330],{"class":1015},[1005,2210,916],{"class":1032},[1005,2212,1335],{"class":1015},[1005,2214,1033],{"class":1032},[1005,2216,2217],{"class":1007,"line":1062},[1005,2218,1342],{"class":1187},[1005,2220,2221,2223,2225,2227,2229,2231,2233,2235],{"class":1007,"line":1068},[1005,2222,1347],{"class":1015},[1005,2224,916],{"class":1032},[1005,2226,5],{"class":1076},[1005,2228,1354],{"class":1032},[1005,2230,1357],{"class":1015},[1005,2232,1178],{"class":1360},[1005,2234,1364],{"class":1363},[1005,2236,1367],{"class":1032},[1005,2238,2239],{"class":1007,"line":1089},[1005,2240,1372],{"class":1032},[844,2242],{},[847,2244,1378],{"id":1377},[835,2246,1381,2247,1384,2249,1388,2251,1391,2253,1394,2255,1398,2257,1402,2259,1406],{},[839,2248,841],{},[839,2250,1387],{},[839,2252,1251],{},[839,2254,947],{},[839,2256,1397],{},[839,2258,1401],{},[839,2260,1405],{},[1408,2262,1411],{"id":1410},[835,2264,1414,2265,1418,2267,1421],{},[839,2266,1417],{},[839,2268,1251],{},[995,2270,2271],{"className":1424,"code":1425,"language":1426,"meta":1001,"style":1001},[839,2272,2273],{"__ignoreMap":1001},[1005,2274,2275,2277,2279,2281,2283,2285,2287,2289,2291,2293],{"class":1007,"line":1008},[1005,2276,1433],{"class":1076},[1005,2278,1436],{"class":1026},[1005,2280,1439],{"class":1026},[1005,2282,1443],{"class":1442},[1005,2284,1446],{"class":1026},[1005,2286,1019],{"class":1026},[1005,2288,1451],{"class":1177},[1005,2290,1454],{"class":1026},[1005,2292,835],{"class":1032},[1005,2294,1459],{"class":1177},[835,2296,1462],{},[1464,2298,2299],{},[835,2300,1468,2301,1472,2303,1475],{},[839,2302,1471],{},[839,2304,1433],{},[1408,2306,1479],{"id":1478},[835,2308,2309,1485,2311,1488,2313,1492,2315,1496,2317,1500],{},[839,2310,1484],{},[839,2312,1387],{},[839,2314,1491],{},[839,2316,1495],{},[839,2318,1499],{},[995,2320,2321],{"className":997,"code":1503,"language":1000,"meta":1001,"style":1001},[839,2322,2323,2347,2369,2373,2377,2413,2457,2461,2465],{"__ignoreMap":1001},[1005,2324,2325,2327,2329,2331,2333,2335,2337,2339,2341,2343,2345],{"class":1007,"line":1008},[1005,2326,1012],{"class":1011},[1005,2328,1041],{"class":1032},[1005,2330,1484],{"class":1015},[1005,2332,955],{"class":1032},[1005,2334,1491],{"class":1015},[1005,2336,1047],{"class":1032},[1005,2338,1050],{"class":1011},[1005,2340,1023],{"class":1022},[1005,2342,1055],{"class":1026},[1005,2344,1029],{"class":1022},[1005,2346,1033],{"class":1032},[1005,2348,2349,2351,2353,2355,2357,2359,2361,2363,2365,2367],{"class":1007,"line":1036},[1005,2350,1012],{"class":1011},[1005,2352,1536],{"class":1011},[1005,2354,1041],{"class":1032},[1005,2356,1541],{"class":1015},[1005,2358,1047],{"class":1032},[1005,2360,1050],{"class":1011},[1005,2362,1023],{"class":1022},[1005,2364,1055],{"class":1026},[1005,2366,1029],{"class":1022},[1005,2368,1033],{"class":1032},[1005,2370,2371],{"class":1007,"line":1062},[1005,2372,1065],{"emptyLinePlaceholder":8},[1005,2374,2375],{"class":1007,"line":1068},[1005,2376,1562],{"class":1187},[1005,2378,2379,2381,2383,2385,2387,2389,2391,2393,2395,2397,2399,2401,2403,2405,2407,2409,2411],{"class":1007,"line":1089},[1005,2380,1567],{"class":1161},[1005,2382,1570],{"class":1316},[1005,2384,1178],{"class":1177},[1005,2386,1575],{"class":1165},[1005,2388,1578],{"class":1177},[1005,2390,1041],{"class":1032},[1005,2392,1583],{"class":1015},[1005,2394,1178],{"class":1360},[1005,2396,1588],{"class":1442},[1005,2398,955],{"class":1032},[1005,2400,1593],{"class":1015},[1005,2402,1178],{"class":1360},[1005,2404,1598],{"class":1032},[1005,2406,1029],{"class":1022},[1005,2408,1603],{"class":1026},[1005,2410,1029],{"class":1022},[1005,2412,1608],{"class":1032},[1005,2414,2415,2417,2419,2421,2423,2425,2427,2429,2431,2433,2435,2437,2439,2441,2443,2445,2447,2449,2451,2453,2455],{"class":1007,"line":1237},[1005,2416,1613],{"class":1011},[1005,2418,1616],{"class":1076},[1005,2420,1080],{"class":1032},[1005,2422,1029],{"class":1022},[1005,2424,1623],{"class":1026},[1005,2426,1029],{"class":1022},[1005,2428,955],{"class":1032},[1005,2430,1029],{"class":1022},[1005,2432,1632],{"class":1026},[1005,2434,1029],{"class":1022},[1005,2436,955],{"class":1032},[1005,2438,1029],{"class":1022},[1005,2440,1641],{"class":1026},[1005,2442,1029],{"class":1022},[1005,2444,955],{"class":1032},[1005,2446,1029],{"class":1022},[1005,2448,1650],{"class":1026},[1005,2450,1029],{"class":1022},[1005,2452,955],{"class":1032},[1005,2454,1657],{"class":1015},[1005,2456,1660],{"class":1032},[1005,2458,2459],{"class":1007,"line":1663},[1005,2460,1065],{"emptyLinePlaceholder":8},[1005,2462,2463],{"class":1007,"line":1668},[1005,2464,1671],{"class":1187},[1005,2466,2467,2469,2471,2473,2475,2477,2479,2481,2483],{"class":1007,"line":1674},[1005,2468,1613],{"class":1011},[1005,2470,1679],{"class":1076},[1005,2472,1080],{"class":1032},[1005,2474,1259],{"class":1363},[1005,2476,955],{"class":1032},[1005,2478,1029],{"class":1022},[1005,2480,1623],{"class":1026},[1005,2482,1029],{"class":1022},[1005,2484,1660],{"class":1032},[844,2486],{},[847,2488,1699],{"id":1698},[835,2490,1702,2491,1705,2493,1708,2495,1711,2497,1714,2499,912,2501,916],{},[839,2492,1499],{},[839,2494,947],{},[839,2496,1495],{},[839,2498,1405],{},[839,2500,1401],{},[839,2502,1719],{},[1721,2504,2505],{},[835,2506,1725,2507,1729,2509,1733],{},[839,2508,1728],{},[839,2510,1732],{},[995,2512,2513],{"className":997,"code":1736,"filename":999,"language":1000,"meta":1001,"style":1001},[839,2514,2515,2543,2547,2591,2599,2603],{"__ignoreMap":1001},[1005,2516,2517,2519,2521,2523,2525,2527,2529,2531,2533,2535,2537,2539,2541],{"class":1007,"line":1008},[1005,2518,1012],{"class":1011},[1005,2520,1041],{"class":1032},[1005,2522,1747],{"class":1015},[1005,2524,955],{"class":1032},[1005,2526,1752],{"class":1015},[1005,2528,955],{"class":1032},[1005,2530,1044],{"class":1015},[1005,2532,1047],{"class":1032},[1005,2534,1050],{"class":1011},[1005,2536,1023],{"class":1022},[1005,2538,1055],{"class":1026},[1005,2540,1029],{"class":1022},[1005,2542,1033],{"class":1032},[1005,2544,2545],{"class":1007,"line":1036},[1005,2546,1065],{"emptyLinePlaceholder":8},[1005,2548,2549,2551,2553,2555,2557,2559,2561,2563,2565,2567,2569,2571,2573,2575,2577,2579,2581,2583,2585,2587,2589],{"class":1007,"line":1062},[1005,2550,1613],{"class":1011},[1005,2552,1779],{"class":1076},[1005,2554,1354],{"class":1032},[1005,2556,1784],{"class":1015},[1005,2558,1178],{"class":1360},[1005,2560,1041],{"class":1032},[1005,2562,1791],{"class":1015},[1005,2564,1178],{"class":1360},[1005,2566,1041],{"class":1032},[1005,2568,1798],{"class":1015},[1005,2570,1178],{"class":1360},[1005,2572,1023],{"class":1022},[1005,2574,1805],{"class":1026},[1005,2576,1029],{"class":1022},[1005,2578,955],{"class":1032},[1005,2580,1812],{"class":1015},[1005,2582,1178],{"class":1360},[1005,2584,1023],{"class":1022},[1005,2586,1819],{"class":1026},[1005,2588,1029],{"class":1022},[1005,2590,1824],{"class":1032},[1005,2592,2593,2595,2597],{"class":1007,"line":1068},[1005,2594,1613],{"class":1011},[1005,2596,1831],{"class":1076},[1005,2598,1834],{"class":1032},[1005,2600,2601],{"class":1007,"line":1089},[1005,2602,1065],{"emptyLinePlaceholder":8},[1005,2604,2605,2607,2609,2611,2613,2615],{"class":1007,"line":1237},[1005,2606,1071],{"class":1015},[1005,2608,916],{"class":1032},[1005,2610,1077],{"class":1076},[1005,2612,1080],{"class":1032},[1005,2614,1044],{"class":1076},[1005,2616,1086],{"class":1032},[1854,2618,1856],{},{"title":1001,"searchDepth":1036,"depth":1036,"links":2620},[2621,2622,2623,2624,2625,2626,2630],{"id":849,"depth":1036,"text":850},{"id":903,"depth":1036,"text":904},{"id":936,"depth":1036,"text":937},{"id":1106,"depth":1036,"text":1107},{"id":1140,"depth":1036,"text":1141},{"id":1377,"depth":1036,"text":1378,"children":2627},[2628,2629],{"id":1410,"depth":1062,"text":1411},{"id":1478,"depth":1062,"text":1479},{"id":1698,"depth":1036,"text":1699},{},{"title":38,"description":1869},1780436274841]