s3db.js 13.6.0 → 14.0.2
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 +139 -43
- package/dist/s3db.cjs +72425 -38970
- package/dist/s3db.cjs.map +1 -1
- package/dist/s3db.es.js +72177 -38764
- package/dist/s3db.es.js.map +1 -1
- package/mcp/lib/base-handler.js +157 -0
- package/mcp/lib/handlers/connection-handler.js +280 -0
- package/mcp/lib/handlers/query-handler.js +533 -0
- package/mcp/lib/handlers/resource-handler.js +428 -0
- package/mcp/lib/tool-registry.js +336 -0
- package/mcp/lib/tools/connection-tools.js +161 -0
- package/mcp/lib/tools/query-tools.js +267 -0
- package/mcp/lib/tools/resource-tools.js +404 -0
- package/package.json +94 -49
- package/src/clients/memory-client.class.js +346 -191
- package/src/clients/memory-storage.class.js +300 -84
- package/src/clients/s3-client.class.js +7 -6
- package/src/concerns/geo-encoding.js +19 -2
- package/src/concerns/ip.js +59 -9
- package/src/concerns/money.js +8 -1
- package/src/concerns/password-hashing.js +49 -8
- package/src/concerns/plugin-storage.js +186 -18
- package/src/concerns/storage-drivers/filesystem-driver.js +284 -0
- package/src/database.class.js +139 -29
- package/src/errors.js +332 -42
- package/src/plugins/api/auth/oidc-auth.js +66 -17
- package/src/plugins/api/auth/strategies/base-strategy.class.js +74 -0
- package/src/plugins/api/auth/strategies/factory.class.js +63 -0
- package/src/plugins/api/auth/strategies/global-strategy.class.js +44 -0
- package/src/plugins/api/auth/strategies/path-based-strategy.class.js +83 -0
- package/src/plugins/api/auth/strategies/path-rules-strategy.class.js +118 -0
- package/src/plugins/api/concerns/failban-manager.js +106 -57
- package/src/plugins/api/concerns/opengraph-helper.js +116 -0
- package/src/plugins/api/concerns/route-context.js +601 -0
- package/src/plugins/api/concerns/state-machine.js +288 -0
- package/src/plugins/api/index.js +180 -41
- package/src/plugins/api/routes/auth-routes.js +198 -30
- package/src/plugins/api/routes/resource-routes.js +19 -4
- package/src/plugins/api/server/health-manager.class.js +163 -0
- package/src/plugins/api/server/middleware-chain.class.js +310 -0
- package/src/plugins/api/server/router.class.js +472 -0
- package/src/plugins/api/server.js +280 -1303
- package/src/plugins/api/utils/custom-routes.js +17 -5
- package/src/plugins/api/utils/guards.js +76 -17
- package/src/plugins/api/utils/openapi-generator-cached.class.js +133 -0
- package/src/plugins/api/utils/openapi-generator.js +7 -6
- package/src/plugins/api/utils/template-engine.js +77 -3
- package/src/plugins/audit.plugin.js +30 -8
- package/src/plugins/backup.plugin.js +110 -14
- package/src/plugins/cache/cache.class.js +22 -5
- package/src/plugins/cache/filesystem-cache.class.js +116 -19
- package/src/plugins/cache/memory-cache.class.js +211 -57
- package/src/plugins/cache/multi-tier-cache.class.js +371 -0
- package/src/plugins/cache/partition-aware-filesystem-cache.class.js +168 -47
- package/src/plugins/cache/redis-cache.class.js +552 -0
- package/src/plugins/cache/s3-cache.class.js +17 -8
- package/src/plugins/cache.plugin.js +176 -61
- package/src/plugins/cloud-inventory/drivers/alibaba-driver.js +8 -1
- package/src/plugins/cloud-inventory/drivers/aws-driver.js +60 -29
- package/src/plugins/cloud-inventory/drivers/azure-driver.js +8 -1
- package/src/plugins/cloud-inventory/drivers/base-driver.js +16 -2
- package/src/plugins/cloud-inventory/drivers/cloudflare-driver.js +8 -1
- package/src/plugins/cloud-inventory/drivers/digitalocean-driver.js +8 -1
- package/src/plugins/cloud-inventory/drivers/hetzner-driver.js +8 -1
- package/src/plugins/cloud-inventory/drivers/linode-driver.js +8 -1
- package/src/plugins/cloud-inventory/drivers/mongodb-atlas-driver.js +8 -1
- package/src/plugins/cloud-inventory/drivers/vultr-driver.js +8 -1
- package/src/plugins/cloud-inventory/index.js +29 -8
- package/src/plugins/cloud-inventory/registry.js +64 -42
- package/src/plugins/cloud-inventory.plugin.js +240 -138
- package/src/plugins/concerns/plugin-dependencies.js +54 -0
- package/src/plugins/concerns/resource-names.js +100 -0
- package/src/plugins/consumers/index.js +10 -2
- package/src/plugins/consumers/sqs-consumer.js +12 -2
- package/src/plugins/cookie-farm-suite.plugin.js +278 -0
- package/src/plugins/cookie-farm.errors.js +73 -0
- package/src/plugins/cookie-farm.plugin.js +869 -0
- package/src/plugins/costs.plugin.js +7 -1
- package/src/plugins/eventual-consistency/analytics.js +94 -19
- package/src/plugins/eventual-consistency/config.js +15 -7
- package/src/plugins/eventual-consistency/consolidation.js +29 -11
- package/src/plugins/eventual-consistency/garbage-collection.js +3 -1
- package/src/plugins/eventual-consistency/helpers.js +39 -14
- package/src/plugins/eventual-consistency/install.js +21 -2
- package/src/plugins/eventual-consistency/utils.js +32 -10
- package/src/plugins/fulltext.plugin.js +38 -11
- package/src/plugins/geo.plugin.js +61 -9
- package/src/plugins/identity/concerns/config.js +61 -0
- package/src/plugins/identity/concerns/mfa-manager.js +15 -2
- package/src/plugins/identity/concerns/rate-limit.js +124 -0
- package/src/plugins/identity/concerns/resource-schemas.js +9 -1
- package/src/plugins/identity/concerns/token-generator.js +29 -4
- package/src/plugins/identity/drivers/auth-driver.interface.js +76 -0
- package/src/plugins/identity/drivers/client-credentials-driver.js +127 -0
- package/src/plugins/identity/drivers/index.js +18 -0
- package/src/plugins/identity/drivers/password-driver.js +122 -0
- package/src/plugins/identity/email-service.js +17 -2
- package/src/plugins/identity/index.js +413 -69
- package/src/plugins/identity/oauth2-server.js +413 -30
- package/src/plugins/identity/oidc-discovery.js +16 -8
- package/src/plugins/identity/rsa-keys.js +115 -35
- package/src/plugins/identity/server.js +166 -45
- package/src/plugins/identity/session-manager.js +53 -7
- package/src/plugins/identity/ui/pages/mfa-verification.js +17 -15
- package/src/plugins/identity/ui/routes.js +363 -255
- package/src/plugins/importer/index.js +153 -20
- package/src/plugins/index.js +9 -2
- package/src/plugins/kubernetes-inventory/index.js +6 -0
- package/src/plugins/kubernetes-inventory/k8s-driver.js +867 -0
- package/src/plugins/kubernetes-inventory/resource-types.js +274 -0
- package/src/plugins/kubernetes-inventory.plugin.js +980 -0
- package/src/plugins/metrics.plugin.js +64 -16
- package/src/plugins/ml/base-model.class.js +25 -15
- package/src/plugins/ml/regression-model.class.js +1 -1
- package/src/plugins/ml.errors.js +57 -25
- package/src/plugins/ml.plugin.js +28 -4
- package/src/plugins/namespace.js +210 -0
- package/src/plugins/plugin.class.js +180 -8
- package/src/plugins/puppeteer/console-monitor.js +729 -0
- package/src/plugins/puppeteer/cookie-manager.js +492 -0
- package/src/plugins/puppeteer/network-monitor.js +816 -0
- package/src/plugins/puppeteer/performance-manager.js +746 -0
- package/src/plugins/puppeteer/proxy-manager.js +478 -0
- package/src/plugins/puppeteer/stealth-manager.js +556 -0
- package/src/plugins/puppeteer.errors.js +81 -0
- package/src/plugins/puppeteer.plugin.js +1327 -0
- package/src/plugins/queue-consumer.plugin.js +69 -14
- package/src/plugins/recon/behaviors/uptime-behavior.js +691 -0
- package/src/plugins/recon/concerns/command-runner.js +148 -0
- package/src/plugins/recon/concerns/diff-detector.js +372 -0
- package/src/plugins/recon/concerns/fingerprint-builder.js +307 -0
- package/src/plugins/recon/concerns/process-manager.js +338 -0
- package/src/plugins/recon/concerns/report-generator.js +478 -0
- package/src/plugins/recon/concerns/security-analyzer.js +571 -0
- package/src/plugins/recon/concerns/target-normalizer.js +68 -0
- package/src/plugins/recon/config/defaults.js +321 -0
- package/src/plugins/recon/config/resources.js +370 -0
- package/src/plugins/recon/index.js +778 -0
- package/src/plugins/recon/managers/dependency-manager.js +174 -0
- package/src/plugins/recon/managers/scheduler-manager.js +179 -0
- package/src/plugins/recon/managers/storage-manager.js +745 -0
- package/src/plugins/recon/managers/target-manager.js +274 -0
- package/src/plugins/recon/stages/asn-stage.js +314 -0
- package/src/plugins/recon/stages/certificate-stage.js +84 -0
- package/src/plugins/recon/stages/dns-stage.js +107 -0
- package/src/plugins/recon/stages/dnsdumpster-stage.js +362 -0
- package/src/plugins/recon/stages/fingerprint-stage.js +71 -0
- package/src/plugins/recon/stages/google-dorks-stage.js +440 -0
- package/src/plugins/recon/stages/http-stage.js +89 -0
- package/src/plugins/recon/stages/latency-stage.js +148 -0
- package/src/plugins/recon/stages/massdns-stage.js +302 -0
- package/src/plugins/recon/stages/osint-stage.js +1373 -0
- package/src/plugins/recon/stages/ports-stage.js +169 -0
- package/src/plugins/recon/stages/screenshot-stage.js +94 -0
- package/src/plugins/recon/stages/secrets-stage.js +514 -0
- package/src/plugins/recon/stages/subdomains-stage.js +295 -0
- package/src/plugins/recon/stages/tls-audit-stage.js +78 -0
- package/src/plugins/recon/stages/vulnerability-stage.js +78 -0
- package/src/plugins/recon/stages/web-discovery-stage.js +113 -0
- package/src/plugins/recon/stages/whois-stage.js +349 -0
- package/src/plugins/recon.plugin.js +75 -0
- package/src/plugins/recon.plugin.js.backup +2635 -0
- package/src/plugins/relation.errors.js +87 -14
- package/src/plugins/replicator.plugin.js +514 -137
- package/src/plugins/replicators/base-replicator.class.js +89 -1
- package/src/plugins/replicators/bigquery-replicator.class.js +66 -22
- package/src/plugins/replicators/dynamodb-replicator.class.js +22 -15
- package/src/plugins/replicators/mongodb-replicator.class.js +22 -15
- package/src/plugins/replicators/mysql-replicator.class.js +52 -17
- package/src/plugins/replicators/planetscale-replicator.class.js +30 -4
- package/src/plugins/replicators/postgres-replicator.class.js +62 -27
- package/src/plugins/replicators/s3db-replicator.class.js +25 -18
- package/src/plugins/replicators/schema-sync.helper.js +3 -3
- package/src/plugins/replicators/sqs-replicator.class.js +8 -2
- package/src/plugins/replicators/turso-replicator.class.js +23 -3
- package/src/plugins/replicators/webhook-replicator.class.js +42 -4
- package/src/plugins/s3-queue.plugin.js +464 -65
- package/src/plugins/scheduler.plugin.js +20 -6
- package/src/plugins/state-machine.plugin.js +40 -9
- package/src/plugins/tfstate/README.md +126 -126
- package/src/plugins/tfstate/base-driver.js +28 -4
- package/src/plugins/tfstate/errors.js +65 -10
- package/src/plugins/tfstate/filesystem-driver.js +52 -8
- package/src/plugins/tfstate/index.js +163 -90
- package/src/plugins/tfstate/s3-driver.js +64 -6
- package/src/plugins/ttl.plugin.js +72 -17
- package/src/plugins/vector/distances.js +18 -12
- package/src/plugins/vector/kmeans.js +26 -4
- package/src/resource.class.js +115 -19
- package/src/testing/factory.class.js +20 -3
- package/src/testing/seeder.class.js +7 -1
- package/src/clients/memory-client.md +0 -917
- package/src/plugins/cloud-inventory/drivers/mock-drivers.js +0 -449
|
@@ -0,0 +1,556 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* StealthManager - Advanced Anti-Detection for Cookie Farm
|
|
3
|
+
*
|
|
4
|
+
* Implements sophisticated evasion techniques against cookie farm detection:
|
|
5
|
+
*
|
|
6
|
+
* Defense Mechanisms We Counter:
|
|
7
|
+
* 1. Session binding (IP/UA consistency)
|
|
8
|
+
* 2. Rate limiting (human-like timing)
|
|
9
|
+
* 3. JS challenges (automatic execution)
|
|
10
|
+
* 4. Fingerprint tracking (consistent profiles)
|
|
11
|
+
* 5. Geographic impossible travel (proxy planning)
|
|
12
|
+
* 6. User-Agent churn detection (stable profiles)
|
|
13
|
+
* 7. Honeypot cookies (JS execution simulation)
|
|
14
|
+
* 8. Behavioral analysis (mouse/scroll/timing)
|
|
15
|
+
* 9. Request velocity monitoring (throttled requests)
|
|
16
|
+
* 10. Multi-session concurrent use detection (serialized usage)
|
|
17
|
+
*
|
|
18
|
+
* Key Strategies:
|
|
19
|
+
* - ONE persona = ONE complete identity (never mix!)
|
|
20
|
+
* - Maintain consistency: IP, UA, viewport, timezone, language
|
|
21
|
+
* - Human-like timing: delays, jitter, patterns
|
|
22
|
+
* - JS execution: load resources, execute challenges
|
|
23
|
+
* - Geographic coherence: proxy geo + timezone + language match
|
|
24
|
+
*/
|
|
25
|
+
import { NavigationError } from '../puppeteer.errors.js';
|
|
26
|
+
|
|
27
|
+
export class StealthManager {
|
|
28
|
+
constructor(plugin) {
|
|
29
|
+
this.plugin = plugin;
|
|
30
|
+
this.config = plugin.config.stealth || {};
|
|
31
|
+
|
|
32
|
+
// Timing profiles (human-like delays)
|
|
33
|
+
this.timingProfiles = {
|
|
34
|
+
'very-slow': { min: 5000, max: 15000, jitter: 2000 },
|
|
35
|
+
'slow': { min: 3000, max: 8000, jitter: 1500 },
|
|
36
|
+
'normal': { min: 1000, max: 5000, jitter: 1000 },
|
|
37
|
+
'fast': { min: 500, max: 2000, jitter: 500 }
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
// Geographic data (timezone + language mapping)
|
|
41
|
+
this.geoData = {
|
|
42
|
+
'US': { timezones: ['America/New_York', 'America/Chicago', 'America/Los_Angeles'], languages: ['en-US'] },
|
|
43
|
+
'BR': { timezones: ['America/Sao_Paulo'], languages: ['pt-BR', 'en-US'] },
|
|
44
|
+
'GB': { timezones: ['Europe/London'], languages: ['en-GB', 'en-US'] },
|
|
45
|
+
'DE': { timezones: ['Europe/Berlin'], languages: ['de-DE', 'en-US'] },
|
|
46
|
+
'FR': { timezones: ['Europe/Paris'], languages: ['fr-FR', 'en-US'] },
|
|
47
|
+
'JP': { timezones: ['Asia/Tokyo'], languages: ['ja-JP', 'en-US'] },
|
|
48
|
+
'CN': { timezones: ['Asia/Shanghai'], languages: ['zh-CN', 'en-US'] }
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Create a stealth-optimized persona profile
|
|
54
|
+
* Ensures all fingerprint components are consistent
|
|
55
|
+
*/
|
|
56
|
+
async createStealthProfile(options = {}) {
|
|
57
|
+
const {
|
|
58
|
+
proxy = null,
|
|
59
|
+
country = null, // Target country (if known from proxy)
|
|
60
|
+
timingProfile = 'normal',
|
|
61
|
+
screenResolution = null
|
|
62
|
+
} = options;
|
|
63
|
+
|
|
64
|
+
// 1. Select consistent timezone + language based on country
|
|
65
|
+
const geoProfile = this._selectGeoProfile(country);
|
|
66
|
+
|
|
67
|
+
// 2. Generate consistent user agent
|
|
68
|
+
const userAgent = this._generateConsistentUserAgent();
|
|
69
|
+
|
|
70
|
+
// 3. Generate viewport that matches screen resolution
|
|
71
|
+
const viewport = this._generateConsistentViewport(screenResolution);
|
|
72
|
+
|
|
73
|
+
// 4. Create fingerprint profile
|
|
74
|
+
const profile = {
|
|
75
|
+
// Core identity
|
|
76
|
+
userAgent,
|
|
77
|
+
viewport,
|
|
78
|
+
timezone: geoProfile.timezone,
|
|
79
|
+
language: geoProfile.language,
|
|
80
|
+
|
|
81
|
+
// Consistency markers
|
|
82
|
+
acceptLanguage: `${geoProfile.language},en;q=0.9`,
|
|
83
|
+
acceptEncoding: 'gzip, deflate, br',
|
|
84
|
+
accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
|
|
85
|
+
|
|
86
|
+
// Platform consistency
|
|
87
|
+
platform: this._getPlatformFromUA(userAgent),
|
|
88
|
+
hardwareConcurrency: this._getHardwareConcurrency(userAgent),
|
|
89
|
+
deviceMemory: this._getDeviceMemory(userAgent),
|
|
90
|
+
|
|
91
|
+
// Timing behavior
|
|
92
|
+
timingProfile: this.timingProfiles[timingProfile],
|
|
93
|
+
|
|
94
|
+
// Proxy binding
|
|
95
|
+
proxyId: proxy?.id || null,
|
|
96
|
+
proxyCountry: country,
|
|
97
|
+
|
|
98
|
+
// Behavioral markers
|
|
99
|
+
behavioral: {
|
|
100
|
+
typingSpeed: { min: 100, max: 300 }, // ms per character
|
|
101
|
+
mouseMovements: true,
|
|
102
|
+
scrollBehavior: 'smooth',
|
|
103
|
+
clickDelay: { min: 200, max: 800 }
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
return profile;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Select geo profile (timezone + language) based on country
|
|
112
|
+
* @private
|
|
113
|
+
*/
|
|
114
|
+
_selectGeoProfile(country) {
|
|
115
|
+
if (country && this.geoData[country]) {
|
|
116
|
+
const data = this.geoData[country];
|
|
117
|
+
const timezone = data.timezones[Math.floor(Math.random() * data.timezones.length)];
|
|
118
|
+
const language = data.languages[0]; // Primary language
|
|
119
|
+
return { timezone, language };
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Default to US
|
|
123
|
+
return {
|
|
124
|
+
timezone: 'America/New_York',
|
|
125
|
+
language: 'en-US'
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Generate consistent user agent (avoid churn)
|
|
131
|
+
* @private
|
|
132
|
+
*/
|
|
133
|
+
_generateConsistentUserAgent() {
|
|
134
|
+
// Use common, stable user agents
|
|
135
|
+
const stableUserAgents = [
|
|
136
|
+
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
|
|
137
|
+
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36',
|
|
138
|
+
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
|
|
139
|
+
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
|
|
140
|
+
];
|
|
141
|
+
|
|
142
|
+
return stableUserAgents[Math.floor(Math.random() * stableUserAgents.length)];
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Generate viewport consistent with screen resolution
|
|
147
|
+
* @private
|
|
148
|
+
*/
|
|
149
|
+
_generateConsistentViewport(screenResolution) {
|
|
150
|
+
const commonResolutions = [
|
|
151
|
+
{ width: 1920, height: 1080, deviceScaleFactor: 1 }, // Full HD
|
|
152
|
+
{ width: 1680, height: 1050, deviceScaleFactor: 1 }, // WSXGA+
|
|
153
|
+
{ width: 1440, height: 900, deviceScaleFactor: 1 }, // WXGA+
|
|
154
|
+
{ width: 1366, height: 768, deviceScaleFactor: 1 }, // HD
|
|
155
|
+
{ width: 2560, height: 1440, deviceScaleFactor: 1 } // QHD
|
|
156
|
+
];
|
|
157
|
+
|
|
158
|
+
if (screenResolution) {
|
|
159
|
+
return screenResolution;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return commonResolutions[Math.floor(Math.random() * commonResolutions.length)];
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Get platform from user agent
|
|
167
|
+
* @private
|
|
168
|
+
*/
|
|
169
|
+
_getPlatformFromUA(userAgent) {
|
|
170
|
+
if (userAgent.includes('Windows')) return 'Win32';
|
|
171
|
+
if (userAgent.includes('Mac')) return 'MacIntel';
|
|
172
|
+
if (userAgent.includes('Linux')) return 'Linux x86_64';
|
|
173
|
+
return 'Win32';
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Get hardware concurrency (CPU cores) from user agent
|
|
178
|
+
* @private
|
|
179
|
+
*/
|
|
180
|
+
_getHardwareConcurrency(userAgent) {
|
|
181
|
+
// Desktop: 4-8 cores common
|
|
182
|
+
// Mobile: 4-8 cores
|
|
183
|
+
return [4, 6, 8][Math.floor(Math.random() * 3)];
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Get device memory from user agent
|
|
188
|
+
* @private
|
|
189
|
+
*/
|
|
190
|
+
_getDeviceMemory(userAgent) {
|
|
191
|
+
// Common: 4, 8, 16 GB
|
|
192
|
+
return [4, 8, 16][Math.floor(Math.random() * 3)];
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Apply stealth profile to page
|
|
197
|
+
* Overrides navigator properties to match profile
|
|
198
|
+
*/
|
|
199
|
+
async applyStealthProfile(page, profile) {
|
|
200
|
+
// 1. Set timezone
|
|
201
|
+
await page.emulateTimezone(profile.timezone);
|
|
202
|
+
|
|
203
|
+
// 2. Override navigator properties
|
|
204
|
+
await page.evaluateOnNewDocument((profile) => {
|
|
205
|
+
// Override platform
|
|
206
|
+
Object.defineProperty(navigator, 'platform', {
|
|
207
|
+
get: () => profile.platform
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
// Override hardwareConcurrency
|
|
211
|
+
Object.defineProperty(navigator, 'hardwareConcurrency', {
|
|
212
|
+
get: () => profile.hardwareConcurrency
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
// Override deviceMemory
|
|
216
|
+
Object.defineProperty(navigator, 'deviceMemory', {
|
|
217
|
+
get: () => profile.deviceMemory
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
// Override languages
|
|
221
|
+
Object.defineProperty(navigator, 'languages', {
|
|
222
|
+
get: () => [profile.language, 'en']
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
// Override webdriver flag (anti-detection)
|
|
226
|
+
Object.defineProperty(navigator, 'webdriver', {
|
|
227
|
+
get: () => false
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
// Add chrome object (missing in headless)
|
|
231
|
+
if (!window.chrome) {
|
|
232
|
+
window.chrome = {
|
|
233
|
+
runtime: {}
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Override permissions
|
|
238
|
+
const originalQuery = window.navigator.permissions.query;
|
|
239
|
+
window.navigator.permissions.query = (parameters) => (
|
|
240
|
+
parameters.name === 'notifications'
|
|
241
|
+
? Promise.resolve({ state: Notification.permission })
|
|
242
|
+
: originalQuery(parameters)
|
|
243
|
+
);
|
|
244
|
+
}, profile);
|
|
245
|
+
|
|
246
|
+
// 3. Set extra HTTP headers
|
|
247
|
+
await page.setExtraHTTPHeaders({
|
|
248
|
+
'Accept-Language': profile.acceptLanguage,
|
|
249
|
+
'Accept-Encoding': profile.acceptEncoding,
|
|
250
|
+
'Accept': profile.accept
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Execute JS challenges automatically
|
|
256
|
+
* Simulates human JS execution
|
|
257
|
+
*/
|
|
258
|
+
async executeJSChallenges(page) {
|
|
259
|
+
try {
|
|
260
|
+
// 1. Wait for page to be fully loaded
|
|
261
|
+
await page.waitForFunction(() => document.readyState === 'complete', {
|
|
262
|
+
timeout: 10000
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
// 2. Execute common JS challenges
|
|
266
|
+
await page.evaluate(() => {
|
|
267
|
+
// Set honeypot cookie (if expected)
|
|
268
|
+
if (!document.cookie.includes('js_ok')) {
|
|
269
|
+
document.cookie = 'js_ok=1; path=/';
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// Execute any window.__JS_CHALLENGE if present
|
|
273
|
+
if (window.__JS_CHALLENGE_INIT) {
|
|
274
|
+
window.__JS_CHALLENGE_INIT();
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// Trigger load events
|
|
278
|
+
window.dispatchEvent(new Event('load'));
|
|
279
|
+
document.dispatchEvent(new Event('DOMContentLoaded'));
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
// 3. Load resources (images, CSS, fonts) to simulate real browser
|
|
283
|
+
await this._loadPageResources(page);
|
|
284
|
+
|
|
285
|
+
// 4. Execute fetch to /.js-challenge if endpoint exists
|
|
286
|
+
await page.evaluate(async () => {
|
|
287
|
+
try {
|
|
288
|
+
const response = await fetch('/.js-challenge', {
|
|
289
|
+
method: 'GET',
|
|
290
|
+
credentials: 'include'
|
|
291
|
+
});
|
|
292
|
+
if (response.ok) {
|
|
293
|
+
const data = await response.json();
|
|
294
|
+
if (data.token) {
|
|
295
|
+
window.__JS_CHALLENGE_TOKEN = data.token;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
} catch (err) {
|
|
299
|
+
// Endpoint may not exist - that's ok
|
|
300
|
+
}
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
} catch (err) {
|
|
304
|
+
// JS challenge execution failed - continue anyway
|
|
305
|
+
this.plugin.emit('stealth.jsChallengeWarning', {
|
|
306
|
+
error: err.message
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Load page resources to simulate real browser
|
|
313
|
+
* @private
|
|
314
|
+
*/
|
|
315
|
+
async _loadPageResources(page) {
|
|
316
|
+
try {
|
|
317
|
+
// Get all images and trigger load
|
|
318
|
+
await page.evaluate(() => {
|
|
319
|
+
const images = Array.from(document.querySelectorAll('img'));
|
|
320
|
+
images.forEach(img => {
|
|
321
|
+
if (!img.complete) {
|
|
322
|
+
img.dispatchEvent(new Event('load'));
|
|
323
|
+
}
|
|
324
|
+
});
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
// Wait for fonts to load
|
|
328
|
+
await page.evaluateHandle(() => document.fonts.ready);
|
|
329
|
+
|
|
330
|
+
} catch (err) {
|
|
331
|
+
// Resource loading failed - continue
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Add human-like delay between actions
|
|
337
|
+
* Uses timing profile from persona
|
|
338
|
+
*/
|
|
339
|
+
async humanDelay(profile, action = 'default') {
|
|
340
|
+
const timing = profile.timingProfile;
|
|
341
|
+
|
|
342
|
+
const baseDelay = timing.min + Math.random() * (timing.max - timing.min);
|
|
343
|
+
const jitter = (Math.random() - 0.5) * timing.jitter;
|
|
344
|
+
const totalDelay = Math.max(100, baseDelay + jitter);
|
|
345
|
+
|
|
346
|
+
await this._delay(totalDelay);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* Simulate human typing with profile-specific speed
|
|
351
|
+
*/
|
|
352
|
+
async humanType(page, selector, text, profile) {
|
|
353
|
+
const element = await page.$(selector);
|
|
354
|
+
if (!element) {
|
|
355
|
+
throw new NavigationError(`Element not found: ${selector}`, {
|
|
356
|
+
operation: 'humanType',
|
|
357
|
+
retriable: false,
|
|
358
|
+
suggestion: 'Verify the selector exists on the target page before invoking humanType.',
|
|
359
|
+
selector
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
await element.click();
|
|
364
|
+
|
|
365
|
+
// Type character by character with human timing
|
|
366
|
+
for (const char of text) {
|
|
367
|
+
await page.keyboard.type(char);
|
|
368
|
+
|
|
369
|
+
const { min, max } = profile.behavioral.typingSpeed;
|
|
370
|
+
const charDelay = min + Math.random() * (max - min);
|
|
371
|
+
await this._delay(charDelay);
|
|
372
|
+
|
|
373
|
+
// Random pause after punctuation
|
|
374
|
+
if (['.', ',', '!', '?'].includes(char)) {
|
|
375
|
+
await this._delay(200 + Math.random() * 300);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* Simulate realistic request pacing
|
|
382
|
+
* Prevents rate limiting / velocity detection
|
|
383
|
+
*/
|
|
384
|
+
async paceRequests(persona, requestCount) {
|
|
385
|
+
// Calculate safe delay based on request count
|
|
386
|
+
// Goal: stay under detection thresholds (e.g., 50 req/min)
|
|
387
|
+
|
|
388
|
+
const maxRequestsPerMinute = 30; // Conservative
|
|
389
|
+
const minDelayMs = (60 * 1000) / maxRequestsPerMinute;
|
|
390
|
+
|
|
391
|
+
// Add random jitter to avoid patterns
|
|
392
|
+
const jitter = minDelayMs * (0.5 + Math.random() * 0.5);
|
|
393
|
+
const totalDelay = minDelayMs + jitter;
|
|
394
|
+
|
|
395
|
+
await this._delay(totalDelay);
|
|
396
|
+
|
|
397
|
+
// Track request for persona stats
|
|
398
|
+
persona.metadata.lastRequestTime = Date.now();
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
/**
|
|
402
|
+
* Check if persona should "rest" (cooldown)
|
|
403
|
+
* Prevents velocity/concurrent use detection
|
|
404
|
+
*/
|
|
405
|
+
shouldRest(persona) {
|
|
406
|
+
const now = Date.now();
|
|
407
|
+
const lastUsed = persona.metadata.lastUsed || 0;
|
|
408
|
+
const timeSinceLastUse = now - lastUsed;
|
|
409
|
+
|
|
410
|
+
// If used in last 5 seconds, should rest
|
|
411
|
+
if (timeSinceLastUse < 5000) {
|
|
412
|
+
return true;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
// If high request count in short time, should rest
|
|
416
|
+
const requestsInLastMinute = persona.metadata.recentRequests || 0;
|
|
417
|
+
if (requestsInLastMinute > 20) {
|
|
418
|
+
return true;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
return false;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/**
|
|
425
|
+
* Simulate mouse movements and scrolling
|
|
426
|
+
* Generates behavioral fingerprint
|
|
427
|
+
*/
|
|
428
|
+
async simulateHumanBehavior(page, profile) {
|
|
429
|
+
try {
|
|
430
|
+
// 1. Random scroll
|
|
431
|
+
const scrollDistance = Math.floor(Math.random() * 500) + 200;
|
|
432
|
+
await page.evaluate((distance) => {
|
|
433
|
+
window.scrollBy({
|
|
434
|
+
top: distance,
|
|
435
|
+
behavior: 'smooth'
|
|
436
|
+
});
|
|
437
|
+
}, scrollDistance);
|
|
438
|
+
|
|
439
|
+
await this._delay(1000 + Math.random() * 1000);
|
|
440
|
+
|
|
441
|
+
// 2. Random mouse movement (if ghost-cursor available)
|
|
442
|
+
if (page._cursor) {
|
|
443
|
+
try {
|
|
444
|
+
// Move to random position
|
|
445
|
+
const viewport = await page.viewport();
|
|
446
|
+
const x = Math.floor(Math.random() * viewport.width);
|
|
447
|
+
const y = Math.floor(Math.random() * viewport.height);
|
|
448
|
+
|
|
449
|
+
await page._cursor.move({ x, y });
|
|
450
|
+
await this._delay(500);
|
|
451
|
+
} catch (err) {
|
|
452
|
+
// Cursor movement failed - continue
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
// 3. Random hover over elements
|
|
457
|
+
const elements = await page.$$('a, button, input');
|
|
458
|
+
if (elements.length > 0) {
|
|
459
|
+
const randomElement = elements[Math.floor(Math.random() * elements.length)];
|
|
460
|
+
await randomElement.hover().catch(() => {});
|
|
461
|
+
await this._delay(300 + Math.random() * 500);
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
} catch (err) {
|
|
465
|
+
// Behavior simulation failed - continue
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
/**
|
|
470
|
+
* Validate persona consistency before use
|
|
471
|
+
* Ensures no fingerprint leakage
|
|
472
|
+
*/
|
|
473
|
+
validatePersonaConsistency(persona, currentContext) {
|
|
474
|
+
const warnings = [];
|
|
475
|
+
|
|
476
|
+
// Check IP consistency (if proxy bound)
|
|
477
|
+
if (persona.proxyId && currentContext.proxyId !== persona.proxyId) {
|
|
478
|
+
warnings.push({
|
|
479
|
+
type: 'PROXY_MISMATCH',
|
|
480
|
+
message: `Persona ${persona.personaId} bound to ${persona.proxyId} but using ${currentContext.proxyId}`,
|
|
481
|
+
severity: 'HIGH'
|
|
482
|
+
});
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// Check UA consistency
|
|
486
|
+
if (currentContext.userAgent && currentContext.userAgent !== persona.userAgent) {
|
|
487
|
+
warnings.push({
|
|
488
|
+
type: 'UA_MISMATCH',
|
|
489
|
+
message: 'User agent changed',
|
|
490
|
+
severity: 'HIGH'
|
|
491
|
+
});
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
// Check viewport consistency
|
|
495
|
+
if (currentContext.viewport) {
|
|
496
|
+
if (currentContext.viewport.width !== persona.viewport.width ||
|
|
497
|
+
currentContext.viewport.height !== persona.viewport.height) {
|
|
498
|
+
warnings.push({
|
|
499
|
+
type: 'VIEWPORT_MISMATCH',
|
|
500
|
+
message: 'Viewport changed',
|
|
501
|
+
severity: 'MEDIUM'
|
|
502
|
+
});
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
// Check usage velocity
|
|
507
|
+
if (this.shouldRest(persona)) {
|
|
508
|
+
warnings.push({
|
|
509
|
+
type: 'HIGH_VELOCITY',
|
|
510
|
+
message: 'Persona used too frequently',
|
|
511
|
+
severity: 'MEDIUM'
|
|
512
|
+
});
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
return warnings;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
/**
|
|
519
|
+
* Generate realistic browsing session
|
|
520
|
+
* Visits pages with human-like patterns
|
|
521
|
+
*/
|
|
522
|
+
async generateBrowsingSession(page, profile, urls) {
|
|
523
|
+
for (let i = 0; i < urls.length; i++) {
|
|
524
|
+
const url = urls[i];
|
|
525
|
+
|
|
526
|
+
// Navigate
|
|
527
|
+
await page.goto(url, { waitUntil: 'networkidle2' });
|
|
528
|
+
|
|
529
|
+
// Execute JS challenges
|
|
530
|
+
await this.executeJSChallenges(page);
|
|
531
|
+
|
|
532
|
+
// Simulate human behavior
|
|
533
|
+
await this.simulateHumanBehavior(page, profile);
|
|
534
|
+
|
|
535
|
+
// Human delay before next page
|
|
536
|
+
await this.humanDelay(profile);
|
|
537
|
+
|
|
538
|
+
// Random chance to go back
|
|
539
|
+
if (i > 0 && Math.random() < 0.1) {
|
|
540
|
+
await page.goBack();
|
|
541
|
+
await this._delay(1000);
|
|
542
|
+
await page.goForward();
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
/**
|
|
548
|
+
* Delay helper
|
|
549
|
+
* @private
|
|
550
|
+
*/
|
|
551
|
+
async _delay(ms) {
|
|
552
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
export default StealthManager;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { PluginError } from '../errors.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Custom errors for PuppeteerPlugin with actionable messaging.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export class PuppeteerError extends PluginError {
|
|
8
|
+
constructor(message, details = {}) {
|
|
9
|
+
const merged = {
|
|
10
|
+
pluginName: details.pluginName || 'PuppeteerPlugin',
|
|
11
|
+
operation: details.operation || 'unknown',
|
|
12
|
+
statusCode: details.statusCode ?? 500,
|
|
13
|
+
retriable: details.retriable ?? false,
|
|
14
|
+
suggestion: details.suggestion ?? 'Review PuppeteerPlugin configuration (proxies, sessions, scripts) and retry.',
|
|
15
|
+
...details
|
|
16
|
+
};
|
|
17
|
+
super(message, merged);
|
|
18
|
+
this.name = 'PuppeteerError';
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export class BrowserPoolError extends PuppeteerError {
|
|
23
|
+
constructor(message, details = {}) {
|
|
24
|
+
super(message, {
|
|
25
|
+
code: 'BROWSER_POOL_ERROR',
|
|
26
|
+
retriable: details.retriable ?? true,
|
|
27
|
+
suggestion: details.suggestion ?? 'Verify browser instances are healthy and increase pool size or restart browsers.',
|
|
28
|
+
docs: details.docs || 'https://github.com/forattini-dev/s3db.js/blob/main/docs/plugins/puppeteer.md#browser-pool',
|
|
29
|
+
...details
|
|
30
|
+
});
|
|
31
|
+
this.name = 'BrowserPoolError';
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export class CookieManagerError extends PuppeteerError {
|
|
36
|
+
constructor(message, details = {}) {
|
|
37
|
+
super(message, {
|
|
38
|
+
code: 'COOKIE_MANAGER_ERROR',
|
|
39
|
+
retriable: details.retriable ?? false,
|
|
40
|
+
suggestion: details.suggestion ?? 'Check cookie storage configuration and ensure persona sessions are valid.',
|
|
41
|
+
...details
|
|
42
|
+
});
|
|
43
|
+
this.name = 'CookieManagerError';
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export class NavigationError extends PuppeteerError {
|
|
48
|
+
constructor(message, details = {}) {
|
|
49
|
+
super(message, {
|
|
50
|
+
code: 'NAVIGATION_ERROR',
|
|
51
|
+
retriable: details.retriable ?? true,
|
|
52
|
+
suggestion: details.suggestion ?? 'Validate target URLs, network access, and waitFor options before retrying.',
|
|
53
|
+
...details
|
|
54
|
+
});
|
|
55
|
+
this.name = 'NavigationError';
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export class HumanBehaviorError extends PuppeteerError {
|
|
60
|
+
constructor(message, details = {}) {
|
|
61
|
+
super(message, {
|
|
62
|
+
code: 'HUMAN_BEHAVIOR_ERROR',
|
|
63
|
+
retriable: details.retriable ?? false,
|
|
64
|
+
suggestion: details.suggestion ?? 'Adjust human behavior thresholds or provide alternate interaction scripts.',
|
|
65
|
+
...details
|
|
66
|
+
});
|
|
67
|
+
this.name = 'HumanBehaviorError';
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export class SessionError extends PuppeteerError {
|
|
72
|
+
constructor(message, details = {}) {
|
|
73
|
+
super(message, {
|
|
74
|
+
code: 'SESSION_ERROR',
|
|
75
|
+
retriable: details.retriable ?? true,
|
|
76
|
+
suggestion: details.suggestion ?? 'Refresh or recreate the browser session and ensure session storage is writable.',
|
|
77
|
+
...details
|
|
78
|
+
});
|
|
79
|
+
this.name = 'SessionError';
|
|
80
|
+
}
|
|
81
|
+
}
|