cloak22 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,420 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.CHROME_COOKIE_APP_BOUND_UNSUPPORTED_ERROR = exports.CHROME_COOKIE_SUPPORT_MISSING_ERROR = exports.CHROME_COOKIE_LIMITATION_WARNING = void 0;
7
+ exports.readChromeCookies = readChromeCookies;
8
+ exports.createChromeCookieReader = createChromeCookieReader;
9
+ exports.candidateChromeCookieHosts = candidateChromeCookieHosts;
10
+ exports.chromeCookieMatchesUrl = chromeCookieMatchesUrl;
11
+ const node_child_process_1 = require("node:child_process");
12
+ const node_crypto_1 = require("node:crypto");
13
+ const node_fs_1 = __importDefault(require("node:fs"));
14
+ const node_os_1 = __importDefault(require("node:os"));
15
+ const node_path_1 = __importDefault(require("node:path"));
16
+ const cookies_js_1 = require("./cookies.js");
17
+ const chrome_profile_sites_js_1 = require("./chrome-profile-sites.js");
18
+ const chrome_profiles_js_1 = require("./chrome-profiles.js");
19
+ exports.CHROME_COOKIE_LIMITATION_WARNING = "Chrome cookie import is best-effort and only reuses cookies, not the rest of the browser profile. If a login still fails after injection, the site may depend on additional browser state.";
20
+ exports.CHROME_COOKIE_SUPPORT_MISSING_ERROR = "Chrome cookie support is not available in this environment. cloak could not access the selected profile's cookie database or the platform secret storage needed to decrypt it.";
21
+ exports.CHROME_COOKIE_APP_BOUND_UNSUPPORTED_ERROR = "Chrome app-bound cookie encryption on Windows is not supported by cloak yet.";
22
+ const CHROMIUM_EPOCH_MICROSECONDS = 11644473600000000n;
23
+ const POSIX_IV = Buffer.from(" ".repeat(16), "utf8");
24
+ const POSIX_SALT = "saltysalt";
25
+ const MAC_SAFE_STORAGE_SERVICE = "Chrome Safe Storage";
26
+ const MAC_SAFE_STORAGE_ACCOUNT = "Chrome";
27
+ const LINUX_V10_PASSWORD = "peanuts";
28
+ const WINDOWS_DPAPI_KEY_PREFIX = Buffer.from("DPAPI", "utf8");
29
+ const WINDOWS_APP_BOUND_KEY_PREFIX = Buffer.from("APPB", "utf8");
30
+ function loadDatabaseConstructor() {
31
+ const sqlite = require("node:sqlite");
32
+ return sqlite.DatabaseSync;
33
+ }
34
+ function chromiumTimestampToUnixSeconds(timestamp) {
35
+ if (typeof timestamp === "bigint") {
36
+ return Number((timestamp - CHROMIUM_EPOCH_MICROSECONDS) / 1000000n);
37
+ }
38
+ return Math.trunc((timestamp - Number(CHROMIUM_EPOCH_MICROSECONDS)) / 1000000);
39
+ }
40
+ function normalizeChromeCookie(raw) {
41
+ const normalized = {
42
+ name: raw.name,
43
+ value: raw.value,
44
+ domain: raw.domain,
45
+ path: raw.path,
46
+ httpOnly: raw.HttpOnly,
47
+ secure: raw.Secure,
48
+ sameSite: raw.sameSite,
49
+ };
50
+ if (!isZeroChromiumTimestamp(raw.expires)) {
51
+ normalized.expires = chromiumTimestampToUnixSeconds(raw.expires);
52
+ }
53
+ return (0, cookies_js_1.normalizeCookie)(normalized);
54
+ }
55
+ function isZeroChromiumTimestamp(timestamp) {
56
+ return typeof timestamp === "bigint" ? timestamp === 0n : timestamp === 0;
57
+ }
58
+ async function readChromeCookies(options, getCookies = createChromeCookieReader()) {
59
+ const cookies = await getCookies(options.url, "puppeteer", options.profile);
60
+ return cookies.map(normalizeChromeCookie);
61
+ }
62
+ function createChromeCookieReader(dependencies = {}) {
63
+ return async (url, format, profile) => {
64
+ if (format !== "puppeteer") {
65
+ throw new Error("cloak only supports Chrome cookie export in puppeteer format");
66
+ }
67
+ const parsedUrl = parseCookieUrl(url);
68
+ const chromeUserDataDir = dependencies.chromeUserDataDir ?? (0, chrome_profiles_js_1.defaultChromeUserDataDir)();
69
+ const pathExists = dependencies.pathExists ?? node_fs_1.default.existsSync;
70
+ const makeTempDir = dependencies.makeTempDir ?? node_fs_1.default.mkdtempSync;
71
+ const copyFile = dependencies.copyFile ?? node_fs_1.default.copyFileSync;
72
+ const removeDir = dependencies.removeDir ?? node_fs_1.default.rmSync;
73
+ const queryRows = dependencies.queryRows ?? queryChromeCookieRows;
74
+ const sourcePath = (0, chrome_profile_sites_js_1.resolveChromeCookiesDatabasePath)({
75
+ chromeUserDataDir,
76
+ profileDirectory: profile ?? "Default",
77
+ }, {
78
+ pathExists,
79
+ });
80
+ if (!sourcePath) {
81
+ return [];
82
+ }
83
+ const tempRoot = makeTempDir(node_path_1.default.join(node_os_1.default.tmpdir(), "cloak-cookie-db-"));
84
+ const stagedPath = node_path_1.default.join(tempRoot, node_path_1.default.basename(sourcePath));
85
+ try {
86
+ copyFile(sourcePath, stagedPath);
87
+ copyOptionalSidecar(`${sourcePath}-wal`, `${stagedPath}-wal`, pathExists, copyFile);
88
+ copyOptionalSidecar(`${sourcePath}-shm`, `${stagedPath}-shm`, pathExists, copyFile);
89
+ const rows = queryRows(stagedPath, candidateChromeCookieHosts(parsedUrl.hostname));
90
+ const cryptoCache = {};
91
+ const cookies = [];
92
+ for (const row of rows) {
93
+ if (!chromeCookieMatchesUrl(row, parsedUrl)) {
94
+ continue;
95
+ }
96
+ cookies.push(rowToPuppeteerCookie(row, {
97
+ ...dependencies,
98
+ chromeUserDataDir,
99
+ }, cryptoCache));
100
+ }
101
+ return cookies;
102
+ }
103
+ finally {
104
+ removeDir(tempRoot, { recursive: true, force: true });
105
+ }
106
+ };
107
+ }
108
+ function candidateChromeCookieHosts(hostname) {
109
+ const normalizedHost = hostname.trim().toLowerCase().replace(/\.$/, "");
110
+ if (!normalizedHost) {
111
+ return [];
112
+ }
113
+ const labels = normalizedHost.split(".");
114
+ const hosts = new Set();
115
+ for (let index = 0; index < labels.length; index += 1) {
116
+ const candidate = labels.slice(index).join(".");
117
+ hosts.add(candidate);
118
+ hosts.add(`.${candidate}`);
119
+ }
120
+ return [...hosts];
121
+ }
122
+ function chromeCookieMatchesUrl(row, url) {
123
+ if (toBoolean(row.is_secure) && url.protocol !== "https:") {
124
+ return false;
125
+ }
126
+ if (!hostMatchesCookieDomain(url.hostname, row.host_key)) {
127
+ return false;
128
+ }
129
+ return pathMatchesCookiePath(url.pathname || "/", row.path || "/");
130
+ }
131
+ function parseCookieUrl(url) {
132
+ let parsedUrl;
133
+ try {
134
+ parsedUrl = new URL(url);
135
+ }
136
+ catch {
137
+ throw new Error("Could not parse URI, format should be http://www.example.com/path/");
138
+ }
139
+ if (parsedUrl.protocol !== "http:" && parsedUrl.protocol !== "https:") {
140
+ throw new Error("Could not parse URI, format should be http://www.example.com/path/");
141
+ }
142
+ return parsedUrl;
143
+ }
144
+ function rowToPuppeteerCookie(row, dependencies, cryptoCache) {
145
+ const resolvedValue = resolveChromeCookieValue(row, dependencies, cryptoCache);
146
+ const cookie = {
147
+ name: row.name,
148
+ value: resolvedValue,
149
+ domain: row.host_key,
150
+ path: row.path,
151
+ expires: row.expires_utc,
152
+ };
153
+ if (toBoolean(row.is_secure)) {
154
+ cookie.Secure = true;
155
+ }
156
+ if (toBoolean(row.is_httponly)) {
157
+ cookie.HttpOnly = true;
158
+ }
159
+ const sameSite = databaseSameSiteToChromeSameSite(row.samesite);
160
+ if (sameSite) {
161
+ cookie.sameSite = sameSite;
162
+ }
163
+ return cookie;
164
+ }
165
+ function resolveChromeCookieValue(row, dependencies, cryptoCache) {
166
+ const encryptedValue = toBuffer(row.encrypted_value);
167
+ if (encryptedValue.length === 0) {
168
+ return row.value;
169
+ }
170
+ const decrypted = decryptChromeCookieValue(encryptedValue, row.host_key, dependencies, cryptoCache);
171
+ return stripEncryptedCookieDomainHash(decrypted, row.host_key).toString("utf8");
172
+ }
173
+ function decryptChromeCookieValue(encryptedValue, hostKey, dependencies, cryptoCache) {
174
+ const platform = dependencies.platform ?? process.platform;
175
+ if (platform === "darwin") {
176
+ return decryptPosixCookieValue(encryptedValue, getMacEncryptionKey(dependencies, cryptoCache));
177
+ }
178
+ if (platform === "linux") {
179
+ return decryptLinuxCookieValue(encryptedValue, dependencies, cryptoCache);
180
+ }
181
+ if (platform === "win32") {
182
+ return decryptWindowsCookieValue(encryptedValue, hostKey, dependencies, cryptoCache);
183
+ }
184
+ throw new Error(exports.CHROME_COOKIE_SUPPORT_MISSING_ERROR);
185
+ }
186
+ function decryptPosixCookieValue(encryptedValue, key) {
187
+ const version = encryptedValue.subarray(0, 3).toString("utf8");
188
+ if (version !== "v10" && version !== "v11") {
189
+ throw new Error(exports.CHROME_COOKIE_SUPPORT_MISSING_ERROR);
190
+ }
191
+ const decipher = (0, node_crypto_1.createDecipheriv)("aes-128-cbc", key, POSIX_IV);
192
+ return Buffer.concat([
193
+ decipher.update(encryptedValue.subarray(3)),
194
+ decipher.final(),
195
+ ]);
196
+ }
197
+ function decryptLinuxCookieValue(encryptedValue, dependencies, cryptoCache) {
198
+ const version = encryptedValue.subarray(0, 3).toString("utf8");
199
+ if (version === "v10") {
200
+ return decryptPosixCookieValue(encryptedValue, derivePosixKey(LINUX_V10_PASSWORD, 1));
201
+ }
202
+ if (version === "v11") {
203
+ const key = getLinuxV11EncryptionKey(dependencies, cryptoCache);
204
+ if (key) {
205
+ return decryptPosixCookieValue(encryptedValue, key);
206
+ }
207
+ return decryptPosixCookieValue(encryptedValue, derivePosixKey("", 1));
208
+ }
209
+ throw new Error(exports.CHROME_COOKIE_SUPPORT_MISSING_ERROR);
210
+ }
211
+ function decryptWindowsCookieValue(encryptedValue, _hostKey, dependencies, cryptoCache) {
212
+ const version = encryptedValue.subarray(0, 3).toString("utf8");
213
+ if (version === "v20") {
214
+ throw new Error(exports.CHROME_COOKIE_APP_BOUND_UNSUPPORTED_ERROR);
215
+ }
216
+ if (version !== "v10") {
217
+ return decryptWindowsDpapi(encryptedValue, dependencies);
218
+ }
219
+ const key = getWindowsEncryptionKey(dependencies, cryptoCache);
220
+ const nonce = encryptedValue.subarray(3, 15);
221
+ const ciphertext = encryptedValue.subarray(15, encryptedValue.length - 16);
222
+ const authTag = encryptedValue.subarray(encryptedValue.length - 16);
223
+ const decipher = (0, node_crypto_1.createDecipheriv)("aes-256-gcm", key, nonce);
224
+ decipher.setAuthTag(authTag);
225
+ return Buffer.concat([decipher.update(ciphertext), decipher.final()]);
226
+ }
227
+ function getMacEncryptionKey(dependencies, cryptoCache) {
228
+ if (cryptoCache.macKey) {
229
+ return cryptoCache.macKey;
230
+ }
231
+ const password = runCommand(dependencies, "security", [
232
+ "find-generic-password",
233
+ "-w",
234
+ "-s",
235
+ MAC_SAFE_STORAGE_SERVICE,
236
+ "-a",
237
+ MAC_SAFE_STORAGE_ACCOUNT,
238
+ ]).trimEnd();
239
+ if (!password) {
240
+ throw new Error(exports.CHROME_COOKIE_SUPPORT_MISSING_ERROR);
241
+ }
242
+ cryptoCache.macKey = derivePosixKey(password, 1003);
243
+ return cryptoCache.macKey;
244
+ }
245
+ function getLinuxV11EncryptionKey(dependencies, cryptoCache) {
246
+ if (cryptoCache.linuxV11Key !== undefined) {
247
+ return cryptoCache.linuxV11Key;
248
+ }
249
+ try {
250
+ const password = runCommand(dependencies, "secret-tool", [
251
+ "lookup",
252
+ "xdg:schema",
253
+ "chrome_libsecret_os_crypt_password",
254
+ ]).trimEnd();
255
+ cryptoCache.linuxV11Key =
256
+ password.length > 0 ? derivePosixKey(password, 1) : null;
257
+ }
258
+ catch {
259
+ cryptoCache.linuxV11Key = null;
260
+ }
261
+ return cryptoCache.linuxV11Key;
262
+ }
263
+ function getWindowsEncryptionKey(dependencies, cryptoCache) {
264
+ if (cryptoCache.windowsKey) {
265
+ return cryptoCache.windowsKey;
266
+ }
267
+ const chromeUserDataDir = dependencies.chromeUserDataDir ?? (0, chrome_profiles_js_1.defaultChromeUserDataDir)();
268
+ const readFile = dependencies.readFile ?? ((targetPath) => node_fs_1.default.readFileSync(targetPath, "utf8"));
269
+ const localStatePath = node_path_1.default.join(chromeUserDataDir, "Local State");
270
+ const localState = JSON.parse(readFile(localStatePath));
271
+ const encodedKey = localState.os_crypt?.encrypted_key;
272
+ if (!encodedKey) {
273
+ throw new Error(exports.CHROME_COOKIE_SUPPORT_MISSING_ERROR);
274
+ }
275
+ const encryptedKey = Buffer.from(encodedKey, "base64");
276
+ if (encryptedKey.subarray(0, WINDOWS_APP_BOUND_KEY_PREFIX.length).equals(WINDOWS_APP_BOUND_KEY_PREFIX)) {
277
+ throw new Error(exports.CHROME_COOKIE_APP_BOUND_UNSUPPORTED_ERROR);
278
+ }
279
+ if (!encryptedKey.subarray(0, WINDOWS_DPAPI_KEY_PREFIX.length).equals(WINDOWS_DPAPI_KEY_PREFIX)) {
280
+ throw new Error(exports.CHROME_COOKIE_SUPPORT_MISSING_ERROR);
281
+ }
282
+ cryptoCache.windowsKey = decryptWindowsDpapi(encryptedKey.subarray(WINDOWS_DPAPI_KEY_PREFIX.length), dependencies);
283
+ return cryptoCache.windowsKey;
284
+ }
285
+ function decryptWindowsDpapi(encryptedValue, dependencies) {
286
+ const script = [
287
+ "$inputBase64 = $args[0]",
288
+ "$bytes = [Convert]::FromBase64String($inputBase64)",
289
+ "$plain = [System.Security.Cryptography.ProtectedData]::Unprotect(",
290
+ " $bytes,",
291
+ " $null,",
292
+ " [System.Security.Cryptography.DataProtectionScope]::CurrentUser",
293
+ ")",
294
+ "[Console]::Out.Write([Convert]::ToBase64String($plain))",
295
+ ].join("; ");
296
+ const base64Value = encryptedValue.toString("base64");
297
+ const binaries = ["powershell.exe", "powershell", "pwsh"];
298
+ for (const binary of binaries) {
299
+ try {
300
+ const output = runCommand(dependencies, binary, [
301
+ "-NoProfile",
302
+ "-NonInteractive",
303
+ "-Command",
304
+ script,
305
+ base64Value,
306
+ ]).trim();
307
+ return Buffer.from(output, "base64");
308
+ }
309
+ catch {
310
+ continue;
311
+ }
312
+ }
313
+ throw new Error(exports.CHROME_COOKIE_SUPPORT_MISSING_ERROR);
314
+ }
315
+ function derivePosixKey(password, iterations) {
316
+ return (0, node_crypto_1.pbkdf2Sync)(password, POSIX_SALT, iterations, 16, "sha1");
317
+ }
318
+ function stripEncryptedCookieDomainHash(decryptedValue, hostKey) {
319
+ const domainHash = (0, node_crypto_1.createHash)("sha256").update(hostKey).digest();
320
+ if (decryptedValue.length < domainHash.length) {
321
+ return decryptedValue;
322
+ }
323
+ if (decryptedValue.subarray(0, domainHash.length).equals(domainHash)) {
324
+ return decryptedValue.subarray(domainHash.length);
325
+ }
326
+ return decryptedValue;
327
+ }
328
+ function hostMatchesCookieDomain(hostname, cookieDomain) {
329
+ const normalizedHost = hostname.toLowerCase().replace(/\.$/, "");
330
+ const normalizedDomain = cookieDomain.toLowerCase().replace(/^\./, "").replace(/\.$/, "");
331
+ const isDomainCookie = cookieDomain.startsWith(".");
332
+ if (!isDomainCookie) {
333
+ return normalizedHost === normalizedDomain;
334
+ }
335
+ return (normalizedHost === normalizedDomain ||
336
+ normalizedHost.endsWith(`.${normalizedDomain}`));
337
+ }
338
+ function pathMatchesCookiePath(requestPath, cookiePath) {
339
+ const normalizedRequestPath = requestPath || "/";
340
+ const normalizedCookiePath = cookiePath || "/";
341
+ if (normalizedRequestPath === normalizedCookiePath) {
342
+ return true;
343
+ }
344
+ if (!normalizedRequestPath.startsWith(normalizedCookiePath)) {
345
+ return false;
346
+ }
347
+ if (normalizedCookiePath.endsWith("/")) {
348
+ return true;
349
+ }
350
+ return normalizedRequestPath.charAt(normalizedCookiePath.length) === "/";
351
+ }
352
+ function databaseSameSiteToChromeSameSite(sameSite) {
353
+ const normalizedSameSite = typeof sameSite === "bigint" ? Number(sameSite) : sameSite;
354
+ switch (normalizedSameSite) {
355
+ case 0:
356
+ return "no_restriction";
357
+ case 1:
358
+ return "lax";
359
+ case 2:
360
+ return "strict";
361
+ default:
362
+ return undefined;
363
+ }
364
+ }
365
+ function queryChromeCookieRows(databasePath, hostKeys) {
366
+ if (hostKeys.length === 0) {
367
+ return [];
368
+ }
369
+ const DatabaseSync = loadDatabaseConstructor();
370
+ const database = new DatabaseSync(databasePath, {
371
+ readOnly: true,
372
+ });
373
+ try {
374
+ const placeholders = hostKeys.map(() => "?").join(", ");
375
+ const statement = database.prepare([
376
+ "SELECT",
377
+ "host_key,",
378
+ "path,",
379
+ "is_secure,",
380
+ "expires_utc,",
381
+ "name,",
382
+ "value,",
383
+ "encrypted_value,",
384
+ "creation_utc,",
385
+ "is_httponly,",
386
+ "samesite",
387
+ "FROM cookies",
388
+ `WHERE host_key IN (${placeholders})`,
389
+ "ORDER BY LENGTH(path) DESC, creation_utc ASC",
390
+ ].join(" "));
391
+ statement.setReadBigInts?.(true);
392
+ return statement.all(...hostKeys);
393
+ }
394
+ finally {
395
+ database.close();
396
+ }
397
+ }
398
+ function copyOptionalSidecar(sourcePath, targetPath, pathExists, copyFile) {
399
+ if (!pathExists(sourcePath)) {
400
+ return;
401
+ }
402
+ copyFile(sourcePath, targetPath);
403
+ }
404
+ function runCommand(dependencies, command, args) {
405
+ const invoke = dependencies.runCommand ??
406
+ ((binary, binaryArgs) => (0, node_child_process_1.execFileSync)(binary, binaryArgs, {
407
+ encoding: "utf8",
408
+ windowsHide: true,
409
+ }));
410
+ return invoke(command, args);
411
+ }
412
+ function toBoolean(value) {
413
+ if (typeof value === "bigint") {
414
+ return value !== 0n;
415
+ }
416
+ return value !== 0;
417
+ }
418
+ function toBuffer(value) {
419
+ return value ? Buffer.from(value) : Buffer.alloc(0);
420
+ }
@@ -0,0 +1,155 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.normalizeCookieHosts = normalizeCookieHosts;
7
+ exports.siteHostToUrl = siteHostToUrl;
8
+ exports.listChromeProfileCookieUrls = listChromeProfileCookieUrls;
9
+ exports.resolveChromeCookiesDatabasePath = resolveChromeCookiesDatabasePath;
10
+ exports.listChromeProfileSites = listChromeProfileSites;
11
+ exports.formatChromeProfileSitesReport = formatChromeProfileSitesReport;
12
+ const node_fs_1 = __importDefault(require("node:fs"));
13
+ const node_net_1 = __importDefault(require("node:net"));
14
+ const node_os_1 = __importDefault(require("node:os"));
15
+ const node_path_1 = __importDefault(require("node:path"));
16
+ const chrome_profiles_js_1 = require("./chrome-profiles.js");
17
+ function loadDatabaseConstructor() {
18
+ const sqlite = require("node:sqlite");
19
+ return sqlite.DatabaseSync;
20
+ }
21
+ function normalizeCookieHosts(hosts) {
22
+ const seen = new Set();
23
+ const normalized = [];
24
+ for (const host of hosts) {
25
+ const candidate = host.trim().toLowerCase().replace(/^\.+/, "");
26
+ if (!candidate || seen.has(candidate)) {
27
+ continue;
28
+ }
29
+ seen.add(candidate);
30
+ normalized.push(candidate);
31
+ }
32
+ return normalized.sort((left, right) => left.localeCompare(right));
33
+ }
34
+ function siteHostToUrl(host) {
35
+ const ipCandidate = host.startsWith("[") && host.endsWith("]") ? host.slice(1, -1) : host;
36
+ const protocol = host === "localhost" || node_net_1.default.isIP(ipCandidate) !== 0 ? "http" : "https";
37
+ return `${protocol}://${host}`;
38
+ }
39
+ async function listChromeProfileCookieUrls(options, dependencies = {}) {
40
+ const chromeUserDataDir = dependencies.chromeUserDataDir ?? (0, chrome_profiles_js_1.defaultChromeUserDataDir)();
41
+ const readCookieHosts = dependencies.readCookieHosts ?? readCookieHostsForChromeProfile;
42
+ const hosts = await readCookieHosts({
43
+ chromeUserDataDir,
44
+ profileDirectory: options.profileDirectory,
45
+ }, {
46
+ ...dependencies,
47
+ chromeUserDataDir,
48
+ });
49
+ return hosts.map(siteHostToUrl);
50
+ }
51
+ function resolveChromeCookiesDatabasePath(options, dependencies = {}) {
52
+ const pathExists = dependencies.pathExists ?? node_fs_1.default.existsSync;
53
+ const profileDir = node_path_1.default.join(options.chromeUserDataDir, options.profileDirectory);
54
+ const candidates = [
55
+ node_path_1.default.join(profileDir, "Network", "Cookies"),
56
+ node_path_1.default.join(profileDir, "Cookies"),
57
+ ];
58
+ return candidates.find((candidate) => pathExists(candidate));
59
+ }
60
+ async function readCookieHostsForChromeProfile(options, dependencies = {}) {
61
+ const pathExists = dependencies.pathExists ?? node_fs_1.default.existsSync;
62
+ const makeTempDir = dependencies.makeTempDir ?? node_fs_1.default.mkdtempSync;
63
+ const copyFile = dependencies.copyFile ?? node_fs_1.default.copyFileSync;
64
+ const removeDir = dependencies.removeDir ?? node_fs_1.default.rmSync;
65
+ const queryHosts = dependencies.queryHosts ?? queryDistinctCookieHosts;
66
+ const sourcePath = resolveChromeCookiesDatabasePath(options, {
67
+ pathExists,
68
+ });
69
+ if (!sourcePath) {
70
+ return [];
71
+ }
72
+ const tempRoot = makeTempDir(node_path_1.default.join(node_os_1.default.tmpdir(), "cloak-cookie-db-"));
73
+ const stagedPath = node_path_1.default.join(tempRoot, node_path_1.default.basename(sourcePath));
74
+ try {
75
+ copyFile(sourcePath, stagedPath);
76
+ copyOptionalSidecar(`${sourcePath}-wal`, `${stagedPath}-wal`, pathExists, copyFile);
77
+ copyOptionalSidecar(`${sourcePath}-shm`, `${stagedPath}-shm`, pathExists, copyFile);
78
+ const hosts = await queryHosts(stagedPath);
79
+ return normalizeCookieHosts(hosts);
80
+ }
81
+ catch {
82
+ return [];
83
+ }
84
+ finally {
85
+ removeDir(tempRoot, { recursive: true, force: true });
86
+ }
87
+ }
88
+ async function listChromeProfileSites(dependencies = {}) {
89
+ const chromeUserDataDir = dependencies.chromeUserDataDir ?? (0, chrome_profiles_js_1.defaultChromeUserDataDir)();
90
+ const listProfiles = dependencies.listProfiles ?? chrome_profiles_js_1.listChromeProfiles;
91
+ const readCookieHosts = dependencies.readCookieHosts ?? readCookieHostsForChromeProfile;
92
+ const profiles = listProfiles({ chromeUserDataDir });
93
+ return Promise.all(profiles.map(async (profile) => {
94
+ const hosts = await readCookieHosts({
95
+ chromeUserDataDir,
96
+ profileDirectory: profile.directory,
97
+ }, {
98
+ chromeUserDataDir,
99
+ });
100
+ return {
101
+ ...profile,
102
+ sites: hosts.map((host) => ({
103
+ host,
104
+ url: siteHostToUrl(host),
105
+ })),
106
+ };
107
+ }));
108
+ }
109
+ function formatChromeProfileSitesReport(profiles) {
110
+ if (profiles.length === 0) {
111
+ return "No Chrome profiles found.\n";
112
+ }
113
+ const lines = [];
114
+ for (const profile of profiles) {
115
+ const label = profile.accountName ?? profile.name;
116
+ const countLabel = `${profile.sites.length} ${profile.sites.length === 1 ? "site" : "sites"}`;
117
+ lines.push(`${profile.directory}: ${label} (${countLabel})`);
118
+ if (profile.sites.length === 0) {
119
+ lines.push(" (no cookie-bearing sites found)");
120
+ }
121
+ else {
122
+ for (const site of profile.sites) {
123
+ lines.push(` ${site.host}`);
124
+ }
125
+ }
126
+ lines.push("");
127
+ }
128
+ return `${lines.join("\n").trimEnd()}\n`;
129
+ }
130
+ function copyOptionalSidecar(sourcePath, targetPath, pathExists, copyFile) {
131
+ if (!pathExists(sourcePath)) {
132
+ return;
133
+ }
134
+ copyFile(sourcePath, targetPath);
135
+ }
136
+ async function queryDistinctCookieHosts(databasePath) {
137
+ const DatabaseSync = loadDatabaseConstructor();
138
+ const database = new DatabaseSync(databasePath);
139
+ try {
140
+ const rows = database
141
+ .prepare([
142
+ "SELECT DISTINCT host_key",
143
+ "FROM cookies",
144
+ "WHERE host_key IS NOT NULL AND host_key != ''",
145
+ "ORDER BY host_key COLLATE NOCASE",
146
+ ].join(" "))
147
+ .all();
148
+ return rows
149
+ .map((row) => row.host_key ?? "")
150
+ .filter((host) => host.length > 0);
151
+ }
152
+ finally {
153
+ database.close();
154
+ }
155
+ }
@@ -0,0 +1,71 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.defaultChromeUserDataDir = defaultChromeUserDataDir;
7
+ exports.listChromeProfiles = listChromeProfiles;
8
+ exports.hasChromeUserDataDir = hasChromeUserDataDir;
9
+ const node_path_1 = __importDefault(require("node:path"));
10
+ const node_fs_1 = __importDefault(require("node:fs"));
11
+ const node_os_1 = __importDefault(require("node:os"));
12
+ function defaultChromeUserDataDir() {
13
+ const platform = node_os_1.default.platform();
14
+ const home = node_os_1.default.homedir();
15
+ if (platform === "darwin") {
16
+ return node_path_1.default.join(home, "Library", "Application Support", "Google", "Chrome");
17
+ }
18
+ if (platform === "win32") {
19
+ return node_path_1.default.join(home, "AppData", "Local", "Google", "Chrome", "User Data");
20
+ }
21
+ // Linux
22
+ return node_path_1.default.join(home, ".config", "google-chrome");
23
+ }
24
+ function readProfileInfo(prefsPath, readFile) {
25
+ try {
26
+ const raw = readFile(prefsPath);
27
+ const prefs = JSON.parse(raw);
28
+ const name = prefs?.profile?.name;
29
+ if (name === undefined)
30
+ return undefined;
31
+ const accountName = prefs?.account_info?.[0]?.full_name || undefined;
32
+ return { name, accountName };
33
+ }
34
+ catch {
35
+ return undefined;
36
+ }
37
+ }
38
+ function listChromeProfiles(dependencies = {}) {
39
+ const userDataDir = dependencies.chromeUserDataDir ?? defaultChromeUserDataDir();
40
+ const readdir = dependencies.readdir ?? ((dir) => node_fs_1.default.readdirSync(dir));
41
+ const readFile = dependencies.readFile ?? ((p) => node_fs_1.default.readFileSync(p, "utf8"));
42
+ let entries;
43
+ try {
44
+ entries = readdir(userDataDir);
45
+ }
46
+ catch {
47
+ return [];
48
+ }
49
+ const profiles = [];
50
+ for (const entry of entries) {
51
+ if (entry !== "Default" && !entry.startsWith("Profile ")) {
52
+ continue;
53
+ }
54
+ const prefsPath = node_path_1.default.join(userDataDir, entry, "Preferences");
55
+ const info = readProfileInfo(prefsPath, readFile);
56
+ if (info !== undefined) {
57
+ profiles.push({
58
+ directory: entry,
59
+ name: info.name,
60
+ ...(info.accountName ? { accountName: info.accountName } : {}),
61
+ });
62
+ }
63
+ }
64
+ profiles.sort((a, b) => a.directory.localeCompare(b.directory));
65
+ return profiles;
66
+ }
67
+ function hasChromeUserDataDir(dependencies = {}) {
68
+ const userDataDir = dependencies.chromeUserDataDir ?? defaultChromeUserDataDir();
69
+ const pathExists = dependencies.pathExists ?? node_fs_1.default.existsSync;
70
+ return pathExists(userDataDir);
71
+ }