vektor-slipstream 1.4.4 → 2.0.0
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 +67 -306
- package/package.json +14 -146
- package/CHANGELOG.md +0 -139
- package/LICENSE +0 -33
- package/TENETS.md +0 -189
- package/audn-log.js +0 -143
- package/axon.js +0 -389
- package/boot-patch.js +0 -33
- package/boot-screen.html +0 -210
- package/briefing.js +0 -150
- package/cerebellum.js +0 -439
- package/cloak-behaviour.js +0 -596
- package/cloak-captcha.js +0 -541
- package/cloak-core.js +0 -499
- package/cloak-identity.js +0 -484
- package/cloak-index.js +0 -261
- package/cloak-llms.js +0 -163
- package/cloak-pattern-store.js +0 -471
- package/cloak-recorder-auto.js +0 -297
- package/cloak-recorder-snippet.js +0 -119
- package/cloak-turbo-quant.js +0 -357
- package/cloak-warmup.js +0 -240
- package/cortex.js +0 -221
- package/detect-hardware.js +0 -181
- package/entity-resolver.js +0 -298
- package/errors.js +0 -66
- package/examples/example-claude-mcp.js +0 -220
- package/examples/example-langchain-researcher.js +0 -82
- package/examples/example-openai-assistant.js +0 -84
- package/examples/examples-README.md +0 -161
- package/export-import.js +0 -221
- package/forget.js +0 -148
- package/inspect.js +0 -199
- package/mistral/README-mistral.md +0 -123
- package/mistral/mistral-bridge.js +0 -218
- package/mistral/mistral-setup.js +0 -220
- package/mistral/vektor-tool-manifest.json +0 -41
- package/models/model_quantized.onnx +0 -0
- package/models/vocab.json +0 -1
- package/namespace.js +0 -186
- package/pin.js +0 -91
- package/slipstream-core-extended.js +0 -134
- package/slipstream-core.js +0 -1
- package/slipstream-db.js +0 -140
- package/slipstream-embedder.js +0 -338
- package/sovereign.js +0 -142
- package/token.js +0 -322
- package/types/index.d.ts +0 -269
- package/vektor-banner-loader.js +0 -109
- package/vektor-cli.js +0 -259
- package/vektor-licence-prompt.js +0 -128
- package/vektor-licence.js +0 -192
- package/vektor-setup.js +0 -270
- package/vektor-slipstream.dxt +0 -0
- package/vektor-tui.js +0 -373
- package/visualize.js +0 -235
package/cloak-identity.js
DELETED
|
@@ -1,484 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* cloak-identity.js
|
|
5
|
-
*
|
|
6
|
-
* Persistent agent identity layer — full browser fingerprint storage,
|
|
7
|
-
* not just cookies. Builds a convincing long-lived browser identity
|
|
8
|
-
* that accumulates trust history on target sites.
|
|
9
|
-
*
|
|
10
|
-
* Stores per-identity:
|
|
11
|
-
* - User agent string (consistent Chrome version)
|
|
12
|
-
* - Viewport dimensions
|
|
13
|
-
* - WebGL renderer + vendor (spoofed consistently)
|
|
14
|
-
* - Canvas fingerprint noise seed (deterministic per identity)
|
|
15
|
-
* - Installed font set (subset of real fonts)
|
|
16
|
-
* - Timezone + locale + language
|
|
17
|
-
* - Hardware concurrency + device memory
|
|
18
|
-
* - Platform string
|
|
19
|
-
* - Screen dimensions + color depth
|
|
20
|
-
* - Cookie jar (per-domain)
|
|
21
|
-
* - Session history (URL visits with timestamps)
|
|
22
|
-
* - Creation date (age builds trust)
|
|
23
|
-
*
|
|
24
|
-
* Usage:
|
|
25
|
-
* const id = CloakIdentity.create('shopping-bot-1');
|
|
26
|
-
* await id.applyToPage(page); // applies full fingerprint to Playwright page
|
|
27
|
-
* id.recordVisit('https://example.com');
|
|
28
|
-
* id.save(); // persists to encrypted vault
|
|
29
|
-
*
|
|
30
|
-
* const id2 = CloakIdentity.load('shopping-bot-1');
|
|
31
|
-
* await id2.applyToPage(page); // same fingerprint, consistent identity
|
|
32
|
-
*/
|
|
33
|
-
|
|
34
|
-
const crypto = require('crypto');
|
|
35
|
-
const fs = require('fs');
|
|
36
|
-
const path = require('path');
|
|
37
|
-
const os = require('os');
|
|
38
|
-
|
|
39
|
-
const VAULT_DIR = path.join(os.homedir(), '.vektor', 'identities');
|
|
40
|
-
const AES_KEY = () => {
|
|
41
|
-
const keyPath = path.join(os.homedir(), '.vektor', 'identity.key');
|
|
42
|
-
if (fs.existsSync(keyPath)) return fs.readFileSync(keyPath);
|
|
43
|
-
const key = crypto.randomBytes(32);
|
|
44
|
-
fs.mkdirSync(path.dirname(keyPath), { recursive: true });
|
|
45
|
-
fs.writeFileSync(keyPath, key, { mode: 0o600 });
|
|
46
|
-
return key;
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
// ── Fingerprint pools (real-world distributions) ──────────────────────────────
|
|
50
|
-
|
|
51
|
-
const UA_POOL = [
|
|
52
|
-
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36',
|
|
53
|
-
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36',
|
|
54
|
-
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36',
|
|
55
|
-
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
|
|
56
|
-
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36',
|
|
57
|
-
];
|
|
58
|
-
|
|
59
|
-
const WEBGL_RENDERERS = [
|
|
60
|
-
{ vendor: 'Google Inc. (NVIDIA)', renderer: 'ANGLE (NVIDIA, NVIDIA GeForce RTX 3070 Direct3D11 vs_5_0 ps_5_0, D3D11)' },
|
|
61
|
-
{ vendor: 'Google Inc. (Intel)', renderer: 'ANGLE (Intel, Intel(R) UHD Graphics 630 Direct3D11 vs_5_0 ps_5_0, D3D11)' },
|
|
62
|
-
{ vendor: 'Google Inc. (AMD)', renderer: 'ANGLE (AMD, AMD Radeon RX 580 Series Direct3D11 vs_5_0 ps_5_0, D3D11)' },
|
|
63
|
-
{ vendor: 'Apple Inc.', renderer: 'Apple M1' },
|
|
64
|
-
{ vendor: 'Google Inc. (NVIDIA)', renderer: 'ANGLE (NVIDIA, NVIDIA GeForce GTX 1660 Direct3D11 vs_5_0 ps_5_0, D3D11)' },
|
|
65
|
-
];
|
|
66
|
-
|
|
67
|
-
const FONT_SETS = [
|
|
68
|
-
['Arial', 'Arial Black', 'Comic Sans MS', 'Courier New', 'Georgia', 'Impact', 'Times New Roman', 'Trebuchet MS', 'Verdana', 'Calibri', 'Cambria', 'Segoe UI'],
|
|
69
|
-
['Arial', 'Helvetica', 'Times New Roman', 'Courier New', 'Verdana', 'Georgia', 'Palatino', 'Garamond', 'Trebuchet MS', 'Arial Narrow'],
|
|
70
|
-
['Arial', 'Arial Black', 'Courier New', 'Georgia', 'Impact', 'Lucida Console', 'Lucida Sans Unicode', 'Palatino Linotype', 'Tahoma', 'Times New Roman', 'Trebuchet MS', 'Verdana'],
|
|
71
|
-
];
|
|
72
|
-
|
|
73
|
-
const TIMEZONES = [
|
|
74
|
-
'America/New_York', 'America/Chicago', 'America/Los_Angeles', 'America/Denver',
|
|
75
|
-
'Europe/London', 'Europe/Paris', 'Europe/Berlin', 'Asia/Tokyo', 'Australia/Sydney',
|
|
76
|
-
];
|
|
77
|
-
|
|
78
|
-
const VIEWPORTS = [
|
|
79
|
-
{ width: 1920, height: 1080 },
|
|
80
|
-
{ width: 1440, height: 900 },
|
|
81
|
-
{ width: 1366, height: 768 },
|
|
82
|
-
{ width: 1280, height: 720 },
|
|
83
|
-
{ width: 2560, height: 1440 },
|
|
84
|
-
];
|
|
85
|
-
|
|
86
|
-
const SCREEN_DEPTHS = [24, 24, 24, 32]; // weighted toward 24
|
|
87
|
-
|
|
88
|
-
// ── CloakIdentity class ───────────────────────────────────────────────────────
|
|
89
|
-
|
|
90
|
-
class CloakIdentity {
|
|
91
|
-
constructor(profile) {
|
|
92
|
-
this.profile = profile;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* Create a new deterministic identity from a seed string.
|
|
97
|
-
* Same seed always produces the same fingerprint.
|
|
98
|
-
*/
|
|
99
|
-
static create(name, seed = null) {
|
|
100
|
-
const identitySeed = seed || crypto.randomBytes(16).toString('hex');
|
|
101
|
-
const rng = _seededRng(identitySeed);
|
|
102
|
-
|
|
103
|
-
const ua = UA_POOL[rng(UA_POOL.length)];
|
|
104
|
-
const webgl = WEBGL_RENDERERS[rng(WEBGL_RENDERERS.length)];
|
|
105
|
-
const fonts = FONT_SETS[rng(FONT_SETS.length)];
|
|
106
|
-
const tz = TIMEZONES[rng(TIMEZONES.length)];
|
|
107
|
-
const vp = VIEWPORTS[rng(VIEWPORTS.length)];
|
|
108
|
-
const hwConc = [2, 4, 4, 8, 8, 8, 12, 16][rng(8)];
|
|
109
|
-
const devMem = [2, 4, 4, 8, 8, 16][rng(6)];
|
|
110
|
-
const platform = ua.includes('Windows') ? 'Win32' : ua.includes('Mac') ? 'MacIntel' : 'Linux x86_64';
|
|
111
|
-
|
|
112
|
-
const profile = {
|
|
113
|
-
name,
|
|
114
|
-
seed: identitySeed,
|
|
115
|
-
createdAt: new Date().toISOString(),
|
|
116
|
-
lastUsedAt: null,
|
|
117
|
-
visitCount: 0,
|
|
118
|
-
|
|
119
|
-
// Browser identity
|
|
120
|
-
userAgent: ua,
|
|
121
|
-
platform,
|
|
122
|
-
vendor: 'Google Inc.',
|
|
123
|
-
appVersion: ua.replace('Mozilla/', ''),
|
|
124
|
-
language: 'en-US',
|
|
125
|
-
languages: ['en-US', 'en'],
|
|
126
|
-
|
|
127
|
-
// Hardware
|
|
128
|
-
hardwareConcurrency: hwConc,
|
|
129
|
-
deviceMemory: devMem,
|
|
130
|
-
|
|
131
|
-
// Screen
|
|
132
|
-
viewport: vp,
|
|
133
|
-
screen: {
|
|
134
|
-
width: vp.width,
|
|
135
|
-
height: vp.height,
|
|
136
|
-
availWidth: vp.width,
|
|
137
|
-
availHeight: vp.height - 40, // taskbar
|
|
138
|
-
colorDepth: SCREEN_DEPTHS[rng(SCREEN_DEPTHS.length)],
|
|
139
|
-
pixelDepth: SCREEN_DEPTHS[rng(SCREEN_DEPTHS.length)],
|
|
140
|
-
},
|
|
141
|
-
|
|
142
|
-
// WebGL
|
|
143
|
-
webgl: {
|
|
144
|
-
vendor: webgl.vendor,
|
|
145
|
-
renderer: webgl.renderer,
|
|
146
|
-
},
|
|
147
|
-
|
|
148
|
-
// Canvas noise seed (adds tiny deterministic noise to canvas fingerprint)
|
|
149
|
-
canvasNoiseSeed: rng(0xFFFFFF),
|
|
150
|
-
|
|
151
|
-
// Fonts
|
|
152
|
-
fonts,
|
|
153
|
-
|
|
154
|
-
// Timezone
|
|
155
|
-
timezone: tz,
|
|
156
|
-
timezoneOffset: _tzOffset(tz),
|
|
157
|
-
|
|
158
|
-
// Plugin list (mimics real Chrome)
|
|
159
|
-
plugins: [
|
|
160
|
-
'PDF Viewer',
|
|
161
|
-
'Chrome PDF Viewer',
|
|
162
|
-
'Chromium PDF Viewer',
|
|
163
|
-
'Microsoft Edge PDF Viewer',
|
|
164
|
-
'WebKit built-in PDF',
|
|
165
|
-
],
|
|
166
|
-
|
|
167
|
-
// Session data (accumulated over time)
|
|
168
|
-
cookies: {}, // domain → cookie string
|
|
169
|
-
history: [], // [{ url, visitedAt, durationMs }]
|
|
170
|
-
trustScore: {}, // domain → estimated score
|
|
171
|
-
};
|
|
172
|
-
|
|
173
|
-
return new CloakIdentity(profile);
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
/**
|
|
177
|
-
* Load a saved identity by name.
|
|
178
|
-
*/
|
|
179
|
-
static load(name) {
|
|
180
|
-
const filePath = path.join(VAULT_DIR, `${_sanitiseName(name)}.enc`);
|
|
181
|
-
if (!fs.existsSync(filePath)) return null;
|
|
182
|
-
try {
|
|
183
|
-
const enc = fs.readFileSync(filePath);
|
|
184
|
-
const profile = _decrypt(enc);
|
|
185
|
-
return new CloakIdentity(profile);
|
|
186
|
-
} catch { return null; }
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
/**
|
|
190
|
-
* List all saved identities.
|
|
191
|
-
*/
|
|
192
|
-
static list() {
|
|
193
|
-
if (!fs.existsSync(VAULT_DIR)) return [];
|
|
194
|
-
return fs.readdirSync(VAULT_DIR)
|
|
195
|
-
.filter(f => f.endsWith('.enc'))
|
|
196
|
-
.map(f => f.replace('.enc', ''));
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
/**
|
|
200
|
-
* Delete a saved identity.
|
|
201
|
-
*/
|
|
202
|
-
static delete(name) {
|
|
203
|
-
const filePath = path.join(VAULT_DIR, `${_sanitiseName(name)}.enc`);
|
|
204
|
-
if (fs.existsSync(filePath)) fs.unlinkSync(filePath);
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
/**
|
|
208
|
-
* Apply this identity to a Playwright browser context/page.
|
|
209
|
-
* Call before page.goto().
|
|
210
|
-
*/
|
|
211
|
-
async applyToPage(page) {
|
|
212
|
-
const p = this.profile;
|
|
213
|
-
|
|
214
|
-
// Set user agent
|
|
215
|
-
await page.setExtraHTTPHeaders({
|
|
216
|
-
'User-Agent': p.userAgent,
|
|
217
|
-
'Accept-Language': p.language,
|
|
218
|
-
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8',
|
|
219
|
-
'Accept-Encoding': 'gzip, deflate, br',
|
|
220
|
-
'Sec-CH-UA-Platform': `"${p.platform === 'Win32' ? 'Windows' : p.platform === 'MacIntel' ? 'macOS' : 'Linux'}"`,
|
|
221
|
-
});
|
|
222
|
-
|
|
223
|
-
// Inject fingerprint overrides via evaluateOnNewDocument
|
|
224
|
-
await page.addInitScript(({ profile }) => {
|
|
225
|
-
// Navigator overrides
|
|
226
|
-
Object.defineProperties(navigator, {
|
|
227
|
-
userAgent: { get: () => profile.userAgent, configurable: true },
|
|
228
|
-
platform: { get: () => profile.platform, configurable: true },
|
|
229
|
-
vendor: { get: () => profile.vendor, configurable: true },
|
|
230
|
-
language: { get: () => profile.language, configurable: true },
|
|
231
|
-
languages: { get: () => profile.languages, configurable: true },
|
|
232
|
-
hardwareConcurrency: { get: () => profile.hardwareConcurrency, configurable: true },
|
|
233
|
-
deviceMemory: { get: () => profile.deviceMemory, configurable: true },
|
|
234
|
-
webdriver: { get: () => undefined, configurable: true },
|
|
235
|
-
plugins: {
|
|
236
|
-
get: () => {
|
|
237
|
-
const arr = profile.plugins.map(name => ({ name, filename: name.toLowerCase().replace(/ /g, '-') + '.dll' }));
|
|
238
|
-
arr.__proto__ = PluginArray.prototype;
|
|
239
|
-
return arr;
|
|
240
|
-
},
|
|
241
|
-
configurable: true,
|
|
242
|
-
},
|
|
243
|
-
});
|
|
244
|
-
|
|
245
|
-
// Screen overrides
|
|
246
|
-
Object.defineProperties(screen, {
|
|
247
|
-
width: { get: () => profile.screen.width, configurable: true },
|
|
248
|
-
height: { get: () => profile.screen.height, configurable: true },
|
|
249
|
-
availWidth: { get: () => profile.screen.availWidth, configurable: true },
|
|
250
|
-
availHeight: { get: () => profile.screen.availHeight, configurable: true },
|
|
251
|
-
colorDepth: { get: () => profile.screen.colorDepth, configurable: true },
|
|
252
|
-
pixelDepth: { get: () => profile.screen.pixelDepth, configurable: true },
|
|
253
|
-
});
|
|
254
|
-
|
|
255
|
-
// WebGL renderer spoofing
|
|
256
|
-
const getParam = WebGLRenderingContext.prototype.getParameter;
|
|
257
|
-
WebGLRenderingContext.prototype.getParameter = function(param) {
|
|
258
|
-
if (param === 37445) return profile.webgl.vendor; // UNMASKED_VENDOR_WEBGL
|
|
259
|
-
if (param === 37446) return profile.webgl.renderer; // UNMASKED_RENDERER_WEBGL
|
|
260
|
-
return getParam.call(this, param);
|
|
261
|
-
};
|
|
262
|
-
const getParam2 = WebGL2RenderingContext.prototype.getParameter;
|
|
263
|
-
WebGL2RenderingContext.prototype.getParameter = function(param) {
|
|
264
|
-
if (param === 37445) return profile.webgl.vendor;
|
|
265
|
-
if (param === 37446) return profile.webgl.renderer;
|
|
266
|
-
return getParam2.call(this, param);
|
|
267
|
-
};
|
|
268
|
-
|
|
269
|
-
// Canvas noise (deterministic per identity — consistent fingerprint)
|
|
270
|
-
const origToDataURL = HTMLCanvasElement.prototype.toDataURL;
|
|
271
|
-
const origGetData = CanvasRenderingContext2D.prototype.getImageData;
|
|
272
|
-
const seed = profile.canvasNoiseSeed;
|
|
273
|
-
HTMLCanvasElement.prototype.toDataURL = function(...args) {
|
|
274
|
-
_addCanvasNoise(this, seed);
|
|
275
|
-
return origToDataURL.apply(this, args);
|
|
276
|
-
};
|
|
277
|
-
CanvasRenderingContext2D.prototype.getImageData = function(...args) {
|
|
278
|
-
const data = origGetData.apply(this, args);
|
|
279
|
-
// Add tiny deterministic noise to pixel values
|
|
280
|
-
for (let i = 0; i < data.data.length; i += 4) {
|
|
281
|
-
const noise = ((seed ^ i) & 0x3) - 1; // -1, 0, or 1
|
|
282
|
-
data.data[i] = Math.max(0, Math.min(255, data.data[i] + noise));
|
|
283
|
-
data.data[i + 1] = Math.max(0, Math.min(255, data.data[i + 1] + noise));
|
|
284
|
-
data.data[i + 2] = Math.max(0, Math.min(255, data.data[i + 2] + noise));
|
|
285
|
-
}
|
|
286
|
-
return data;
|
|
287
|
-
};
|
|
288
|
-
|
|
289
|
-
function _addCanvasNoise(canvas, seed) {
|
|
290
|
-
try {
|
|
291
|
-
const ctx = canvas.getContext('2d');
|
|
292
|
-
if (!ctx) return;
|
|
293
|
-
ctx.save();
|
|
294
|
-
ctx.globalAlpha = 0.002;
|
|
295
|
-
ctx.fillStyle = `rgb(${seed & 0xff},${(seed >> 8) & 0xff},${(seed >> 16) & 0xff})`;
|
|
296
|
-
ctx.fillRect(0, 0, 1, 1);
|
|
297
|
-
ctx.restore();
|
|
298
|
-
} catch { /* ignore */ }
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
}, { profile: p });
|
|
302
|
-
|
|
303
|
-
// Restore cookies if any
|
|
304
|
-
const domain = _extractDomain(page.url ? page.url() : '');
|
|
305
|
-
if (domain && p.cookies[domain]) {
|
|
306
|
-
try {
|
|
307
|
-
await page.context().addCookies(p.cookies[domain]);
|
|
308
|
-
} catch { /* ignore */ }
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
/**
|
|
313
|
-
* Apply identity to a Playwright BrowserContext (preferred — covers all pages).
|
|
314
|
-
*/
|
|
315
|
-
async applyToContext(context) {
|
|
316
|
-
const p = this.profile;
|
|
317
|
-
|
|
318
|
-
await context.setExtraHTTPHeaders({
|
|
319
|
-
'User-Agent': p.userAgent,
|
|
320
|
-
'Accept-Language': p.language,
|
|
321
|
-
});
|
|
322
|
-
|
|
323
|
-
await context.addInitScript(({ profile }) => {
|
|
324
|
-
Object.defineProperties(navigator, {
|
|
325
|
-
userAgent: { get: () => profile.userAgent, configurable: true },
|
|
326
|
-
platform: { get: () => profile.platform, configurable: true },
|
|
327
|
-
vendor: { get: () => profile.vendor, configurable: true },
|
|
328
|
-
language: { get: () => profile.language, configurable: true },
|
|
329
|
-
languages: { get: () => profile.languages, configurable: true },
|
|
330
|
-
hardwareConcurrency: { get: () => profile.hardwareConcurrency, configurable: true },
|
|
331
|
-
deviceMemory: { get: () => profile.deviceMemory, configurable: true },
|
|
332
|
-
webdriver: { get: () => undefined, configurable: true },
|
|
333
|
-
});
|
|
334
|
-
}, { profile: p });
|
|
335
|
-
|
|
336
|
-
// Restore all saved cookies
|
|
337
|
-
const allCookies = Object.values(p.cookies).flat();
|
|
338
|
-
if (allCookies.length) {
|
|
339
|
-
try { await context.addCookies(allCookies); } catch { /* ignore */ }
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
/**
|
|
344
|
-
* Save cookies from a Playwright context back into this identity.
|
|
345
|
-
*/
|
|
346
|
-
async saveCookiesFromContext(context) {
|
|
347
|
-
try {
|
|
348
|
-
const cookies = await context.cookies();
|
|
349
|
-
for (const cookie of cookies) {
|
|
350
|
-
const domain = cookie.domain.replace(/^\./, '');
|
|
351
|
-
if (!this.profile.cookies[domain]) this.profile.cookies[domain] = [];
|
|
352
|
-
// Upsert — replace if same name+domain
|
|
353
|
-
const idx = this.profile.cookies[domain].findIndex(
|
|
354
|
-
c => c.name === cookie.name && c.domain === cookie.domain
|
|
355
|
-
);
|
|
356
|
-
if (idx >= 0) this.profile.cookies[domain][idx] = cookie;
|
|
357
|
-
else this.profile.cookies[domain].push(cookie);
|
|
358
|
-
}
|
|
359
|
-
} catch { /* ignore */ }
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
/**
|
|
363
|
-
* Record a page visit in this identity's history.
|
|
364
|
-
* Builds session history that improves trust scores.
|
|
365
|
-
*/
|
|
366
|
-
recordVisit(url, durationMs = null) {
|
|
367
|
-
this.profile.history.push({ url, visitedAt: new Date().toISOString(), durationMs });
|
|
368
|
-
this.profile.visitCount++;
|
|
369
|
-
this.profile.lastUsedAt = new Date().toISOString();
|
|
370
|
-
|
|
371
|
-
// Keep history to last 500 visits
|
|
372
|
-
if (this.profile.history.length > 500) {
|
|
373
|
-
this.profile.history = this.profile.history.slice(-500);
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
/**
|
|
378
|
-
* Get age of this identity in days.
|
|
379
|
-
* Older identities have higher trust scores.
|
|
380
|
-
*/
|
|
381
|
-
get ageDays() {
|
|
382
|
-
const created = new Date(this.profile.createdAt);
|
|
383
|
-
return Math.floor((Date.now() - created.getTime()) / (1000 * 60 * 60 * 24));
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
/**
|
|
387
|
-
* Summary of this identity's trust profile.
|
|
388
|
-
*/
|
|
389
|
-
get summary() {
|
|
390
|
-
return {
|
|
391
|
-
name: this.profile.name,
|
|
392
|
-
ageDays: this.ageDays,
|
|
393
|
-
visitCount: this.profile.visitCount,
|
|
394
|
-
lastUsed: this.profile.lastUsedAt,
|
|
395
|
-
ua: this.profile.userAgent.slice(0, 60) + '...',
|
|
396
|
-
platform: this.profile.platform,
|
|
397
|
-
webgl: this.profile.webgl.renderer.slice(0, 50),
|
|
398
|
-
timezone: this.profile.timezone,
|
|
399
|
-
domainsWithCookies: Object.keys(this.profile.cookies).length,
|
|
400
|
-
};
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
/**
|
|
404
|
-
* Persist this identity to the encrypted vault.
|
|
405
|
-
*/
|
|
406
|
-
save() {
|
|
407
|
-
fs.mkdirSync(VAULT_DIR, { recursive: true, mode: 0o700 });
|
|
408
|
-
const encrypted = _encrypt(this.profile);
|
|
409
|
-
const filePath = path.join(VAULT_DIR, `${_sanitiseName(this.profile.name)}.enc`);
|
|
410
|
-
fs.writeFileSync(filePath, encrypted, { mode: 0o600 });
|
|
411
|
-
return filePath;
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
/**
|
|
415
|
-
* Export identity as JSON (for backup/transfer).
|
|
416
|
-
*/
|
|
417
|
-
export() {
|
|
418
|
-
return JSON.stringify(this.profile, null, 2);
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
/**
|
|
422
|
-
* Import identity from JSON string.
|
|
423
|
-
*/
|
|
424
|
-
static import(json, overrideName = null) {
|
|
425
|
-
const profile = JSON.parse(json);
|
|
426
|
-
if (overrideName) profile.name = overrideName;
|
|
427
|
-
return new CloakIdentity(profile);
|
|
428
|
-
}
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
// ── Helpers ───────────────────────────────────────────────────────────────────
|
|
432
|
-
|
|
433
|
-
/** Simple seeded PRNG (xorshift32) returning integers in [0, max) */
|
|
434
|
-
function _seededRng(seed) {
|
|
435
|
-
let s = 0;
|
|
436
|
-
// Hash seed string to uint32
|
|
437
|
-
for (let i = 0; i < seed.length; i++) {
|
|
438
|
-
s = (Math.imul(31, s) + seed.charCodeAt(i)) | 0;
|
|
439
|
-
}
|
|
440
|
-
s = s >>> 0 || 1;
|
|
441
|
-
return function(max) {
|
|
442
|
-
s ^= s << 13; s ^= s >> 17; s ^= s << 5; s = s >>> 0;
|
|
443
|
-
return s % max;
|
|
444
|
-
};
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
function _sanitiseName(name) {
|
|
448
|
-
return name.replace(/[^a-zA-Z0-9_-]/g, '_').slice(0, 64);
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
function _extractDomain(url) {
|
|
452
|
-
try { return new URL(url).hostname; } catch { return ''; }
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
function _tzOffset(tz) {
|
|
456
|
-
const offsets = {
|
|
457
|
-
'America/New_York': -300, 'America/Chicago': -360,
|
|
458
|
-
'America/Los_Angeles': -480, 'America/Denver': -420,
|
|
459
|
-
'Europe/London': 0, 'Europe/Paris': 60,
|
|
460
|
-
'Europe/Berlin': 60, 'Asia/Tokyo': 540,
|
|
461
|
-
'Australia/Sydney': 600,
|
|
462
|
-
};
|
|
463
|
-
return offsets[tz] || 0;
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
function _encrypt(obj) {
|
|
467
|
-
const key = AES_KEY();
|
|
468
|
-
const iv = crypto.randomBytes(16);
|
|
469
|
-
const cipher = crypto.createCipheriv('aes-256-cbc', key, iv);
|
|
470
|
-
const text = JSON.stringify(obj);
|
|
471
|
-
const enc = Buffer.concat([cipher.update(text, 'utf8'), cipher.final()]);
|
|
472
|
-
return Buffer.concat([iv, enc]);
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
function _decrypt(buf) {
|
|
476
|
-
const key = AES_KEY();
|
|
477
|
-
const iv = buf.slice(0, 16);
|
|
478
|
-
const enc = buf.slice(16);
|
|
479
|
-
const decipher = crypto.createDecipheriv('aes-256-cbc', key, iv);
|
|
480
|
-
const dec = Buffer.concat([decipher.update(enc), decipher.final()]);
|
|
481
|
-
return JSON.parse(dec.toString('utf8'));
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
module.exports = { CloakIdentity };
|