chrome-tools 1.1.16 → 1.2.1

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/dist/index.js CHANGED
@@ -1,2297 +1,41 @@
1
- // src/core/chrome.ts
2
- import { existsSync as existsSync2, readdirSync, readFileSync as readFileSync2, statSync as statSync2 } from "node:fs";
3
- import { basename, join as join4 } from "node:path";
4
-
5
- // src/core/platform/darwin.ts
6
- import { homedir } from "node:os";
7
- import { join } from "node:path";
8
-
9
- // src/core/keychain.ts
10
- import { execFileSync } from "node:child_process";
11
- import { pbkdf2Sync } from "node:crypto";
12
- var PBKDF2_ITERATIONS = 1003;
13
- var PBKDF2_KEY_LENGTH = 16;
14
- var PBKDF2_SALT = Buffer.from("saltysalt");
15
- var DEFAULT_SAFE_STORAGE_SERVICE = "Chrome Safe Storage";
16
- var SECURITY_EXIT = {
17
- ITEM_NOT_FOUND: 44,
18
- // errSecItemNotFound
19
- INTERACTION_NOT_ALLOWED: 36,
20
- // errSecInteractionNotAllowed (SSH / headless / locked)
21
- AUTH_FAILED: 45,
22
- // errSecAuthFailed
23
- USER_CANCELED: 128
24
- // user clicked "Deny"/cancelled the GUI authorization prompt
25
- };
26
- var KeychainAccessError = class extends Error {
27
- service;
28
- kind;
29
- constructor(message, service, kind) {
30
- super(message);
31
- this.name = "KeychainAccessError";
32
- this.service = service;
33
- this.kind = kind;
34
- }
35
- /** True when the failure means we couldn't get permission to read the key. */
36
- get isAccessDenied() {
37
- return this.kind === "access-denied";
38
- }
39
- };
40
- var _cachedPasswords = /* @__PURE__ */ new Map();
41
- var _cachedDerivedKeys = /* @__PURE__ */ new Map();
42
- function isLikelyHeadless() {
43
- return Boolean(process.env.SSH_CONNECTION || process.env.SSH_TTY) || !process.env.TERM_PROGRAM;
44
- }
45
- function parseSecurityError(err) {
46
- const e = err;
47
- return {
48
- status: typeof e?.status === "number" ? e.status : null,
49
- stderr: (e?.stderr ?? "").toString().trim()
50
- };
51
- }
52
- function buildKeychainError(service, status, stderr) {
53
- const docs = "See docs/remote-keychain-access.md for non-interactive / SSH setup.";
54
- let kind;
55
- let hint;
56
- if (status === SECURITY_EXIT.ITEM_NOT_FOUND || /could not be found/i.test(stderr)) {
57
- kind = "not-found";
58
- hint = `No '${service}' item found in the Keychain.
59
- Run this browser at least once on this machine so it creates the key.`;
60
- } else if (status === SECURITY_EXIT.INTERACTION_NOT_ALLOWED || /interaction.*not allowed/i.test(stderr)) {
61
- kind = "access-denied";
62
- hint = "The Keychain needs a GUI authorization prompt that can't be shown here " + (isLikelyHeadless() ? "(you appear to be on a remote/SSH session).\n" : ".\n") + 'Authorize once from a GUI Terminal (click "Always Allow"), then unlock the\nkeychain in this session:\n security unlock-keychain ~/Library/Keychains/login.keychain-db\n' + docs;
63
- } else if (status === SECURITY_EXIT.AUTH_FAILED) {
64
- kind = "access-denied";
65
- hint = `Keychain authorization failed (wrong password or denied).
1
+ import{existsSync as R,readdirSync as Yr,readFileSync as Jr,statSync as Qo}from"node:fs";import{basename as Xr,join as A}from"node:path";import{createDecipheriv as ue}from"node:crypto";var Er=Buffer.alloc(16," "),U=12,K=16;function ee(e,t){if(!e||e.length===0)return Buffer.alloc(0);let r=Buffer.isBuffer(e)?e:Buffer.from(e),o=r.subarray(0,3).toString("utf-8");if(o!=="v10"&&o!=="v11")return"<unsupported encryption format>";let n=r.subarray(3);try{let s=ue("aes-128-cbc",t,Er),i=s.update(n);return i=Buffer.concat([i,s.final()]),i}catch{return"<decryption failed>"}}function Ae(e,t){if(!e||e.length===0)return Buffer.alloc(0);let r=Buffer.isBuffer(e)?e:Buffer.from(e),o=r.subarray(0,3).toString("utf-8");if(o!=="v10"&&o!=="v11")return"<unsupported encryption format>";if(r.length<3+U+K)return"<decryption failed>";let n=r.subarray(3,3+U),s=r.subarray(r.length-K),i=r.subarray(3+U,r.length-K);try{let c=ue("aes-256-gcm",t,n);return c.setAuthTag(s),Buffer.concat([c.update(i),c.final()])}catch{return"<decryption failed>"}}function re(e,t){if(!e||e.length===0)return Buffer.alloc(0);let r=Buffer.isBuffer(e)?e:Buffer.from(e);if(r.subarray(0,3).toString("utf-8")!=="v20")return"<unsupported encryption format>";if(r.length<3+U+K)return"<decryption failed>";let n=r.subarray(3,3+U),s=r.subarray(r.length-K),i=r.subarray(3+U,r.length-K);try{let c=ue("aes-256-gcm",t,n);return c.setAuthTag(s),Buffer.concat([c.update(i),c.final()])}catch{return"<decryption failed>"}}function E(e){if(e==null)return"N/A";try{let t=BigInt(e);if(t===0n)return"N/A";let r=t-11644473600000000n,o=Number(r/1000n),n=new Date(o);return isNaN(n.getTime())?"N/A":n.toLocaleString()}catch{return"N/A"}}var h=class extends Error{constructor(r,o,n){super(o,n);this.code=r}code};function w(e,t,r,o){if(!e)return;let n=o instanceof h?o.code:o?.code==="ENOENT"?"missing_store":"unreadable";e({profile:t,store:r,code:n,message:o instanceof Error?o.message:String(o)})}import{homedir as Or}from"node:os";import{join as Ar}from"node:path";import{execFileSync as Br}from"node:child_process";import{pbkdf2Sync as vr}from"node:crypto";var xr=1003,br=16,Dr=Buffer.from("saltysalt"),ge="Chrome Safe Storage",pe={ITEM_NOT_FOUND:44,INTERACTION_NOT_ALLOWED:36,AUTH_FAILED:45,USER_CANCELED:128},J=class extends Error{service;kind;constructor(t,r,o){super(t),this.name="KeychainAccessError",this.service=r,this.kind=o}get isAccessDenied(){return this.kind==="access-denied"}};var de=new Map,me=new Map;function Cr(){return!!(process.env.SSH_CONNECTION||process.env.SSH_TTY)||!process.env.TERM_PROGRAM}function Pr(e){let t=e;return{status:typeof t?.status=="number"?t.status:null,stderr:(t?.stderr??"").toString().trim()}}function Rr(e,t,r){let o="See docs/remote-keychain-access.md for non-interactive / SSH setup.",n,s;t===pe.ITEM_NOT_FOUND||/could not be found/i.test(r)?(n="not-found",s=`No '${e}' item found in the Keychain.
2
+ Run this browser at least once on this machine so it creates the key.`):t===pe.INTERACTION_NOT_ALLOWED||/interaction.*not allowed/i.test(r)?(n="access-denied",s="The Keychain needs a GUI authorization prompt that can't be shown here "+(Cr()?`(you appear to be on a remote/SSH session).
3
+ `:`.
4
+ `)+`Authorize once from a GUI Terminal (click "Always Allow"), then unlock the
5
+ keychain in this session:
6
+ security unlock-keychain ~/Library/Keychains/login.keychain-db
7
+ `+o):t===pe.AUTH_FAILED?(n="access-denied",s=`Keychain authorization failed (wrong password or denied).
66
8
  Try: security unlock-keychain ~/Library/Keychains/login.keychain-db
67
- ${docs}`;
68
- } else {
69
- kind = "access-denied";
70
- hint = `macOS showed a Keychain access prompt that was denied or dismissed.
9
+ ${o}`):(n="access-denied",s=`macOS showed a Keychain access prompt that was denied or dismissed.
71
10
  Re-run the command and click "Allow" (or "Always Allow"), or authorize once with:
72
- security find-generic-password -w -s "${service}"
73
- ${docs}`;
74
- }
75
- const detail = [
76
- status !== null ? `exit ${status}` : null,
77
- stderr || null
78
- ].filter(Boolean).join(": ");
79
- return new KeychainAccessError(
80
- `Could not retrieve ${service} key from macOS Keychain.
81
- ${hint}` + (detail ? `
82
- (security: ${detail})` : ""),
83
- service,
84
- kind
85
- );
86
- }
87
- var _passwordProvider = null;
88
- function getKeychainPasswordProvider() {
89
- return _passwordProvider;
90
- }
91
- function setKeychainPasswordProvider(provider) {
92
- _passwordProvider = provider;
93
- }
94
- function getKeychainPassword(service = DEFAULT_SAFE_STORAGE_SERVICE) {
95
- const cached = _cachedPasswords.get(service);
96
- if (cached !== void 0) {
97
- return cached;
98
- }
99
- let password;
100
- if (_passwordProvider) {
101
- password = _passwordProvider(service);
102
- } else {
103
- try {
104
- password = execFileSync(
105
- "security",
106
- ["find-generic-password", "-w", "-s", service],
107
- { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
108
- ).trim();
109
- } catch (err) {
110
- const { status, stderr } = parseSecurityError(err);
111
- throw buildKeychainError(service, status, stderr);
112
- }
113
- }
114
- if (!password) {
115
- throw new KeychainAccessError(
116
- `Keychain returned an empty ${service} key.
117
- The item may exist with no password. Re-run the browser to recreate it.`,
118
- service,
119
- "empty"
120
- );
121
- }
122
- _cachedPasswords.set(service, password);
123
- return password;
124
- }
125
- function getChromeKeychainPassword() {
126
- return getKeychainPassword(DEFAULT_SAFE_STORAGE_SERVICE);
127
- }
128
- function deriveEncryptionKey(service = DEFAULT_SAFE_STORAGE_SERVICE, keychainPassword) {
129
- const cached = _cachedDerivedKeys.get(service);
130
- if (cached !== void 0) {
131
- return cached;
132
- }
133
- const password = keychainPassword || getKeychainPassword(service);
134
- if (!password) {
135
- throw new Error("Keychain password cannot be empty for key derivation.");
136
- }
137
- const key = pbkdf2Sync(
138
- password,
139
- PBKDF2_SALT,
140
- PBKDF2_ITERATIONS,
141
- PBKDF2_KEY_LENGTH,
142
- "sha1"
143
- );
144
- _cachedDerivedKeys.set(service, key);
145
- return key;
146
- }
147
- function clearKeychainCache() {
148
- _cachedPasswords.clear();
149
- _cachedDerivedKeys.clear();
150
- }
151
-
152
- // src/core/crypto.ts
153
- import { createDecipheriv } from "node:crypto";
154
- var AES_IV = Buffer.alloc(16, " ");
155
- var GCM_NONCE_LENGTH = 12;
156
- var GCM_TAG_LENGTH = 16;
157
- function decryptValueBuffer(encryptedBlob, derivedKey) {
158
- if (!encryptedBlob || encryptedBlob.length === 0) {
159
- return Buffer.alloc(0);
160
- }
161
- const blob = Buffer.isBuffer(encryptedBlob) ? encryptedBlob : Buffer.from(encryptedBlob);
162
- const prefix = blob.subarray(0, 3).toString("utf-8");
163
- if (prefix !== "v10" && prefix !== "v11") {
164
- return "<unsupported encryption format>";
165
- }
166
- const ciphertext = blob.subarray(3);
167
- try {
168
- const decipher = createDecipheriv("aes-128-cbc", derivedKey, AES_IV);
169
- let decrypted = decipher.update(ciphertext);
170
- decrypted = Buffer.concat([decrypted, decipher.final()]);
171
- return decrypted;
172
- } catch {
173
- return "<decryption failed>";
174
- }
175
- }
176
- function decryptValueBufferGcm(encryptedBlob, key) {
177
- if (!encryptedBlob || encryptedBlob.length === 0) {
178
- return Buffer.alloc(0);
179
- }
180
- const blob = Buffer.isBuffer(encryptedBlob) ? encryptedBlob : Buffer.from(encryptedBlob);
181
- const prefix = blob.subarray(0, 3).toString("utf-8");
182
- if (prefix !== "v10" && prefix !== "v11") {
183
- return "<unsupported encryption format>";
184
- }
185
- if (blob.length < 3 + GCM_NONCE_LENGTH + GCM_TAG_LENGTH) {
186
- return "<decryption failed>";
187
- }
188
- const nonce = blob.subarray(3, 3 + GCM_NONCE_LENGTH);
189
- const tag = blob.subarray(blob.length - GCM_TAG_LENGTH);
190
- const ciphertext = blob.subarray(3 + GCM_NONCE_LENGTH, blob.length - GCM_TAG_LENGTH);
191
- try {
192
- const decipher = createDecipheriv("aes-256-gcm", key, nonce);
193
- decipher.setAuthTag(tag);
194
- return Buffer.concat([decipher.update(ciphertext), decipher.final()]);
195
- } catch {
196
- return "<decryption failed>";
197
- }
198
- }
199
- function decryptValueBufferGcmV20(encryptedBlob, key) {
200
- if (!encryptedBlob || encryptedBlob.length === 0) {
201
- return Buffer.alloc(0);
202
- }
203
- const blob = Buffer.isBuffer(encryptedBlob) ? encryptedBlob : Buffer.from(encryptedBlob);
204
- const prefix = blob.subarray(0, 3).toString("utf-8");
205
- if (prefix !== "v20") {
206
- return "<unsupported encryption format>";
207
- }
208
- if (blob.length < 3 + GCM_NONCE_LENGTH + GCM_TAG_LENGTH) {
209
- return "<decryption failed>";
210
- }
211
- const nonce = blob.subarray(3, 3 + GCM_NONCE_LENGTH);
212
- const tag = blob.subarray(blob.length - GCM_TAG_LENGTH);
213
- const ciphertext = blob.subarray(3 + GCM_NONCE_LENGTH, blob.length - GCM_TAG_LENGTH);
214
- try {
215
- const decipher = createDecipheriv("aes-256-gcm", key, nonce);
216
- decipher.setAuthTag(tag);
217
- return Buffer.concat([decipher.update(ciphertext), decipher.final()]);
218
- } catch {
219
- return "<decryption failed>";
220
- }
221
- }
222
- function chromeTimeToDate(chromeTimestamp) {
223
- if (chromeTimestamp === null || chromeTimestamp === void 0) {
224
- return "N/A";
225
- }
226
- try {
227
- const timestampBigInt = BigInt(chromeTimestamp);
228
- if (timestampBigInt === 0n) {
229
- return "N/A";
230
- }
231
- const unixMicroseconds = timestampBigInt - 11644473600000000n;
232
- const ms = Number(unixMicroseconds / 1000n);
233
- const date = new Date(ms);
234
- if (isNaN(date.getTime())) {
235
- return "N/A";
236
- }
237
- return date.toLocaleString();
238
- } catch {
239
- return "N/A";
240
- }
241
- }
242
-
243
- // src/core/browsers.ts
244
- var BROWSERS = {
245
- chrome: {
246
- id: "chrome",
247
- name: "Google Chrome",
248
- aliases: ["google-chrome", "google chrome"],
249
- safeStorageLabel: "Chrome Safe Storage",
250
- linuxSecretApp: "chrome",
251
- dirs: {
252
- darwin: "Google/Chrome",
253
- win32: "Google/Chrome/User Data",
254
- linux: "google-chrome"
255
- }
256
- },
257
- "chrome-beta": {
258
- id: "chrome-beta",
259
- name: "Google Chrome Beta",
260
- safeStorageLabel: "Chrome Safe Storage",
261
- linuxSecretApp: "chrome",
262
- dirs: {
263
- darwin: "Google/Chrome Beta",
264
- win32: "Google/Chrome Beta/User Data",
265
- linux: "google-chrome-beta"
266
- }
267
- },
268
- "chrome-canary": {
269
- id: "chrome-canary",
270
- name: "Google Chrome Canary",
271
- aliases: ["canary"],
272
- safeStorageLabel: "Chrome Safe Storage",
273
- linuxSecretApp: "chrome",
274
- dirs: {
275
- darwin: "Google/Chrome Canary",
276
- win32: "Google/Chrome SxS/User Data",
277
- linux: "google-chrome-unstable"
278
- }
279
- },
280
- chromium: {
281
- id: "chromium",
282
- name: "Chromium",
283
- safeStorageLabel: "Chromium Safe Storage",
284
- linuxSecretApp: "chromium",
285
- dirs: {
286
- darwin: "Chromium",
287
- win32: "Chromium/User Data",
288
- linux: "chromium"
289
- }
290
- },
291
- edge: {
292
- id: "edge",
293
- name: "Microsoft Edge",
294
- aliases: ["msedge", "microsoft-edge", "microsoft edge"],
295
- safeStorageLabel: "Microsoft Edge Safe Storage",
296
- linuxSecretApp: "chromium",
297
- dirs: {
298
- darwin: "Microsoft Edge",
299
- win32: "Microsoft/Edge/User Data",
300
- linux: "microsoft-edge"
301
- }
302
- },
303
- brave: {
304
- id: "brave",
305
- name: "Brave",
306
- aliases: ["brave-browser"],
307
- safeStorageLabel: "Brave Safe Storage",
308
- linuxSecretApp: "chrome",
309
- dirs: {
310
- darwin: "BraveSoftware/Brave-Browser",
311
- win32: "BraveSoftware/Brave-Browser/User Data",
312
- linux: "BraveSoftware/Brave-Browser"
313
- }
314
- },
315
- comet: {
316
- id: "comet",
317
- name: "Comet",
318
- aliases: ["perplexity", "perplexity-comet"],
319
- safeStorageLabel: "Comet Safe Storage",
320
- linuxSecretApp: "chromium",
321
- // macOS dir verified (~/Library/Application Support/Comet); Win/Linux best-effort.
322
- dirs: {
323
- darwin: "Comet",
324
- win32: "Perplexity/Comet/User Data",
325
- linux: "comet"
326
- }
327
- },
328
- vivaldi: {
329
- id: "vivaldi",
330
- name: "Vivaldi",
331
- // Vivaldi reuses Chrome's Keychain item rather than minting its own.
332
- safeStorageLabel: "Chrome Safe Storage",
333
- linuxSecretApp: "chrome",
334
- dirs: {
335
- darwin: "Vivaldi",
336
- win32: "Vivaldi/User Data",
337
- linux: "vivaldi"
338
- }
339
- },
340
- opera: {
341
- id: "opera",
342
- name: "Opera",
343
- safeStorageLabel: "Opera Safe Storage",
344
- linuxSecretApp: "chromium",
345
- // Opera's product dir *is* the user-data root (no nested "User Data").
346
- // Profiles are usually still under Default/; some layouts are flat at root —
347
- // discoverProfiles handles both.
348
- dirs: {
349
- darwin: "com.operasoftware.Opera",
350
- win32: "Opera Software/Opera Stable",
351
- win32Base: "roaming",
352
- linux: "opera"
353
- }
354
- }
355
- };
356
- var DEFAULT_BROWSER = "chrome";
357
- function listBrowsers() {
358
- return Object.values(BROWSERS);
359
- }
360
- function resolveBrowser(idOrName) {
361
- if (!idOrName) return null;
362
- const needle = idOrName.trim().toLowerCase();
363
- for (const b of listBrowsers()) {
364
- if (b.id.toLowerCase() === needle) return b;
365
- if (b.name.toLowerCase() === needle) return b;
366
- if (b.aliases?.some((a) => a.toLowerCase() === needle)) return b;
367
- }
368
- return null;
369
- }
370
- function requireBrowser(idOrName) {
371
- const resolved = resolveBrowser(idOrName);
372
- if (resolved) return resolved;
373
- throw new Error(
374
- `Unknown browser "${idOrName}". Supported: ${listBrowsers().map((b) => b.id).join(", ")}.`
375
- );
376
- }
377
- var _active = BROWSERS[DEFAULT_BROWSER];
378
- function getActiveBrowser() {
379
- return _active;
380
- }
381
- function setActiveBrowser(idOrName) {
382
- if (!idOrName || !idOrName.trim()) {
383
- _active = BROWSERS[DEFAULT_BROWSER];
384
- return _active;
385
- }
386
- _active = requireBrowser(idOrName);
387
- return _active;
388
- }
389
-
390
- // src/core/platform/darwin.ts
391
- var darwin = {
392
- name: "darwin",
393
- getDefaultChromeDir(browser = getActiveBrowser()) {
394
- return join(
395
- homedir(),
396
- "Library",
397
- "Application Support",
398
- ...browser.dirs.darwin.split("/")
399
- );
400
- },
401
- getDecryptionKey(_chromeDir, browser = getActiveBrowser()) {
402
- return deriveEncryptionKey(browser.safeStorageLabel);
403
- },
404
- decrypt(blob, key) {
405
- return decryptValueBuffer(blob, key);
406
- },
407
- clearCache() {
408
- clearKeychainCache();
409
- }
410
- };
411
-
412
- // src/core/platform/win32.ts
413
- import { execFileSync as execFileSync2 } from "node:child_process";
414
- import { copyFileSync, existsSync, mkdirSync, readFileSync, statSync } from "node:fs";
415
- import { dirname, join as join2 } from "node:path";
416
- var DPAPI_PREFIX = Buffer.from("DPAPI", "ascii");
417
- var APPB_PREFIX = Buffer.from("APPB", "ascii");
418
- var _cachedKeys = /* @__PURE__ */ new Map();
419
- var _cachedABEKeys = /* @__PURE__ */ new Map();
420
- var _lastResolvedDir;
421
- var _lastResolvedBrowser;
422
- var ABE_BROWSER_MAP = {
423
- chrome: "chrome",
424
- "chrome-beta": "chrome-beta",
425
- "chrome-canary": "chrome",
426
- chromium: "chrome",
427
- brave: "brave",
428
- edge: "edge",
429
- vivaldi: "chrome",
430
- comet: "chrome",
431
- opera: "chrome"
432
- };
433
- function dpapiDecrypt(encrypted) {
434
- const script = [
435
- "$ErrorActionPreference='Stop';",
436
- "Add-Type -AssemblyName System.Security;",
437
- "$b=[Console]::In.ReadToEnd().Trim();",
438
- "$enc=[Convert]::FromBase64String($b);",
439
- "$dec=[System.Security.Cryptography.ProtectedData]::Unprotect($enc,$null,[System.Security.Cryptography.DataProtectionScope]::CurrentUser);",
440
- "[Console]::Out.Write([Convert]::ToBase64String($dec));"
441
- ].join(" ");
442
- let out;
443
- try {
444
- out = execFileSync2(
445
- "powershell.exe",
446
- ["-NoProfile", "-NonInteractive", "-Command", script],
447
- { input: encrypted.toString("base64"), encoding: "utf-8" }
448
- );
449
- } catch (err) {
450
- throw new Error(
451
- `DPAPI decryption via PowerShell failed.
11
+ security find-generic-password -w -s "${e}"
12
+ ${o}`);let i=[t!==null?`exit ${t}`:null,r||null].filter(Boolean).join(": ");return new J(`Could not retrieve ${e} key from macOS Keychain.
13
+ ${s}`+(i?`
14
+ (security: ${i})`:""),e,n)}var te=null;function ye(){return te}function kr(e){te=e}function he(e=ge){let t=de.get(e);if(t!==void 0)return t;let r;if(te)r=te(e);else try{r=Br("security",["find-generic-password","-w","-s",e],{encoding:"utf-8",stdio:["pipe","pipe","pipe"]}).trim()}catch(o){let{status:n,stderr:s}=Pr(o);throw Rr(e,n,s)}if(!r)throw new J(`Keychain returned an empty ${e} key.
15
+ The item may exist with no password. Re-run the browser to recreate it.`,e,"empty");return de.set(e,r),r}function Lr(){return he(ge)}function Te(e=ge,t){let r=me.get(e);if(r!==void 0)return r;let o=t||he(e);if(!o)throw new Error("Keychain password cannot be empty for key derivation.");let n=vr(o,Dr,xr,br,"sha1");return me.set(e,n),n}function we(){de.clear(),me.clear()}var Q={chrome:{id:"chrome",name:"Google Chrome",aliases:["google-chrome","google chrome"],safeStorageLabel:"Chrome Safe Storage",linuxSecretApp:"chrome",dirs:{darwin:"Google/Chrome",win32:"Google/Chrome/User Data",linux:"google-chrome"}},"chrome-beta":{id:"chrome-beta",name:"Google Chrome Beta",safeStorageLabel:"Chrome Safe Storage",linuxSecretApp:"chrome",dirs:{darwin:"Google/Chrome Beta",win32:"Google/Chrome Beta/User Data",linux:"google-chrome-beta"}},"chrome-canary":{id:"chrome-canary",name:"Google Chrome Canary",aliases:["canary"],safeStorageLabel:"Chrome Safe Storage",linuxSecretApp:"chrome",dirs:{darwin:"Google/Chrome Canary",win32:"Google/Chrome SxS/User Data",linux:"google-chrome-unstable"}},chromium:{id:"chromium",name:"Chromium",safeStorageLabel:"Chromium Safe Storage",linuxSecretApp:"chromium",dirs:{darwin:"Chromium",win32:"Chromium/User Data",linux:"chromium"}},edge:{id:"edge",name:"Microsoft Edge",aliases:["msedge","microsoft-edge","microsoft edge"],safeStorageLabel:"Microsoft Edge Safe Storage",linuxSecretApp:"chromium",dirs:{darwin:"Microsoft Edge",win32:"Microsoft/Edge/User Data",linux:"microsoft-edge"}},brave:{id:"brave",name:"Brave",aliases:["brave-browser"],safeStorageLabel:"Brave Safe Storage",linuxSecretApp:"chrome",dirs:{darwin:"BraveSoftware/Brave-Browser",win32:"BraveSoftware/Brave-Browser/User Data",linux:"BraveSoftware/Brave-Browser"}},comet:{id:"comet",name:"Comet",aliases:["perplexity","perplexity-comet"],safeStorageLabel:"Comet Safe Storage",linuxSecretApp:"chromium",dirs:{darwin:"Comet",win32:"Perplexity/Comet/User Data",linux:"comet"}},vivaldi:{id:"vivaldi",name:"Vivaldi",safeStorageLabel:"Chrome Safe Storage",linuxSecretApp:"chrome",dirs:{darwin:"Vivaldi",win32:"Vivaldi/User Data",linux:"vivaldi"}},opera:{id:"opera",name:"Opera",safeStorageLabel:"Opera Safe Storage",linuxSecretApp:"chromium",dirs:{darwin:"com.operasoftware.Opera",win32:"Opera Software/Opera Stable",win32Base:"roaming",linux:"opera"}}},Ie="chrome";function $(){return Object.values(Q)}function Se(e){if(!e)return null;let t=e.trim().toLowerCase();for(let r of $())if(r.id.toLowerCase()===t||r.name.toLowerCase()===t||r.aliases?.some(o=>o.toLowerCase()===t))return r;return null}function M(e){let t=Se(e);if(t)return t;throw new Error(`Unknown browser "${e}". Supported: ${$().map(r=>r.id).join(", ")}.`)}var X=Q[Ie];function v(){return X}function Ne(e){return!e||!e.trim()?(X=Q[Ie],X):(X=M(e),X)}var Me={name:"darwin",getDefaultChromeDir(e=v()){return Ar(Or(),"Library","Application Support",...e.dirs.darwin.split("/"))},getDecryptionKey(e,t=v()){return Te(t.safeStorageLabel)},decrypt(e,t){return ee(e,t)},clearCache(){we()}};import{execFileSync as Ke}from"node:child_process";import{existsSync as Z,readFileSync as Fe}from"node:fs";import{dirname as $e,join as O,resolve as Tr}from"node:path";var _e=Buffer.from("DPAPI","ascii"),Ee=Buffer.from("APPB","ascii"),Be=new Map,ve=new Map,xe=new WeakMap,Ir=new Set(["chrome","chrome-beta","brave","edge"]);function Nr(e){let t=["$ErrorActionPreference='Stop';","Add-Type -AssemblyName System.Security;","$b=[Console]::In.ReadToEnd().Trim();","$enc=[Convert]::FromBase64String($b);","$dec=[System.Security.Cryptography.ProtectedData]::Unprotect($enc,$null,[System.Security.Cryptography.DataProtectionScope]::CurrentUser);","[Console]::Out.Write([Convert]::ToBase64String($dec));"].join(" "),r;try{r=Ke("powershell.exe",["-NoProfile","-NonInteractive","-Command",t],{input:e.toString("base64"),encoding:"utf-8"})}catch(o){throw new Error(`DPAPI decryption via PowerShell failed.
452
16
  The encrypted_key can only be unprotected by the same Windows user that created it. Run as that user on the same machine.
453
- (powershell: ${err?.message ?? err})`
454
- );
455
- }
456
- return Buffer.from(out.trim(), "base64");
457
- }
458
- function resolveLocalStateDir(requestedDir) {
459
- const parentDir = dirname(requestedDir);
460
- return existsSync(join2(requestedDir, "Local State")) ? requestedDir : parentDir !== requestedDir && existsSync(join2(parentDir, "Local State")) ? parentDir : requestedDir;
461
- }
462
- function findABEHelper() {
463
- const base = import.meta.dirname ?? "";
464
- const candidates = [
465
- join2(base, "chrome_abe_decrypt.exe"),
466
- join2(base, "..", "..", "..", "bin", "chrome_abe_decrypt.exe"),
467
- join2(base, "..", "..", "..", "..", "bin", "chrome_abe_decrypt.exe"),
468
- join2(base, "..", "..", "..", "..", "src", "native", "chrome_abe_decrypt.exe")
469
- ];
470
- for (const p of candidates) {
471
- if (existsSync(p)) return p;
472
- }
473
- try {
474
- execFileSync2("where", ["chrome_abe_decrypt.exe"], { encoding: "utf-8", stdio: "pipe" });
475
- return "chrome_abe_decrypt.exe";
476
- } catch {
477
- return null;
478
- }
479
- }
480
- function getBrowserTempDirs(browserId) {
481
- const dirs = [];
482
- const progFiles = process.env["ProgramFiles"] || "C:\\Program Files";
483
- const progFilesX86 = process.env["ProgramFiles(x86)"] || "C:\\Program Files (x86)";
484
- const localAppData = process.env.LOCALAPPDATA || (process.env.USERPROFILE ? join2(process.env.USERPROFILE, "AppData", "Local") : "");
485
- const subpaths = {
486
- chrome: ["Google\\Chrome"],
487
- "chrome-beta": ["Google\\Chrome Beta"],
488
- "chrome-canary": ["Google\\Chrome SxS"],
489
- chromium: ["Chromium"],
490
- edge: ["Microsoft\\Edge"],
491
- brave: ["BraveSoftware\\Brave-Browser"],
492
- vivaldi: ["Vivaldi"]
493
- };
494
- const rels = subpaths[browserId] || ["Google\\Chrome"];
495
- for (const rel of rels) {
496
- dirs.push(join2(progFiles, rel, "Temp"));
497
- dirs.push(join2(progFilesX86, rel, "Temp"));
498
- if (localAppData) {
499
- dirs.push(join2(localAppData, rel, "Temp"));
500
- }
501
- }
502
- return dirs;
503
- }
504
- function findOrStageABEHelper(browserId) {
505
- const sourceExe = findABEHelper();
506
- if (!sourceExe) return null;
507
- const targetDirs = getBrowserTempDirs(browserId);
508
- for (const targetDir of targetDirs) {
509
- try {
510
- if (!existsSync(targetDir)) {
511
- mkdirSync(targetDir, { recursive: true });
512
- }
513
- const stagedExe = join2(targetDir, "chrome_abe_decrypt.exe");
514
- if (!existsSync(stagedExe) || statSync(stagedExe).size !== statSync(sourceExe).size) {
515
- copyFileSync(sourceExe, stagedExe);
516
- }
517
- return stagedExe;
518
- } catch {
519
- continue;
520
- }
521
- }
522
- return sourceExe;
523
- }
524
- var _abeWarningShown = false;
525
- function abeDecrypt(rawEncryptedBytes, browserId) {
526
- const helperExe = findOrStageABEHelper(browserId);
527
- if (!helperExe) {
528
- if (!_abeWarningShown) {
529
- _abeWarningShown = true;
530
- process.stderr.write(
531
- "[chrome-tools] chrome_abe_decrypt.exe not found. Chrome 127+ v20-encrypted\ncookies/passwords cannot be decrypted.\n"
532
- );
533
- }
534
- return null;
535
- }
536
- const comBrowser = ABE_BROWSER_MAP[browserId] || "chrome";
537
- try {
538
- const out = execFileSync2(
539
- helperExe,
540
- [comBrowser],
541
- {
542
- input: rawEncryptedBytes.toString("base64"),
543
- encoding: "utf-8",
544
- timeout: 3e4,
545
- stdio: ["pipe", "pipe", "pipe"],
546
- cwd: dirname(helperExe)
547
- }
548
- );
549
- return Buffer.from(out.trim(), "base64");
550
- } catch {
551
- return null;
552
- }
553
- }
554
- var win32 = {
555
- name: "win32",
556
- getDefaultChromeDir(browser = getActiveBrowser()) {
557
- const roaming = browser.dirs.win32Base === "roaming";
558
- const appData = roaming ? process.env.APPDATA || (process.env.USERPROFILE ? join2(process.env.USERPROFILE, "AppData", "Roaming") : "") : process.env.LOCALAPPDATA || (process.env.USERPROFILE ? join2(process.env.USERPROFILE, "AppData", "Local") : "");
559
- if (!appData) {
560
- throw new Error("Could not resolve the Windows application-data directory.");
561
- }
562
- return join2(appData, ...browser.dirs.win32.split("/"));
563
- },
564
- getDecryptionKey(chromeDir, browser = getActiveBrowser()) {
565
- const requestedDir = chromeDir || this.getDefaultChromeDir(browser);
566
- const dir = resolveLocalStateDir(requestedDir);
567
- _lastResolvedDir = dir;
568
- _lastResolvedBrowser = browser;
569
- const cached = _cachedKeys.get(dir);
570
- if (cached !== void 0) {
571
- if (!_cachedABEKeys.has(dir)) {
572
- try {
573
- this.getAppBoundKey?.(dir, browser);
574
- } catch {
575
- }
576
- }
577
- return cached;
578
- }
579
- const localStatePath = join2(dir, "Local State");
580
- if (!existsSync(localStatePath)) {
581
- throw new Error(
582
- `Could not find Chrome's "Local State" at ${localStatePath}.
583
- Run Google Chrome at least once so it creates the encryption key.`
584
- );
585
- }
586
- let encryptedKeyB64;
587
- try {
588
- const raw = readFileSync(localStatePath, "utf-8");
589
- encryptedKeyB64 = JSON.parse(raw)?.os_crypt?.encrypted_key;
590
- } catch (err) {
591
- throw new Error(`Failed to read os_crypt.encrypted_key from Local State: ${err.message}`);
592
- }
593
- if (!encryptedKeyB64) {
594
- throw new Error(
595
- "No os_crypt.encrypted_key found in Local State.\nThis profile may predate Chrome's encrypted-key scheme."
596
- );
597
- }
598
- const encryptedKey = Buffer.from(encryptedKeyB64, "base64");
599
- if (!encryptedKey.subarray(0, DPAPI_PREFIX.length).equals(DPAPI_PREFIX)) {
600
- throw new Error('Unexpected encrypted_key format (missing "DPAPI" prefix).');
601
- }
602
- const key = dpapiDecrypt(encryptedKey.subarray(DPAPI_PREFIX.length));
603
- _cachedKeys.set(dir, key);
604
- try {
605
- this.getAppBoundKey?.(dir, browser);
606
- } catch {
607
- }
608
- return key;
609
- },
610
- getAppBoundKey(chromeDir, browser = _lastResolvedBrowser ?? getActiveBrowser()) {
611
- const requestedDir = chromeDir || _lastResolvedDir || this.getDefaultChromeDir(browser);
612
- const dir = resolveLocalStateDir(requestedDir);
613
- _lastResolvedDir = dir;
614
- _lastResolvedBrowser = browser;
615
- const cached = _cachedABEKeys.get(dir);
616
- if (cached !== void 0) {
617
- return cached;
618
- }
619
- const localStatePath = join2(dir, "Local State");
620
- if (!existsSync(localStatePath)) return void 0;
621
- let appBoundKeyB64;
622
- try {
623
- const raw = readFileSync(localStatePath, "utf-8");
624
- appBoundKeyB64 = JSON.parse(raw)?.os_crypt?.app_bound_encrypted_key;
625
- } catch {
626
- return void 0;
627
- }
628
- if (!appBoundKeyB64) return void 0;
629
- const appBoundKey = Buffer.from(appBoundKeyB64, "base64");
630
- if (!appBoundKey.subarray(0, APPB_PREFIX.length).equals(APPB_PREFIX)) {
631
- return void 0;
632
- }
633
- const abeKey = abeDecrypt(appBoundKey.subarray(APPB_PREFIX.length), browser.id);
634
- if (!abeKey) return void 0;
635
- const key = abeKey.length > 32 ? abeKey.subarray(abeKey.length - 32) : abeKey;
636
- _cachedABEKeys.set(dir, key);
637
- return key;
638
- },
639
- decrypt(blob, key) {
640
- const prefix = blob.subarray(0, 3).toString("utf-8");
641
- if (prefix === "v20") {
642
- const abeKey = this.getAppBoundKey?.(_lastResolvedDir, _lastResolvedBrowser);
643
- if (!abeKey) {
644
- return "<v20 app-bound encryption - requires chrome_abe_decrypt.exe>";
645
- }
646
- return decryptValueBufferGcmV20(blob, abeKey);
647
- }
648
- return decryptValueBufferGcm(blob, key);
649
- },
650
- clearCache() {
651
- _cachedKeys.clear();
652
- _cachedABEKeys.clear();
653
- _lastResolvedDir = void 0;
654
- _lastResolvedBrowser = void 0;
655
- }
656
- };
657
-
658
- // src/core/platform/linux.ts
659
- import { execFileSync as execFileSync3 } from "node:child_process";
660
- import { homedir as homedir2 } from "node:os";
661
- import { join as join3 } from "node:path";
662
- import { pbkdf2Sync as pbkdf2Sync2 } from "node:crypto";
663
- var PBKDF2_ITERATIONS2 = 1;
664
- var PBKDF2_KEY_LENGTH2 = 16;
665
- var PBKDF2_SALT2 = Buffer.from("saltysalt");
666
- var FALLBACK_PASSWORD = "peanuts";
667
- var _cachedKeys2 = /* @__PURE__ */ new Map();
668
- function getStoragePassword(application) {
669
- const provider = getKeychainPasswordProvider();
670
- if (provider) {
671
- return provider(application);
672
- }
673
- try {
674
- const out = execFileSync3(
675
- "secret-tool",
676
- ["lookup", "application", application],
677
- { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
678
- ).trim();
679
- if (out) return out;
680
- } catch {
681
- }
682
- return FALLBACK_PASSWORD;
683
- }
684
- var linux = {
685
- name: "linux",
686
- getDefaultChromeDir(browser = getActiveBrowser()) {
687
- const configHome = process.env.XDG_CONFIG_HOME || join3(homedir2(), ".config");
688
- return join3(configHome, ...browser.dirs.linux.split("/"));
689
- },
690
- getDecryptionKey(_chromeDir, browser = getActiveBrowser()) {
691
- const application = browser.linuxSecretApp || "chrome";
692
- const cached = _cachedKeys2.get(application);
693
- if (cached !== void 0) {
694
- return cached;
695
- }
696
- const key = pbkdf2Sync2(
697
- getStoragePassword(application),
698
- PBKDF2_SALT2,
699
- PBKDF2_ITERATIONS2,
700
- PBKDF2_KEY_LENGTH2,
701
- "sha1"
702
- );
703
- _cachedKeys2.set(application, key);
704
- return key;
705
- },
706
- decrypt(blob, key) {
707
- return decryptValueBuffer(blob, key);
708
- },
709
- clearCache() {
710
- _cachedKeys2.clear();
711
- }
712
- };
713
-
714
- // src/core/platform/index.ts
715
- var _active2 = null;
716
- function selectPlatform() {
717
- if (_active2) return _active2;
718
- switch (process.platform) {
719
- case "darwin":
720
- _active2 = darwin;
721
- break;
722
- case "win32":
723
- _active2 = win32;
724
- break;
725
- case "linux":
726
- _active2 = linux;
727
- break;
728
- default:
729
- throw new Error(
730
- `Unsupported platform: ${process.platform}. chrome-tools supports macOS, Windows, and Linux.`
731
- );
732
- }
733
- return _active2;
734
- }
735
- function getPlatformChromeDir(browser) {
736
- return selectPlatform().getDefaultChromeDir(resolveTargetBrowser(browser));
737
- }
738
- function deriveEncryptionKey2(chromeDir, browser) {
739
- return selectPlatform().getDecryptionKey(chromeDir, resolveTargetBrowser(browser));
740
- }
741
- function decryptValueBuffer2(blob, key) {
742
- if (!blob || blob.length === 0) {
743
- return Buffer.alloc(0);
744
- }
745
- const buf = Buffer.isBuffer(blob) ? blob : Buffer.from(blob);
746
- return selectPlatform().decrypt(buf, key);
747
- }
748
- function decryptValue(blob, key) {
749
- const decrypted = decryptValueBuffer2(blob, key);
750
- return Buffer.isBuffer(decrypted) ? decrypted.toString("utf-8") : decrypted;
751
- }
752
- function clearKeyCache() {
753
- selectPlatform().clearCache();
754
- }
755
- function resolveTargetBrowser(browser) {
756
- if (!browser) return getActiveBrowser();
757
- return typeof browser === "string" ? requireBrowser(browser) : browser;
758
- }
759
-
760
- // src/core/chrome.ts
761
- var PROFILE_INDICATORS = [
762
- "Login Data",
763
- "History",
764
- "Bookmarks",
765
- "Cookies",
766
- "Web Data",
767
- "Local Storage",
768
- "Preferences"
769
- ];
770
- function resolveCookiesDatabasePath(profilePath) {
771
- const networkPath = join4(profilePath, "Network", "Cookies");
772
- return existsSync2(networkPath) ? networkPath : join4(profilePath, "Cookies");
773
- }
774
- function isProfileDirectory(dir) {
775
- return PROFILE_INDICATORS.some((file) => existsSync2(join4(dir, file))) || existsSync2(resolveCookiesDatabasePath(dir));
776
- }
777
- function getDefaultChromeDir(browser) {
778
- return getPlatformChromeDir(browser);
779
- }
780
- function detectInstalledBrowsers({ includeMissing = false } = {}) {
781
- const out = [];
782
- for (const browser of listBrowsers()) {
783
- const chromeDir = getDefaultChromeDir(browser);
784
- const installed = existsSync2(chromeDir);
785
- if (!installed && !includeMissing) continue;
786
- out.push({
787
- browser,
788
- chromeDir,
789
- installed,
790
- profileCount: installed ? discoverProfiles(chromeDir).length : 0
791
- });
792
- }
793
- return out;
794
- }
795
- function discoverProfiles(chromeDir) {
796
- const dir = chromeDir || getDefaultChromeDir();
797
- if (!dir || !existsSync2(dir)) {
798
- return [];
799
- }
800
- try {
801
- const entries = readdirSync(dir, { withFileTypes: true });
802
- const profiles = [];
803
- for (const entry of entries) {
804
- if (!entry.isDirectory()) continue;
805
- if (entry.name === "Network") continue;
806
- const profilePath = join4(dir, entry.name);
807
- if (isProfileDirectory(profilePath)) {
808
- profiles.push({ name: entry.name, path: profilePath });
809
- }
810
- }
811
- if (profiles.length === 0 && isProfileDirectory(dir)) {
812
- const name = basename(dir) || "Default";
813
- profiles.push({ name, path: dir });
814
- }
815
- return profiles;
816
- } catch {
817
- return [];
818
- }
819
- }
820
- function getProfileMetadata(chromeDir) {
821
- const dir = chromeDir || getDefaultChromeDir();
822
- const localStatePath = join4(dir, "Local State");
823
- if (!existsSync2(localStatePath)) {
824
- return [];
825
- }
826
- try {
827
- const raw = readFileSync2(localStatePath, "utf-8");
828
- const localState = JSON.parse(raw);
829
- const infoCache = localState?.profile?.info_cache;
830
- if (!infoCache || typeof infoCache !== "object") {
831
- return [];
832
- }
833
- return Object.entries(infoCache).map(([dirName, infoAny]) => {
834
- const info = infoAny;
835
- return {
836
- directory: dirName,
837
- name: info.name || dirName,
838
- gaiaName: info.gaia_name || "",
839
- userName: info.user_name || "",
840
- email: info.user_name || "",
841
- avatarIcon: info.avatar_icon || "",
842
- isUsingDefaultName: info.is_using_default_name ?? true,
843
- isEphemeral: info.is_ephemeral ?? false,
844
- activeTime: info.active_time || 0
845
- };
846
- });
847
- } catch {
848
- return [];
849
- }
850
- }
851
- function getProfileStates(chromeDir) {
852
- const dir = chromeDir || getDefaultChromeDir();
853
- const discovered = discoverProfiles(dir);
854
- const metadata = getProfileMetadata(dir);
855
- const metaMap = /* @__PURE__ */ new Map();
856
- for (const m of metadata) {
857
- metaMap.set(m.directory, m);
858
- }
859
- return discovered.map((profile) => {
860
- const meta = metaMap.get(profile.name);
861
- const profilePath = profile.path;
862
- const dataFiles = {};
863
- for (const file of PROFILE_INDICATORS) {
864
- dataFiles[file.replace(/\s+/g, "_").toLowerCase()] = existsSync2(
865
- join4(profilePath, file)
866
- );
867
- }
868
- dataFiles.cookies = existsSync2(resolveCookiesDatabasePath(profilePath));
869
- dataFiles.top_sites = existsSync2(join4(profilePath, "Top Sites"));
870
- dataFiles.extensions = existsSync2(join4(profilePath, "Extensions"));
871
- return {
872
- directory: profile.name,
873
- path: profilePath,
874
- displayName: meta?.name || profile.name,
875
- gaiaName: meta?.gaiaName || "",
876
- email: meta?.email || "",
877
- avatarIcon: meta?.avatarIcon || "",
878
- isEphemeral: meta?.isEphemeral ?? false,
879
- activeTime: meta?.activeTime || 0,
880
- dataFiles
881
- };
882
- });
883
- }
884
- function resolveProfilePath(chromeDir, profileName) {
885
- const dir = chromeDir || getDefaultChromeDir();
886
- const profilePath = join4(dir, profileName);
887
- return existsSync2(profilePath) ? profilePath : null;
888
- }
889
- function resolveTargetProfiles({
890
- profilePath,
891
- chromeDir,
892
- profile,
893
- browser
894
- } = {}) {
895
- const targetBrowser = browser ? requireBrowser(browser) : void 0;
896
- const dir = profilePath || chromeDir || getDefaultChromeDir(targetBrowser);
897
- const allProfiles = discoverProfiles(dir);
898
- if (profile) {
899
- const match = allProfiles.filter((p) => p.name === profile);
900
- if (match.length === 0) {
901
- throw new Error(`Profile "${profile}" not found in ${dir}`);
902
- }
903
- return match;
904
- }
905
- return allProfiles;
906
- }
907
-
908
- // src/core/db.ts
909
- import { copyFileSync as copyFileSync2, existsSync as existsSync3, mkdtempSync, rmSync } from "node:fs";
910
- import { tmpdir } from "node:os";
911
- import { join as join5, basename as basename2 } from "node:path";
912
- import { DatabaseSync } from "node:sqlite";
913
- var SQLITE_BUSY = 5;
914
- var SQLITE_LOCKED = 6;
915
- var SQLITE_CANTOPEN = 14;
916
- function shouldCopyDatabase(err) {
917
- const errcode = err?.errcode;
918
- if (typeof errcode === "number") {
919
- const primary = errcode & 255;
920
- if (primary === SQLITE_BUSY || primary === SQLITE_LOCKED || primary === SQLITE_CANTOPEN) {
921
- return true;
922
- }
923
- }
924
- const message = err instanceof Error ? err.message : String(err);
925
- return /database is locked|database is busy|unable to open database file/i.test(message);
926
- }
927
- function toCallerTypes(rows) {
928
- return rows.map((row) => {
929
- const plain = {};
930
- for (const key of Object.keys(row)) {
931
- const value = row[key];
932
- if (typeof value === "bigint") {
933
- plain[key] = Number(value);
934
- } else if (value instanceof Uint8Array && !Buffer.isBuffer(value)) {
935
- plain[key] = Buffer.from(value.buffer, value.byteOffset, value.byteLength);
936
- } else {
937
- plain[key] = value;
938
- }
939
- }
940
- return plain;
941
- });
942
- }
943
- function copyLockedDatabase(dbFilePath, tmpDb) {
944
- copyFileSync2(dbFilePath, tmpDb);
945
- for (const suffix of ["-wal", "-shm"]) {
946
- const sidecar = `${dbFilePath}${suffix}`;
947
- if (existsSync3(sidecar)) copyFileSync2(sidecar, `${tmpDb}${suffix}`);
948
- }
949
- }
950
- function queryDatabase(dbFilePath, sql, params = []) {
951
- if (!existsSync3(dbFilePath)) {
952
- throw new Error(`Database file not found: ${dbFilePath}`);
953
- }
954
- const tmpDir = mkdtempSync(join5(tmpdir(), "chrome-tools-"));
955
- const tmpDb = join5(tmpDir, basename2(dbFilePath));
956
- let sourceDb;
957
- try {
958
- sourceDb = new DatabaseSync(dbFilePath, {
959
- // `readOnly`, not `readonly`. An unrecognized option is *ignored* rather
960
- // than rejected, so the lowercase spelling — which is what better-sqlite3
961
- // called it, and what this line used to say — silently hands back a
962
- // writable handle onto the user's live browser profile.
963
- readOnly: true,
964
- // No `fileMustExist` equivalent, and none needed: a read-only open will
965
- // not create the file, and the `existsSync` guard above is the explicit
966
- // check that turns a missing store into a message worth reading.
967
- //
968
- // No wait. A running Chrome holds these stores in `locking_mode =
969
- // EXCLUSIVE` for as long as it is open, so a busy timeout here buys
970
- // nothing that waiting longer would win — the lock is not transient, and
971
- // the copy below is the answer to it either way. Five seconds *per store*
972
- // was the whole cost of reading a saved login while the browser was up:
973
- // two stores, ten seconds, spent to arrive at the fallback that then
974
- // succeeded instantly.
975
- timeout: 0
976
- });
977
- sourceDb.prepare("VACUUM INTO ?").run(tmpDb);
978
- } catch (err) {
979
- if (!shouldCopyDatabase(err)) {
980
- cleanupTmp(tmpDir);
981
- throw new Error(`Failed to snapshot database file '${basename2(dbFilePath)}': ${err.message}`);
982
- }
983
- try {
984
- copyLockedDatabase(dbFilePath, tmpDb);
985
- } catch (copyErr) {
986
- cleanupTmp(tmpDir);
987
- throw new Error(
988
- `Failed to snapshot database file '${basename2(dbFilePath)}': ${err.message} (copying it instead also failed: ${copyErr.message})`
989
- );
990
- }
991
- } finally {
992
- if (sourceDb) {
993
- try {
994
- sourceDb.close();
995
- } catch {
996
- }
997
- }
998
- }
999
- let db;
1000
- try {
1001
- db = new DatabaseSync(tmpDb, { readOnly: true, timeout: 5e3 });
1002
- const stmt = db.prepare(sql);
1003
- stmt.setReadBigInts(true);
1004
- return toCallerTypes(
1005
- params.length > 0 ? stmt.all(...params) : stmt.all()
1006
- );
1007
- } catch (err) {
1008
- throw new Error(`Database query failed on '${basename2(dbFilePath)}': ${err.message}`);
1009
- } finally {
1010
- if (db) {
1011
- try {
1012
- db.close();
1013
- } catch {
1014
- }
1015
- }
1016
- cleanupTmp(tmpDir);
1017
- }
1018
- }
1019
- function tableExists(dbFilePath, tableName) {
1020
- try {
1021
- const rows = queryDatabase(
1022
- dbFilePath,
1023
- `SELECT name FROM sqlite_master WHERE type='table' AND name=?`,
1024
- [tableName]
1025
- );
1026
- return rows.length > 0;
1027
- } catch {
1028
- return false;
1029
- }
1030
- }
1031
- function cleanupTmp(tmpDir) {
1032
- try {
1033
- rmSync(tmpDir, { recursive: true, force: true });
1034
- } catch {
1035
- }
1036
- }
1037
-
1038
- // src/modules/passwords.ts
1039
- import { existsSync as existsSync4, readFileSync as readFileSync3 } from "node:fs";
1040
- import { homedir as homedir3 } from "node:os";
1041
- import { join as join6 } from "node:path";
1042
- var PASSWORD_STORES = [
1043
- { file: "Login Data", source: "device" },
1044
- { file: "Login Data For Account", source: "account" }
1045
- ];
1046
- var AUTOMATION_DIR = join6(homedir3(), ".chrome_cdp_profile");
1047
- var AUTOMATION_CONFIG = join6(homedir3(), ".config", "chrome-cdp-manager", "config.json");
1048
- var AUTOMATION_OFF = ["0", "off", "none", "false"];
1049
- function automationChromeDir() {
1050
- const override = process.env.CHROME_AUTOMATION_DIR?.trim();
1051
- if (override && AUTOMATION_OFF.includes(override.toLowerCase())) return void 0;
1052
- const dir = override || configuredAutomationDir() || AUTOMATION_DIR;
1053
- return discoverProfiles(dir).length > 0 ? dir : void 0;
1054
- }
1055
- function configuredAutomationDir() {
1056
- try {
1057
- const stored = JSON.parse(readFileSync3(AUTOMATION_CONFIG, "utf-8"));
1058
- return typeof stored.profileDir === "string" && stored.profileDir ? stored.profileDir : void 0;
1059
- } catch {
1060
- return void 0;
1061
- }
1062
- }
1063
- function passwordRoots(userDataDir, browser) {
1064
- const roots = [];
1065
- const automation = userDataDir || browser ? void 0 : automationChromeDir();
1066
- if (automation) roots.push(automation);
1067
- roots.push(userDataDir);
1068
- return roots;
1069
- }
1070
- function describePasswordStores({
1071
- chromeDir,
1072
- profilePath,
1073
- profile,
1074
- browser
1075
- } = {}) {
1076
- const userDataDir = profilePath || chromeDir;
1077
- const reports = [];
1078
- for (const root of passwordRoots(userDataDir, browser)) {
1079
- let profiles;
1080
- try {
1081
- profiles = resolveTargetProfiles({ chromeDir: root, profile, browser });
1082
- } catch (error) {
1083
- reports.push({
1084
- root: root ?? "default",
1085
- profile: profile ?? "*",
1086
- file: "*",
1087
- source: "device",
1088
- exists: false,
1089
- rows: null,
1090
- error: error instanceof Error ? error.message : String(error)
1091
- });
1092
- continue;
1093
- }
1094
- for (const p of profiles) {
1095
- for (const store of PASSWORD_STORES) {
1096
- const dbPath = join6(p.path, store.file);
1097
- const report = {
1098
- root: root ?? "default",
1099
- profile: p.name,
1100
- file: store.file,
1101
- source: store.source,
1102
- exists: existsSync4(dbPath),
1103
- rows: null
1104
- };
1105
- try {
1106
- report.rows = queryDatabase(dbPath, "SELECT COUNT(*) AS n FROM logins")[0]?.n ?? 0;
1107
- } catch (error) {
1108
- report.error = error instanceof Error ? error.message : String(error);
1109
- }
1110
- reports.push(report);
1111
- }
1112
- }
1113
- }
1114
- return reports;
1115
- }
1116
- function getPasswords({
1117
- chromeDir,
1118
- profilePath,
1119
- profile,
1120
- search,
1121
- browser,
1122
- decryptionKey
1123
- } = {}) {
1124
- const userDataDir = profilePath || chromeDir;
1125
- const roots = passwordRoots(userDataDir, browser);
1126
- const results = [];
1127
- const term = search?.toLowerCase();
1128
- let refusal;
1129
- let resolved = false;
1130
- for (const root of roots) {
1131
- let profiles;
1132
- try {
1133
- profiles = resolveTargetProfiles({ chromeDir: root, profile, browser });
1134
- } catch (error) {
1135
- refusal ??= error;
1136
- continue;
1137
- }
1138
- resolved = true;
1139
- let derivedKey = decryptionKey;
1140
- for (const p of profiles) {
1141
- for (const store of PASSWORD_STORES) {
1142
- const dbPath = join6(p.path, store.file);
1143
- try {
1144
- const rows = queryDatabase(
1145
- dbPath,
1146
- `SELECT origin_url, username_value, password_value, date_last_used
1147
- FROM logins ORDER BY origin_url`
1148
- );
1149
- for (const row of rows) {
1150
- if (term && !(row.origin_url && row.origin_url.toLowerCase().includes(term)) && !(row.username_value && row.username_value.toLowerCase().includes(term))) {
1151
- continue;
1152
- }
1153
- results.push({
1154
- profile: p.name,
1155
- source: store.source,
1156
- url: row.origin_url,
1157
- username: row.username_value,
1158
- password: row.password_value?.length > 0 ? decryptValue(
1159
- row.password_value,
1160
- derivedKey ??= deriveEncryptionKey2(root, browser)
1161
- ) : "",
1162
- lastUsed: chromeTimeToDate(row.date_last_used || 0)
1163
- });
1164
- }
1165
- } catch {
1166
- }
1167
- }
1168
- }
1169
- }
1170
- if (!resolved && refusal) throw refusal;
1171
- return results;
1172
- }
1173
-
1174
- // src/modules/bookmarks.ts
1175
- import { existsSync as existsSync5, readFileSync as readFileSync4 } from "node:fs";
1176
- import { join as join7 } from "node:path";
1177
- function flattenBookmarks(node, folderPath = "") {
1178
- const results = [];
1179
- if (!node) return results;
1180
- const currentPath = folderPath ? `${folderPath}/${node.name || ""}` : node.name || "";
1181
- if (node.type === "url") {
1182
- results.push({
1183
- title: node.name || "",
1184
- url: node.url || "",
1185
- folder: folderPath,
1186
- dateAdded: chromeTimeToDate(node.date_added),
1187
- dateLastUsed: chromeTimeToDate(node.date_last_used),
1188
- id: node.id,
1189
- guid: node.guid || ""
1190
- });
1191
- }
1192
- if (node.children && Array.isArray(node.children)) {
1193
- for (const child of node.children) {
1194
- results.push(...flattenBookmarks(child, currentPath));
1195
- }
1196
- }
1197
- return results;
1198
- }
1199
- function getBookmarks({
1200
- chromeDir,
1201
- profilePath,
1202
- profile,
1203
- folder,
1204
- search,
1205
- browser
1206
- } = {}) {
1207
- const profiles = resolveTargetProfiles({ chromeDir, profilePath, profile, browser });
1208
- const results = [];
1209
- for (const p of profiles) {
1210
- const bookmarksPath = join7(p.path, "Bookmarks");
1211
- if (!existsSync5(bookmarksPath)) continue;
1212
- try {
1213
- const raw = readFileSync4(bookmarksPath, "utf-8");
1214
- const data = JSON.parse(raw);
1215
- const roots = data.roots;
1216
- if (!roots) continue;
1217
- const rootNames = ["bookmark_bar", "other", "synced"];
1218
- for (const rootName of rootNames) {
1219
- if (roots[rootName]) {
1220
- const bookmarks = flattenBookmarks(roots[rootName], "");
1221
- for (const bm of bookmarks) {
1222
- bm.profile = p.name;
1223
- results.push(bm);
1224
- }
1225
- }
1226
- }
1227
- } catch {
1228
- }
1229
- }
1230
- let filtered = results;
1231
- if (folder) {
1232
- const term = folder.toLowerCase();
1233
- filtered = filtered.filter((b) => b.folder.toLowerCase().includes(term));
1234
- }
1235
- if (search) {
1236
- const term = search.toLowerCase();
1237
- filtered = filtered.filter(
1238
- (b) => b.title.toLowerCase().includes(term) || b.url.toLowerCase().includes(term)
1239
- );
1240
- }
1241
- return filtered;
1242
- }
1243
-
1244
- // src/modules/history.ts
1245
- import { join as join8 } from "node:path";
1246
-
1247
- // src/core/limit.ts
1248
- function normalizeLimit(limit) {
1249
- if (limit === void 0) return void 0;
1250
- const parsed = Math.trunc(Number(limit));
1251
- return Number.isFinite(parsed) && parsed > 0 ? parsed : void 0;
1252
- }
1253
- function limitResults(results, limit) {
1254
- const normalized = normalizeLimit(limit);
1255
- return normalized === void 0 ? results : results.slice(0, normalized);
1256
- }
1257
-
1258
- // src/core/sql.ts
1259
- function sqlLikePattern(term) {
1260
- return `%${term.replace(/([%_\\])/g, "\\$1")}%`;
1261
- }
1262
-
1263
- // src/modules/history.ts
1264
- function getHistory({
1265
- chromeDir,
1266
- profilePath,
1267
- profile,
1268
- search,
1269
- limit = 100,
1270
- browser
1271
- } = {}) {
1272
- const profiles = resolveTargetProfiles({ chromeDir, profilePath, profile, browser });
1273
- const results = [];
1274
- const rowLimit = normalizeLimit(limit);
1275
- for (const p of profiles) {
1276
- const dbPath = join8(p.path, "History");
1277
- try {
1278
- let sql = `SELECT url, title, visit_count, last_visit_time FROM urls`;
1279
- const params = [];
1280
- if (search) {
1281
- sql += ` WHERE url LIKE ? ESCAPE '\\' OR IFNULL(title, '') LIKE ? ESCAPE '\\'`;
1282
- const like = sqlLikePattern(search);
1283
- params.push(like, like);
1284
- }
1285
- sql += ` ORDER BY last_visit_time DESC`;
1286
- if (rowLimit) {
1287
- sql += ` LIMIT ${rowLimit}`;
1288
- }
1289
- const rows = queryDatabase(dbPath, sql, params);
1290
- for (const row of rows) {
1291
- results.push({
1292
- profile: p.name,
1293
- url: row.url,
1294
- title: row.title || "",
1295
- visitCount: row.visit_count || 0,
1296
- lastVisit: chromeTimeToDate(row.last_visit_time || 0)
1297
- });
1298
- }
1299
- } catch {
1300
- }
1301
- }
1302
- return limitResults(results, limit);
1303
- }
1304
- function getSearchTerms({
1305
- chromeDir,
1306
- profilePath,
1307
- profile,
1308
- limit = 100,
1309
- browser
1310
- } = {}) {
1311
- const profiles = resolveTargetProfiles({ chromeDir, profilePath, profile, browser });
1312
- const results = [];
1313
- const rowLimit = normalizeLimit(limit);
1314
- for (const p of profiles) {
1315
- const dbPath = join8(p.path, "History");
1316
- try {
1317
- let sql = `SELECT kst.term, u.url
17
+ (powershell: ${o?.message??o})`)}return Buffer.from(r.trim(),"base64")}function Ue(e){let t=Tr(e),r=$e(t);return Z(O(t,"Local State"))?t:r!==t&&Z(O(r,"Local State"))?r:t}function Mr(e,t){if(!Ir.has(t))return;let r=import.meta.dirname,o=[O(r,"chrome_abe_decrypt.exe"),O(r,"../../native/windows/chrome_abe_decrypt.exe")].find(Z);if(o)try{let n=Ke(o,[t],{input:e.toString("base64"),encoding:"utf8",timeout:1e4,windowsHide:!0,stdio:["pipe","pipe","pipe"],cwd:$e(o)}),s=Buffer.from(n.trim(),"base64");return s.length===32?s:void 0}catch{return}}var He={name:"win32",getDefaultChromeDir(e=v()){let r=e.dirs.win32Base==="roaming"?process.env.APPDATA||(process.env.USERPROFILE?O(process.env.USERPROFILE,"AppData","Roaming"):""):process.env.LOCALAPPDATA||(process.env.USERPROFILE?O(process.env.USERPROFILE,"AppData","Local"):"");if(!r)throw new Error("Could not resolve the Windows application-data directory.");return O(r,...e.dirs.win32.split("/"))},getDecryptionKey(e,t=v()){let r=e||this.getDefaultChromeDir(t),o=Ue(r),n=JSON.stringify([o,t.id]),s=Be.get(n);if(s)return s;let i=O(o,"Local State");if(!Z(i))throw new Error(`Could not find Chrome's "Local State" at ${i}.
18
+ Run Google Chrome at least once so it creates the encryption key.`);let c;try{let l=Fe(i,"utf-8");c=JSON.parse(l)?.os_crypt?.encrypted_key}catch(l){throw new Error(`Failed to read os_crypt.encrypted_key from Local State: ${l.message}`)}if(!c)throw new Error(`No os_crypt.encrypted_key found in Local State.
19
+ This profile may predate Chrome's encrypted-key scheme.`);let a=Buffer.from(c,"base64");if(!a.subarray(0,_e.length).equals(_e))throw new Error('Unexpected encrypted_key format (missing "DPAPI" prefix).');let f=Nr(a.subarray(_e.length));return Be.set(n,f),xe.set(f,{chromeDir:o,browser:t}),f},getAppBoundKey(e,t=v()){let r=Ue(e||this.getDefaultChromeDir(t)),o=JSON.stringify([r,t.id]),n=ve.get(o);if(n)return n;let s=O(r,"Local State");if(!Z(s))return;let i;try{let f=Fe(s,"utf-8");i=JSON.parse(f)?.os_crypt?.app_bound_encrypted_key}catch{return}if(!i)return;let c=Buffer.from(i,"base64");if(!c.subarray(0,Ee.length).equals(Ee))return;let a=Mr(c.subarray(Ee.length),t.id);if(a)return ve.set(o,a),a},decrypt(e,t){if(e.subarray(0,3).toString()==="v20"){let r=xe.get(t),o=r&&this.getAppBoundKey?.(r.chromeDir,r.browser);return o?re(e,o):"<app-bound encryption unavailable>"}return Ae(e,t)},clearCache(){Be.clear(),ve.clear(),xe=new WeakMap}};import{execFileSync as Fr}from"node:child_process";import{homedir as Ur}from"node:os";import{join as Ge}from"node:path";import{pbkdf2Sync as Kr}from"node:crypto";var $r=1,Hr=16,Gr=Buffer.from("saltysalt"),jr="peanuts",be=new Map;function Wr(e){let t=ye();if(t)return t(e);try{let r=Fr("secret-tool",["lookup","application",e],{encoding:"utf-8",stdio:["pipe","pipe","pipe"]}).trim();if(r)return r}catch{}return jr}var je={name:"linux",getDefaultChromeDir(e=v()){let t=process.env.XDG_CONFIG_HOME||Ge(Ur(),".config");return Ge(t,...e.dirs.linux.split("/"))},getDecryptionKey(e,t=v()){let r=t.linuxSecretApp||"chrome",o=be.get(r);if(o!==void 0)return o;let n=Kr(Wr(r),Gr,$r,Hr,"sha1");return be.set(r,n),n},decrypt(e,t){return ee(e,t)},clearCache(){be.clear()}};var H=null;function G(){if(H)return H;switch(process.platform){case"darwin":H=Me;break;case"win32":H=He;break;case"linux":H=je;break;default:throw new Error(`Unsupported platform: ${process.platform}. chrome-tools supports macOS, Windows, and Linux.`)}return H}function De(e){return G().getDefaultChromeDir(Ce(e))}function Vr(e,t){return G().getDecryptionKey(e,Ce(t))}function We(e,t){if(!e||e.length===0)return Buffer.alloc(0);let r=Buffer.isBuffer(e)?e:Buffer.from(e);return G().decrypt(r,t)}function zr(e,t){let r=We(e,t);return Buffer.isBuffer(r)?r.toString("utf-8"):r}function qr(){G().clearCache()}function Ce(e){return e?typeof e=="string"?M(e):e:v()}function j(e,t,r,o,n,s){let i=Ce(t),c=r,a,f=new Set;return l=>{let d;try{let u=G();u.getAppBoundKey&&l.subarray(0,3).toString()==="v20"?(a===void 0&&(a=u.getAppBoundKey(e,i)??null),d=a?re(l,a):"<app-bound encryption unavailable>"):(c??=u.getDecryptionKey(e,i),d=u.decrypt(l,c))}catch(u){throw new h("decryption_unavailable",u instanceof Error?u.message:String(u),{cause:u})}return typeof d=="string"&&!f.has(d)&&(f.add(d),w(o,n,s,new h("decryption_unavailable",d))),d}}var ze=["Login Data","History","Bookmarks","Cookies","Web Data","Local Storage","LocalStorage","Preferences"];function oe(e){let t=A(e,"Network","Cookies");return R(t)?t:A(e,"Cookies")}function Ve(e){return ze.some(t=>R(A(e,t)))||R(oe(e))}function F(e){return De(e)}function Qr({includeMissing:e=!1}={}){let t=[];for(let r of $()){let o=F(r),n=R(o);!n&&!e||t.push({browser:r,chromeDir:o,installed:n,profileCount:n?W(o).length:0})}return t}function W(e,{reportAccessDenied:t=!1}={}){let r=e||F();if(!r)return[];try{let o=Yr(r,{withFileTypes:!0}),n=[];for(let s of o){if(!s.isDirectory()||s.name==="Network")continue;let i=A(r,s.name);Ve(i)&&n.push({name:s.name,path:i})}if(n.length===0&&Ve(r)){let s=Xr(r)||"Default";n.push({name:s,path:r})}return n}catch(o){let n=o.code;if(t&&(n==="EACCES"||n==="EPERM")){let s=process.platform==="darwin"?" Try Terminal.app if it already has access, or review the current app's permissions in System Settings \u2192 Privacy & Security.":" Check that your user has permission to read this directory.";throw new Error(`Permission denied reading the browser profile directory "${r}" (${n}).${s}`,{cause:o})}return[]}}function qe(e){let t=e||F(),r=A(t,"Local State");if(!R(r))return[];try{let o=Jr(r,"utf-8"),s=JSON.parse(o)?.profile?.info_cache;return!s||typeof s!="object"?[]:Object.entries(s).map(([i,c])=>{let a=c;return{directory:i,name:a.name||i,gaiaName:a.gaia_name||"",userName:a.user_name||"",email:a.user_name||"",avatarIcon:a.avatar_icon||"",isUsingDefaultName:a.is_using_default_name??!0,isEphemeral:a.is_ephemeral??!1,activeTime:a.active_time||0}})}catch{return[]}}function Zr(e){let t=e||F(),r=W(t),o=qe(t),n=new Map;for(let s of o)n.set(s.directory,s);return r.map(s=>{let i=n.get(s.name),c=s.path,a={};for(let f of ze)a[f.replace(/\s+/g,"_").toLowerCase()]=R(A(c,f));return a.cookies=R(oe(c)),a.top_sites=R(A(c,"Top Sites")),a.extensions=R(A(c,"Extensions")),{directory:s.name,path:c,displayName:i?.name||s.name,gaiaName:i?.gaiaName||"",email:i?.email||"",avatarIcon:i?.avatarIcon||"",isEphemeral:i?.isEphemeral??!1,activeTime:i?.activeTime||0,dataFiles:a}})}function et(e,t){let r=e||F(),o=A(r,t);return R(o)?o:null}function S({profilePath:e,chromeDir:t,profile:r,browser:o,onDiagnostic:n}={}){let s=o?M(o):void 0,i=e||t||F(s),c;try{c=W(i,{reportAccessDenied:!!n})}catch(a){n?.({profile:r??"",store:"User Data",code:"unreadable",message:a instanceof Error?a.message:String(a)}),c=[]}if(r){let a=c.filter(f=>f.name===r);if(a.length===0)throw new Error(`Profile "${r}" not found in ${i}`);return a}return c}import{copyFileSync as ct,rmSync as ft}from"node:fs";import{basename as ie,dirname as Je,join as lt}from"node:path";import{DatabaseSync as ut}from"node:sqlite";import{lstatSync as rt,mkdtempSync as tt,realpathSync as Ye}from"node:fs";import{tmpdir as ot}from"node:os";import{isAbsolute as nt,join as st,relative as it,sep as at}from"node:path";function T(e){try{let t=rt(e,{bigint:!0});if(!t.isFile())throw new h("unreadable",`Expected a regular store file: ${e}`);return t}catch(t){if(t.code==="ENOENT")return;throw t}}function ne(e,t){return!e||!t?e===t:e.dev===t.dev&&e.ino===t.ino&&e.size===t.size&&e.mtimeNs===t.mtimeNs&&e.ctimeNs===t.ctimeNs}function se(e,t){let r=Ye(ot()),o=Ye(e),n=it(o,r);if(!nt(n)&&n!==".."&&!n.startsWith(".."+at))throw new h("snapshot_unavailable","The temporary directory is inside the source profile. Set it to a directory outside the profile.");return tt(st(r,t))}function pt(e){return e.map(t=>{let r={};for(let o of Object.keys(t)){let n=t[o];typeof n=="bigint"?r[o]=Number(n):n instanceof Uint8Array&&!Buffer.isBuffer(n)?r[o]=Buffer.from(n.buffer,n.byteOffset,n.byteLength):r[o]=n}return r})}function dt(e,t){let r=["","-wal","-journal"],o=r.map(n=>T(e+n));if(!o[0])throw new h("missing_store",`Database file not found: ${e}`);try{for(let[n,s]of r.entries())o[n]&&ct(e+s,t+s)}catch(n){let s=n.code;throw["EBUSY","EPERM","EACCES","ENOENT"].includes(s??"")?new h("snapshot_unavailable",`Could not copy ${ie(e)} and its journals. Close the browser and retry if it is open.`,{cause:n}):n}if(r.some((n,s)=>!ne(o[s],T(e+n))))throw new h("snapshot_unavailable",`${ie(e)} changed during the copy. Close the browser and retry.`)}function Pe(e,t){let r=Je(e),o=ie(r)==="Network"?Je(r):r,n=se(o,"chrome-tools-"),s=lt(n,"snapshot.sqlite"),i;try{dt(e,s),i=new ut(s,{timeout:0}),i.exec("PRAGMA trusted_schema = OFF; PRAGMA query_only = ON;");let c=i;return t((a,f=[])=>{let l=a.replace(/^(?:\s+|--[^\n]*(?:\n|$)|\/\*[\s\S]*?\*\/)+/,"");if(!/^(SELECT|WITH|VALUES|EXPLAIN|PRAGMA)\b/i.test(l))throw new h("unreadable","Only read queries are supported.");let d=c.prepare(l);return d.setReadBigInts(!0),pt(d.all(...f))})}catch(c){if(c instanceof h)throw c;let a=c instanceof Error?c.message:String(c),f=/no such (table|column)/i.test(a)?"unsupported_schema":"unreadable";throw new h(f,`Database query failed on '${ie(e)}': ${a}`,{cause:c})}finally{try{i?.close()}finally{ft(n,{recursive:!0,force:!0,maxRetries:3})}}}function B(e,t,r=[]){return Pe(e,o=>o(t,r))}function mt(e,t){try{return B(e,"SELECT 1 FROM sqlite_schema WHERE type = 'table' AND name = ?",[t]).length>0}catch{return!1}}import{existsSync as gt,readFileSync as yt}from"node:fs";import{homedir as Xe}from"node:os";import{join as ae}from"node:path";var Qe=[{file:"Login Data",source:"device"},{file:"Login Data For Account",source:"account"}],ht=ae(Xe(),".chrome_cdp_profile"),wt=ae(Xe(),".config","chrome-cdp-manager","config.json"),St=["0","off","none","false"];function _t(){let e=process.env.CHROME_AUTOMATION_DIR?.trim();if(e&&St.includes(e.toLowerCase()))return;let t=e||Et()||ht;return W(t).length>0?t:void 0}function Et(){try{let e=JSON.parse(yt(wt,"utf-8"));return typeof e.profileDir=="string"&&e.profileDir?e.profileDir:void 0}catch{return}}function Ze(e,t){let r=[],o=e||t?void 0:_t();return o&&r.push(o),r.push(e),r}function Bt({chromeDir:e,profilePath:t,profile:r,browser:o}={}){let n=t||e,s=[];for(let i of Ze(n,o)){let c;try{c=S({chromeDir:i,profile:r,browser:o})}catch(a){s.push({root:i??"default",profile:r??"*",file:"*",source:"device",exists:!1,rows:null,error:a instanceof Error?a.message:String(a)});continue}for(let a of c)for(let f of Qe){let l=ae(a.path,f.file),d={root:i??"default",profile:a.name,file:f.file,source:f.source,exists:gt(l),rows:null};try{d.rows=B(l,"SELECT COUNT(*) AS n FROM logins")[0]?.n??0}catch(u){d.error=u instanceof Error?u.message:String(u)}s.push(d)}}return s}function vt({chromeDir:e,profilePath:t,profile:r,search:o,browser:n,decryptionKey:s,onDiagnostic:i}={}){let c=t||e,a=n??v().id,f=Ze(c,n),l=[],d=o?.toLowerCase(),u,g=!1;for(let m of f){let p;try{p=S({chromeDir:m,profile:r,browser:a,onDiagnostic:i})}catch(y){u??=y;continue}g=!0;for(let y of p)for(let _ of Qe){let L=ae(y.path,_.file),le=j(m??y.path,a,s,i,y.name,_.file);try{let C=B(L,`SELECT origin_url, username_value, password_value, date_last_used
20
+ FROM logins ORDER BY origin_url`);for(let b of C){if(d&&!(b.origin_url&&b.origin_url.toLowerCase().includes(d))&&!(b.username_value&&b.username_value.toLowerCase().includes(d)))continue;let N=b.password_value?.length>0?le(b.password_value):"";l.push({profile:y.name,source:_.source,url:b.origin_url,username:b.username_value,password:Buffer.isBuffer(N)?N.toString("utf8"):N,lastUsed:E(b.date_last_used||0)})}}catch(C){w(i,y.name,_.file,C)}}}if(!g&&u)throw u;return l}import{readFileSync as xt}from"node:fs";import{join as bt}from"node:path";function er(e,t=""){let r=[];if(!e)return r;let o=t?`${t}/${e.name||""}`:e.name||"";if(e.type==="url"&&r.push({title:e.name||"",url:e.url||"",folder:t,dateAdded:E(e.date_added),dateLastUsed:E(e.date_last_used),id:e.id,guid:e.guid||""}),e.children&&Array.isArray(e.children))for(let n of e.children)r.push(...er(n,o));return r}function Dt({chromeDir:e,profilePath:t,profile:r,folder:o,search:n,browser:s,onDiagnostic:i}={}){let c=S({chromeDir:e,profilePath:t,profile:r,browser:s,onDiagnostic:i}),a=[];for(let l of c){let d=bt(l.path,"Bookmarks");try{let u=xt(d,"utf-8"),m=JSON.parse(u).roots;if(!m)continue;let p=["bookmark_bar","other","synced"];for(let y of p)if(m[y]){let _=er(m[y],"");for(let L of _)L.profile=l.name,a.push(L)}}catch(u){w(i,l.name,"Bookmarks",u instanceof SyntaxError?new Error("Invalid Bookmarks JSON."):u)}}let f=a;if(o){let l=o.toLowerCase();f=f.filter(d=>d.folder.toLowerCase().includes(l))}if(n){let l=n.toLowerCase();f=f.filter(d=>d.title.toLowerCase().includes(l)||d.url.toLowerCase().includes(l))}return f}import{join as rr}from"node:path";function k(e){if(e===void 0)return;let t=Math.trunc(Number(e));return Number.isFinite(t)&&t>0?t:void 0}function P(e,t){let r=k(t);return r===void 0?e:e.slice(0,r)}function V(e){return`%${e.replace(/([%_\\])/g,"\\$1")}%`}function Ct({chromeDir:e,profilePath:t,profile:r,search:o,limit:n=100,browser:s,onDiagnostic:i}={}){let c=S({chromeDir:e,profilePath:t,profile:r,browser:s,onDiagnostic:i}),a=[],f=k(n);for(let l of c){let d=rr(l.path,"History");try{let u="SELECT url, title, visit_count, last_visit_time FROM urls",g=[];if(o){u+=" WHERE url LIKE ? ESCAPE '\\' OR IFNULL(title, '') LIKE ? ESCAPE '\\'";let p=V(o);g.push(p,p)}u+=" ORDER BY last_visit_time DESC",f&&(u+=` LIMIT ${f}`);let m=B(d,u,g);for(let p of m)a.push({profile:l.name,url:p.url,title:p.title||"",visitCount:p.visit_count||0,lastVisit:E(p.last_visit_time||0)})}catch(u){w(i,l.name,"History",u)}}return P(a,n)}function Pt({chromeDir:e,profilePath:t,profile:r,limit:o=100,browser:n,onDiagnostic:s}={}){let i=S({chromeDir:e,profilePath:t,profile:r,browser:n,onDiagnostic:s}),c=[],a=k(o);for(let f of i){let l=rr(f.path,"History");try{let d=`SELECT kst.term, u.url
1318
21
  FROM keyword_search_terms kst
1319
22
  JOIN urls u ON kst.url_id = u.id
1320
- ORDER BY u.last_visit_time DESC`;
1321
- if (rowLimit) {
1322
- sql += ` LIMIT ${rowLimit}`;
1323
- }
1324
- const rows = queryDatabase(dbPath, sql);
1325
- for (const row of rows) {
1326
- results.push({
1327
- profile: p.name,
1328
- term: row.term || "",
1329
- url: row.url || ""
1330
- });
1331
- }
1332
- } catch {
1333
- }
1334
- }
1335
- return limitResults(results, limit);
1336
- }
1337
-
1338
- // src/modules/cookies.ts
1339
- import { existsSync as existsSync6 } from "node:fs";
1340
- import { createHash } from "node:crypto";
1341
- function decodeCookieValue(encryptedValue, derivedKey, hostKey) {
1342
- const decrypted = decryptValueBuffer2(encryptedValue, derivedKey);
1343
- if (!Buffer.isBuffer(decrypted)) {
1344
- return decrypted;
1345
- }
1346
- const hostHash = createHash("sha256").update(hostKey).digest();
1347
- const valueBytes = decrypted.length >= hostHash.length && decrypted.subarray(0, hostHash.length).equals(hostHash) ? decrypted.subarray(hostHash.length) : decrypted;
1348
- return valueBytes.toString("utf-8");
1349
- }
1350
- function parseURL(urlStr) {
1351
- if (!urlStr) return null;
1352
- try {
1353
- return new URL(urlStr);
1354
- } catch {
1355
- try {
1356
- if (!/^[a-zA-Z][a-zA-Z0-9+.-]*:/.test(urlStr)) {
1357
- return new URL("https://" + urlStr);
1358
- }
1359
- } catch {
1360
- }
1361
- }
1362
- return null;
1363
- }
1364
- function getPossibleHostKeys(hostname) {
1365
- const keys = /* @__PURE__ */ new Set();
1366
- keys.add(hostname);
1367
- keys.add("." + hostname);
1368
- const parts = hostname.split(".");
1369
- for (let i = 1; i < parts.length; i++) {
1370
- const suffix = parts.slice(i).join(".");
1371
- keys.add(suffix);
1372
- keys.add("." + suffix);
1373
- }
1374
- return Array.from(keys);
1375
- }
1376
- function domainMatch(hostname, hostKey) {
1377
- if (hostname === hostKey) {
1378
- return true;
1379
- }
1380
- if (hostKey.startsWith(".")) {
1381
- if (hostname === hostKey.slice(1) || hostname.endsWith(hostKey)) {
1382
- return true;
1383
- }
1384
- }
1385
- return false;
1386
- }
1387
- function pathMatch(urlPath, cookiePath) {
1388
- const uPath = urlPath || "/";
1389
- const cPath = cookiePath || "/";
1390
- if (cPath === "/") {
1391
- return true;
1392
- }
1393
- if (uPath === cPath) {
1394
- return true;
1395
- }
1396
- if (uPath.startsWith(cPath)) {
1397
- if (cPath.endsWith("/") || uPath[cPath.length] === "/") {
1398
- return true;
1399
- }
1400
- }
1401
- return false;
1402
- }
1403
- function getCookies({
1404
- chromeDir,
1405
- profilePath,
1406
- profile,
1407
- browser,
1408
- domain,
1409
- url,
1410
- decrypt = true,
1411
- limit = 100,
1412
- decryptionKey
1413
- } = {}) {
1414
- const userDataDir = profilePath || chromeDir;
1415
- const profiles = resolveTargetProfiles({ chromeDir, profilePath, profile, browser });
1416
- let derivedKey = decryptionKey;
1417
- const results = [];
1418
- const parsedUrl = parseURL(url);
1419
- const rowLimit = normalizeLimit(limit);
1420
- let firstReadError;
1421
- for (const p of profiles) {
1422
- const dbPath = resolveCookiesDatabasePath(p.path);
1423
- if (!existsSync6(dbPath)) continue;
1424
- try {
1425
- let sql = `SELECT host_key, name, value, encrypted_value, path,
23
+ ORDER BY u.last_visit_time DESC`;a&&(d+=` LIMIT ${a}`);let u=B(l,d);for(let g of u)c.push({profile:f.name,term:g.term||"",url:g.url||""})}catch(d){w(s,f.name,"History",d)}}return P(c,o)}import{existsSync as Rt}from"node:fs";import{createHash as kt}from"node:crypto";function Lt(e,t){if(!Buffer.isBuffer(e))return e;let r=kt("sha256").update(t).digest();return(e.length>=r.length&&e.subarray(0,r.length).equals(r)?e.subarray(r.length):e).toString("utf-8")}function Ot(e){if(!e)return null;try{return new URL(e)}catch{try{if(!/^[a-zA-Z][a-zA-Z0-9+.-]*:/.test(e))return new URL("https://"+e)}catch{}}return null}function At(e){let t=new Set;t.add(e),t.add("."+e);let r=e.split(".");for(let o=1;o<r.length;o++){let n=r.slice(o).join(".");t.add(n),t.add("."+n)}return Array.from(t)}function Tt(e,t){return!!(e===t||t.startsWith(".")&&(e===t.slice(1)||e.endsWith(t)))}function It(e,t){let r=e||"/",o=t||"/";return!!(o==="/"||r===o||r.startsWith(o)&&(o.endsWith("/")||r[o.length]==="/"))}function Nt({chromeDir:e,profilePath:t,profile:r,browser:o,onDiagnostic:n,domain:s,url:i,decrypt:c=!0,limit:a=100,decryptionKey:f}={}){let l=t||e,d=o??v().id,u=S({chromeDir:e,profilePath:t,profile:r,browser:d,onDiagnostic:n}),g=[],m=Ot(i),p=k(a),y;for(let _ of u){let L=oe(_.path);if(!Rt(L)){w(n,_.name,"Cookies",new h("missing_store","Cookies database file not found."));continue}let le=j(l??_.path,d,f,n,_.name,"Cookies");try{let C=`SELECT host_key, name, value, encrypted_value, path,
1426
24
  expires_utc, is_secure, is_httponly, samesite,
1427
25
  creation_utc, last_access_utc
1428
- FROM cookies`;
1429
- const conditions = [];
1430
- const params = [];
1431
- if (domain) {
1432
- conditions.push(`host_key LIKE ?`);
1433
- params.push(`%${domain}%`);
1434
- } else if (parsedUrl) {
1435
- const hostKeys = getPossibleHostKeys(parsedUrl.hostname);
1436
- const placeholders = hostKeys.map(() => "?").join(", ");
1437
- conditions.push(`host_key IN (${placeholders})`);
1438
- params.push(...hostKeys);
1439
- }
1440
- if (conditions.length > 0) {
1441
- sql += ` WHERE ${conditions.join(" AND ")}`;
1442
- }
1443
- sql += ` ORDER BY host_key, name`;
1444
- if (rowLimit && !url) {
1445
- sql += ` LIMIT ${rowLimit}`;
1446
- }
1447
- const rows = queryDatabase(dbPath, sql, params);
1448
- for (const row of rows) {
1449
- if (parsedUrl) {
1450
- if (!domainMatch(parsedUrl.hostname, row.host_key || "")) continue;
1451
- if (!pathMatch(parsedUrl.pathname || "/", row.path || "/")) continue;
1452
- if (row.is_secure && parsedUrl.protocol !== "https:") continue;
1453
- }
1454
- let cookieValue = row.value || "";
1455
- if (!cookieValue && row.encrypted_value?.length > 0 && decrypt) {
1456
- cookieValue = decodeCookieValue(
1457
- row.encrypted_value,
1458
- derivedKey ??= deriveEncryptionKey2(userDataDir, browser),
1459
- row.host_key || ""
1460
- );
1461
- }
1462
- results.push({
1463
- profile: p.name,
1464
- domain: row.host_key || "",
1465
- name: row.name || "",
1466
- value: cookieValue,
1467
- path: row.path || "/",
1468
- expires: chromeTimeToDate(row.expires_utc || 0),
1469
- secure: Boolean(row.is_secure),
1470
- httpOnly: Boolean(row.is_httponly),
1471
- sameSite: ["unspecified", "lax", "strict", "none"][row.samesite] || "unspecified",
1472
- created: chromeTimeToDate(row.creation_utc || 0),
1473
- lastAccessed: chromeTimeToDate(row.last_access_utc || 0)
1474
- });
1475
- }
1476
- } catch (error) {
1477
- firstReadError ??= new Error(
1478
- `Could not read cookies for profile "${p.name}" at ${dbPath}: ` + (error instanceof Error ? error.message : String(error))
1479
- );
1480
- }
1481
- }
1482
- if (results.length === 0 && firstReadError) throw firstReadError;
1483
- return limitResults(results, limit);
1484
- }
1485
-
1486
- // src/modules/local-storage.ts
1487
- import { cpSync, existsSync as existsSync7, mkdtempSync as mkdtempSync2, readdirSync as readdirSync2, readFileSync as readFileSync5, rmSync as rmSync2 } from "node:fs";
1488
- import { tmpdir as tmpdir2 } from "node:os";
1489
- import { basename as basename3, join as join9 } from "node:path";
1490
- var TABLE_MAGIC = 0xdb4775248b80fb57n;
1491
- var LOG_BLOCK_SIZE = 32768;
1492
- var VALUE_TYPE_DELETION = 0;
1493
- var VALUE_TYPE_VALUE = 1;
1494
- function getLocalStorage({
1495
- chromeDir,
1496
- profilePath,
1497
- profile,
1498
- browser,
1499
- origin,
1500
- url,
1501
- key,
1502
- search,
1503
- limit = 100
1504
- } = {}) {
1505
- const profiles = resolveTargetProfiles({ chromeDir, profilePath, profile, browser });
1506
- const results = [];
1507
- const originFilter = normalizeOriginFilter(url || origin);
1508
- const keyFilter = key?.toLowerCase();
1509
- const searchFilter = search?.toLowerCase();
1510
- for (const p of profiles) {
1511
- const dbPath = join9(p.path, "Local Storage", "leveldb");
1512
- if (!existsSync7(dbPath)) continue;
1513
- let tmpPath = null;
1514
- try {
1515
- tmpPath = copyLevelDBToTemp(dbPath);
1516
- const rawRecords = readLevelDBRecords(tmpPath);
1517
- for (const record of rawRecords.values()) {
1518
- if (record.deleted || !record.value) continue;
1519
- const parsed = parseLocalStorageRecord(record.key, record.value);
1520
- if (!parsed) continue;
1521
- const entry = {
1522
- profile: p.name,
1523
- origin: parsed.origin,
1524
- key: parsed.key,
1525
- value: parsed.value
1526
- };
1527
- if (originFilter && !entry.origin.toLowerCase().includes(originFilter)) {
1528
- continue;
1529
- }
1530
- if (keyFilter && !entry.key.toLowerCase().includes(keyFilter)) {
1531
- continue;
1532
- }
1533
- if (searchFilter) {
1534
- const haystack = `${entry.origin}
1535
- ${entry.key}
1536
- ${entry.value}`.toLowerCase();
1537
- if (!haystack.includes(searchFilter)) continue;
1538
- }
1539
- results.push(entry);
1540
- }
1541
- } catch {
1542
- } finally {
1543
- if (tmpPath) {
1544
- rmSync2(tmpPath, { recursive: true, force: true });
1545
- }
1546
- }
1547
- }
1548
- results.sort(
1549
- (a, b) => a.profile.localeCompare(b.profile) || a.origin.localeCompare(b.origin) || a.key.localeCompare(b.key)
1550
- );
1551
- return limitResults(results, limit);
1552
- }
1553
- function copyLevelDBToTemp(dbPath) {
1554
- const tmpPath = mkdtempSync2(join9(tmpdir2(), "chrome-local-storage-"));
1555
- cpSync(dbPath, tmpPath, {
1556
- recursive: true,
1557
- force: true,
1558
- filter: (source) => basename3(source) !== "LOCK"
1559
- });
1560
- return tmpPath;
1561
- }
1562
- function readLevelDBRecords(dbPath) {
1563
- const records = /* @__PURE__ */ new Map();
1564
- const files = readdirSync2(dbPath).filter((file) => /\.(log|ldb|sst)$/i.test(file)).sort();
1565
- for (const file of files) {
1566
- const filePath = join9(dbPath, file);
1567
- const data = readFileSync5(filePath);
1568
- if (/\.(ldb|sst)$/i.test(file)) {
1569
- for (const record of parseTableRecords(data)) {
1570
- applyLevelDBRecord(records, record);
1571
- }
1572
- }
1573
- }
1574
- for (const file of files) {
1575
- if (!/\.log$/i.test(file)) continue;
1576
- const filePath = join9(dbPath, file);
1577
- const data = readFileSync5(filePath);
1578
- for (const record of parseLogRecords(data)) {
1579
- applyLevelDBRecord(records, record);
1580
- }
1581
- }
1582
- return records;
1583
- }
1584
- function applyLevelDBRecord(records, record) {
1585
- const id = record.key.toString("hex");
1586
- const existing = records.get(id);
1587
- if (existing && existing.sequence > record.sequence) return;
1588
- records.set(id, record);
1589
- }
1590
- function parseLocalStorageRecord(rawKey, rawValue) {
1591
- if (rawKey.length < 3 || rawKey[0] !== 95) return null;
1592
- const separator = rawKey.indexOf(0, 1);
1593
- if (separator === -1) return null;
1594
- const origin = rawKey.subarray(1, separator).toString("utf8");
1595
- const key = decodeLocalStorageString(rawKey.subarray(separator + 1));
1596
- const value = decodeLocalStorageString(rawValue);
1597
- if (!origin || !key) return null;
1598
- return { origin, key, value };
1599
- }
1600
- function decodeLocalStorageString(data) {
1601
- if (data.length === 0) return "";
1602
- const format = data[0];
1603
- const body = data.subarray(1);
1604
- if (format === 0 && body.length % 2 === 0) {
1605
- return body.toString("utf16le");
1606
- }
1607
- if (format === 1) {
1608
- return body.toString("latin1");
1609
- }
1610
- return data.toString("utf8");
1611
- }
1612
- function normalizeOriginFilter(input) {
1613
- if (!input) return void 0;
1614
- try {
1615
- return new URL(input).origin.toLowerCase();
1616
- } catch {
1617
- return input.toLowerCase();
1618
- }
1619
- }
1620
- function parseLogRecords(data) {
1621
- const records = [];
1622
- let offset = 0;
1623
- let fragments = [];
1624
- while (offset + 7 <= data.length) {
1625
- const blockRemaining = LOG_BLOCK_SIZE - offset % LOG_BLOCK_SIZE;
1626
- if (blockRemaining < 7) {
1627
- offset += blockRemaining;
1628
- continue;
1629
- }
1630
- const length = data.readUInt16LE(offset + 4);
1631
- const type = data[offset + 6];
1632
- offset += 7;
1633
- if (type === 0 && length === 0) {
1634
- offset += length;
1635
- continue;
1636
- }
1637
- if (length > data.length - offset || length > blockRemaining - 7) {
1638
- break;
1639
- }
1640
- const payload = data.subarray(offset, offset + length);
1641
- offset += length;
1642
- if (type === 1) {
1643
- records.push(...parseWriteBatch(payload));
1644
- fragments = [];
1645
- } else if (type === 2) {
1646
- fragments = [Buffer.from(payload)];
1647
- } else if (type === 3 && fragments.length > 0) {
1648
- fragments.push(Buffer.from(payload));
1649
- } else if (type === 4 && fragments.length > 0) {
1650
- fragments.push(Buffer.from(payload));
1651
- records.push(...parseWriteBatch(Buffer.concat(fragments)));
1652
- fragments = [];
1653
- }
1654
- }
1655
- return records;
1656
- }
1657
- function parseWriteBatch(batch) {
1658
- if (batch.length < 12) return [];
1659
- const records = [];
1660
- const startSequence = readUInt64LE(batch, 0);
1661
- const count = batch.readUInt32LE(8);
1662
- let offset = 12;
1663
- let seen = 0;
1664
- while (offset < batch.length && seen < count) {
1665
- const tag = batch[offset++];
1666
- const keyPart = readLengthPrefixedSlice(batch, offset);
1667
- if (!keyPart) break;
1668
- offset = keyPart.nextOffset;
1669
- const sequence = startSequence + BigInt(seen);
1670
- if (tag === VALUE_TYPE_VALUE) {
1671
- const valuePart = readLengthPrefixedSlice(batch, offset);
1672
- if (!valuePart) break;
1673
- offset = valuePart.nextOffset;
1674
- records.push({ key: keyPart.value, value: valuePart.value, sequence, deleted: false });
1675
- } else if (tag === VALUE_TYPE_DELETION) {
1676
- records.push({ key: keyPart.value, sequence, deleted: true });
1677
- } else {
1678
- break;
1679
- }
1680
- seen++;
1681
- }
1682
- return records;
1683
- }
1684
- function parseTableRecords(data) {
1685
- const records = [];
1686
- if (data.length < 48) return records;
1687
- const magic = data.readBigUInt64LE(data.length - 8);
1688
- if (magic !== TABLE_MAGIC) return records;
1689
- const footer = data.subarray(data.length - 48, data.length - 8);
1690
- const metaHandle = readBlockHandle(footer, 0);
1691
- if (!metaHandle) return records;
1692
- const indexHandle = readBlockHandle(footer, metaHandle.bytesRead);
1693
- if (!indexHandle) return records;
1694
- const indexBlock = readBlock(data, indexHandle);
1695
- if (!indexBlock) return records;
1696
- for (const indexEntry of parseBlockEntries(indexBlock)) {
1697
- const dataHandle = readBlockHandle(indexEntry.value, 0);
1698
- if (!dataHandle) continue;
1699
- const dataBlock = readBlock(data, dataHandle);
1700
- if (!dataBlock) continue;
1701
- for (const entry of parseBlockEntries(dataBlock)) {
1702
- if (entry.key.length < 8) continue;
1703
- const internalKey = entry.key;
1704
- const tagOffset = internalKey.length - 8;
1705
- const type = internalKey[tagOffset];
1706
- const sequence = readSequence(internalKey.subarray(tagOffset));
1707
- const key = internalKey.subarray(0, tagOffset);
1708
- if (type === VALUE_TYPE_VALUE) {
1709
- records.push({ key, value: entry.value, sequence, deleted: false });
1710
- } else if (type === VALUE_TYPE_DELETION) {
1711
- records.push({ key, sequence, deleted: true });
1712
- }
1713
- }
1714
- }
1715
- return records;
1716
- }
1717
- function readBlock(data, handle) {
1718
- const trailerOffset = handle.offset + handle.size;
1719
- if (handle.offset < 0 || handle.size < 0 || trailerOffset + 5 > data.length) {
1720
- return null;
1721
- }
1722
- const block = data.subarray(handle.offset, trailerOffset);
1723
- const compressionType = data[trailerOffset];
1724
- if (compressionType === 0) {
1725
- return block;
1726
- }
1727
- if (compressionType === 1) {
1728
- try {
1729
- return snappyUncompress(block);
1730
- } catch {
1731
- return null;
1732
- }
1733
- }
1734
- return null;
1735
- }
1736
- function parseBlockEntries(block) {
1737
- if (block.length < 4) return [];
1738
- const restartCount = block.readUInt32LE(block.length - 4);
1739
- const restartsSize = restartCount * 4 + 4;
1740
- if (restartCount < 1 || restartsSize > block.length) return [];
1741
- const entriesEnd = block.length - restartsSize;
1742
- const entries = [];
1743
- let offset = 0;
1744
- let previousKey = Buffer.alloc(0);
1745
- while (offset < entriesEnd) {
1746
- const shared = readVarint32(block, offset);
1747
- if (!shared) break;
1748
- offset = shared.nextOffset;
1749
- const nonShared = readVarint32(block, offset);
1750
- if (!nonShared) break;
1751
- offset = nonShared.nextOffset;
1752
- const valueLength = readVarint32(block, offset);
1753
- if (!valueLength) break;
1754
- offset = valueLength.nextOffset;
1755
- if (shared.value > previousKey.length || offset + nonShared.value + valueLength.value > entriesEnd) {
1756
- break;
1757
- }
1758
- const key = Buffer.concat([
1759
- previousKey.subarray(0, shared.value),
1760
- block.subarray(offset, offset + nonShared.value)
1761
- ]);
1762
- offset += nonShared.value;
1763
- const value = Buffer.from(block.subarray(offset, offset + valueLength.value));
1764
- offset += valueLength.value;
1765
- entries.push({ key, value });
1766
- previousKey = key;
1767
- }
1768
- return entries;
1769
- }
1770
- function readBlockHandle(data, offset) {
1771
- const blockOffset = readVarint64(data, offset);
1772
- if (!blockOffset) return null;
1773
- const blockSize = readVarint64(data, blockOffset.nextOffset);
1774
- if (!blockSize) return null;
1775
- if (blockOffset.value > BigInt(Number.MAX_SAFE_INTEGER) || blockSize.value > BigInt(Number.MAX_SAFE_INTEGER)) {
1776
- return null;
1777
- }
1778
- return {
1779
- offset: Number(blockOffset.value),
1780
- size: Number(blockSize.value),
1781
- bytesRead: blockSize.nextOffset - offset
1782
- };
1783
- }
1784
- function readLengthPrefixedSlice(data, offset) {
1785
- const length = readVarint32(data, offset);
1786
- if (!length) return null;
1787
- const end = length.nextOffset + length.value;
1788
- if (end > data.length) return null;
1789
- return { value: Buffer.from(data.subarray(length.nextOffset, end)), nextOffset: end };
1790
- }
1791
- function readVarint32(data, offset) {
1792
- let result = 0;
1793
- let shift = 0;
1794
- for (let i = offset; i < data.length && i < offset + 5; i++) {
1795
- const byte = data[i];
1796
- result |= (byte & 127) << shift;
1797
- if ((byte & 128) === 0) {
1798
- return { value: result >>> 0, nextOffset: i + 1 };
1799
- }
1800
- shift += 7;
1801
- }
1802
- return null;
1803
- }
1804
- function readVarint64(data, offset) {
1805
- let result = 0n;
1806
- let shift = 0n;
1807
- for (let i = offset; i < data.length && i < offset + 10; i++) {
1808
- const byte = data[i];
1809
- result |= BigInt(byte & 127) << shift;
1810
- if ((byte & 128) === 0) {
1811
- return { value: result, nextOffset: i + 1 };
1812
- }
1813
- shift += 7n;
1814
- }
1815
- return null;
1816
- }
1817
- function readUInt64LE(data, offset) {
1818
- let value = 0n;
1819
- for (let i = 0; i < 8; i++) {
1820
- value |= BigInt(data[offset + i] || 0) << BigInt(i * 8);
1821
- }
1822
- return value;
1823
- }
1824
- function readSequence(tagBytes) {
1825
- let sequence = 0n;
1826
- for (let i = 1; i < 8; i++) {
1827
- sequence |= BigInt(tagBytes[i] || 0) << BigInt((i - 1) * 8);
1828
- }
1829
- return sequence;
1830
- }
1831
- function snappyUncompress(input) {
1832
- const expectedLength = readVarint32(input, 0);
1833
- if (!expectedLength) throw new Error("Invalid Snappy length");
1834
- const output = Buffer.alloc(expectedLength.value);
1835
- let ip = expectedLength.nextOffset;
1836
- let op = 0;
1837
- while (ip < input.length && op < output.length) {
1838
- const tag = input[ip++];
1839
- const type = tag & 3;
1840
- if (type === 0) {
1841
- let literalLength = tag >> 2;
1842
- if (literalLength < 60) {
1843
- literalLength += 1;
1844
- } else {
1845
- const bytesToRead = literalLength - 59;
1846
- if (ip + bytesToRead > input.length) throw new Error("Invalid Snappy literal");
1847
- literalLength = 1;
1848
- for (let i = 0; i < bytesToRead; i++) {
1849
- literalLength += input[ip++] << i * 8;
1850
- }
1851
- }
1852
- if (ip + literalLength > input.length || op + literalLength > output.length) {
1853
- throw new Error("Invalid Snappy literal length");
1854
- }
1855
- input.copy(output, op, ip, ip + literalLength);
1856
- ip += literalLength;
1857
- op += literalLength;
1858
- continue;
1859
- }
1860
- let copyLength;
1861
- let copyOffset;
1862
- if (type === 1) {
1863
- if (ip >= input.length) throw new Error("Invalid Snappy copy");
1864
- copyLength = (tag >> 2 & 7) + 4;
1865
- copyOffset = (tag & 224) << 3 | input[ip++];
1866
- } else if (type === 2) {
1867
- if (ip + 2 > input.length) throw new Error("Invalid Snappy copy");
1868
- copyLength = (tag >> 2) + 1;
1869
- copyOffset = input.readUInt16LE(ip);
1870
- ip += 2;
1871
- } else {
1872
- if (ip + 4 > input.length) throw new Error("Invalid Snappy copy");
1873
- copyLength = (tag >> 2) + 1;
1874
- copyOffset = input.readUInt32LE(ip);
1875
- ip += 4;
1876
- }
1877
- if (copyOffset <= 0 || copyOffset > op || op + copyLength > output.length) {
1878
- throw new Error("Invalid Snappy offset");
1879
- }
1880
- for (let i = 0; i < copyLength; i++) {
1881
- output[op] = output[op - copyOffset];
1882
- op++;
1883
- }
1884
- }
1885
- if (op !== output.length) throw new Error("Truncated Snappy stream");
1886
- return output;
1887
- }
1888
-
1889
- // src/modules/autofill.ts
1890
- import { join as join10 } from "node:path";
1891
- function getAutofillEntries({
1892
- chromeDir,
1893
- profilePath,
1894
- profile,
1895
- browser,
1896
- search,
1897
- limit = 100
1898
- } = {}) {
1899
- const profiles = resolveTargetProfiles({ chromeDir, profilePath, profile, browser });
1900
- const results = [];
1901
- const rowLimit = normalizeLimit(limit);
1902
- for (const p of profiles) {
1903
- const dbPath = join10(p.path, "Web Data");
1904
- try {
1905
- let sql = `SELECT name, value, date_created, date_last_used, count FROM autofill`;
1906
- const params = [];
1907
- if (search) {
1908
- sql += ` WHERE IFNULL(name, '') LIKE ? ESCAPE '\\' OR IFNULL(value, '') LIKE ? ESCAPE '\\'`;
1909
- const like = sqlLikePattern(search);
1910
- params.push(like, like);
1911
- }
1912
- sql += ` ORDER BY date_last_used DESC`;
1913
- if (rowLimit) {
1914
- sql += ` LIMIT ${rowLimit}`;
1915
- }
1916
- const rows = queryDatabase(dbPath, sql, params);
1917
- for (const row of rows) {
1918
- results.push({
1919
- profile: p.name,
1920
- name: row.name || "",
1921
- value: row.value || "",
1922
- dateCreated: chromeTimeToDate(row.date_created ? BigInt(row.date_created) * 1000000n + 11644473600000000n : 0),
1923
- dateLastUsed: chromeTimeToDate(row.date_last_used ? BigInt(row.date_last_used) * 1000000n + 11644473600000000n : 0),
1924
- count: row.count || 0
1925
- });
1926
- }
1927
- } catch {
1928
- }
1929
- }
1930
- return limitResults(results, limit);
1931
- }
1932
- function getCreditCards({
1933
- chromeDir,
1934
- profilePath,
1935
- profile,
1936
- browser,
1937
- decryptionKey
1938
- } = {}) {
1939
- const userDataDir = profilePath || chromeDir;
1940
- const profiles = resolveTargetProfiles({ chromeDir, profilePath, profile, browser });
1941
- let derivedKey = decryptionKey;
1942
- const results = [];
1943
- for (const p of profiles) {
1944
- const dbPath = join10(p.path, "Web Data");
1945
- try {
1946
- const rows = queryDatabase(
1947
- dbPath,
1948
- `SELECT guid, name_on_card, expiration_month, expiration_year,
26
+ FROM cookies`,b=[],N=[];if(s)b.push("host_key LIKE ?"),N.push(`%${s}%`);else if(m){let x=At(m.hostname),Y=x.map(()=>"?").join(", ");b.push(`host_key IN (${Y})`),N.push(...x)}b.length>0&&(C+=` WHERE ${b.join(" AND ")}`),C+=" ORDER BY host_key, name",p&&!i&&(C+=` LIMIT ${p}`);let _r=B(L,C,N);for(let x of _r){if(m&&(!Tt(m.hostname,x.host_key||"")||!It(m.pathname||"/",x.path||"/")||x.is_secure&&m.protocol!=="https:"))continue;let Y=x.value||"";!Y&&x.encrypted_value?.length>0&&c&&(Y=Lt(le(x.encrypted_value),x.host_key||"")),g.push({profile:_.name,domain:x.host_key||"",name:x.name||"",value:Y,path:x.path||"/",expires:E(x.expires_utc||0),secure:!!x.is_secure,httpOnly:!!x.is_httponly,sameSite:["unspecified","lax","strict","none"][x.samesite]||"unspecified",created:E(x.creation_utc||0),lastAccessed:E(x.last_access_utc||0)})}}catch(C){w(n,_.name,"Cookies",C),y??=new Error(`Could not read cookies for profile "${_.name}" at ${L}: `+(C instanceof Error?C.message:String(C)))}}if(!n&&g.length===0&&y)throw y;return P(g,a)}import{join as dr}from"node:path";import{zstdDecompressSync as Vt}from"node:zlib";function z(e,t){let r=0,o=0;for(let n=t;n<e.length&&n<t+5;n++){let s=e[n];if(r|=(s&127)<<o,(s&128)===0)return{value:r>>>0,nextOffset:n+1};o+=7}return null}var Re=64*1024*1024;function ce(e){let t=z(e,0);if(!t||t.value>Re)throw new Error("Invalid Snappy length");let r=Buffer.alloc(t.value),o=t.nextOffset,n=0;for(;o<e.length&&n<r.length;){let s=e[o++],i=s&3;if(i===0){let f=s>>2;if(f<60)f+=1;else{let l=f-59;if(o+l>e.length)throw new Error("Invalid Snappy literal");f=1;for(let d=0;d<l;d++)f+=e[o++]<<d*8}if(o+f>e.length||n+f>r.length)throw new Error("Invalid Snappy literal length");e.copy(r,n,o,o+f),o+=f,n+=f;continue}let c,a;if(i===1){if(o>=e.length)throw new Error("Invalid Snappy copy");c=(s>>2&7)+4,a=(s&224)<<3|e[o++]}else if(i===2){if(o+2>e.length)throw new Error("Invalid Snappy copy");c=(s>>2)+1,a=e.readUInt16LE(o),o+=2}else{if(o+4>e.length)throw new Error("Invalid Snappy copy");c=(s>>2)+1,a=e.readUInt32LE(o),o+=4}if(a<=0||a>n||n+c>r.length)throw new Error("Invalid Snappy offset");for(let f=0;f<c;f++)r[n]=r[n-a],n++}if(n!==r.length)throw new Error("Truncated Snappy stream");return r}import{copyFileSync as Mt,readFileSync as tr,readdirSync as Ft,rmSync as Ut}from"node:fs";import{join as q}from"node:path";function Le(e){return Ft(e).filter(t=>/\.(log|ldb|sst)$/i.test(t)).sort()}function lr(e,t){let r=se(t,"chrome-local-storage-");try{let o=Le(e),n=o.map(i=>T(q(e,i)));try{for(let i of o)Mt(q(e,i),q(r,i))}catch(i){throw new h("snapshot_unavailable","Could not copy Local Storage. Close the browser and retry if it is open.",{cause:i})}let s=Le(e);if(s.length!==o.length||o.some((i,c)=>i!==s[c]||!ne(n[c],T(q(e,i)))))throw new h("snapshot_unavailable","Local Storage changed during the copy. Close the browser and retry.");return $t(r).values()}finally{Ut(r,{recursive:!0,force:!0,maxRetries:3})}}var Kt=0xdb4775248b80fb57n,or=32768,ur=0,pr=1;function $t(e){let t=new Map,r=Le(e);for(let o of r){let n=q(e,o),s=tr(n);if(/\.(ldb|sst)$/i.test(o))for(let i of Gt(s))nr(t,i)}for(let o of r){if(!/\.log$/i.test(o))continue;let n=q(e,o),s=tr(n);for(let i of Ht(s))nr(t,i)}return t}function nr(e,t){let r=t.key.toString("hex"),o=e.get(r);o&&o.sequence>t.sequence||e.set(r,t)}function Ht(e){let t=[],r=0,o=[];for(;r+7<=e.length;){let n=or-r%or;if(n<7){r+=n;continue}let s=e.readUInt16LE(r+4),i=e[r+6];if(r+=7,i===0&&s===0){r+=s;continue}if(s>e.length-r||s>n-7)break;let c=e.subarray(r,r+s);r+=s,i===1?(t.push(...sr(c)),o=[]):i===2?o=[Buffer.from(c)]:i===3&&o.length>0?o.push(Buffer.from(c)):i===4&&o.length>0&&(o.push(Buffer.from(c)),t.push(...sr(Buffer.concat(o))),o=[])}return t}function sr(e){if(e.length<12)return[];let t=[],r=jt(e,0),o=e.readUInt32LE(8),n=12,s=0;for(;n<e.length&&s<o;){let i=e[n++],c=cr(e,n);if(!c)break;n=c.nextOffset;let a=r+BigInt(s);if(i===pr){let f=cr(e,n);if(!f)break;n=f.nextOffset,t.push({key:c.value,value:f.value,sequence:a,deleted:!1})}else if(i===ur)t.push({key:c.value,sequence:a,deleted:!0});else break;s++}return t}function Gt(e){let t=[];if(e.length<48||e.readBigUInt64LE(e.length-8)!==Kt)return t;let o=e.subarray(e.length-48,e.length-8),n=ke(o,0);if(!n)return t;let s=ke(o,n.bytesRead);if(!s)return t;let i=ir(e,s);if(!i)return t;for(let c of ar(i)){let a=ke(c.value,0);if(!a)continue;let f=ir(e,a);if(f)for(let l of ar(f)){if(l.key.length<8)continue;let d=l.key,u=d.length-8,g=d[u],m=Wt(d.subarray(u)),p=d.subarray(0,u);g===pr?t.push({key:p,value:l.value,sequence:m,deleted:!1}):g===ur&&t.push({key:p,sequence:m,deleted:!0})}}return t}function ir(e,t){let r=t.offset+t.size;if(t.offset<0||t.size<0||r+5>e.length)return null;let o=e.subarray(t.offset,r),n=e[r];if(n===0)return o;if(n===1)try{return ce(o)}catch{return null}return null}function ar(e){if(e.length<4)return[];let t=e.readUInt32LE(e.length-4),r=t*4+4;if(t<1||r>e.length)return[];let o=e.length-r,n=[],s=0,i=Buffer.alloc(0);for(;s<o;){let c=z(e,s);if(!c)break;s=c.nextOffset;let a=z(e,s);if(!a)break;s=a.nextOffset;let f=z(e,s);if(!f||(s=f.nextOffset,c.value>i.length||s+a.value+f.value>o))break;let l=Buffer.concat([i.subarray(0,c.value),e.subarray(s,s+a.value)]);s+=a.value;let d=Buffer.from(e.subarray(s,s+f.value));s+=f.value,n.push({key:l,value:d}),i=l}return n}function ke(e,t){let r=fr(e,t);if(!r)return null;let o=fr(e,r.nextOffset);return!o||r.value>BigInt(Number.MAX_SAFE_INTEGER)||o.value>BigInt(Number.MAX_SAFE_INTEGER)?null:{offset:Number(r.value),size:Number(o.value),bytesRead:o.nextOffset-t}}function cr(e,t){let r=z(e,t);if(!r)return null;let o=r.nextOffset+r.value;return o>e.length?null:{value:Buffer.from(e.subarray(r.nextOffset,o)),nextOffset:o}}function fr(e,t){let r=0n,o=0n;for(let n=t;n<e.length&&n<t+10;n++){let s=e[n];if(r|=BigInt(s&127)<<o,(s&128)===0)return{value:r,nextOffset:n+1};o+=7n}return null}function jt(e,t){let r=0n;for(let o=0;o<8;o++)r|=BigInt(e[t+o]||0)<<BigInt(o*8);return r}function Wt(e){let t=0n;for(let r=1;r<8;r++)t|=BigInt(e[r]||0)<<BigInt((r-1)*8);return t}function*mr(e){let t=dr(e,"LocalStorage");if(T(t)){let r=B(t,`SELECT maps.storage_key, map_entries.key,
27
+ map_entries.value, map_entries.value_compression_type
28
+ FROM map_entries JOIN maps ON maps.row_id = map_entries.map_id`);for(let o of r){if(!Buffer.isBuffer(o.storage_key)||!Buffer.isBuffer(o.key)||!Buffer.isBuffer(o.value))throw new h("unsupported_schema","Unexpected LocalStorage record encoding.");let n=o.storage_key.toString("utf8");yield{origin:n.includes("^")||n==="file:///"?n:n.replace(/\/$/,""),key:fe(o.key),value:fe(zt(o.value,o.value_compression_type))}}return}for(let r of lr(dr(e,"Local Storage","leveldb"),e)){if(r.deleted||!r.value)continue;let o=qt(r.key,r.value);o&&(yield o)}}function zt(e,t){switch(t){case 0:return e;case 1:return Vt(e,{maxOutputLength:Re});case 2:return ce(e);default:throw new h("unsupported_schema","Unknown LocalStorage value compression type.")}}function qt(e,t){if(e.length<3||e[0]!==95)return null;let r=e.indexOf(0,1);if(r===-1)return null;let o=e.subarray(1,r).toString("utf8"),n=fe(e.subarray(r+1)),s=fe(t);return o?{origin:o,key:n,value:s}:null}function fe(e){if(e.length===0)return"";let t=e[0],r=e.subarray(1);return t===0&&r.length%2===0?r.toString("utf16le"):t===1?r.toString("latin1"):e.toString("utf8")}function Yt({chromeDir:e,profilePath:t,profile:r,browser:o,onDiagnostic:n,origin:s,url:i,key:c,search:a,limit:f=100}={}){let l=S({chromeDir:e,profilePath:t,profile:r,browser:o,onDiagnostic:n}),d=[],u=Jt(i||s),g=c?.toLowerCase(),m=a?.toLowerCase();for(let p of l)try{for(let y of mr(p.path)){let _={profile:p.name,...y};u&&!_.origin.toLowerCase().includes(u)||g&&!_.key.toLowerCase().includes(g)||m&&!`${_.origin}
29
+ ${_.key}
30
+ ${_.value}`.toLowerCase().includes(m)||d.push(_)}}catch(y){w(n,p.name,"Local Storage",y)}return d.sort((p,y)=>p.profile.localeCompare(y.profile)||p.origin.localeCompare(y.origin)||p.key.localeCompare(y.key)),P(d,f)}function Jt(e){if(e)try{return new URL(e).origin.toLowerCase()}catch{return e.toLowerCase()}}var D={firstName:3,middleName:4,lastName:5,fullName:7,email:9,phone:14,line1:30,line2:31,city:33,state:34,zipcode:35,countryCode:36,companyName:60,streetAddress:77},gr=["guid","use_count","use_date","date_modified"],yr=["guid","type","value"];function I(e,t,r){let o=new Set(e("SELECT name FROM pragma_table_info(?)",[t]).map(n=>n.name));return r.every(n=>o.has(n))}function wr(e){return E(e?BigInt(e)*1000000n+11644473600000000n:0)}function hr(e,t,r,o){let n=new Map;for(let s of e("SELECT guid, type, value FROM "+o)){let i=n.get(s.guid);i||(i=new Map,n.set(s.guid,i)),i.set(s.type,s.value)}return e("SELECT guid, use_count, use_date, date_modified FROM "+r).map(s=>{let i=c=>n.get(s.guid)?.get(c)||"";return{useDate:s.use_date,entry:{profile:t,guid:s.guid,fullName:i(D.fullName)||[D.firstName,D.middleName,D.lastName].map(i).filter(Boolean).join(" "),email:i(D.email),phone:i(D.phone),companyName:i(D.companyName),streetAddress:i(D.streetAddress)||[D.line1,D.line2].map(i).filter(Boolean).join(`
31
+ `),city:i(D.city),state:i(D.state),zipcode:i(D.zipcode),countryCode:i(D.countryCode),origin:"",useCount:s.use_count||0,dateModified:wr(s.date_modified)}}})}function Xt(e,t){let r=I(e,"autofill_profile_names",["guid","first_name","middle_name","last_name","full_name"])?e("SELECT guid, first_name, middle_name, last_name, full_name FROM autofill_profile_names"):[],o=I(e,"autofill_profile_emails",["guid","email"])?e("SELECT guid, email FROM autofill_profile_emails"):[],n=I(e,"autofill_profile_phones",["guid","number"])?e("SELECT guid, number FROM autofill_profile_phones"):[],s=new Map(r.map(a=>[a.guid,a.full_name||[a.first_name,a.middle_name,a.last_name].filter(Boolean).join(" ")])),i=new Map(o.map(a=>[a.guid,a.email])),c=new Map(n.map(a=>[a.guid,a.number]));return e("SELECT * FROM autofill_profiles ORDER BY use_date DESC").map(a=>({profile:t,guid:a.guid||"",fullName:s.get(a.guid)||"",email:i.get(a.guid)||"",phone:c.get(a.guid)||"",companyName:a.company_name||"",streetAddress:a.street_address||"",city:a.city||"",state:a.state||"",zipcode:a.zipcode||"",countryCode:a.country_code||"",origin:a.origin||"",useCount:a.use_count||0,dateModified:wr(a.date_modified)}))}function Sr(e,t){return Pe(e,r=>{if(I(r,"addresses",gr)&&I(r,"address_type_tokens",yr))return hr(r,t,"addresses","address_type_tokens").sort((s,i)=>i.useDate-s.useDate).map(({entry:s})=>s);let n=[["local_addresses","local_addresses_type_tokens"],["contact_info","contact_info_type_tokens"]].filter(([s,i])=>I(r,s,gr)&&I(r,i,yr));if(n.length)return n.flatMap(([s,i])=>hr(r,t,s,i)).sort((s,i)=>i.useDate-s.useDate).map(({entry:s})=>s);if(I(r,"autofill_profiles",["guid","use_date"]))return Xt(r,t);throw new h("unsupported_schema","Unrecognized saved-address schema in Web Data.")})}import{join as Oe}from"node:path";function Qt({chromeDir:e,profilePath:t,profile:r,browser:o,onDiagnostic:n,search:s,limit:i=100}={}){let c=S({chromeDir:e,profilePath:t,profile:r,browser:o,onDiagnostic:n}),a=[],f=k(i);for(let l of c){let d=Oe(l.path,"Web Data");try{let u="SELECT name, value, date_created, date_last_used, count FROM autofill",g=[];if(s){u+=" WHERE IFNULL(name, '') LIKE ? ESCAPE '\\' OR IFNULL(value, '') LIKE ? ESCAPE '\\'";let p=V(s);g.push(p,p)}u+=" ORDER BY date_last_used DESC",f&&(u+=` LIMIT ${f}`);let m=B(d,u,g);for(let p of m)a.push({profile:l.name,name:p.name||"",value:p.value||"",dateCreated:E(p.date_created?BigInt(p.date_created)*1000000n+11644473600000000n:0),dateLastUsed:E(p.date_last_used?BigInt(p.date_last_used)*1000000n+11644473600000000n:0),count:p.count||0})}catch(u){w(n,l.name,"Web Data",u)}}return P(a,i)}function Zt({chromeDir:e,profilePath:t,profile:r,browser:o,onDiagnostic:n,decryptionKey:s}={}){let i=t||e,c=o??v().id,a=S({chromeDir:e,profilePath:t,profile:r,browser:c,onDiagnostic:n}),f=[];for(let l of a){let d=Oe(l.path,"Web Data"),u=j(i??l.path,c,s,n,l.name,"Web Data");try{let g=B(d,`SELECT guid, name_on_card, expiration_month, expiration_year,
1949
32
  card_number_encrypted, date_modified, origin, use_count,
1950
33
  nickname
1951
- FROM credit_cards ORDER BY use_date DESC`
1952
- );
1953
- for (const row of rows) {
1954
- let cardNumber = "";
1955
- if (row.card_number_encrypted?.length > 0) {
1956
- cardNumber = decryptValue(
1957
- row.card_number_encrypted,
1958
- derivedKey ??= deriveEncryptionKey2(userDataDir, browser)
1959
- );
1960
- }
1961
- results.push({
1962
- profile: p.name,
1963
- guid: row.guid || "",
1964
- nameOnCard: row.name_on_card || "",
1965
- expirationMonth: row.expiration_month || 0,
1966
- expirationYear: row.expiration_year || 0,
1967
- cardNumber,
1968
- nickname: row.nickname || "",
1969
- origin: row.origin || "",
1970
- useCount: row.use_count || 0,
1971
- dateModified: chromeTimeToDate(row.date_modified ? BigInt(row.date_modified) * 1000000n + 11644473600000000n : 0)
1972
- });
1973
- }
1974
- } catch {
1975
- }
1976
- }
1977
- return results;
1978
- }
1979
- function getAddresses({ chromeDir, profilePath, profile, browser } = {}) {
1980
- const profiles = resolveTargetProfiles({ chromeDir, profilePath, profile, browser });
1981
- const results = [];
1982
- for (const p of profiles) {
1983
- const dbPath = join10(p.path, "Web Data");
1984
- try {
1985
- const profileRows = queryDatabase(
1986
- dbPath,
1987
- `SELECT guid, company_name, street_address,
1988
- city, state, zipcode, country_code, date_modified,
1989
- origin, use_count
1990
- FROM autofill_profiles ORDER BY use_date DESC`
1991
- );
1992
- let nameRows = [];
1993
- let emailRows = [];
1994
- let phoneRows = [];
1995
- try {
1996
- nameRows = queryDatabase(
1997
- dbPath,
1998
- `SELECT guid, first_name, middle_name, last_name, full_name FROM autofill_profile_names`
1999
- );
2000
- } catch {
2001
- }
2002
- try {
2003
- emailRows = queryDatabase(
2004
- dbPath,
2005
- `SELECT guid, email FROM autofill_profile_emails`
2006
- );
2007
- } catch {
2008
- }
2009
- try {
2010
- phoneRows = queryDatabase(
2011
- dbPath,
2012
- `SELECT guid, number FROM autofill_profile_phones`
2013
- );
2014
- } catch {
2015
- }
2016
- const nameMap = /* @__PURE__ */ new Map();
2017
- for (const n of nameRows) {
2018
- nameMap.set(n.guid, n.full_name || [n.first_name, n.middle_name, n.last_name].filter(Boolean).join(" "));
2019
- }
2020
- const emailMap = /* @__PURE__ */ new Map();
2021
- for (const e of emailRows) {
2022
- emailMap.set(e.guid, e.email || "");
2023
- }
2024
- const phoneMap = /* @__PURE__ */ new Map();
2025
- for (const ph of phoneRows) {
2026
- phoneMap.set(ph.guid, ph.number || "");
2027
- }
2028
- for (const row of profileRows) {
2029
- results.push({
2030
- profile: p.name,
2031
- guid: row.guid || "",
2032
- fullName: nameMap.get(row.guid) || "",
2033
- email: emailMap.get(row.guid) || "",
2034
- phone: phoneMap.get(row.guid) || "",
2035
- companyName: row.company_name || "",
2036
- streetAddress: row.street_address || "",
2037
- city: row.city || "",
2038
- state: row.state || "",
2039
- zipcode: row.zipcode || "",
2040
- countryCode: row.country_code || "",
2041
- origin: row.origin || "",
2042
- useCount: row.use_count || 0,
2043
- dateModified: chromeTimeToDate(row.date_modified ? BigInt(row.date_modified) * 1000000n + 11644473600000000n : 0)
2044
- });
2045
- }
2046
- } catch {
2047
- }
2048
- }
2049
- return results;
2050
- }
2051
-
2052
- // src/modules/downloads.ts
2053
- import { join as join11 } from "node:path";
2054
- var DOWNLOAD_STATES = {
2055
- 0: "in_progress",
2056
- 1: "complete",
2057
- 2: "cancelled",
2058
- 3: "interrupted"
2059
- };
2060
- var DANGER_TYPES = {
2061
- 0: "not_dangerous",
2062
- 1: "dangerous_file",
2063
- 2: "dangerous_url",
2064
- 3: "dangerous_content",
2065
- 4: "maybe_dangerous_content",
2066
- 5: "uncommon_content",
2067
- 6: "user_validated",
2068
- 7: "dangerous_host",
2069
- 8: "potentially_unwanted",
2070
- 9: "allowlisted_by_policy"
2071
- };
2072
- function getDownloads({
2073
- chromeDir,
2074
- profilePath,
2075
- profile,
2076
- browser,
2077
- search,
2078
- limit = 100
2079
- } = {}) {
2080
- const profiles = resolveTargetProfiles({ chromeDir, profilePath, profile, browser });
2081
- const results = [];
2082
- const rowLimit = normalizeLimit(limit);
2083
- for (const p of profiles) {
2084
- const dbPath = join11(p.path, "History");
2085
- try {
2086
- let sql = `SELECT target_path, tab_url, total_bytes, received_bytes,
34
+ FROM credit_cards ORDER BY use_date DESC`);for(let m of g){let p="";if(m.card_number_encrypted?.length>0){let y=u(m.card_number_encrypted);p=Buffer.isBuffer(y)?y.toString("utf8"):y}f.push({profile:l.name,guid:m.guid||"",nameOnCard:m.name_on_card||"",expirationMonth:m.expiration_month||0,expirationYear:m.expiration_year||0,cardNumber:p,nickname:m.nickname||"",origin:m.origin||"",useCount:m.use_count||0,dateModified:E(m.date_modified?BigInt(m.date_modified)*1000000n+11644473600000000n:0)})}}catch(g){w(n,l.name,"Web Data",g)}}return f}function eo(e={}){let t=[];for(let r of S(e))try{for(let o of Sr(Oe(r.path,"Web Data"),r.name))t.push(o)}catch(o){w(e.onDiagnostic,r.name,"Web Data",o)}return t}import{join as ro}from"node:path";var to={0:"in_progress",1:"complete",2:"cancelled",3:"interrupted"},oo={0:"not_dangerous",1:"dangerous_file",2:"dangerous_url",3:"dangerous_content",4:"maybe_dangerous_content",5:"uncommon_content",6:"user_validated",7:"dangerous_host",8:"potentially_unwanted",9:"allowlisted_by_policy"};function no({chromeDir:e,profilePath:t,profile:r,browser:o,onDiagnostic:n,search:s,limit:i=100}={}){let c=S({chromeDir:e,profilePath:t,profile:r,browser:o,onDiagnostic:n}),a=[],f=k(i);for(let l of c){let d=ro(l.path,"History");try{let u=`SELECT target_path, tab_url, total_bytes, received_bytes,
2087
35
  start_time, end_time, mime_type, original_mime_type,
2088
36
  state, danger_type, opened, referrer, site_url
2089
- FROM downloads`;
2090
- const params = [];
2091
- if (search) {
2092
- sql += ` WHERE IFNULL(target_path, '') LIKE ? ESCAPE '\\'
2093
- OR IFNULL(tab_url, '') LIKE ? ESCAPE '\\'`;
2094
- const like = sqlLikePattern(search);
2095
- params.push(like, like);
2096
- }
2097
- sql += ` ORDER BY start_time DESC`;
2098
- if (rowLimit) {
2099
- sql += ` LIMIT ${rowLimit}`;
2100
- }
2101
- const rows = queryDatabase(dbPath, sql, params);
2102
- for (const row of rows) {
2103
- results.push({
2104
- profile: p.name,
2105
- targetPath: row.target_path || "",
2106
- url: row.tab_url || "",
2107
- siteUrl: row.site_url || "",
2108
- referrer: row.referrer || "",
2109
- totalBytes: row.total_bytes || 0,
2110
- receivedBytes: row.received_bytes || 0,
2111
- mimeType: row.mime_type || row.original_mime_type || "",
2112
- state: DOWNLOAD_STATES[row.state] || `unknown(${row.state})`,
2113
- dangerType: DANGER_TYPES[row.danger_type] || `unknown(${row.danger_type})`,
2114
- opened: Boolean(row.opened),
2115
- startTime: chromeTimeToDate(row.start_time || 0),
2116
- endTime: chromeTimeToDate(row.end_time || 0)
2117
- });
2118
- }
2119
- } catch {
2120
- }
2121
- }
2122
- return limitResults(results, limit);
2123
- }
2124
-
2125
- // src/modules/extensions.ts
2126
- import { existsSync as existsSync8, readFileSync as readFileSync6 } from "node:fs";
2127
- import { join as join12 } from "node:path";
2128
- function getExtensions({
2129
- chromeDir,
2130
- profilePath,
2131
- profile,
2132
- search,
2133
- browser
2134
- } = {}) {
2135
- const profiles = resolveTargetProfiles({ chromeDir, profilePath, profile, browser });
2136
- const results = [];
2137
- for (const p of profiles) {
2138
- let settings = null;
2139
- for (const filename of ["Secure Preferences", "Preferences"]) {
2140
- const prefsPath = join12(p.path, filename);
2141
- if (existsSync8(prefsPath)) {
2142
- try {
2143
- const prefs = JSON.parse(readFileSync6(prefsPath, "utf-8"));
2144
- const candidate = prefs?.extensions?.settings;
2145
- if (candidate && typeof candidate === "object") {
2146
- settings = candidate;
2147
- break;
2148
- }
2149
- } catch {
2150
- continue;
2151
- }
2152
- }
2153
- }
2154
- if (!settings) continue;
2155
- for (const [extId, extDataAny] of Object.entries(settings)) {
2156
- const extData = extDataAny;
2157
- if (extData.location === 5 || extData.location === 10) continue;
2158
- const manifest = extData.manifest || {};
2159
- const entry = {
2160
- profile: p.name,
2161
- id: extId,
2162
- name: manifest.name || extData.name || extId,
2163
- version: manifest.version || extData.version || "unknown",
2164
- description: manifest.description || "",
2165
- enabled: extData.state === 1,
2166
- homepageUrl: manifest.homepage_url || "",
2167
- updateUrl: manifest.update_url || "",
2168
- installTime: extData.install_time || "",
2169
- fromWebstore: extData.from_webstore || false,
2170
- location: extData.location || 0
2171
- };
2172
- if (entry.name.startsWith("__MSG_")) {
2173
- entry.name = extId;
2174
- }
2175
- if (entry.description.startsWith("__MSG_")) {
2176
- entry.description = "";
2177
- }
2178
- if (search) {
2179
- const term = search.toLowerCase();
2180
- if (!(entry.name && entry.name.toLowerCase().includes(term)) && !(entry.id && entry.id.toLowerCase().includes(term))) {
2181
- continue;
2182
- }
2183
- }
2184
- results.push(entry);
2185
- }
2186
- }
2187
- return results;
2188
- }
2189
-
2190
- // src/modules/search-engines.ts
2191
- import { join as join13 } from "node:path";
2192
- function getSearchEngines({
2193
- chromeDir,
2194
- profilePath,
2195
- profile,
2196
- browser
2197
- } = {}) {
2198
- const profiles = resolveTargetProfiles({ chromeDir, profilePath, profile, browser });
2199
- const results = [];
2200
- for (const p of profiles) {
2201
- const dbPath = join13(p.path, "Web Data");
2202
- try {
2203
- const rows = queryDatabase(
2204
- dbPath,
2205
- `SELECT id, short_name, keyword, url, suggestions_url,
37
+ FROM downloads`,g=[];if(s){u+=` WHERE IFNULL(target_path, '') LIKE ? ESCAPE '\\'
38
+ OR IFNULL(tab_url, '') LIKE ? ESCAPE '\\'`;let p=V(s);g.push(p,p)}u+=" ORDER BY start_time DESC",f&&(u+=` LIMIT ${f}`);let m=B(d,u,g);for(let p of m)a.push({profile:l.name,targetPath:p.target_path||"",url:p.tab_url||"",siteUrl:p.site_url||"",referrer:p.referrer||"",totalBytes:p.total_bytes||0,receivedBytes:p.received_bytes||0,mimeType:p.mime_type||p.original_mime_type||"",state:to[p.state]||`unknown(${p.state})`,dangerType:oo[p.danger_type]||`unknown(${p.danger_type})`,opened:!!p.opened,startTime:E(p.start_time||0),endTime:E(p.end_time||0)})}catch(u){w(n,l.name,"History",u)}}return P(a,i)}import{existsSync as so,readFileSync as io}from"node:fs";import{join as ao}from"node:path";function co({chromeDir:e,profilePath:t,profile:r,search:o,browser:n,onDiagnostic:s}={}){let i=S({chromeDir:e,profilePath:t,profile:r,browser:n,onDiagnostic:s}),c=[];for(let a of i){let f=null;for(let l of["Secure Preferences","Preferences"]){let d=ao(a.path,l);if(so(d))try{let g=JSON.parse(io(d,"utf-8"))?.extensions?.settings;if(g&&typeof g=="object"){f=g;break}}catch(u){w(s,a.name,l,u instanceof SyntaxError?new Error("Invalid preferences JSON."):u)}}if(f)for(let[l,d]of Object.entries(f)){let u=d;if(u.location===5||u.location===10)continue;let g=u.manifest||{},m={profile:a.name,id:l,name:g.name||u.name||l,version:g.version||u.version||"unknown",description:g.description||"",enabled:u.state===1,homepageUrl:g.homepage_url||"",updateUrl:g.update_url||"",installTime:u.install_time||"",fromWebstore:u.from_webstore||!1,location:u.location||0};if(m.name.startsWith("__MSG_")&&(m.name=l),m.description.startsWith("__MSG_")&&(m.description=""),o){let p=o.toLowerCase();if(!(m.name&&m.name.toLowerCase().includes(p))&&!(m.id&&m.id.toLowerCase().includes(p)))continue}c.push(m)}}return c}import{join as fo}from"node:path";function lo({chromeDir:e,profilePath:t,profile:r,browser:o,onDiagnostic:n}={}){let s=S({chromeDir:e,profilePath:t,profile:r,browser:o,onDiagnostic:n}),i=[];for(let c of s){let a=fo(c.path,"Web Data");try{let f=B(a,`SELECT id, short_name, keyword, url, suggestions_url,
2206
39
  favicon_url, prepopulate_id, is_active
2207
- FROM keywords ORDER BY short_name`
2208
- );
2209
- for (const row of rows) {
2210
- results.push({
2211
- profile: p.name,
2212
- id: row.id,
2213
- name: row.short_name || "",
2214
- keyword: row.keyword || "",
2215
- url: row.url || "",
2216
- suggestionsUrl: row.suggestions_url || "",
2217
- faviconUrl: row.favicon_url || "",
2218
- isDefault: row.prepopulate_id === 1,
2219
- isActive: row.is_active === 1
2220
- });
2221
- }
2222
- } catch {
2223
- }
2224
- }
2225
- return results;
2226
- }
2227
-
2228
- // src/modules/top-sites.ts
2229
- import { join as join14 } from "node:path";
2230
- function getTopSites({ chromeDir, profilePath, profile, browser } = {}) {
2231
- const profiles = resolveTargetProfiles({ chromeDir, profilePath, profile, browser });
2232
- const results = [];
2233
- for (const p of profiles) {
2234
- const dbPath = join14(p.path, "Top Sites");
2235
- try {
2236
- const rows = queryDatabase(
2237
- dbPath,
2238
- `SELECT url, url_rank, title, redirects
2239
- FROM top_sites ORDER BY url_rank ASC`
2240
- );
2241
- for (const row of rows) {
2242
- results.push({
2243
- profile: p.name,
2244
- url: row.url || "",
2245
- title: row.title || "",
2246
- rank: row.url_rank ?? 0,
2247
- redirects: row.redirects || ""
2248
- });
2249
- }
2250
- } catch {
2251
- }
2252
- }
2253
- return results;
2254
- }
2255
- export {
2256
- BROWSERS,
2257
- chromeTimeToDate,
2258
- clearKeyCache,
2259
- clearKeychainCache,
2260
- decryptValue,
2261
- decryptValueBuffer2 as decryptValueBuffer,
2262
- deriveEncryptionKey2 as deriveEncryptionKey,
2263
- describePasswordStores,
2264
- detectInstalledBrowsers,
2265
- discoverProfiles,
2266
- getActiveBrowser,
2267
- getAddresses,
2268
- getAutofillEntries,
2269
- getBookmarks,
2270
- getChromeKeychainPassword,
2271
- getCookies,
2272
- getCreditCards,
2273
- getDefaultChromeDir,
2274
- getDownloads,
2275
- getExtensions,
2276
- getHistory,
2277
- getKeychainPassword,
2278
- getKeychainPasswordProvider,
2279
- getLocalStorage,
2280
- getPasswords,
2281
- getPlatformChromeDir,
2282
- getProfileMetadata,
2283
- getProfileStates,
2284
- getSearchEngines,
2285
- getSearchTerms,
2286
- getTopSites,
2287
- listBrowsers,
2288
- queryDatabase,
2289
- requireBrowser,
2290
- resolveBrowser,
2291
- resolveProfilePath,
2292
- resolveTargetProfiles,
2293
- selectPlatform,
2294
- setActiveBrowser,
2295
- setKeychainPasswordProvider,
2296
- tableExists
2297
- };
40
+ FROM keywords ORDER BY short_name`);for(let l of f)i.push({profile:c.name,id:l.id,name:l.short_name||"",keyword:l.keyword||"",url:l.url||"",suggestionsUrl:l.suggestions_url||"",faviconUrl:l.favicon_url||"",isDefault:l.prepopulate_id===1,isActive:l.is_active===1})}catch(f){w(n,c.name,"Web Data",f)}}return i}import{join as uo}from"node:path";function po({chromeDir:e,profilePath:t,profile:r,browser:o,onDiagnostic:n}={}){let s=S({chromeDir:e,profilePath:t,profile:r,browser:o,onDiagnostic:n}),i=[];for(let c of s){let a=uo(c.path,"Top Sites");try{let f=B(a,`SELECT url, url_rank, title, redirects
41
+ FROM top_sites ORDER BY url_rank ASC`);for(let l of f)i.push({profile:c.name,url:l.url||"",title:l.title||"",rank:l.url_rank??0,redirects:l.redirects||""})}catch(f){w(n,c.name,"Top Sites",f)}}return i}export{Q as BROWSERS,E as chromeTimeToDate,qr as clearKeyCache,we as clearKeychainCache,zr as decryptValue,We as decryptValueBuffer,Vr as deriveEncryptionKey,Bt as describePasswordStores,Qr as detectInstalledBrowsers,W as discoverProfiles,v as getActiveBrowser,eo as getAddresses,Qt as getAutofillEntries,Dt as getBookmarks,Lr as getChromeKeychainPassword,Nt as getCookies,Zt as getCreditCards,F as getDefaultChromeDir,no as getDownloads,co as getExtensions,Ct as getHistory,he as getKeychainPassword,ye as getKeychainPasswordProvider,Yt as getLocalStorage,vt as getPasswords,De as getPlatformChromeDir,qe as getProfileMetadata,Zr as getProfileStates,lo as getSearchEngines,Pt as getSearchTerms,po as getTopSites,$ as listBrowsers,B as queryDatabase,M as requireBrowser,Se as resolveBrowser,et as resolveProfilePath,S as resolveTargetProfiles,G as selectPlatform,Ne as setActiveBrowser,kr as setKeychainPasswordProvider,mt as tableExists};