@niksbanna/bot-detector 1.0.2 → 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -0
- package/dist/bot-detector.cjs.js +115 -74
- package/dist/bot-detector.cjs.js.map +2 -2
- package/dist/bot-detector.esm.js +115 -74
- package/dist/bot-detector.esm.js.map +2 -2
- package/dist/bot-detector.iife.js +115 -74
- package/dist/bot-detector.iife.js.map +2 -2
- package/dist/bot-detector.iife.min.js +3 -1
- package/package.json +1 -1
- package/src/core/BotDetector.js +29 -18
- package/src/core/ScoringEngine.js +3 -2
- package/src/index.js +18 -10
- package/src/signals/automation/PhantomJSSignal.js +2 -4
- package/src/signals/automation/PlaywrightSignal.js +0 -6
- package/src/signals/automation/PuppeteerSignal.js +20 -19
- package/src/signals/automation/SeleniumSignal.js +2 -5
- package/src/signals/behavior/KeyboardPatternSignal.js +1 -1
- package/src/signals/behavior/MouseMovementSignal.js +1 -1
- package/src/signals/behavior/ScrollBehaviorSignal.js +1 -1
- package/src/signals/environment/HeadlessSignal.js +4 -4
- package/src/signals/environment/NavigatorAnomalySignal.js +3 -2
- package/src/signals/timing/DOMContentTimingSignal.js +2 -1
- package/src/signals/timing/PageLoadSignal.js +38 -20
|
@@ -1 +1,3 @@
|
|
|
1
|
-
var BotDetectorLib=(()=>{var $=Object.defineProperty;var nt=Object.getOwnPropertyDescriptor;var st=Object.getOwnPropertyNames;var ot=Object.prototype.hasOwnProperty;var at=(g,e,t)=>e in g?$(g,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):g[e]=t;var rt=(g,e)=>{for(var t in e)$(g,t,{get:e[t],enumerable:!0})},ct=(g,e,t,n)=>{if(e&&typeof e=="object"||typeof e=="function")for(let i of st(e))!ot.call(g,i)&&i!==t&&$(g,i,{get:()=>e[i],enumerable:!(n=nt(e,i))||n.enumerable});return g};var ht=g=>ct($({},"__esModule",{value:!0}),g);var o=(g,e,t)=>at(g,typeof e!="symbol"?e+"":e,t);var dt={};rt(dt,{AudioContextSignal:()=>I,BotDetector:()=>W,CanvasSignal:()=>P,DOMContentTimingSignal:()=>O,HeadlessSignal:()=>C,InteractionTimingSignal:()=>b,KeyboardPatternSignal:()=>M,MouseMovementSignal:()=>y,NavigatorAnomalySignal:()=>R,PageLoadSignal:()=>A,PermissionsSignal:()=>D,PhantomJSSignal:()=>q,PlaywrightSignal:()=>V,PluginsSignal:()=>S,PuppeteerSignal:()=>B,ScoringEngine:()=>U,ScreenSignal:()=>H,ScrollBehaviorSignal:()=>T,SeleniumSignal:()=>K,Signal:()=>m,Signals:()=>tt,Verdict:()=>z,VerdictEngine:()=>N,WebDriverSignal:()=>E,WebGLSignal:()=>L,createDetector:()=>Z,default:()=>ut,defaultInstantSignals:()=>G,defaultInteractionSignals:()=>Q,defaultSignals:()=>lt,detect:()=>J,detectInstant:()=>et});var m=class{constructor(e={}){this.options=e,this._lastResult=null}get id(){return this.constructor.id}get category(){return this.constructor.category}get weight(){var e;return(e=this.options.weight)!=null?e:this.constructor.weight}get description(){return this.constructor.description}get requiresInteraction(){return this.constructor.requiresInteraction}get lastResult(){return this._lastResult}async detect(){throw new Error(`Signal.detect() must be implemented by ${this.constructor.name}`)}async run(){try{return this._lastResult=await this.detect(),this._lastResult}catch(e){return this._lastResult={triggered:!1,value:null,confidence:0,error:e.message},this._lastResult}}reset(){this._lastResult=null}createResult(e,t=null,n=1){return{triggered:!!e,value:t,confidence:Math.max(0,Math.min(1,n))}}};o(m,"id","base-signal"),o(m,"category","uncategorized"),o(m,"weight",.5),o(m,"description","Base signal class"),o(m,"requiresInteraction",!1);var U=class{constructor(e={}){this.weightOverrides=e.weightOverrides||{},this.maxScore=e.maxScore||100,this._results=new Map}getWeight(e,t){var n;return(n=this.weightOverrides[e])!=null?n:t}addResult(e,t,n){let i=this.getWeight(e,n);this._results.set(e,{...t,weight:i,contribution:t.triggered?i*t.confidence:0})}calculate(){if(this._results.size===0)return 0;let e=0,t=0;for(let[,i]of this._results)e+=i.weight,t+=i.contribution;if(e===0)return 0;let n=t/e*this.maxScore;return Math.round(n*100)/100}getBreakdown(){let e=[];for(let[t,n]of this._results)e.push({signalId:t,triggered:n.triggered,confidence:n.confidence,weight:n.weight,contribution:n.contribution,percentOfScore:this.calculate()>0?(n.contribution/this.calculate()*100).toFixed(1):"0.0"});return e.sort((t,n)=>n.contribution-t.contribution)}getTriggeredSignals(){let e=[];for(let[t,n]of this._results)n.triggered&&e.push(t);return e}getTriggeredCount(){let e=0;for(let[,t]of this._results)t.triggered&&e++;return e}reset(){this._results.clear()}};var z={HUMAN:"human",SUSPICIOUS:"suspicious",BOT:"bot"},F=class F{constructor(e={}){var t,n;this.humanThreshold=(t=e.humanThreshold)!=null?t:F.DEFAULT_THRESHOLDS.human,this.suspiciousThreshold=(n=e.suspiciousThreshold)!=null?n:F.DEFAULT_THRESHOLDS.suspicious,this.instantBotSignals=new Set(e.instantBotSignals||[])}getVerdict(e,t=[]){for(let c of t)if(this.instantBotSignals.has(c))return{verdict:z.BOT,score:e,confidence:"high",reason:`Instant bot signal triggered: ${c}`,triggeredCount:t.length};let n,i,s;return e<this.humanThreshold?(n=z.HUMAN,i=e<10?"high":"medium",s="Low bot score"):e<this.suspiciousThreshold?(n=z.SUSPICIOUS,i="medium",s="Moderate bot indicators detected"):(n=z.BOT,i=e>=75?"high":"medium",s="High accumulation of bot indicators"),{verdict:n,score:e,confidence:i,reason:s,triggeredCount:t.length}}isInstantBotSignal(e){return this.instantBotSignals.has(e)}addInstantBotSignal(e){this.instantBotSignals.add(e)}setThresholds(e){e.human!==void 0&&(this.humanThreshold=e.human),e.suspicious!==void 0&&(this.suspiciousThreshold=e.suspicious)}};o(F,"DEFAULT_THRESHOLDS",{human:20,suspicious:50});var N=F;var W=class g{constructor(e={}){if(this.options=e,this._signals=new Map,this._scoringEngine=new U({weightOverrides:e.weightOverrides}),this._verdictEngine=new N({humanThreshold:e.humanThreshold,suspiciousThreshold:e.suspiciousThreshold,instantBotSignals:e.instantBotSignals}),this._lastDetection=null,this._detectionTimeout=e.detectionTimeout||5e3,this._isRunning=!1,e.signals)for(let t of e.signals)this.registerSignal(t)}registerSignal(e){if(!(e instanceof m))throw new Error("Signal must be an instance of Signal class");let t=e.id;if(this._signals.has(t))throw new Error(`Signal with ID "${t}" is already registered`);return this._signals.set(t,e),this}registerSignals(e){for(let t of e)this.registerSignal(t);return this}unregisterSignal(e){return this._signals.delete(e)}getSignal(e){return this._signals.get(e)}getSignals(){return Array.from(this._signals.values())}getSignalsByCategory(e){return this.getSignals().filter(t=>t.category===e)}async detect(e={}){if(this._isRunning)throw new Error("Detection is already running");this._isRunning=!0,this._scoringEngine.reset();let t=performance.now(),n=new Map;try{let i=this.getSignals().filter(u=>!(e.skipInteractionSignals&&u.requiresInteraction)),s=i.map(async u=>{let l=await Promise.race([u.run(),new Promise(h=>setTimeout(()=>h({triggered:!1,value:null,confidence:0,error:"timeout"}),this._detectionTimeout))]);return{signal:u,result:l}}),c=await Promise.all(s);for(let{signal:u,result:l}of c)n.set(u.id,{...l,category:u.category,weight:u.weight,description:u.description}),this._scoringEngine.addResult(u.id,l,u.weight);let r=this._scoringEngine.calculate(),a=this._scoringEngine.getTriggeredSignals(),p=this._verdictEngine.getVerdict(r,a),d=performance.now()-t;return this._lastDetection={...p,signals:Object.fromEntries(n),breakdown:this._scoringEngine.getBreakdown(),timestamp:Date.now(),detectionTimeMs:Math.round(d),totalSignals:i.length,triggeredSignals:a},this._lastDetection}finally{this._isRunning=!1}}getLastDetection(){return this._lastDetection}getScore(){var e,t;return(t=(e=this._lastDetection)==null?void 0:e.score)!=null?t:0}getTriggeredSignals(){var e,t;return(t=(e=this._lastDetection)==null?void 0:e.triggeredSignals)!=null?t:[]}isRunning(){return this._isRunning}reset(){this._scoringEngine.reset(),this._lastDetection=null;for(let e of this._signals.values())e.reset()}configure(e){(e.humanThreshold!==void 0||e.suspiciousThreshold!==void 0)&&this._verdictEngine.setThresholds({human:e.humanThreshold,suspicious:e.suspiciousThreshold}),e.detectionTimeout!==void 0&&(this._detectionTimeout=e.detectionTimeout)}static withDefaults(e={}){return new g(e)}};var E=class extends m{async detect(){if(navigator.webdriver===!0)return this.createResult(!0,{webdriver:!0},1);let e=Object.getOwnPropertyDescriptor(navigator,"webdriver");if(e&&(e.get||!e.configurable))return this.createResult(!0,{webdriver:"modified",descriptor:{configurable:e.configurable,enumerable:e.enumerable,hasGetter:!!e.get}},.8);try{let t=Object.getPrototypeOf(navigator),n=Object.getOwnPropertyDescriptor(t,"webdriver");if(n&&n.get&&n.get.call(navigator)===!0)return this.createResult(!0,{webdriver:!0,source:"prototype"},1)}catch{}return this.createResult(!1)}};o(E,"id","webdriver"),o(E,"category","environment"),o(E,"weight",1),o(E,"description","Detects navigator.webdriver automation flag");var C=class extends m{async detect(){let e=[],t=0,n=navigator.userAgent||"";n.includes("HeadlessChrome")&&(e.push("headless-ua"),t=Math.max(t,1)),n.includes("Chrome")&&!n.includes("Chromium")&&(typeof window.chrome>"u"?(e.push("missing-chrome-object"),t=Math.max(t,.6)):window.chrome.runtime||(e.push("missing-chrome-runtime"),t=Math.max(t,.4))),navigator.plugins&&navigator.plugins.length===0&&(e.push("no-plugins"),t=Math.max(t,.5)),(!navigator.languages||navigator.languages.length===0)&&(e.push("no-languages"),t=Math.max(t,.6)),window.outerWidth===0&&window.outerHeight===0&&(e.push("zero-outer-dimensions"),t=Math.max(t,.7)),typeof navigator.connection>"u"&&n.includes("Chrome")&&(e.push("missing-connection-api"),t=Math.max(t,.3));try{typeof Notification<"u"&&Notification.permission==="denied"&&window.outerWidth===0&&(e.push("notification-headless-pattern"),t=Math.max(t,.5))}catch{}(window.callPhantom||window._phantom)&&(e.push("phantomjs"),t=Math.max(t,1)),window.__nightmare&&(e.push("nightmare"),t=Math.max(t,1));let i=e.length>0;return e.length>=3&&(t=Math.min(1,t+.2)),this.createResult(i,{indicators:e},t)}};o(C,"id","headless"),o(C,"category","environment"),o(C,"weight",.8),o(C,"description","Detects headless browser indicators");var R=class extends m{async detect(){let e=[],t=0,n=0,i=navigator.userAgent||"",s=navigator.platform||"";n++,s.includes("Win")&&!i.includes("Windows")?(e.push("platform-ua-mismatch-windows"),t+=1):s.includes("Mac")&&!i.includes("Mac")?(e.push("platform-ua-mismatch-mac"),t+=1):s.includes("Linux")&&!i.includes("Linux")&&!i.includes("Android")&&(e.push("platform-ua-mismatch-linux"),t+=1),n++,(!s||s===""||s==="undefined")&&(e.push("empty-platform"),t+=1),n++,navigator.language&&navigator.languages&&(navigator.languages.includes(navigator.language)||(e.push("language-mismatch"),t+=.5)),n++,i.includes("Chrome")&&navigator.vendor!=="Google Inc."?(e.push("vendor-mismatch-chrome"),t+=.5):i.includes("Firefox")&&navigator.vendor!==""?(e.push("vendor-mismatch-firefox"),t+=.5):i.includes("Safari")&&!i.includes("Chrome")&&navigator.vendor!=="Apple Computer, Inc."&&(e.push("vendor-mismatch-safari"),t+=.5),n++,typeof navigator.hardwareConcurrency<"u"&&(navigator.hardwareConcurrency===0||navigator.hardwareConcurrency>128)&&(e.push("suspicious-hardware-concurrency"),t+=.5),n++,typeof navigator.deviceMemory<"u"&&(navigator.deviceMemory===0||navigator.deviceMemory>512)&&(e.push("suspicious-device-memory"),t+=.5),n++;let c=/Android|iPhone|iPad|iPod|Mobile/i.test(i),r=navigator.maxTouchPoints>0;!c&&navigator.maxTouchPoints>5&&(e.push("desktop-high-touch-points"),t+=.3),n++;try{let d=Object.getOwnPropertyDescriptor(Navigator.prototype,"userAgent");d&&d.get&&d.get.toString().includes("native code")===!1&&(e.push("spoofed-user-agent"),t+=1)}catch{}let a=e.length>0,p=Math.min(1,t/Math.max(1,n));return this.createResult(a,{anomalies:e},p)}};o(R,"id","navigator-anomaly"),o(R,"category","environment"),o(R,"weight",.7),o(R,"description","Detects navigator property inconsistencies");var D=class extends m{async detect(){let e=[];if(!navigator.permissions)return this.createResult(!1,{supported:!1},0);try{let i=await navigator.permissions.query({name:"notifications"});if(typeof Notification<"u"){let s=Notification.permission;(s==="granted"&&i.state!=="granted"||s==="denied"&&i.state!=="denied"||s==="default"&&i.state!=="prompt")&&e.push("notification-permission-mismatch")}try{(await navigator.permissions.query({name:"geolocation"})).state==="denied"&&window.outerWidth===0&&e.push("geo-denied-headless")}catch{}try{await navigator.permissions.query({name:"camera"})}catch(s){s.name==="TypeError"&&e.push("camera-permission-error")}}catch(i){i.name!=="TypeError"&&e.push("permissions-query-error")}let t=e.length>0,n=Math.min(1,e.length*.4);return this.createResult(t,{anomalies:e},n)}};o(D,"id","permissions"),o(D,"category","environment"),o(D,"weight",.5),o(D,"description","Detects Permissions API anomalies");var y=class extends m{constructor(e={}){super(e),this._movements=[],this._isTracking=!1,this._trackingDuration=e.trackingDuration||3e3,this._minMovements=e.minMovements||5,this._boundHandler=null}startTracking(){this._isTracking||(this._movements=[],this._isTracking=!0,this._boundHandler=e=>{this._movements.push({x:e.clientX,y:e.clientY,t:performance.now()})},document.addEventListener("mousemove",this._boundHandler,{passive:!0}))}stopTracking(){this._isTracking&&(this._isTracking=!1,this._boundHandler&&(document.removeEventListener("mousemove",this._boundHandler),this._boundHandler=null))}async detect(){let e=[],t=0;this._movements.length===0&&(this.startTracking(),await new Promise(c=>setTimeout(c,this._trackingDuration)),this.stopTracking());let n=this._movements;if(n.length<this._minMovements)return e.push("no-mouse-movement"),t=Math.max(t,.6),this.createResult(!0,{anomalies:e,movements:n.length},t);let i=this._analyzeMovements(n);i.teleportCount>0&&(e.push("mouse-teleportation"),t=Math.max(t,.7)),i.linearPathRatio>.9&&(e.push("linear-path"),t=Math.max(t,.8)),i.velocityVariance<.01&&n.length>10&&(e.push("constant-velocity"),t=Math.max(t,.7)),i.accelerationChanges===0&&n.length>10&&(e.push("no-acceleration-variance"),t=Math.max(t,.6)),i.timingVariance<1&&n.length>10&&(e.push("robotic-timing"),t=Math.max(t,.8));let s=e.length>0;return this.createResult(s,{anomalies:e,movementCount:n.length,analysis:i},t)}_analyzeMovements(e){if(e.length<3)return{teleportCount:0,linearPathRatio:0,velocityVariance:0,accelerationChanges:0,timingVariance:0};let t=0,n=[],i=[],s=[];for(let l=1;l<e.length;l++){let h=e[l-1],f=e[l],x=f.x-h.x,_=f.y-h.y,w=f.t-h.t;if(w===0)continue;let k=Math.sqrt(x*x+_*_),v=k/w;n.push(v),i.push(Math.atan2(_,x)),s.push(w),k>300&&w<10&&t++}let c=n.reduce((l,h)=>l+h,0)/n.length,r=n.reduce((l,h)=>l+Math.pow(h-c,2),0)/n.length,a=0;if(i.length>1){let l=0;for(let h=1;h<i.length;h++)Math.abs(i[h]-i[h-1])<.1&&l++;a=l/(i.length-1)}let p=s.reduce((l,h)=>l+h,0)/s.length,d=s.reduce((l,h)=>l+Math.pow(h-p,2),0)/s.length,u=0;for(let l=1;l<n.length;l++)(n[l]-n[l-1])*(n[l-1]-(n[l-2]||0))<0&&u++;return{teleportCount:t,linearPathRatio:a,velocityVariance:r,accelerationChanges:u,timingVariance:d}}reset(){super.reset(),this.stopTracking(),this._movements=[]}};o(y,"id","mouse-movement"),o(y,"category","behavior"),o(y,"weight",.9),o(y,"description","Detects non-human mouse movement patterns"),o(y,"requiresInteraction",!0);var M=class extends m{constructor(e={}){super(e),this._keystrokes=[],this._isTracking=!1,this._trackingDuration=e.trackingDuration||5e3,this._minKeystrokes=e.minKeystrokes||10,this._boundKeydownHandler=null,this._boundKeyupHandler=null}startTracking(){this._isTracking||(this._keystrokes=[],this._isTracking=!0,this._boundKeydownHandler=e=>{this._keystrokes.push({type:"down",key:e.key,code:e.code,t:performance.now()})},this._boundKeyupHandler=e=>{this._keystrokes.push({type:"up",key:e.key,code:e.code,t:performance.now()})},document.addEventListener("keydown",this._boundKeydownHandler,{passive:!0}),document.addEventListener("keyup",this._boundKeyupHandler,{passive:!0}))}stopTracking(){this._isTracking&&(this._isTracking=!1,this._boundKeydownHandler&&(document.removeEventListener("keydown",this._boundKeydownHandler),this._boundKeydownHandler=null),this._boundKeyupHandler&&(document.removeEventListener("keyup",this._boundKeyupHandler),this._boundKeyupHandler=null))}async detect(){let e=[],t=0;this._keystrokes.length===0&&(this.startTracking(),await new Promise(r=>setTimeout(r,this._trackingDuration)),this.stopTracking());let n=this._keystrokes,i=n.filter(r=>r.type==="down");if(i.length<this._minKeystrokes)return this.createResult(!1,{reason:"insufficient-data",keystrokes:i.length},0);let s=this._analyzeKeystrokes(n);s.avgInterKeystrokeTime<50&&i.length>20&&(e.push("inhuman-speed"),t=Math.max(t,.9)),s.timingVariance<5&&i.length>15&&(e.push("robotic-timing"),t=Math.max(t,.8)),s.missingKeyups>i.length*.5&&(e.push("missing-keyups"),t=Math.max(t,.7)),s.holdTimeVariance<2&&s.holdTimes.length>10&&(e.push("constant-hold-time"),t=Math.max(t,.6)),s.sequentialKeys>i.length*.8&&i.length>10&&(e.push("sequential-input"),t=Math.max(t,.5)),s.rhythmScore<.1&&i.length>20&&(e.push("no-rhythm-variation"),t=Math.max(t,.6));let c=e.length>0;return this.createResult(c,{anomalies:e,keystrokeCount:i.length,analysis:s},t)}_analyzeKeystrokes(e){let t=e.filter(h=>h.type==="down"),n=e.filter(h=>h.type==="up");if(t.length<2)return{avgInterKeystrokeTime:1/0,timingVariance:1/0,missingKeyups:0,holdTimeVariance:1/0,holdTimes:[],sequentialKeys:0,rhythmScore:1};let i=[];for(let h=1;h<t.length;h++)i.push(t[h].t-t[h-1].t);let s=i.reduce((h,f)=>h+f,0)/i.length,c=i.reduce((h,f)=>h+Math.pow(f-s,2),0)/i.length,r=[];for(let h of t){let f=n.find(x=>x.key===h.key&&x.t>h.t);f&&r.push(f.t-h.t)}let a=r.length>0?r.reduce((h,f)=>h+f,0)/r.length:0,p=r.length>0?r.reduce((h,f)=>h+Math.pow(f-a,2),0)/r.length:1/0,d=t.length-r.length,u=0;for(let h=1;h<t.length;h++){let f=t[h-1].key.charCodeAt(0),x=t[h].key.charCodeAt(0);Math.abs(x-f)===1&&u++}let l=0;if(i.length>5){let h=[...i].sort((_,w)=>_-w),f=h[Math.floor(h.length/2)];l=i.filter(_=>Math.abs(_-f)>f*.3).length/i.length}return{avgInterKeystrokeTime:s,timingVariance:c,missingKeyups:d,holdTimeVariance:p,holdTimes:r,sequentialKeys:u,rhythmScore:l}}reset(){super.reset(),this.stopTracking(),this._keystrokes=[]}};o(M,"id","keyboard-pattern"),o(M,"category","behavior"),o(M,"weight",.8),o(M,"description","Detects non-human keystroke patterns"),o(M,"requiresInteraction",!0);var b=class extends m{constructor(e={}){super(e),this._pageLoadTime=performance.now(),this._firstInteractionTime=null,this._interactions=[],this._isTracking=!1,this._trackingDuration=e.trackingDuration||5e3,this._boundHandler=null}startTracking(){if(this._isTracking)return;this._interactions=[],this._isTracking=!0;let e=["click","mousedown","touchstart","keydown","scroll"];this._boundHandler=t=>{let n=performance.now();this._firstInteractionTime===null&&(this._firstInteractionTime=n),this._interactions.push({type:t.type,t:n,timeSinceLoad:n-this._pageLoadTime})};for(let t of e)document.addEventListener(t,this._boundHandler,{passive:!0,capture:!0})}stopTracking(){if(!this._isTracking)return;this._isTracking=!1;let e=["click","mousedown","touchstart","keydown","scroll"];if(this._boundHandler){for(let t of e)document.removeEventListener(t,this._boundHandler,{capture:!0});this._boundHandler=null}}async detect(){let e=[],t=0;!this._isTracking&&this._interactions.length===0&&(this.startTracking(),await new Promise(r=>setTimeout(r,this._trackingDuration)),this.stopTracking());let n=this._interactions;if(n.length===0)return this.createResult(!1,{reason:"no-interactions"},0);let i=n[0];if(i.timeSinceLoad<100?(e.push("instant-interaction"),t=Math.max(t,.9)):i.timeSinceLoad<300&&(e.push("very-fast-interaction"),t=Math.max(t,.6)),n.length>3){let r=[];for(let l=1;l<n.length;l++)r.push(n[l].t-n[l-1].t);let a=r.reduce((l,h)=>l+h,0)/r.length;r.reduce((l,h)=>l+Math.pow(h-a,2),0)/r.length<10&&n.length>5&&(e.push("robotic-intervals"),t=Math.max(t,.8));let d=50,u=0;for(let l of r)l<d&&u++;u>r.length*.7&&(e.push("burst-interactions"),t=Math.max(t,.7))}let s=n.map(r=>r.type).join(",");if(n.length>=6){let r=Math.floor(n.length/2),a=n.slice(0,r).map(d=>d.type).join(","),p=n.slice(r,r*2).map(d=>d.type).join(",");a===p&&a.length>0&&(e.push("repeated-sequence"),t=Math.max(t,.6))}let c=e.length>0;return this.createResult(c,{anomalies:e,interactionCount:n.length,timeToFirstInteraction:i.timeSinceLoad,firstInteractionType:i.type},t)}reset(){super.reset(),this.stopTracking(),this._pageLoadTime=performance.now(),this._firstInteractionTime=null,this._interactions=[]}};o(b,"id","interaction-timing"),o(b,"category","behavior"),o(b,"weight",.6),o(b,"description","Detects suspicious interaction timing"),o(b,"requiresInteraction",!0);var T=class extends m{constructor(e={}){super(e),this._scrollEvents=[],this._isTracking=!1,this._trackingDuration=e.trackingDuration||3e3,this._boundHandler=null}startTracking(){this._isTracking||(this._scrollEvents=[],this._isTracking=!0,this._boundHandler=()=>{this._scrollEvents.push({scrollY:window.scrollY,scrollX:window.scrollX,t:performance.now()})},window.addEventListener("scroll",this._boundHandler,{passive:!0}))}stopTracking(){this._isTracking&&(this._isTracking=!1,this._boundHandler&&(window.removeEventListener("scroll",this._boundHandler),this._boundHandler=null))}async detect(){let e=[],t=0;this._scrollEvents.length===0&&(this.startTracking(),await new Promise(c=>setTimeout(c,this._trackingDuration)),this.stopTracking());let n=this._scrollEvents;if(n.length<3)return this.createResult(!1,{reason:"insufficient-scroll-data",scrollEvents:n.length},0);let i=this._analyzeScrollPatterns(n);i.instantJumps>0&&(e.push("instant-scroll-jumps"),t=Math.max(t,.7)),i.velocityVariance<.1&&n.length>10&&(e.push("constant-scroll-velocity"),t=Math.max(t,.6)),i.momentumEvents===0&&n.length>5&&(e.push("no-scroll-momentum"),t=Math.max(t,.5)),i.intervalVariance<5&&n.length>10&&(e.push("robotic-scroll-timing"),t=Math.max(t,.7)),i.scrollDirections===1&&Math.abs(i.totalScrollY)>1e3&&i.velocityVariance<1&&(e.push("one-dimensional-scroll"),t=Math.max(t,.4)),i.exactPositionScrolls>2&&(e.push("exact-position-scrolls"),t=Math.max(t,.6));let s=e.length>0;return this.createResult(s,{anomalies:e,scrollEventCount:n.length,analysis:i},t)}_analyzeScrollPatterns(e){if(e.length<2)return{instantJumps:0,velocityVariance:0,momentumEvents:0,intervalVariance:0,scrollDirections:0,totalScrollY:0,exactPositionScrolls:0};let t=0,n=0,i=[],s=[],c=!1,r=!1,a=0,p=[0,100,200,300,400,500,600,800,1e3];for(let _=1;_<e.length;_++){let w=e[_-1],k=e[_],v=k.scrollY-w.scrollY,j=k.scrollX-w.scrollX,Y=k.t-w.t;if(s.push(Y),Math.abs(v)>0&&(c=!0),Math.abs(j)>0&&(r=!0),Y===0)continue;let X=Math.sqrt(v*v+j*j)/Y;if(i.push(X),Math.abs(v)+Math.abs(j)>200&&Y<20&&t++,_>1&&i.length>1){let it=i[i.length-2];X<it*.9&&X>0&&n++}p.includes(Math.round(k.scrollY))&&a++}let d=i.length>0?i.reduce((_,w)=>_+w,0)/i.length:0,u=i.length>0?i.reduce((_,w)=>_+Math.pow(w-d,2),0)/i.length:0,l=s.reduce((_,w)=>_+w,0)/s.length,h=s.reduce((_,w)=>_+Math.pow(w-l,2),0)/s.length,f=0;c&&f++,r&&f++;let x=e[e.length-1].scrollY-e[0].scrollY;return{instantJumps:t,velocityVariance:u,momentumEvents:n,intervalVariance:h,scrollDirections:f,totalScrollY:x,exactPositionScrolls:a}}reset(){super.reset(),this.stopTracking(),this._scrollEvents=[]}};o(T,"id","scroll-behavior"),o(T,"category","behavior"),o(T,"weight",.5),o(T,"description","Detects programmatic scroll patterns"),o(T,"requiresInteraction",!0);var S=class extends m{async detect(){let e=[],t=0,n=navigator.plugins,i=navigator.mimeTypes;if(!n)return e.push("no-plugins-object"),t=Math.max(t,.6),this.createResult(!0,{anomalies:e},t);n.length===0&&(e.push("empty-plugins"),t=Math.max(t,.5));let s=navigator.userAgent||"";if(s.includes("Chrome")&&!s.includes("Chromium")&&!Array.from(n).some(p=>p.name.includes("PDF")||p.name.includes("Chromium PDF"))&&n.length===0&&(e.push("chrome-missing-pdf-plugin"),t=Math.max(t,.4)),n.length>0&&i){let a=0;for(let p=0;p<n.length;p++)a+=n[p].length||0;i.length===0&&a>0&&(e.push("mimetypes-mismatch"),t=Math.max(t,.5))}if(n.length>1){let a=Array.from(n).map(d=>d.name);new Set(a).size<a.length&&(e.push("duplicate-plugins"),t=Math.max(t,.6))}try{let a=Object.getOwnPropertyDescriptor(Navigator.prototype,"plugins");a&&a.get&&(a.get.toString().includes("[native code]")||(e.push("plugins-getter-overridden"),t=Math.max(t,.7)))}catch{}!/Android|iPhone|iPad|iPod|Mobile/i.test(s)&&n.length===1&&(e.push("minimal-plugins"),t=Math.max(t,.3));let r=e.length>0;return this.createResult(r,{anomalies:e,pluginCount:n.length,mimeTypeCount:(i==null?void 0:i.length)||0},t)}};o(S,"id","plugins"),o(S,"category","fingerprint"),o(S,"weight",.6),o(S,"description","Detects browser plugin anomalies");var L=class extends m{async detect(){let e=[],t=0,n=document.createElement("canvas"),i=null;try{i=n.getContext("webgl")||n.getContext("experimental-webgl")}catch{e.push("webgl-error"),t=Math.max(t,.5)}if(!i)return e.push("webgl-unavailable"),t=Math.max(t,.4),this.createResult(!0,{anomalies:e},t);let s=i.getExtension("WEBGL_debug_renderer_info"),c="",r="";s&&(c=i.getParameter(s.UNMASKED_VENDOR_WEBGL)||"",r=i.getParameter(s.UNMASKED_RENDERER_WEBGL)||""),!c&&!r&&(e.push("no-webgl-renderer-info"),t=Math.max(t,.6));let a=["swiftshader","llvmpipe","software","mesa","google swiftshader","vmware","virtualbox"],p=r.toLowerCase();for(let x of a)if(p.includes(x)){e.push(`suspicious-renderer-${x.replace(/\s+/g,"-")}`),t=Math.max(t,.7);break}c&&r&&(p.includes("nvidia")&&!c.toLowerCase().includes("nvidia")&&(e.push("vendor-renderer-mismatch"),t=Math.max(t,.6)),(p.includes("amd")||p.includes("radeon"))&&!c.toLowerCase().includes("amd")&&!c.toLowerCase().includes("ati")&&(e.push("vendor-renderer-mismatch"),t=Math.max(t,.6)));let d=i.getSupportedExtensions()||[];d.length<5&&(e.push("few-webgl-extensions"),t=Math.max(t,.4));let u=i.getParameter(i.MAX_TEXTURE_SIZE),l=i.getParameter(i.MAX_VIEWPORT_DIMS);(u<1024||u>65536)&&(e.push("unrealistic-max-texture"),t=Math.max(t,.5));try{i.clearColor(0,0,0,1),i.clear(i.COLOR_BUFFER_BIT);let x=new Uint8Array(4);i.readPixels(0,0,1,1,i.RGBA,i.UNSIGNED_BYTE,x),x[3]!==255&&(e.push("webgl-render-failure"),t=Math.max(t,.6))}catch{e.push("webgl-render-error"),t=Math.max(t,.5)}let h=i.getExtension("WEBGL_lose_context");h&&h.loseContext();let f=e.length>0;return this.createResult(f,{anomalies:e,vendor:c,renderer:r,extensionCount:d.length,maxTextureSize:u},t)}};o(L,"id","webgl"),o(L,"category","fingerprint"),o(L,"weight",.7),o(L,"description","Detects WebGL rendering anomalies");var P=class extends m{async detect(){let e=[],t=0;try{let i=document.createElement("canvas");i.width=200,i.height=50;let s=i.getContext("2d");if(!s)return e.push("canvas-context-unavailable"),t=Math.max(t,.5),this.createResult(!0,{anomalies:e},t);s.textBaseline="alphabetic",s.font="14px Arial",s.fillStyle="#f60",s.fillRect(0,0,200,50),s.fillStyle="#069",s.fillText("Bot Detection Test \u{1F916}",2,15),s.fillStyle="rgba(102, 204, 0, 0.7)",s.fillText("Canvas Fingerprint",4,30),s.beginPath(),s.arc(100,25,10,0,Math.PI*2,!0),s.closePath(),s.fill();let c=i.toDataURL();s.clearRect(0,0,200,50),s.fillStyle="#f60",s.fillRect(0,0,200,50),s.fillStyle="#069",s.fillText("Bot Detection Test \u{1F916}",2,15),s.fillStyle="rgba(102, 204, 0, 0.7)",s.fillText("Canvas Fingerprint",4,30),s.beginPath(),s.arc(100,25,10,0,Math.PI*2,!0),s.closePath(),s.fill();let r=i.toDataURL();c!==r&&(e.push("canvas-randomized"),t=Math.max(t,.6)),c.length<1e3&&(e.push("canvas-possibly-blank"),t=Math.max(t,.4));let a=document.createElement("canvas");a.width=200,a.height=50;let p=a.toDataURL();c===p&&(e.push("canvas-rendering-blocked"),t=Math.max(t,.7));try{i.toDataURL.toString().includes("[native code]")||(e.push("toDataURL-overridden"),t=Math.max(t,.8))}catch{}let u=s.getImageData(0,0,200,50).data,l=!0,h=[u[0],u[1],u[2],u[3]];for(let f=4;f<u.length;f+=4)if(u[f]!==h[0]||u[f+1]!==h[1]||u[f+2]!==h[2]){l=!1;break}l&&(e.push("uniform-pixel-data"),t=Math.max(t,.6))}catch{e.push("canvas-error"),t=Math.max(t,.4)}let n=e.length>0;return this.createResult(n,{anomalies:e},t)}};o(P,"id","canvas"),o(P,"category","fingerprint"),o(P,"weight",.5),o(P,"description","Detects canvas fingerprint anomalies");var I=class extends m{async detect(){let e=[],t=0,n=window.AudioContext||window.webkitAudioContext;if(!n)return e.push("audio-context-unavailable"),t=Math.max(t,.4),this.createResult(!0,{anomalies:e},t);let i=null,s=null,c=null;try{i=new n;let a=i.sampleRate;if(a!==44100&&a!==48e3&&a!==96e3&&(e.push("unusual-sample-rate"),t=Math.max(t,.3)),s=i.createOscillator(),c=i.createAnalyser(),!s||!c)e.push("audio-nodes-unavailable"),t=Math.max(t,.5);else{let d=c.fftSize,u=i.destination;(!u||u.maxChannelCount===0)&&(e.push("no-audio-destination"),t=Math.max(t,.6)),u&&u.maxChannelCount<2&&(e.push("mono-audio-only"),t=Math.max(t,.3))}try{n.toString().includes("[native code]")||(e.push("audio-context-overridden"),t=Math.max(t,.7))}catch{}try{if(i.state==="suspended"&&await i.resume().catch(()=>{}),i.state==="running"){let d=i.createOscillator(),u=i.createGain(),l=i.createScriptProcessor?i.createScriptProcessor(4096,1,1):null;l&&(d.type="triangle",d.frequency.value=1e4,u.gain.value=0,d.connect(u),u.connect(l),l.connect(i.destination),d.start(0),await new Promise(h=>setTimeout(h,50)),d.stop(),d.disconnect(),u.disconnect(),l.disconnect())}}catch{e.push("audio-fingerprint-blocked"),t=Math.max(t,.4)}window.OfflineAudioContext||window.webkitOfflineAudioContext||(e.push("offline-audio-context-unavailable"),t=Math.max(t,.3))}catch{e.push("audio-context-error"),t=Math.max(t,.4)}finally{if(s)try{s.disconnect()}catch{}if(c)try{c.disconnect()}catch{}if(i)try{i.close()}catch{}}let r=e.length>0;return this.createResult(r,{anomalies:e},t)}};o(I,"id","audio-context"),o(I,"category","fingerprint"),o(I,"weight",.5),o(I,"description","Detects AudioContext anomalies");var H=class extends m{async detect(){let e=[],t=0,n=window.screen;if(!n)return e.push("no-screen-object"),t=Math.max(t,.6),this.createResult(!0,{anomalies:e},t);let i=n.width,s=n.height,c=n.availWidth,r=n.availHeight,a=n.colorDepth,p=n.pixelDepth,d=window.outerWidth,u=window.outerHeight,l=window.innerWidth,h=window.innerHeight;(d===0||u===0)&&(e.push("zero-outer-dimensions"),t=Math.max(t,.8)),(l===0||h===0)&&(e.push("zero-inner-dimensions"),t=Math.max(t,.7));let f=navigator.userAgent||"";!/Android|iPhone|iPad|iPod|Mobile/i.test(f)&&(i<640||s<480)&&(e.push("very-small-screen"),t=Math.max(t,.5)),(i>7680||s>4320)&&(e.push("unrealistic-screen-size"),t=Math.max(t,.4));let _=[{w:800,h:600},{w:1024,h:768},{w:1920,h:1080}];for(let v of _)if(i===v.w&&s===v.h&&d===v.w&&u===v.h){e.push("headless-default-dimensions"),t=Math.max(t,.5);break}(c>i||r>s)&&(e.push("available-exceeds-total"),t=Math.max(t,.7)),(d>i||u>s)&&(e.push("window-exceeds-screen"),t=Math.max(t,.6)),a!==24&&a!==32&&a!==30&&a!==48&&(e.push("unusual-color-depth"),t=Math.max(t,.3)),a!==p&&(e.push("depth-mismatch"),t=Math.max(t,.3));let w=window.devicePixelRatio;if(w===0||w===void 0?(e.push("missing-device-pixel-ratio"),t=Math.max(t,.5)):(w<.5||w>5)&&(e.push("unusual-device-pixel-ratio"),t=Math.max(t,.4)),n.orientation){let v=n.orientation.type,j=n.orientation.angle;v.includes("landscape")&&i<s&&(e.push("orientation-dimension-mismatch"),t=Math.max(t,.4)),v.includes("portrait")&&i>s&&(e.push("orientation-dimension-mismatch"),t=Math.max(t,.4))}l===d&&h===u&&d>0&&u>0&&(e.push("no-browser-chrome"),t=Math.max(t,.5));let k=e.length>0;return this.createResult(k,{anomalies:e,dimensions:{screen:{width:i,height:s},available:{width:c,height:r},window:{outer:{width:d,height:u},inner:{width:l,height:h}},colorDepth:a,devicePixelRatio:w}},t)}};o(H,"id","screen"),o(H,"category","fingerprint"),o(H,"weight",.4),o(H,"description","Detects unusual screen dimensions");var A=class extends m{async detect(){let e=[],t=0;if(!window.performance||!performance.timing){if(performance.getEntriesByType){let v=performance.getEntriesByType("navigation");if(v.length>0)return this._analyzeNavigationTiming(v[0])}return e.push("no-performance-api"),t=Math.max(t,.3),this.createResult(!0,{anomalies:e},t)}let n=performance.timing,i=n.navigationStart,s=n.domContentLoadedEventEnd-i,c=n.domComplete-i,r=n.loadEventEnd-i,a=n.domainLookupEnd-n.domainLookupStart,p=n.connectEnd-n.connectStart,d=n.responseEnd-n.requestStart,u=n.domComplete-n.domLoading;s>0&&s<10&&(e.push("instant-dom-content-loaded"),t=Math.max(t,.7)),a===0&&p===0&&d<5&&(e.push("zero-network-timing"),t=Math.max(t,.4)),(s<0||c<0||r<0)&&(e.push("negative-timing"),t=Math.max(t,.8)),n.domContentLoadedEventEnd>0&&n.loadEventEnd>0&&n.domContentLoadedEventEnd>n.loadEventEnd&&(e.push("timing-order-violation"),t=Math.max(t,.7)),u>3e4&&(e.push("excessive-dom-processing"),t=Math.max(t,.3));let l=n.domContentLoadedEventStart-n.responseEnd;l>0&&l<5&&(e.push("instant-script-execution"),t=Math.max(t,.4));let h=performance.now(),f=performance.now();h===f&&h>0&&(e.push("frozen-performance-now"),t=Math.max(t,.6));let x=Date.now(),_=performance.now(),w=Date.now();Math.abs(w-x-(performance.now()-_))>100&&(e.push("timing-inconsistency"),t=Math.max(t,.5));let k=e.length>0;return this.createResult(k,{anomalies:e,timings:{domContentLoaded:s,domComplete:c,loadComplete:r,dnsLookup:a,tcpConnection:p,serverResponse:d,domProcessing:u}},t)}_analyzeNavigationTiming(e){let t=[],n=0,i=e.domContentLoadedEventEnd,s=e.loadEventEnd,c=e.domainLookupEnd-e.domainLookupStart,r=e.responseEnd-e.requestStart;i>0&&i<10&&(t.push("instant-dom-content-loaded"),n=Math.max(n,.7)),c===0&&r===0&&(t.push("zero-network-timing"),n=Math.max(n,.4));let a=t.length>0;return this.createResult(a,{anomalies:t,timings:{domContentLoaded:i,loadComplete:s,dnsLookup:c,serverResponse:r}},n)}};o(A,"id","page-load"),o(A,"category","timing"),o(A,"weight",.5),o(A,"description","Detects suspicious page load timing");var O=class extends m{constructor(e={}){super(e),this._domContentLoadedTime=null,this._documentReadyState=document.readyState,this._captureTime=performance.now(),document.readyState==="loading"&&document.addEventListener("DOMContentLoaded",()=>{this._domContentLoadedTime=performance.now()})}async detect(){let e=[],t=0,n=performance.now(),i=document.readyState,s=0,c=0,r=0;if(performance.getEntriesByType){let p=performance.getEntriesByType("resource");s=p.length;for(let d of p)c+=d.duration,d.initiatorType==="script"&&d.name.startsWith("http")&&r++}s===0&&i==="complete"&&(e.push("no-resources-loaded"),t=Math.max(t,.4)),this._domContentLoadedTime&&this._domContentLoadedTime<50&&s===0&&(e.push("instant-ready-no-resources"),t=Math.max(t,.6)),document.hidden&&this._documentReadyState==="loading"&&(e.push("hidden-at-load"),t=Math.max(t,.3)),typeof document.visibilityState>"u"&&(e.push("no-visibility-api"),t=Math.max(t,.4));try{let p=performance.now(),d=document.createElement("div");d.id="__bot_detection_test__",document.body.appendChild(d);let u=performance.now();document.body.removeChild(d);let l=performance.now(),h=u-p,f=l-u;h===0&&f===0&&(e.push("instant-dom-operations"),t=Math.max(t,.5))}catch{document.body||(e.push("no-document-body"),t=Math.max(t,.4))}if(typeof MutationObserver>"u"&&(e.push("no-mutation-observer"),t=Math.max(t,.5)),typeof requestAnimationFrame>"u"&&(e.push("no-request-animation-frame"),t=Math.max(t,.5)),performance.getEntriesByType){let p=performance.getEntriesByType("paint");!p.find(l=>l.name==="first-paint")&&i==="complete"&&n>1e3&&(e.push("no-first-paint"),t=Math.max(t,.4)),!p.find(l=>l.name==="first-contentful-paint")&&i==="complete"&&n>1e3&&(e.push("no-first-contentful-paint"),t=Math.max(t,.4))}typeof IntersectionObserver>"u"&&(e.push("no-intersection-observer"),t=Math.max(t,.4));let a=e.length>0;return this.createResult(a,{anomalies:e,metrics:{readyState:i,resourceCount:s,externalScriptCount:r,domContentLoadedTime:this._domContentLoadedTime,documentHidden:document.hidden}},t)}};o(O,"id","dom-content-timing"),o(O,"category","timing"),o(O,"weight",.4),o(O,"description","Analyzes DOM content loaded timing patterns");var B=class extends m{async detect(){let e=[],t=0;window.__puppeteer_evaluation_script__&&(e.push("puppeteer-evaluation-script"),t=Math.max(t,1));let n=["__puppeteer_evaluation_script__","__puppeteer","puppeteer"];for(let r of n)r in window&&(e.push(`global-${r}`),t=Math.max(t,1));(navigator.userAgent||"").includes("HeadlessChrome")&&(e.push("headless-chrome-ua"),t=Math.max(t,.9)),(window.cdc_adoQpoasnfa76pfcZLmcfl_Array||window.cdc_adoQpoasnfa76pfcZLmcfl_Promise||window.cdc_adoQpoasnfa76pfcZLmcfl_Symbol)&&(e.push("cdp-artifacts"),t=Math.max(t,1));try{window.eval.toString().includes("puppeteer")&&(e.push("eval-puppeteer"),t=Math.max(t,.9))}catch{}try{throw new Error("stack trace test")}catch(r){let a=r.stack||"";(a.includes("puppeteer")||a.includes("pptr"))&&(e.push("stack-trace-puppeteer"),t=Math.max(t,.8))}window.innerWidth===800&&window.innerHeight===600&&(e.push("default-viewport"),t=Math.max(t,.3)),navigator.webdriver===!0&&(e.push("webdriver-flag"),t=Math.max(t,.9)),Object.keys(window).filter(r=>r.startsWith("__")&&!r.startsWith("__zone_symbol__")&&typeof window[r]=="function").length>5&&(e.push("suspicious-bindings"),t=Math.max(t,.5)),typeof window.chrome<"u"&&(window.chrome.runtime||(e.push("incomplete-chrome-object"),t=Math.max(t,.4)));let c=e.length>0;return this.createResult(c,{indicators:e},t)}};o(B,"id","puppeteer"),o(B,"category","automation"),o(B,"weight",1),o(B,"description","Detects Puppeteer automation artifacts");var V=class extends m{async detect(){let e=[],t=0;window.__playwright&&(e.push("playwright-namespace"),t=Math.max(t,1));let n=["__playwright","__pw_manual","__pwInitScripts","playwright"];for(let c of n)c in window&&(e.push(`global-${c}`),t=Math.max(t,1));window.__playwright__binding__&&(e.push("playwright-binding"),t=Math.max(t,1));let i=navigator.userAgent||"";(i.includes("Playwright")||i.includes("HeadlessChrome"))&&(e.push("playwright-ua-marker"),t=Math.max(t,i.includes("Playwright")?1:.7)),navigator.webdriver===!0&&(e.push("webdriver-flag"),t=Math.max(t,.8));try{Object.keys(window).filter(a=>a.startsWith("__pw")).length>0&&(e.push("pw-bindings"),t=Math.max(t,1))}catch{}typeof window.__pw_date_intercepted<"u"&&(e.push("date-interception"),t=Math.max(t,.9)),window.__pw_geolocation__&&(e.push("geolocation-mock"),t=Math.max(t,.9)),window.__pw_permissions__&&(e.push("permissions-override"),t=Math.max(t,.9)),window.__cdpSession__&&(e.push("cdp-session"),t=Math.max(t,.8));try{throw new Error("stack trace test")}catch(c){let r=c.stack||"";(r.includes("playwright")||r.includes("__pw"))&&(e.push("stack-trace-playwright"),t=Math.max(t,.8))}try{let r=new Date().toLocaleString();window.__pwTimezone__&&(e.push("timezone-mock"),t=Math.max(t,.8))}catch{}let s=e.length>0;return this.createResult(s,{indicators:e},t)}};o(V,"id","playwright"),o(V,"category","automation"),o(V,"weight",1),o(V,"description","Detects Playwright automation artifacts");var K=class extends m{async detect(){let e=[],t=0;navigator.webdriver===!0&&(e.push("webdriver-flag"),t=Math.max(t,1));let n=["_selenium","callSelenium","_Selenium_IDE_Recorder","__selenium_evaluate","__selenium_unwrap","__webdriver_evaluate","__webdriver_unwrap","__webdriver_script_function","__webdriver_script_func","__fxdriver_evaluate","__fxdriver_unwrap","webdriver"];for(let a of n)a in window&&(e.push(`global-${a}`),t=Math.max(t,1));let i=["__webdriver_script_fn","__driver_evaluate","__webdriver_evaluate","__selenium_evaluate","__fxdriver_evaluate","__driver_unwrap","__webdriver_unwrap","__selenium_unwrap","__fxdriver_unwrap"];for(let a of i)a in document&&(e.push(`document-${a}`),t=Math.max(t,1));Object.keys(window).filter(a=>a.startsWith("$cdc_")||a.startsWith("$wdc_")||a.startsWith("$chrome_asyncScriptInfo")).length>0&&(e.push("chromedriver-variables"),t=Math.max(t,1)),(window.webdriverCallback||document.documentElement.getAttribute("webdriver"))&&(e.push("geckodriver-artifacts"),t=Math.max(t,1));try{let a=document.documentElement;(a.hasAttribute("webdriver")||a.getAttribute("selenium")||a.getAttribute("driver"))&&(e.push("document-webdriver-attr"),t=Math.max(t,1))}catch{}(window.selenium||window.sideex)&&(e.push("selenium-ide"),t=Math.max(t,1));try{let a=Object.getOwnPropertyDescriptor(Navigator.prototype,"webdriver");a&&a.get&&(a.get.toString().includes("[native code]")||(e.push("webdriver-getter-modified"),t=Math.max(t,.7)))}catch{}(window.domAutomation||window.domAutomationController)&&(e.push("dom-automation"),t=Math.max(t,1)),window.awesomium&&(e.push("awesomium"),t=Math.max(t,.9)),window.external&&window.external.toString().includes("Selenium")&&(e.push("external-selenium"),t=Math.max(t,1));let r=e.length>0;return this.createResult(r,{indicators:e},t)}};o(K,"id","selenium"),o(K,"category","automation"),o(K,"weight",1),o(K,"description","Detects Selenium WebDriver artifacts");var q=class extends m{async detect(){let e=[],t=0;window.callPhantom&&(e.push("callPhantom"),t=Math.max(t,1)),window._phantom&&(e.push("_phantom"),t=Math.max(t,1)),window.phantom&&(e.push("phantom"),t=Math.max(t,1));let n=navigator.userAgent||"";n.includes("PhantomJS")&&(e.push("phantomjs-ua"),t=Math.max(t,1)),window.__phantomas&&(e.push("phantomas"),t=Math.max(t,1)),window.__casper&&(e.push("casperjs"),t=Math.max(t,1)),window.casper&&(e.push("casper-global"),t=Math.max(t,1)),window.slimer&&(e.push("slimerjs"),t=Math.max(t,1)),window.__nightmare&&(e.push("nightmare"),t=Math.max(t,1)),window.nightmare&&(e.push("nightmare-global"),t=Math.max(t,1));try{let c=Function.prototype.toString.call(Function);(c.includes("phantom")||c.includes("Phantom"))&&(e.push("function-prototype-phantom"),t=Math.max(t,.8))}catch{}try{throw new Error("test")}catch(c){(c.stack||"").includes("phantom")&&(e.push("stack-trace-phantom"),t=Math.max(t,.9))}navigator.plugins&&navigator.plugins.length===0&&e.length>0&&(e.push("no-plugins-phantom"),t=Math.max(t,.5));let i=["__PHANTOM__","PHANTOM","Buffer","process"];for(let c of i)c in window&&c!=="Buffer"&&c!=="process"&&(e.push(`phantom-prop-${c.toLowerCase()}`),t=Math.max(t,.9));n.includes("QtWebKit")&&(e.push("qtwebkit"),t=Math.max(t,.7));let s=e.length>0;return this.createResult(s,{indicators:e},t)}};o(q,"id","phantomjs"),o(q,"category","automation"),o(q,"weight",1),o(q,"description","Detects PhantomJS automation artifacts");var tt={WebDriverSignal:E,HeadlessSignal:C,NavigatorAnomalySignal:R,PermissionsSignal:D,MouseMovementSignal:y,KeyboardPatternSignal:M,InteractionTimingSignal:b,ScrollBehaviorSignal:T,PluginsSignal:S,WebGLSignal:L,CanvasSignal:P,AudioContextSignal:I,ScreenSignal:H,PageLoadSignal:A,DOMContentTimingSignal:O,PuppeteerSignal:B,PlaywrightSignal:V,SeleniumSignal:K,PhantomJSSignal:q},G=[new E,new C,new R,new D,new S,new L,new P,new I,new H,new A,new O,new B,new V,new K,new q],Q=[new y,new M,new b,new T],lt=[...G,...Q];function Z(g={}){let{includeInteractionSignals:e=!0,instantBotSignals:t=["webdriver","puppeteer","playwright","selenium","phantomjs"],...n}=g,i=e?[...G,...Q.map(s=>{let c=s.constructor;return new c(s.options)})]:G.map(s=>{let c=s.constructor;return new c(s.options)});return new W({signals:i,instantBotSignals:t,...n})}async function J(g={}){return Z({includeInteractionSignals:!g.skipInteractionSignals}).detect(g)}async function et(){return J({skipInteractionSignals:!0})}var ut={BotDetector:W,createDetector:Z,detect:J,detectInstant:et,Signal:m,Signals:tt,Verdict:z};return ht(dt);})();
|
|
1
|
+
var BotDetectorLib=(()=>{var G=Object.defineProperty;var it=Object.getOwnPropertyDescriptor;var st=Object.getOwnPropertyNames;var ot=Object.prototype.hasOwnProperty;var at=(f,e,t)=>e in f?G(f,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):f[e]=t;var rt=(f,e)=>{for(var t in e)G(f,t,{get:e[t],enumerable:!0})},ct=(f,e,t,i)=>{if(e&&typeof e=="object"||typeof e=="function")for(let n of st(e))!ot.call(f,n)&&n!==t&&G(f,n,{get:()=>e[n],enumerable:!(i=it(e,n))||i.enumerable});return f};var ht=f=>ct(G({},"__esModule",{value:!0}),f);var o=(f,e,t)=>at(f,typeof e!="symbol"?e+"":e,t);var dt={};rt(dt,{AudioContextSignal:()=>P,BotDetector:()=>$,CanvasSignal:()=>L,DOMContentTimingSignal:()=>A,HeadlessSignal:()=>M,InteractionTimingSignal:()=>C,KeyboardPatternSignal:()=>E,MouseMovementSignal:()=>k,NavigatorAnomalySignal:()=>b,PageLoadSignal:()=>H,PermissionsSignal:()=>T,PhantomJSSignal:()=>K,PlaywrightSignal:()=>B,PluginsSignal:()=>R,PuppeteerSignal:()=>O,ScoringEngine:()=>N,ScreenSignal:()=>I,ScrollBehaviorSignal:()=>D,SeleniumSignal:()=>V,Signal:()=>m,Signals:()=>Z,Verdict:()=>U,VerdictEngine:()=>F,WebDriverSignal:()=>v,WebGLSignal:()=>S,createDetector:()=>X,default:()=>ut,defaultInstantSignals:()=>J,defaultInteractionSignals:()=>tt,defaultSignals:()=>lt,detect:()=>Q,detectInstant:()=>et});var m=class{constructor(e={}){this.options=e,this._lastResult=null}get id(){return this.constructor.id}get category(){return this.constructor.category}get weight(){var e;return(e=this.options.weight)!=null?e:this.constructor.weight}get description(){return this.constructor.description}get requiresInteraction(){return this.constructor.requiresInteraction}get lastResult(){return this._lastResult}async detect(){throw new Error(`Signal.detect() must be implemented by ${this.constructor.name}`)}async run(){try{return this._lastResult=await this.detect(),this._lastResult}catch(e){return this._lastResult={triggered:!1,value:null,confidence:0,error:e.message},this._lastResult}}reset(){this._lastResult=null}createResult(e,t=null,i=1){return{triggered:!!e,value:t,confidence:Math.max(0,Math.min(1,i))}}};o(m,"id","base-signal"),o(m,"category","uncategorized"),o(m,"weight",.5),o(m,"description","Base signal class"),o(m,"requiresInteraction",!1);var N=class{constructor(e={}){this.weightOverrides=e.weightOverrides||{},this.maxScore=e.maxScore||100,this._results=new Map}getWeight(e,t){var i;return(i=this.weightOverrides[e])!=null?i:t}addResult(e,t,i){let n=this.getWeight(e,i);this._results.set(e,{...t,weight:n,contribution:t.triggered?n*t.confidence:0})}calculate(){if(this._results.size===0)return 0;let e=0,t=0;for(let[,n]of this._results)e+=n.weight,t+=n.contribution;if(e===0)return 0;let i=t/e*this.maxScore;return Math.round(i*100)/100}getBreakdown(){let e=[],t=this.calculate();for(let[i,n]of this._results)e.push({signalId:i,triggered:n.triggered,confidence:n.confidence,weight:n.weight,contribution:n.contribution,percentOfScore:t>0?(n.contribution/t*100).toFixed(1):"0.0"});return e.sort((i,n)=>n.contribution-i.contribution)}getTriggeredSignals(){let e=[];for(let[t,i]of this._results)i.triggered&&e.push(t);return e}getTriggeredCount(){let e=0;for(let[,t]of this._results)t.triggered&&e++;return e}reset(){this._results.clear()}};var U={HUMAN:"human",SUSPICIOUS:"suspicious",BOT:"bot"},Y=class Y{constructor(e={}){var t,i;this.humanThreshold=(t=e.humanThreshold)!=null?t:Y.DEFAULT_THRESHOLDS.human,this.suspiciousThreshold=(i=e.suspiciousThreshold)!=null?i:Y.DEFAULT_THRESHOLDS.suspicious,this.instantBotSignals=new Set(e.instantBotSignals||[])}getVerdict(e,t=[]){for(let r of t)if(this.instantBotSignals.has(r))return{verdict:U.BOT,score:e,confidence:"high",reason:`Instant bot signal triggered: ${r}`,triggeredCount:t.length};let i,n,s;return e<this.humanThreshold?(i=U.HUMAN,n=e<10?"high":"medium",s="Low bot score"):e<this.suspiciousThreshold?(i=U.SUSPICIOUS,n="medium",s="Moderate bot indicators detected"):(i=U.BOT,n=e>=75?"high":"medium",s="High accumulation of bot indicators"),{verdict:i,score:e,confidence:n,reason:s,triggeredCount:t.length}}isInstantBotSignal(e){return this.instantBotSignals.has(e)}addInstantBotSignal(e){this.instantBotSignals.add(e)}setThresholds(e){e.human!==void 0&&(this.humanThreshold=e.human),e.suspicious!==void 0&&(this.suspiciousThreshold=e.suspicious)}};o(Y,"DEFAULT_THRESHOLDS",{human:20,suspicious:50});var F=Y;var $=class{constructor(e={}){if(this.options=e,this._signals=new Map,this._scoringEngine=new N({weightOverrides:e.weightOverrides}),this._verdictEngine=new F({humanThreshold:e.humanThreshold,suspiciousThreshold:e.suspiciousThreshold,instantBotSignals:e.instantBotSignals}),this._lastDetection=null,this._detectionTimeout=e.detectionTimeout||5e3,this._isRunning=!1,e.signals)for(let t of e.signals)this.registerSignal(t)}registerSignal(e){if(!(e instanceof m))throw new Error("Signal must be an instance of Signal class");let t=e.id;if(this._signals.has(t))throw new Error(`Signal with ID "${t}" is already registered`);return this._signals.set(t,e),this}registerSignals(e){for(let t of e)this.registerSignal(t);return this}unregisterSignal(e){return this._signals.delete(e)}getSignal(e){return this._signals.get(e)}getSignals(){return Array.from(this._signals.values())}getSignalsByCategory(e){return this.getSignals().filter(t=>t.category===e)}async detect(e={}){if(this._isRunning)throw new Error("Detection is already running");this._isRunning=!0,this._scoringEngine.reset();let t=performance.now(),i=new Map;try{let n=this.getSignals().filter(u=>!(e.skipInteractionSignals&&u.requiresInteraction)),s=n.map(async u=>{let l,h=new Promise(w=>{l=setTimeout(()=>w({triggered:!1,value:null,confidence:0,error:"timeout"}),this._detectionTimeout)}),g=await Promise.race([u.run(),h]);return clearTimeout(l),{signal:u,result:g}}),r=await Promise.all(s);for(let{signal:u,result:l}of r)i.set(u.id,{...l,category:u.category,weight:u.weight,description:u.description}),this._scoringEngine.addResult(u.id,l,u.weight);let c=this._scoringEngine.calculate(),a=this._scoringEngine.getTriggeredSignals(),p=this._verdictEngine.getVerdict(c,a),d=performance.now()-t;return this._lastDetection={...p,signals:Object.fromEntries(i),breakdown:this._scoringEngine.getBreakdown(),timestamp:Date.now(),detectionTimeMs:Math.round(d),totalSignals:n.length,triggeredSignals:a},this._lastDetection}finally{this._isRunning=!1}}getLastDetection(){return this._lastDetection}getScore(){var e,t;return(t=(e=this._lastDetection)==null?void 0:e.score)!=null?t:0}getTriggeredSignals(){var e,t;return(t=(e=this._lastDetection)==null?void 0:e.triggeredSignals)!=null?t:[]}isRunning(){return this._isRunning}reset(){this._scoringEngine.reset(),this._lastDetection=null;for(let e of this._signals.values())e.reset()}configure(e){(e.humanThreshold!==void 0||e.suspiciousThreshold!==void 0)&&this._verdictEngine.setThresholds({human:e.humanThreshold,suspicious:e.suspiciousThreshold}),e.detectionTimeout!==void 0&&(this._detectionTimeout=e.detectionTimeout)}static withDefaults(){throw new Error(`BotDetector.withDefaults() is not supported. Use createDetector() from '@niksbanna/bot-detector' instead:
|
|
2
|
+
import { createDetector } from '@niksbanna/bot-detector';
|
|
3
|
+
const detector = createDetector();`)}};var v=class extends m{async detect(){if(navigator.webdriver===!0)return this.createResult(!0,{webdriver:!0},1);let e=Object.getOwnPropertyDescriptor(navigator,"webdriver");if(e&&(e.get||!e.configurable))return this.createResult(!0,{webdriver:"modified",descriptor:{configurable:e.configurable,enumerable:e.enumerable,hasGetter:!!e.get}},.8);try{let t=Object.getPrototypeOf(navigator),i=Object.getOwnPropertyDescriptor(t,"webdriver");if(i&&i.get&&i.get.call(navigator)===!0)return this.createResult(!0,{webdriver:!0,source:"prototype"},1)}catch{}return this.createResult(!1)}};o(v,"id","webdriver"),o(v,"category","environment"),o(v,"weight",1),o(v,"description","Detects navigator.webdriver automation flag");var M=class extends m{async detect(){let e=[],t=0,i=navigator.userAgent||"";i.includes("HeadlessChrome")&&(e.push("headless-ua"),t=Math.max(t,1)),i.includes("Chrome")&&!i.includes("Chromium")&&typeof window.chrome>"u"&&(e.push("missing-chrome-object"),t=Math.max(t,.6)),navigator.plugins&&navigator.plugins.length===0&&(e.push("no-plugins"),t=Math.max(t,.5)),(!navigator.languages||navigator.languages.length===0)&&(e.push("no-languages"),t=Math.max(t,.6)),window.outerWidth===0&&window.outerHeight===0&&(e.push("zero-outer-dimensions"),t=Math.max(t,.7)),typeof navigator.connection>"u"&&i.includes("Chrome")&&(e.push("missing-connection-api"),t=Math.max(t,.3));try{typeof Notification<"u"&&Notification.permission==="denied"&&window.outerWidth===0&&(e.push("notification-headless-pattern"),t=Math.max(t,.5))}catch{}(window.callPhantom||window._phantom)&&(e.push("phantomjs"),t=Math.max(t,1)),window.__nightmare&&(e.push("nightmare"),t=Math.max(t,1));let n=e.length>0;return e.length>=3&&(t=Math.min(1,t+.2)),this.createResult(n,{indicators:e},t)}};o(M,"id","headless"),o(M,"category","environment"),o(M,"weight",.8),o(M,"description","Detects headless browser indicators");var b=class extends m{async detect(){let e=[],t=0,i=0,n=navigator.userAgent||"",s=navigator.platform||"";i++,s.includes("Win")&&!n.includes("Windows")?(e.push("platform-ua-mismatch-windows"),t+=1):s.includes("Mac")&&!n.includes("Mac")?(e.push("platform-ua-mismatch-mac"),t+=1):s.includes("Linux")&&!n.includes("Linux")&&!n.includes("Android")&&(e.push("platform-ua-mismatch-linux"),t+=1),i++,!(n.includes("Chrome")&&!n.includes("Chromium"))&&(!s||s===""||s==="undefined")&&(e.push("empty-platform"),t+=1),i++,navigator.language&&navigator.languages&&(navigator.languages.includes(navigator.language)||(e.push("language-mismatch"),t+=.5)),i++,n.includes("Chrome")&&navigator.vendor!=="Google Inc."?(e.push("vendor-mismatch-chrome"),t+=.5):n.includes("Firefox")&&navigator.vendor!==""?(e.push("vendor-mismatch-firefox"),t+=.5):n.includes("Safari")&&!n.includes("Chrome")&&navigator.vendor!=="Apple Computer, Inc."&&(e.push("vendor-mismatch-safari"),t+=.5),i++,typeof navigator.hardwareConcurrency<"u"&&(navigator.hardwareConcurrency===0||navigator.hardwareConcurrency>128)&&(e.push("suspicious-hardware-concurrency"),t+=.5),i++,typeof navigator.deviceMemory<"u"&&(navigator.deviceMemory===0||navigator.deviceMemory>512)&&(e.push("suspicious-device-memory"),t+=.5),i++;let c=/Android|iPhone|iPad|iPod|Mobile/i.test(n),a=navigator.maxTouchPoints>0;!c&&navigator.maxTouchPoints>5&&(e.push("desktop-high-touch-points"),t+=.3),i++;try{let u=Object.getOwnPropertyDescriptor(Navigator.prototype,"userAgent");u&&u.get&&u.get.toString().includes("native code")===!1&&(e.push("spoofed-user-agent"),t+=1)}catch{}let p=e.length>0,d=Math.min(1,t/Math.max(1,i));return this.createResult(p,{anomalies:e},d)}};o(b,"id","navigator-anomaly"),o(b,"category","environment"),o(b,"weight",.7),o(b,"description","Detects navigator property inconsistencies");var T=class extends m{async detect(){let e=[];if(!navigator.permissions)return this.createResult(!1,{supported:!1},0);try{let n=await navigator.permissions.query({name:"notifications"});if(typeof Notification<"u"){let s=Notification.permission;(s==="granted"&&n.state!=="granted"||s==="denied"&&n.state!=="denied"||s==="default"&&n.state!=="prompt")&&e.push("notification-permission-mismatch")}try{(await navigator.permissions.query({name:"geolocation"})).state==="denied"&&window.outerWidth===0&&e.push("geo-denied-headless")}catch{}try{await navigator.permissions.query({name:"camera"})}catch(s){s.name==="TypeError"&&e.push("camera-permission-error")}}catch(n){n.name!=="TypeError"&&e.push("permissions-query-error")}let t=e.length>0,i=Math.min(1,e.length*.4);return this.createResult(t,{anomalies:e},i)}};o(T,"id","permissions"),o(T,"category","environment"),o(T,"weight",.5),o(T,"description","Detects Permissions API anomalies");var k=class extends m{constructor(e={}){super(e),this._movements=[],this._isTracking=!1,this._trackingDuration=Math.min(e.trackingDuration||2500,2500),this._minMovements=e.minMovements||5,this._boundHandler=null}startTracking(){this._isTracking||(this._movements=[],this._isTracking=!0,this._boundHandler=e=>{this._movements.push({x:e.clientX,y:e.clientY,t:performance.now()})},document.addEventListener("mousemove",this._boundHandler,{passive:!0}))}stopTracking(){this._isTracking&&(this._isTracking=!1,this._boundHandler&&(document.removeEventListener("mousemove",this._boundHandler),this._boundHandler=null))}async detect(){let e=[],t=0;this._movements.length===0&&(this.startTracking(),await new Promise(r=>setTimeout(r,this._trackingDuration)),this.stopTracking());let i=this._movements;if(i.length<this._minMovements)return e.push("no-mouse-movement"),t=Math.max(t,.6),this.createResult(!0,{anomalies:e,movements:i.length},t);let n=this._analyzeMovements(i);n.teleportCount>0&&(e.push("mouse-teleportation"),t=Math.max(t,.7)),n.linearPathRatio>.9&&(e.push("linear-path"),t=Math.max(t,.8)),n.velocityVariance<.01&&i.length>10&&(e.push("constant-velocity"),t=Math.max(t,.7)),n.accelerationChanges===0&&i.length>10&&(e.push("no-acceleration-variance"),t=Math.max(t,.6)),n.timingVariance<1&&i.length>10&&(e.push("robotic-timing"),t=Math.max(t,.8));let s=e.length>0;return this.createResult(s,{anomalies:e,movementCount:i.length,analysis:n},t)}_analyzeMovements(e){if(e.length<3)return{teleportCount:0,linearPathRatio:0,velocityVariance:0,accelerationChanges:0,timingVariance:0};let t=0,i=[],n=[],s=[];for(let l=1;l<e.length;l++){let h=e[l-1],g=e[l],w=g.x-h.x,x=g.y-h.y,_=g.t-h.t;if(_===0)continue;let q=Math.sqrt(w*w+x*x),y=q/_;i.push(y),n.push(Math.atan2(x,w)),s.push(_),q>300&&_<10&&t++}let r=i.reduce((l,h)=>l+h,0)/i.length,c=i.reduce((l,h)=>l+Math.pow(h-r,2),0)/i.length,a=0;if(n.length>1){let l=0;for(let h=1;h<n.length;h++)Math.abs(n[h]-n[h-1])<.1&&l++;a=l/(n.length-1)}let p=s.reduce((l,h)=>l+h,0)/s.length,d=s.reduce((l,h)=>l+Math.pow(h-p,2),0)/s.length,u=0;for(let l=1;l<i.length;l++)(i[l]-i[l-1])*(i[l-1]-(i[l-2]||0))<0&&u++;return{teleportCount:t,linearPathRatio:a,velocityVariance:c,accelerationChanges:u,timingVariance:d}}reset(){super.reset(),this.stopTracking(),this._movements=[]}};o(k,"id","mouse-movement"),o(k,"category","behavior"),o(k,"weight",.9),o(k,"description","Detects non-human mouse movement patterns"),o(k,"requiresInteraction",!0);var E=class extends m{constructor(e={}){super(e),this._keystrokes=[],this._isTracking=!1,this._trackingDuration=Math.min(e.trackingDuration||2500,2500),this._minKeystrokes=e.minKeystrokes||10,this._boundKeydownHandler=null,this._boundKeyupHandler=null}startTracking(){this._isTracking||(this._keystrokes=[],this._isTracking=!0,this._boundKeydownHandler=e=>{this._keystrokes.push({type:"down",key:e.key,code:e.code,t:performance.now()})},this._boundKeyupHandler=e=>{this._keystrokes.push({type:"up",key:e.key,code:e.code,t:performance.now()})},document.addEventListener("keydown",this._boundKeydownHandler,{passive:!0}),document.addEventListener("keyup",this._boundKeyupHandler,{passive:!0}))}stopTracking(){this._isTracking&&(this._isTracking=!1,this._boundKeydownHandler&&(document.removeEventListener("keydown",this._boundKeydownHandler),this._boundKeydownHandler=null),this._boundKeyupHandler&&(document.removeEventListener("keyup",this._boundKeyupHandler),this._boundKeyupHandler=null))}async detect(){let e=[],t=0;this._keystrokes.length===0&&(this.startTracking(),await new Promise(c=>setTimeout(c,this._trackingDuration)),this.stopTracking());let i=this._keystrokes,n=i.filter(c=>c.type==="down");if(n.length<this._minKeystrokes)return this.createResult(!1,{reason:"insufficient-data",keystrokes:n.length},0);let s=this._analyzeKeystrokes(i);s.avgInterKeystrokeTime<50&&n.length>20&&(e.push("inhuman-speed"),t=Math.max(t,.9)),s.timingVariance<5&&n.length>15&&(e.push("robotic-timing"),t=Math.max(t,.8)),s.missingKeyups>n.length*.5&&(e.push("missing-keyups"),t=Math.max(t,.7)),s.holdTimeVariance<2&&s.holdTimes.length>10&&(e.push("constant-hold-time"),t=Math.max(t,.6)),s.sequentialKeys>n.length*.8&&n.length>10&&(e.push("sequential-input"),t=Math.max(t,.5)),s.rhythmScore<.1&&n.length>20&&(e.push("no-rhythm-variation"),t=Math.max(t,.6));let r=e.length>0;return this.createResult(r,{anomalies:e,keystrokeCount:n.length,analysis:s},t)}_analyzeKeystrokes(e){let t=e.filter(h=>h.type==="down"),i=e.filter(h=>h.type==="up");if(t.length<2)return{avgInterKeystrokeTime:1/0,timingVariance:1/0,missingKeyups:0,holdTimeVariance:1/0,holdTimes:[],sequentialKeys:0,rhythmScore:1};let n=[];for(let h=1;h<t.length;h++)n.push(t[h].t-t[h-1].t);let s=n.reduce((h,g)=>h+g,0)/n.length,r=n.reduce((h,g)=>h+Math.pow(g-s,2),0)/n.length,c=[];for(let h of t){let g=i.find(w=>w.key===h.key&&w.t>h.t);g&&c.push(g.t-h.t)}let a=c.length>0?c.reduce((h,g)=>h+g,0)/c.length:0,p=c.length>0?c.reduce((h,g)=>h+Math.pow(g-a,2),0)/c.length:1/0,d=t.length-c.length,u=0;for(let h=1;h<t.length;h++){let g=t[h-1].key.charCodeAt(0),w=t[h].key.charCodeAt(0);Math.abs(w-g)===1&&u++}let l=0;if(n.length>5){let h=[...n].sort((x,_)=>x-_),g=h[Math.floor(h.length/2)];l=n.filter(x=>Math.abs(x-g)>g*.3).length/n.length}return{avgInterKeystrokeTime:s,timingVariance:r,missingKeyups:d,holdTimeVariance:p,holdTimes:c,sequentialKeys:u,rhythmScore:l}}reset(){super.reset(),this.stopTracking(),this._keystrokes=[]}};o(E,"id","keyboard-pattern"),o(E,"category","behavior"),o(E,"weight",.8),o(E,"description","Detects non-human keystroke patterns"),o(E,"requiresInteraction",!0);var C=class extends m{constructor(e={}){super(e),this._pageLoadTime=performance.now(),this._firstInteractionTime=null,this._interactions=[],this._isTracking=!1,this._trackingDuration=e.trackingDuration||5e3,this._boundHandler=null}startTracking(){if(this._isTracking)return;this._interactions=[],this._isTracking=!0;let e=["click","mousedown","touchstart","keydown","scroll"];this._boundHandler=t=>{let i=performance.now();this._firstInteractionTime===null&&(this._firstInteractionTime=i),this._interactions.push({type:t.type,t:i,timeSinceLoad:i-this._pageLoadTime})};for(let t of e)document.addEventListener(t,this._boundHandler,{passive:!0,capture:!0})}stopTracking(){if(!this._isTracking)return;this._isTracking=!1;let e=["click","mousedown","touchstart","keydown","scroll"];if(this._boundHandler){for(let t of e)document.removeEventListener(t,this._boundHandler,{capture:!0});this._boundHandler=null}}async detect(){let e=[],t=0;!this._isTracking&&this._interactions.length===0&&(this.startTracking(),await new Promise(c=>setTimeout(c,this._trackingDuration)),this.stopTracking());let i=this._interactions;if(i.length===0)return this.createResult(!1,{reason:"no-interactions"},0);let n=i[0];if(n.timeSinceLoad<100?(e.push("instant-interaction"),t=Math.max(t,.9)):n.timeSinceLoad<300&&(e.push("very-fast-interaction"),t=Math.max(t,.6)),i.length>3){let c=[];for(let l=1;l<i.length;l++)c.push(i[l].t-i[l-1].t);let a=c.reduce((l,h)=>l+h,0)/c.length;c.reduce((l,h)=>l+Math.pow(h-a,2),0)/c.length<10&&i.length>5&&(e.push("robotic-intervals"),t=Math.max(t,.8));let d=50,u=0;for(let l of c)l<d&&u++;u>c.length*.7&&(e.push("burst-interactions"),t=Math.max(t,.7))}let s=i.map(c=>c.type).join(",");if(i.length>=6){let c=Math.floor(i.length/2),a=i.slice(0,c).map(d=>d.type).join(","),p=i.slice(c,c*2).map(d=>d.type).join(",");a===p&&a.length>0&&(e.push("repeated-sequence"),t=Math.max(t,.6))}let r=e.length>0;return this.createResult(r,{anomalies:e,interactionCount:i.length,timeToFirstInteraction:n.timeSinceLoad,firstInteractionType:n.type},t)}reset(){super.reset(),this.stopTracking(),this._pageLoadTime=performance.now(),this._firstInteractionTime=null,this._interactions=[]}};o(C,"id","interaction-timing"),o(C,"category","behavior"),o(C,"weight",.6),o(C,"description","Detects suspicious interaction timing"),o(C,"requiresInteraction",!0);var D=class extends m{constructor(e={}){super(e),this._scrollEvents=[],this._isTracking=!1,this._trackingDuration=Math.min(e.trackingDuration||2500,2500),this._boundHandler=null}startTracking(){this._isTracking||(this._scrollEvents=[],this._isTracking=!0,this._boundHandler=()=>{this._scrollEvents.push({scrollY:window.scrollY,scrollX:window.scrollX,t:performance.now()})},window.addEventListener("scroll",this._boundHandler,{passive:!0}))}stopTracking(){this._isTracking&&(this._isTracking=!1,this._boundHandler&&(window.removeEventListener("scroll",this._boundHandler),this._boundHandler=null))}async detect(){let e=[],t=0;this._scrollEvents.length===0&&(this.startTracking(),await new Promise(r=>setTimeout(r,this._trackingDuration)),this.stopTracking());let i=this._scrollEvents;if(i.length<3)return this.createResult(!1,{reason:"insufficient-scroll-data",scrollEvents:i.length},0);let n=this._analyzeScrollPatterns(i);n.instantJumps>0&&(e.push("instant-scroll-jumps"),t=Math.max(t,.7)),n.velocityVariance<.1&&i.length>10&&(e.push("constant-scroll-velocity"),t=Math.max(t,.6)),n.momentumEvents===0&&i.length>5&&(e.push("no-scroll-momentum"),t=Math.max(t,.5)),n.intervalVariance<5&&i.length>10&&(e.push("robotic-scroll-timing"),t=Math.max(t,.7)),n.scrollDirections===1&&Math.abs(n.totalScrollY)>1e3&&n.velocityVariance<1&&(e.push("one-dimensional-scroll"),t=Math.max(t,.4)),n.exactPositionScrolls>2&&(e.push("exact-position-scrolls"),t=Math.max(t,.6));let s=e.length>0;return this.createResult(s,{anomalies:e,scrollEventCount:i.length,analysis:n},t)}_analyzeScrollPatterns(e){if(e.length<2)return{instantJumps:0,velocityVariance:0,momentumEvents:0,intervalVariance:0,scrollDirections:0,totalScrollY:0,exactPositionScrolls:0};let t=0,i=0,n=[],s=[],r=!1,c=!1,a=0,p=[0,100,200,300,400,500,600,800,1e3];for(let x=1;x<e.length;x++){let _=e[x-1],q=e[x],y=q.scrollY-_.scrollY,W=q.scrollX-_.scrollX,z=q.t-_.t;if(s.push(z),Math.abs(y)>0&&(r=!0),Math.abs(W)>0&&(c=!0),z===0)continue;let j=Math.sqrt(y*y+W*W)/z;if(n.push(j),Math.abs(y)+Math.abs(W)>200&&z<20&&t++,x>1&&n.length>1){let nt=n[n.length-2];j<nt*.9&&j>0&&i++}p.includes(Math.round(q.scrollY))&&a++}let d=n.length>0?n.reduce((x,_)=>x+_,0)/n.length:0,u=n.length>0?n.reduce((x,_)=>x+Math.pow(_-d,2),0)/n.length:0,l=s.reduce((x,_)=>x+_,0)/s.length,h=s.reduce((x,_)=>x+Math.pow(_-l,2),0)/s.length,g=0;r&&g++,c&&g++;let w=e[e.length-1].scrollY-e[0].scrollY;return{instantJumps:t,velocityVariance:u,momentumEvents:i,intervalVariance:h,scrollDirections:g,totalScrollY:w,exactPositionScrolls:a}}reset(){super.reset(),this.stopTracking(),this._scrollEvents=[]}};o(D,"id","scroll-behavior"),o(D,"category","behavior"),o(D,"weight",.5),o(D,"description","Detects programmatic scroll patterns"),o(D,"requiresInteraction",!0);var R=class extends m{async detect(){let e=[],t=0,i=navigator.plugins,n=navigator.mimeTypes;if(!i)return e.push("no-plugins-object"),t=Math.max(t,.6),this.createResult(!0,{anomalies:e},t);i.length===0&&(e.push("empty-plugins"),t=Math.max(t,.5));let s=navigator.userAgent||"";if(s.includes("Chrome")&&!s.includes("Chromium")&&!Array.from(i).some(p=>p.name.includes("PDF")||p.name.includes("Chromium PDF"))&&i.length===0&&(e.push("chrome-missing-pdf-plugin"),t=Math.max(t,.4)),i.length>0&&n){let a=0;for(let p=0;p<i.length;p++)a+=i[p].length||0;n.length===0&&a>0&&(e.push("mimetypes-mismatch"),t=Math.max(t,.5))}if(i.length>1){let a=Array.from(i).map(d=>d.name);new Set(a).size<a.length&&(e.push("duplicate-plugins"),t=Math.max(t,.6))}try{let a=Object.getOwnPropertyDescriptor(Navigator.prototype,"plugins");a&&a.get&&(a.get.toString().includes("[native code]")||(e.push("plugins-getter-overridden"),t=Math.max(t,.7)))}catch{}!/Android|iPhone|iPad|iPod|Mobile/i.test(s)&&i.length===1&&(e.push("minimal-plugins"),t=Math.max(t,.3));let c=e.length>0;return this.createResult(c,{anomalies:e,pluginCount:i.length,mimeTypeCount:(n==null?void 0:n.length)||0},t)}};o(R,"id","plugins"),o(R,"category","fingerprint"),o(R,"weight",.6),o(R,"description","Detects browser plugin anomalies");var S=class extends m{async detect(){let e=[],t=0,i=document.createElement("canvas"),n=null;try{n=i.getContext("webgl")||i.getContext("experimental-webgl")}catch{e.push("webgl-error"),t=Math.max(t,.5)}if(!n)return e.push("webgl-unavailable"),t=Math.max(t,.4),this.createResult(!0,{anomalies:e},t);let s=n.getExtension("WEBGL_debug_renderer_info"),r="",c="";s&&(r=n.getParameter(s.UNMASKED_VENDOR_WEBGL)||"",c=n.getParameter(s.UNMASKED_RENDERER_WEBGL)||""),!r&&!c&&(e.push("no-webgl-renderer-info"),t=Math.max(t,.6));let a=["swiftshader","llvmpipe","software","mesa","google swiftshader","vmware","virtualbox"],p=c.toLowerCase();for(let w of a)if(p.includes(w)){e.push(`suspicious-renderer-${w.replace(/\s+/g,"-")}`),t=Math.max(t,.7);break}r&&c&&(p.includes("nvidia")&&!r.toLowerCase().includes("nvidia")&&(e.push("vendor-renderer-mismatch"),t=Math.max(t,.6)),(p.includes("amd")||p.includes("radeon"))&&!r.toLowerCase().includes("amd")&&!r.toLowerCase().includes("ati")&&(e.push("vendor-renderer-mismatch"),t=Math.max(t,.6)));let d=n.getSupportedExtensions()||[];d.length<5&&(e.push("few-webgl-extensions"),t=Math.max(t,.4));let u=n.getParameter(n.MAX_TEXTURE_SIZE),l=n.getParameter(n.MAX_VIEWPORT_DIMS);(u<1024||u>65536)&&(e.push("unrealistic-max-texture"),t=Math.max(t,.5));try{n.clearColor(0,0,0,1),n.clear(n.COLOR_BUFFER_BIT);let w=new Uint8Array(4);n.readPixels(0,0,1,1,n.RGBA,n.UNSIGNED_BYTE,w),w[3]!==255&&(e.push("webgl-render-failure"),t=Math.max(t,.6))}catch{e.push("webgl-render-error"),t=Math.max(t,.5)}let h=n.getExtension("WEBGL_lose_context");h&&h.loseContext();let g=e.length>0;return this.createResult(g,{anomalies:e,vendor:r,renderer:c,extensionCount:d.length,maxTextureSize:u},t)}};o(S,"id","webgl"),o(S,"category","fingerprint"),o(S,"weight",.7),o(S,"description","Detects WebGL rendering anomalies");var L=class extends m{async detect(){let e=[],t=0;try{let n=document.createElement("canvas");n.width=200,n.height=50;let s=n.getContext("2d");if(!s)return e.push("canvas-context-unavailable"),t=Math.max(t,.5),this.createResult(!0,{anomalies:e},t);s.textBaseline="alphabetic",s.font="14px Arial",s.fillStyle="#f60",s.fillRect(0,0,200,50),s.fillStyle="#069",s.fillText("Bot Detection Test \u{1F916}",2,15),s.fillStyle="rgba(102, 204, 0, 0.7)",s.fillText("Canvas Fingerprint",4,30),s.beginPath(),s.arc(100,25,10,0,Math.PI*2,!0),s.closePath(),s.fill();let r=n.toDataURL();s.clearRect(0,0,200,50),s.fillStyle="#f60",s.fillRect(0,0,200,50),s.fillStyle="#069",s.fillText("Bot Detection Test \u{1F916}",2,15),s.fillStyle="rgba(102, 204, 0, 0.7)",s.fillText("Canvas Fingerprint",4,30),s.beginPath(),s.arc(100,25,10,0,Math.PI*2,!0),s.closePath(),s.fill();let c=n.toDataURL();r!==c&&(e.push("canvas-randomized"),t=Math.max(t,.6)),r.length<1e3&&(e.push("canvas-possibly-blank"),t=Math.max(t,.4));let a=document.createElement("canvas");a.width=200,a.height=50;let p=a.toDataURL();r===p&&(e.push("canvas-rendering-blocked"),t=Math.max(t,.7));try{n.toDataURL.toString().includes("[native code]")||(e.push("toDataURL-overridden"),t=Math.max(t,.8))}catch{}let u=s.getImageData(0,0,200,50).data,l=!0,h=[u[0],u[1],u[2],u[3]];for(let g=4;g<u.length;g+=4)if(u[g]!==h[0]||u[g+1]!==h[1]||u[g+2]!==h[2]){l=!1;break}l&&(e.push("uniform-pixel-data"),t=Math.max(t,.6))}catch{e.push("canvas-error"),t=Math.max(t,.4)}let i=e.length>0;return this.createResult(i,{anomalies:e},t)}};o(L,"id","canvas"),o(L,"category","fingerprint"),o(L,"weight",.5),o(L,"description","Detects canvas fingerprint anomalies");var P=class extends m{async detect(){let e=[],t=0,i=window.AudioContext||window.webkitAudioContext;if(!i)return e.push("audio-context-unavailable"),t=Math.max(t,.4),this.createResult(!0,{anomalies:e},t);let n=null,s=null,r=null;try{n=new i;let a=n.sampleRate;if(a!==44100&&a!==48e3&&a!==96e3&&(e.push("unusual-sample-rate"),t=Math.max(t,.3)),s=n.createOscillator(),r=n.createAnalyser(),!s||!r)e.push("audio-nodes-unavailable"),t=Math.max(t,.5);else{let d=r.fftSize,u=n.destination;(!u||u.maxChannelCount===0)&&(e.push("no-audio-destination"),t=Math.max(t,.6)),u&&u.maxChannelCount<2&&(e.push("mono-audio-only"),t=Math.max(t,.3))}try{i.toString().includes("[native code]")||(e.push("audio-context-overridden"),t=Math.max(t,.7))}catch{}try{if(n.state==="suspended"&&await n.resume().catch(()=>{}),n.state==="running"){let d=n.createOscillator(),u=n.createGain(),l=n.createScriptProcessor?n.createScriptProcessor(4096,1,1):null;l&&(d.type="triangle",d.frequency.value=1e4,u.gain.value=0,d.connect(u),u.connect(l),l.connect(n.destination),d.start(0),await new Promise(h=>setTimeout(h,50)),d.stop(),d.disconnect(),u.disconnect(),l.disconnect())}}catch{e.push("audio-fingerprint-blocked"),t=Math.max(t,.4)}window.OfflineAudioContext||window.webkitOfflineAudioContext||(e.push("offline-audio-context-unavailable"),t=Math.max(t,.3))}catch{e.push("audio-context-error"),t=Math.max(t,.4)}finally{if(s)try{s.disconnect()}catch{}if(r)try{r.disconnect()}catch{}if(n)try{n.close()}catch{}}let c=e.length>0;return this.createResult(c,{anomalies:e},t)}};o(P,"id","audio-context"),o(P,"category","fingerprint"),o(P,"weight",.5),o(P,"description","Detects AudioContext anomalies");var I=class extends m{async detect(){let e=[],t=0,i=window.screen;if(!i)return e.push("no-screen-object"),t=Math.max(t,.6),this.createResult(!0,{anomalies:e},t);let n=i.width,s=i.height,r=i.availWidth,c=i.availHeight,a=i.colorDepth,p=i.pixelDepth,d=window.outerWidth,u=window.outerHeight,l=window.innerWidth,h=window.innerHeight;(d===0||u===0)&&(e.push("zero-outer-dimensions"),t=Math.max(t,.8)),(l===0||h===0)&&(e.push("zero-inner-dimensions"),t=Math.max(t,.7));let g=navigator.userAgent||"";!/Android|iPhone|iPad|iPod|Mobile/i.test(g)&&(n<640||s<480)&&(e.push("very-small-screen"),t=Math.max(t,.5)),(n>7680||s>4320)&&(e.push("unrealistic-screen-size"),t=Math.max(t,.4));let x=[{w:800,h:600},{w:1024,h:768},{w:1920,h:1080}];for(let y of x)if(n===y.w&&s===y.h&&d===y.w&&u===y.h){e.push("headless-default-dimensions"),t=Math.max(t,.5);break}(r>n||c>s)&&(e.push("available-exceeds-total"),t=Math.max(t,.7)),(d>n||u>s)&&(e.push("window-exceeds-screen"),t=Math.max(t,.6)),a!==24&&a!==32&&a!==30&&a!==48&&(e.push("unusual-color-depth"),t=Math.max(t,.3)),a!==p&&(e.push("depth-mismatch"),t=Math.max(t,.3));let _=window.devicePixelRatio;if(_===0||_===void 0?(e.push("missing-device-pixel-ratio"),t=Math.max(t,.5)):(_<.5||_>5)&&(e.push("unusual-device-pixel-ratio"),t=Math.max(t,.4)),i.orientation){let y=i.orientation.type,W=i.orientation.angle;y.includes("landscape")&&n<s&&(e.push("orientation-dimension-mismatch"),t=Math.max(t,.4)),y.includes("portrait")&&n>s&&(e.push("orientation-dimension-mismatch"),t=Math.max(t,.4))}l===d&&h===u&&d>0&&u>0&&(e.push("no-browser-chrome"),t=Math.max(t,.5));let q=e.length>0;return this.createResult(q,{anomalies:e,dimensions:{screen:{width:n,height:s},available:{width:r,height:c},window:{outer:{width:d,height:u},inner:{width:l,height:h}},colorDepth:a,devicePixelRatio:_}},t)}};o(I,"id","screen"),o(I,"category","fingerprint"),o(I,"weight",.4),o(I,"description","Detects unusual screen dimensions");var H=class extends m{async detect(){let e=[],t=0;if(!window.performance||!performance.timing){if(performance.getEntriesByType){let z=performance.getEntriesByType("navigation");if(z.length>0)return this._analyzeNavigationTiming(z[0])}return e.push("no-performance-api"),t=Math.max(t,.3),this.createResult(!0,{anomalies:e},t)}let i=performance.timing,n=i.navigationStart,s=(z,j)=>z===0||j===0?null:z-j,r=s(i.domContentLoadedEventEnd,n),c=s(i.domComplete,n),a=s(i.loadEventEnd,n),p=s(i.domainLookupEnd,i.domainLookupStart),d=s(i.connectEnd,i.connectStart),u=s(i.responseEnd,i.requestStart),l=s(i.domComplete,i.domLoading);r!==null&&r>0&&r<10&&(e.push("instant-dom-content-loaded"),t=Math.max(t,.7)),p===0&&d===0&&u!==null&&u<5&&(e.push("zero-network-timing"),t=Math.max(t,.4)),(r!==null&&r<0||c!==null&&c<0||a!==null&&a<0)&&(e.push("negative-timing"),t=Math.max(t,.8)),i.domContentLoadedEventEnd>0&&i.loadEventEnd>0&&i.domContentLoadedEventEnd>i.loadEventEnd&&(e.push("timing-order-violation"),t=Math.max(t,.7)),l!==null&&l>3e4&&(e.push("excessive-dom-processing"),t=Math.max(t,.3));let h=i.domContentLoadedEventStart-i.responseEnd;i.responseEnd>0&&i.domContentLoadedEventStart>0&&h>0&&h<5&&(e.push("instant-script-execution"),t=Math.max(t,.4));let g=performance.now(),w=g+2;for(;performance.now()<w;);performance.now()===g&&(e.push("frozen-performance-now"),t=Math.max(t,.6));let _=Date.now(),q=performance.now(),y=Date.now();Math.abs(y-_-(performance.now()-q))>100&&(e.push("timing-inconsistency"),t=Math.max(t,.5));let W=e.length>0;return this.createResult(W,{anomalies:e,timings:{domContentLoaded:r,domComplete:c,loadComplete:a,dnsLookup:p,tcpConnection:d,serverResponse:u,domProcessing:l}},t)}_analyzeNavigationTiming(e){let t=[],i=0,n=e.domContentLoadedEventEnd,s=e.loadEventEnd,r=e.domainLookupEnd-e.domainLookupStart,c=e.responseEnd-e.requestStart;n>0&&n<10&&(t.push("instant-dom-content-loaded"),i=Math.max(i,.7)),r===0&&c===0&&(t.push("zero-network-timing"),i=Math.max(i,.4));let a=t.length>0;return this.createResult(a,{anomalies:t,timings:{domContentLoaded:n,loadComplete:s,dnsLookup:r,serverResponse:c}},i)}};o(H,"id","page-load"),o(H,"category","timing"),o(H,"weight",.5),o(H,"description","Detects suspicious page load timing");var A=class extends m{constructor(e={}){super(e),this._domContentLoadedTime=null,this._documentReadyState=document.readyState,this._captureTime=performance.now(),document.readyState==="loading"&&document.addEventListener("DOMContentLoaded",()=>{this._domContentLoadedTime=performance.now()})}async detect(){let e=[],t=0,i=performance.now(),n=document.readyState,s=0,r=0,c=0;if(performance.getEntriesByType){let p=performance.getEntriesByType("resource");s=p.length;for(let d of p)r+=d.duration,d.initiatorType==="script"&&d.name.startsWith("http")&&c++}s===0&&n==="complete"&&(e.push("no-resources-loaded"),t=Math.max(t,.4)),this._domContentLoadedTime&&this._domContentLoadedTime<50&&s===0&&(e.push("instant-ready-no-resources"),t=Math.max(t,.6)),document.hidden&&this._documentReadyState==="loading"&&(e.push("hidden-at-load"),t=Math.max(t,.3)),typeof document.visibilityState>"u"&&(e.push("no-visibility-api"),t=Math.max(t,.4));try{let p=`__bdt_${Math.random().toString(36).slice(2)}`,d=performance.now(),u=document.createElement("div");u.id=p,document.body.appendChild(u);let l=performance.now();document.body.removeChild(u);let h=performance.now(),g=l-d,w=h-l;g===0&&w===0&&(e.push("instant-dom-operations"),t=Math.max(t,.5))}catch{document.body||(e.push("no-document-body"),t=Math.max(t,.4))}if(typeof MutationObserver>"u"&&(e.push("no-mutation-observer"),t=Math.max(t,.5)),typeof requestAnimationFrame>"u"&&(e.push("no-request-animation-frame"),t=Math.max(t,.5)),performance.getEntriesByType){let p=performance.getEntriesByType("paint");!p.find(l=>l.name==="first-paint")&&n==="complete"&&i>1e3&&(e.push("no-first-paint"),t=Math.max(t,.4)),!p.find(l=>l.name==="first-contentful-paint")&&n==="complete"&&i>1e3&&(e.push("no-first-contentful-paint"),t=Math.max(t,.4))}typeof IntersectionObserver>"u"&&(e.push("no-intersection-observer"),t=Math.max(t,.4));let a=e.length>0;return this.createResult(a,{anomalies:e,metrics:{readyState:n,resourceCount:s,externalScriptCount:c,domContentLoadedTime:this._domContentLoadedTime,documentHidden:document.hidden}},t)}};o(A,"id","dom-content-timing"),o(A,"category","timing"),o(A,"weight",.4),o(A,"description","Analyzes DOM content loaded timing patterns");var O=class extends m{async detect(){let e=[],t=0;window.__puppeteer_evaluation_script__&&(e.push("puppeteer-evaluation-script"),t=Math.max(t,1));let i=["__puppeteer_evaluation_script__","__puppeteer","puppeteer"];for(let a of i)a in window&&(e.push(`global-${a}`),t=Math.max(t,1));(navigator.userAgent||"").includes("HeadlessChrome")&&(e.push("headless-chrome-ua"),t=Math.max(t,.9)),(window.cdc_adoQpoasnfa76pfcZLmcfl_Array||window.cdc_adoQpoasnfa76pfcZLmcfl_Promise||window.cdc_adoQpoasnfa76pfcZLmcfl_Symbol)&&(e.push("cdp-artifacts"),t=Math.max(t,1));try{window.eval.toString().includes("puppeteer")&&(e.push("eval-puppeteer"),t=Math.max(t,.9))}catch{}try{throw new Error("stack trace test")}catch(a){let p=a.stack||"";(p.includes("puppeteer")||p.includes("pptr"))&&(e.push("stack-trace-puppeteer"),t=Math.max(t,.8))}window.innerWidth===800&&window.innerHeight===600&&(e.push("default-viewport"),t=Math.max(t,.3));let s=["__zone_symbol__","__next","__webpack","__react","__REACT","__vite","__nuxt"];Object.keys(window).filter(a=>!a.startsWith("__")||typeof window[a]!="function"?!1:!s.some(p=>a.startsWith(p))).length>10&&(e.push("suspicious-bindings"),t=Math.max(t,.5));let c=e.length>0;return this.createResult(c,{indicators:e},t)}};o(O,"id","puppeteer"),o(O,"category","automation"),o(O,"weight",1),o(O,"description","Detects Puppeteer automation artifacts");var B=class extends m{async detect(){let e=[],t=0;window.__playwright&&(e.push("playwright-namespace"),t=Math.max(t,1));let i=["__playwright","__pw_manual","__pwInitScripts","playwright"];for(let r of i)r in window&&(e.push(`global-${r}`),t=Math.max(t,1));window.__playwright__binding__&&(e.push("playwright-binding"),t=Math.max(t,1));let n=navigator.userAgent||"";(n.includes("Playwright")||n.includes("HeadlessChrome"))&&(e.push("playwright-ua-marker"),t=Math.max(t,n.includes("Playwright")?1:.7));try{Object.keys(window).filter(a=>a.startsWith("__pw")).length>0&&(e.push("pw-bindings"),t=Math.max(t,1))}catch{}typeof window.__pw_date_intercepted<"u"&&(e.push("date-interception"),t=Math.max(t,.9)),window.__pw_geolocation__&&(e.push("geolocation-mock"),t=Math.max(t,.9)),window.__pw_permissions__&&(e.push("permissions-override"),t=Math.max(t,.9)),window.__cdpSession__&&(e.push("cdp-session"),t=Math.max(t,.8));try{throw new Error("stack trace test")}catch(r){let c=r.stack||"";(c.includes("playwright")||c.includes("__pw"))&&(e.push("stack-trace-playwright"),t=Math.max(t,.8))}try{let c=new Date().toLocaleString();window.__pwTimezone__&&(e.push("timezone-mock"),t=Math.max(t,.8))}catch{}let s=e.length>0;return this.createResult(s,{indicators:e},t)}};o(B,"id","playwright"),o(B,"category","automation"),o(B,"weight",1),o(B,"description","Detects Playwright automation artifacts");var V=class extends m{async detect(){let e=[],t=0,i=["_selenium","callSelenium","_Selenium_IDE_Recorder","__selenium_evaluate","__selenium_unwrap","__webdriver_evaluate","__webdriver_unwrap","__webdriver_script_function","__webdriver_script_func","__fxdriver_evaluate","__fxdriver_unwrap","webdriver"];for(let a of i)a in window&&(e.push(`global-${a}`),t=Math.max(t,1));let n=["__webdriver_script_fn","__driver_evaluate","__webdriver_evaluate","__selenium_evaluate","__fxdriver_evaluate","__driver_unwrap","__webdriver_unwrap","__selenium_unwrap","__fxdriver_unwrap"];for(let a of n)a in document&&(e.push(`document-${a}`),t=Math.max(t,1));Object.keys(window).filter(a=>a.startsWith("$cdc_")||a.startsWith("$wdc_")||a.startsWith("$chrome_asyncScriptInfo")).length>0&&(e.push("chromedriver-variables"),t=Math.max(t,1)),(window.webdriverCallback||document.documentElement.getAttribute("webdriver"))&&(e.push("geckodriver-artifacts"),t=Math.max(t,1));try{let a=document.documentElement;(a.hasAttribute("webdriver")||a.getAttribute("selenium")||a.getAttribute("driver"))&&(e.push("document-webdriver-attr"),t=Math.max(t,1))}catch{}(window.selenium||window.sideex)&&(e.push("selenium-ide"),t=Math.max(t,1));try{let a=Object.getOwnPropertyDescriptor(Navigator.prototype,"webdriver");a&&a.get&&(a.get.toString().includes("[native code]")||(e.push("webdriver-getter-modified"),t=Math.max(t,.7)))}catch{}(window.domAutomation||window.domAutomationController)&&(e.push("dom-automation"),t=Math.max(t,1)),window.awesomium&&(e.push("awesomium"),t=Math.max(t,.9)),window.external&&window.external.toString().includes("Selenium")&&(e.push("external-selenium"),t=Math.max(t,1));let c=e.length>0;return this.createResult(c,{indicators:e},t)}};o(V,"id","selenium"),o(V,"category","automation"),o(V,"weight",1),o(V,"description","Detects Selenium WebDriver artifacts");var K=class extends m{async detect(){let e=[],t=0;window.callPhantom&&(e.push("callPhantom"),t=Math.max(t,1)),window._phantom&&(e.push("_phantom"),t=Math.max(t,1)),window.phantom&&(e.push("phantom"),t=Math.max(t,1));let i=navigator.userAgent||"";i.includes("PhantomJS")&&(e.push("phantomjs-ua"),t=Math.max(t,1)),window.__phantomas&&(e.push("phantomas"),t=Math.max(t,1)),window.__casper&&(e.push("casperjs"),t=Math.max(t,1)),window.casper&&(e.push("casper-global"),t=Math.max(t,1)),window.slimer&&(e.push("slimerjs"),t=Math.max(t,1)),window.__nightmare&&(e.push("nightmare"),t=Math.max(t,1)),window.nightmare&&(e.push("nightmare-global"),t=Math.max(t,1));try{let r=Function.prototype.toString.call(Function);(r.includes("phantom")||r.includes("Phantom"))&&(e.push("function-prototype-phantom"),t=Math.max(t,.8))}catch{}try{throw new Error("test")}catch(r){(r.stack||"").includes("phantom")&&(e.push("stack-trace-phantom"),t=Math.max(t,.9))}navigator.plugins&&navigator.plugins.length===0&&e.length>0&&(e.push("no-plugins-phantom"),t=Math.max(t,.5));let n=["__PHANTOM__","PHANTOM"];for(let r of n)r in window&&(e.push(`phantom-prop-${r.toLowerCase()}`),t=Math.max(t,.9));i.includes("QtWebKit")&&(e.push("qtwebkit"),t=Math.max(t,.7));let s=e.length>0;return this.createResult(s,{indicators:e},t)}};o(K,"id","phantomjs"),o(K,"category","automation"),o(K,"weight",1),o(K,"description","Detects PhantomJS automation artifacts");var Z={WebDriverSignal:v,HeadlessSignal:M,NavigatorAnomalySignal:b,PermissionsSignal:T,MouseMovementSignal:k,KeyboardPatternSignal:E,InteractionTimingSignal:C,ScrollBehaviorSignal:D,PluginsSignal:R,WebGLSignal:S,CanvasSignal:L,AudioContextSignal:P,ScreenSignal:I,PageLoadSignal:H,DOMContentTimingSignal:A,PuppeteerSignal:O,PlaywrightSignal:B,SeleniumSignal:V,PhantomJSSignal:K},J=[new v,new M,new b,new T,new R,new S,new L,new P,new I,new H,new A,new O,new B,new V,new K],tt=[new k,new E,new C,new D],lt=[...J,...tt];function X(f={}){let{includeInteractionSignals:e=!0,instantBotSignals:t=["webdriver","puppeteer","playwright","selenium","phantomjs"],...i}=f,s=(e?[v,M,b,T,R,S,L,P,I,H,A,O,B,V,K,k,E,C,D]:[v,M,b,T,R,S,L,P,I,H,A,O,B,V,K]).map(r=>new r);return new $({signals:s,instantBotSignals:t,...i})}async function Q(f={}){return X({includeInteractionSignals:!f.skipInteractionSignals}).detect(f)}async function et(){return Q({skipInteractionSignals:!0})}var ut={BotDetector:$,createDetector:X,detect:Q,detectInstant:et,Signal:m,Signals:Z,Verdict:U};return ht(dt);})();
|
package/package.json
CHANGED
package/src/core/BotDetector.js
CHANGED
|
@@ -145,18 +145,19 @@ class BotDetector {
|
|
|
145
145
|
|
|
146
146
|
// Run all signals with timeout
|
|
147
147
|
const detectionPromises = signalsToRun.map(async signal => {
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
148
|
+
// preventing 15 leaked timers per detect() call in SPAs.
|
|
149
|
+
let timeoutId;
|
|
150
|
+
const timeoutPromise = new Promise(resolve => {
|
|
151
|
+
timeoutId = setTimeout(() => resolve({
|
|
152
|
+
triggered: false,
|
|
153
|
+
value: null,
|
|
154
|
+
confidence: 0,
|
|
155
|
+
error: 'timeout',
|
|
156
|
+
}), this._detectionTimeout);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
const result = await Promise.race([signal.run(), timeoutPromise]);
|
|
160
|
+
clearTimeout(timeoutId);
|
|
160
161
|
return { signal, result };
|
|
161
162
|
});
|
|
162
163
|
|
|
@@ -257,13 +258,23 @@ class BotDetector {
|
|
|
257
258
|
}
|
|
258
259
|
|
|
259
260
|
/**
|
|
260
|
-
*
|
|
261
|
-
*
|
|
262
|
-
*
|
|
261
|
+
* @deprecated Use `createDetector()` from '@niksbanna/bot-detector' instead.
|
|
262
|
+
* This method cannot load default signals from here due to module boundaries.
|
|
263
|
+
*
|
|
264
|
+
* @example
|
|
265
|
+
* // Correct:
|
|
266
|
+
* import { createDetector } from '@niksbanna/bot-detector';
|
|
267
|
+
* const detector = createDetector();
|
|
268
|
+
*
|
|
269
|
+
* @throws {Error} Always — to prevent silent empty-detector bugs.
|
|
263
270
|
*/
|
|
264
|
-
static withDefaults(
|
|
265
|
-
|
|
266
|
-
|
|
271
|
+
static withDefaults() {
|
|
272
|
+
throw new Error(
|
|
273
|
+
'BotDetector.withDefaults() is not supported. ' +
|
|
274
|
+
'Use createDetector() from \'@niksbanna/bot-detector\' instead:\n' +
|
|
275
|
+
' import { createDetector } from \'@niksbanna/bot-detector\';\n' +
|
|
276
|
+
' const detector = createDetector();'
|
|
277
|
+
);
|
|
267
278
|
}
|
|
268
279
|
}
|
|
269
280
|
|
|
@@ -79,6 +79,7 @@ class ScoringEngine {
|
|
|
79
79
|
*/
|
|
80
80
|
getBreakdown() {
|
|
81
81
|
const breakdown = [];
|
|
82
|
+
const score = this.calculate();
|
|
82
83
|
|
|
83
84
|
for (const [signalId, data] of this._results) {
|
|
84
85
|
breakdown.push({
|
|
@@ -87,8 +88,8 @@ class ScoringEngine {
|
|
|
87
88
|
confidence: data.confidence,
|
|
88
89
|
weight: data.weight,
|
|
89
90
|
contribution: data.contribution,
|
|
90
|
-
percentOfScore:
|
|
91
|
-
? (data.contribution /
|
|
91
|
+
percentOfScore: score > 0
|
|
92
|
+
? (data.contribution / score * 100).toFixed(1)
|
|
92
93
|
: '0.0',
|
|
93
94
|
});
|
|
94
95
|
}
|
package/src/index.js
CHANGED
|
@@ -158,16 +158,24 @@ function createDetector(options = {}) {
|
|
|
158
158
|
...detectorOptions
|
|
159
159
|
} = options;
|
|
160
160
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
161
|
+
// singletons. If two detectors share the same signal instances, calling
|
|
162
|
+
// detector1.reset() would corrupt detector2's cached results.
|
|
163
|
+
const signalClasses = includeInteractionSignals
|
|
164
|
+
? [
|
|
165
|
+
WebDriverSignal, HeadlessSignal, NavigatorAnomalySignal, PermissionsSignal,
|
|
166
|
+
PluginsSignal, WebGLSignal, CanvasSignal, AudioContextSignal, ScreenSignal,
|
|
167
|
+
PageLoadSignal, DOMContentTimingSignal,
|
|
168
|
+
PuppeteerSignal, PlaywrightSignal, SeleniumSignal, PhantomJSSignal,
|
|
169
|
+
MouseMovementSignal, KeyboardPatternSignal, InteractionTimingSignal, ScrollBehaviorSignal,
|
|
170
|
+
]
|
|
171
|
+
: [
|
|
172
|
+
WebDriverSignal, HeadlessSignal, NavigatorAnomalySignal, PermissionsSignal,
|
|
173
|
+
PluginsSignal, WebGLSignal, CanvasSignal, AudioContextSignal, ScreenSignal,
|
|
174
|
+
PageLoadSignal, DOMContentTimingSignal,
|
|
175
|
+
PuppeteerSignal, PlaywrightSignal, SeleniumSignal, PhantomJSSignal,
|
|
176
|
+
];
|
|
177
|
+
|
|
178
|
+
const signals = signalClasses.map(Cls => new Cls());
|
|
171
179
|
|
|
172
180
|
return new BotDetector({
|
|
173
181
|
signals,
|
|
@@ -107,16 +107,14 @@ class PhantomJSSignal extends Signal {
|
|
|
107
107
|
}
|
|
108
108
|
}
|
|
109
109
|
|
|
110
|
-
// Check for specific PhantomJS window properties
|
|
110
|
+
// Check for specific PhantomJS window properties.
|
|
111
111
|
const phantomProps = [
|
|
112
112
|
'__PHANTOM__',
|
|
113
113
|
'PHANTOM',
|
|
114
|
-
'Buffer', // PhantomJS exposes Node.js Buffer
|
|
115
|
-
'process', // May expose Node.js process
|
|
116
114
|
];
|
|
117
115
|
|
|
118
116
|
for (const prop of phantomProps) {
|
|
119
|
-
if (prop in window
|
|
117
|
+
if (prop in window) {
|
|
120
118
|
indicators.push(`phantom-prop-${prop.toLowerCase()}`);
|
|
121
119
|
confidence = Math.max(confidence, 0.9);
|
|
122
120
|
}
|
|
@@ -52,12 +52,6 @@ class PlaywrightSignal extends Signal {
|
|
|
52
52
|
confidence = Math.max(confidence, ua.includes('Playwright') ? 1.0 : 0.7);
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
-
// Check for navigator.webdriver (Playwright sets this in headless)
|
|
56
|
-
if (navigator.webdriver === true) {
|
|
57
|
-
indicators.push('webdriver-flag');
|
|
58
|
-
confidence = Math.max(confidence, 0.8);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
55
|
// Check for Playwright's evaluate scope pattern
|
|
62
56
|
try {
|
|
63
57
|
// Playwright injects __pwBinding__ functions
|
|
@@ -83,35 +83,36 @@ class PuppeteerSignal extends Signal {
|
|
|
83
83
|
confidence = Math.max(confidence, 0.3);
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
-
//
|
|
87
|
-
|
|
88
|
-
indicators.push('webdriver-flag');
|
|
89
|
-
confidence = Math.max(confidence, 0.9);
|
|
90
|
-
}
|
|
86
|
+
// navigator.webdriver is already exclusively checked by WebDriverSignal (environment).
|
|
87
|
+
// Duplicating it here causes triple-counting of the same property across signals.
|
|
91
88
|
|
|
92
89
|
// Check for binding injection pattern
|
|
93
90
|
// Puppeteer's exposeFunction creates window bindings
|
|
94
|
-
//
|
|
91
|
+
// (Next.js, webpack, React DevTools, Angular) push __* count past the
|
|
92
|
+
// old threshold of 5 on perfectly normal pages.
|
|
93
|
+
const FRAMEWORK_PREFIXES = [
|
|
94
|
+
'__zone_symbol__', // Angular / Zone.js
|
|
95
|
+
'__next', // Next.js
|
|
96
|
+
'__webpack', // webpack
|
|
97
|
+
'__react', // React DevTools
|
|
98
|
+
'__REACT',
|
|
99
|
+
'__vite', // Vite
|
|
100
|
+
'__nuxt', // Nuxt.js
|
|
101
|
+
];
|
|
95
102
|
const suspiciousBindings = Object.keys(window).filter(key => {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
103
|
+
if (!key.startsWith('__')) return false;
|
|
104
|
+
if (typeof window[key] !== 'function') return false;
|
|
105
|
+
return !FRAMEWORK_PREFIXES.some(prefix => key.startsWith(prefix));
|
|
99
106
|
});
|
|
100
107
|
|
|
101
|
-
if (suspiciousBindings.length >
|
|
108
|
+
if (suspiciousBindings.length > 10) {
|
|
102
109
|
indicators.push('suspicious-bindings');
|
|
103
110
|
confidence = Math.max(confidence, 0.5);
|
|
104
111
|
}
|
|
105
112
|
|
|
106
|
-
//
|
|
107
|
-
//
|
|
108
|
-
//
|
|
109
|
-
if (typeof window.chrome !== 'undefined') {
|
|
110
|
-
if (!window.chrome.runtime) {
|
|
111
|
-
indicators.push('incomplete-chrome-object');
|
|
112
|
-
confidence = Math.max(confidence, 0.4);
|
|
113
|
-
}
|
|
114
|
-
}
|
|
113
|
+
// window.chrome.runtime is ONLY populated inside Chrome extensions.
|
|
114
|
+
// Its absence is completely normal for all real Chrome users.
|
|
115
|
+
// This check was causing every non-extension Chrome user to be flagged as Puppeteer.
|
|
115
116
|
|
|
116
117
|
const triggered = indicators.length > 0;
|
|
117
118
|
|
|
@@ -18,11 +18,8 @@ class SeleniumSignal extends Signal {
|
|
|
18
18
|
const indicators = [];
|
|
19
19
|
let confidence = 0;
|
|
20
20
|
|
|
21
|
-
//
|
|
22
|
-
|
|
23
|
-
indicators.push('webdriver-flag');
|
|
24
|
-
confidence = Math.max(confidence, 1.0);
|
|
25
|
-
}
|
|
21
|
+
// navigator.webdriver is already exclusively checked by WebDriverSignal (environment).
|
|
22
|
+
// Duplicating it here causes triple-counting of the same property across signals.
|
|
26
23
|
|
|
27
24
|
// Check for Selenium-specific globals
|
|
28
25
|
const seleniumGlobals = [
|
|
@@ -19,7 +19,7 @@ class KeyboardPatternSignal extends Signal {
|
|
|
19
19
|
super(options);
|
|
20
20
|
this._keystrokes = [];
|
|
21
21
|
this._isTracking = false;
|
|
22
|
-
this._trackingDuration = options.trackingDuration ||
|
|
22
|
+
this._trackingDuration = Math.min(options.trackingDuration || 2500, 2500);
|
|
23
23
|
this._minKeystrokes = options.minKeystrokes || 10;
|
|
24
24
|
this._boundKeydownHandler = null;
|
|
25
25
|
this._boundKeyupHandler = null;
|
|
@@ -19,7 +19,7 @@ class MouseMovementSignal extends Signal {
|
|
|
19
19
|
super(options);
|
|
20
20
|
this._movements = [];
|
|
21
21
|
this._isTracking = false;
|
|
22
|
-
this._trackingDuration = options.trackingDuration ||
|
|
22
|
+
this._trackingDuration = Math.min(options.trackingDuration || 2500, 2500);
|
|
23
23
|
this._minMovements = options.minMovements || 5;
|
|
24
24
|
this._boundHandler = null;
|
|
25
25
|
}
|
|
@@ -19,7 +19,7 @@ class ScrollBehaviorSignal extends Signal {
|
|
|
19
19
|
super(options);
|
|
20
20
|
this._scrollEvents = [];
|
|
21
21
|
this._isTracking = false;
|
|
22
|
-
this._trackingDuration = options.trackingDuration ||
|
|
22
|
+
this._trackingDuration = Math.min(options.trackingDuration || 2500, 2500);
|
|
23
23
|
this._boundHandler = null;
|
|
24
24
|
}
|
|
25
25
|
|
|
@@ -25,14 +25,14 @@ class HeadlessSignal extends Signal {
|
|
|
25
25
|
confidence = Math.max(confidence, 1.0);
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
// Check for missing chrome
|
|
28
|
+
// Check for missing window.chrome in Chrome (true headless indicator).
|
|
29
|
+
// NOTE: chrome.runtime is only populated inside Chrome extensions, so
|
|
30
|
+
// its absence is perfectly normal for real users without extensions.
|
|
31
|
+
// Only flag when the entire window.chrome object is absent.
|
|
29
32
|
if (ua.includes('Chrome') && !ua.includes('Chromium')) {
|
|
30
33
|
if (typeof window.chrome === 'undefined') {
|
|
31
34
|
indicators.push('missing-chrome-object');
|
|
32
35
|
confidence = Math.max(confidence, 0.6);
|
|
33
|
-
} else if (!window.chrome.runtime) {
|
|
34
|
-
indicators.push('missing-chrome-runtime');
|
|
35
|
-
confidence = Math.max(confidence, 0.4);
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
38
|
|
|
@@ -35,9 +35,10 @@ class NavigatorAnomalySignal extends Signal {
|
|
|
35
35
|
totalScore += 1;
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
// Check for empty or suspicious platform
|
|
38
|
+
// Check for empty or suspicious platform.
|
|
39
39
|
checksPerformed++;
|
|
40
|
-
|
|
40
|
+
const isModernChrome = ua.includes('Chrome') && !ua.includes('Chromium');
|
|
41
|
+
if (!isModernChrome && (!platform || platform === '' || platform === 'undefined')) {
|
|
41
42
|
anomalies.push('empty-platform');
|
|
42
43
|
totalScore += 1;
|
|
43
44
|
}
|
|
@@ -81,9 +81,10 @@ class DOMContentTimingSignal extends Signal {
|
|
|
81
81
|
|
|
82
82
|
// Check DOM manipulation timing
|
|
83
83
|
try {
|
|
84
|
+
const randomId = `__bdt_${Math.random().toString(36).slice(2)}`;
|
|
84
85
|
const startMutation = performance.now();
|
|
85
86
|
const testDiv = document.createElement('div');
|
|
86
|
-
testDiv.id =
|
|
87
|
+
testDiv.id = randomId;
|
|
87
88
|
document.body.appendChild(testDiv);
|
|
88
89
|
const afterAppend = performance.now();
|
|
89
90
|
document.body.removeChild(testDiv);
|