chrome-tools 1.0.2 → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -116,6 +116,9 @@ npx chrome-tools cookies
116
116
  # Filter cookies for a specific domain name
117
117
  npx chrome-tools cookies --domain "google.com" --limit 30
118
118
 
119
+ # Filter cookies for a specific URL (matches domain, path, and secure status)
120
+ npx chrome-tools cookies --url "https://github.com/settings/profile"
121
+
119
122
  # Skip decryption of encrypted values (fast metadata lookup)
120
123
  npx chrome-tools cookies --no-decrypt
121
124
  ```
@@ -224,7 +227,7 @@ All core functions accept an options object with the following fields:
224
227
  | `getBookmarks({chromeDir, profile, folder, search})` | `chrome-tools/bookmarks` | `Array<Bookmark>` |
225
228
  | `getHistory({chromeDir, profile, search, limit})` | `chrome-tools/history` | `Array<HistoryEntry>` |
226
229
  | `getSearchTerms({chromeDir, profile, limit})` | `chrome-tools/history` | `Array<SearchTerm>` |
227
- | `getCookies({chromeDir, profile, domain, decrypt, limit})` | `chrome-tools/cookies` | `Array<Cookie>` |
230
+ | `getCookies({chromeDir, profile, domain, url, decrypt, limit})` | `chrome-tools/cookies` | `Array<Cookie>` |
228
231
  | `getAutofillEntries({chromeDir, profile, search, limit})` | `chrome-tools/autofill` | `Array<AutofillEntry>` |
229
232
  | `getCreditCards({chromeDir, profile})` | `chrome-tools/autofill` | `Array<CreditCard>` |
230
233
  | `getAddresses({chromeDir, profile})` | `chrome-tools/autofill` | `Array<Address>` |
package/bin/cli.js CHANGED
@@ -325,6 +325,7 @@ addCommonOptions(addLimitOption(searchTermsCmd)).action((opts, cmd) => {
325
325
  const cookiesCmd = program.command("cookies").description("List cookies (encrypted values decrypted)");
326
326
  addCommonOptions(addLimitOption(cookiesCmd))
327
327
  .option("--domain <domain>", "Filter by domain")
328
+ .option("--url <url>", "Filter by URL with path")
328
329
  .option("--no-decrypt", "Skip decryption of encrypted values")
329
330
  .action((opts, cmd) => {
330
331
  const mergedOpts = cmd.optsWithGlobals();
@@ -332,6 +333,7 @@ addCommonOptions(addLimitOption(cookiesCmd))
332
333
  chromeDir: mergedOpts.chromeDir,
333
334
  profile: mergedOpts.profile,
334
335
  domain: mergedOpts.domain,
336
+ url: mergedOpts.url,
335
337
  decrypt: mergedOpts.decrypt !== false,
336
338
  limit: parseInt(mergedOpts.limit, 10) || 100,
337
339
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chrome-tools",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
4
4
  "description": "CLI & Node.js library to extract Chrome browser data (bookmarks, history, cookies, autofill, extensions, and more) on macOS",
5
5
  "type": "module",
6
6
  "main": "./src/index.js",
@@ -27,7 +27,7 @@
27
27
  ],
28
28
  "scripts": {
29
29
  "start": "node bin/cli.js",
30
- "test": "node bin/cli.js --help",
30
+ "test": "node --test && node bin/cli.js --help",
31
31
  "release": "npm version patch --no-git-tag-version && npm publish"
32
32
  },
33
33
  "keywords": [
@@ -47,7 +47,7 @@
47
47
  "better-sqlite3": "^12.10.0",
48
48
  "chalk": "^5.6.2",
49
49
  "cli-table3": "^0.6.5",
50
- "commander": "^13.0.0"
50
+ "commander": "^14.0.3"
51
51
  },
52
52
  "engines": {
53
53
  "node": ">=18.0.0"
@@ -19,6 +19,67 @@ function decodeCookieValue(encryptedValue, derivedKey, hostKey) {
19
19
  return valueBytes.toString("utf-8");
20
20
  }
21
21
 
22
+ function parseURL(urlStr) {
23
+ if (!urlStr) return null;
24
+ try {
25
+ return new URL(urlStr);
26
+ } catch (e) {
27
+ try {
28
+ // If it doesn't have a protocol, prepend https://
29
+ if (!/^[a-zA-Z][a-zA-Z0-9+.-]*:/.test(urlStr)) {
30
+ return new URL("https://" + urlStr);
31
+ }
32
+ } catch (err) {
33
+ // Ignore
34
+ }
35
+ }
36
+ return null;
37
+ }
38
+
39
+ function getPossibleHostKeys(hostname) {
40
+ const keys = new Set();
41
+ keys.add(hostname);
42
+ keys.add("." + hostname);
43
+
44
+ const parts = hostname.split(".");
45
+ for (let i = 1; i < parts.length; i++) {
46
+ const suffix = parts.slice(i).join(".");
47
+ keys.add(suffix);
48
+ keys.add("." + suffix);
49
+ }
50
+ return Array.from(keys);
51
+ }
52
+
53
+ function domainMatch(hostname, hostKey) {
54
+ if (hostname === hostKey) {
55
+ return true;
56
+ }
57
+ if (hostKey.startsWith(".")) {
58
+ if (hostname === hostKey.slice(1) || hostname.endsWith(hostKey)) {
59
+ return true;
60
+ }
61
+ }
62
+ return false;
63
+ }
64
+
65
+ function pathMatch(urlPath, cookiePath) {
66
+ const uPath = urlPath || "/";
67
+ const cPath = cookiePath || "/";
68
+
69
+ if (cPath === "/") {
70
+ return true;
71
+ }
72
+ if (uPath === cPath) {
73
+ return true;
74
+ }
75
+ if (uPath.startsWith(cPath)) {
76
+ if (cPath.endsWith("/") || uPath[cPath.length] === "/") {
77
+ return true;
78
+ }
79
+ }
80
+ return false;
81
+ }
82
+
22
83
  /**
23
84
  * Extracts cookies from Chrome's Cookies database.
24
85
  * Encrypted values are decrypted using the same mechanism as passwords.
@@ -27,14 +88,16 @@ function decodeCookieValue(encryptedValue, derivedKey, hostKey) {
27
88
  * @param {string} [options.chromeDir] - Chrome User Data directory.
28
89
  * @param {string} [options.profile] - Specific profile to target.
29
90
  * @param {string} [options.domain] - Filter by domain (case-insensitive, partial match).
91
+ * @param {string} [options.url] - Filter by URL (domain, path, and secure matching).
30
92
  * @param {boolean} [options.decrypt=true] - Whether to decrypt encrypted values.
31
93
  * @param {number} [options.limit=100] - Maximum number of results.
32
94
  * @returns {Array<Object>}
33
95
  */
34
- export function getCookies({ chromeDir, profile, domain, decrypt = true, limit = 100 } = {}) {
96
+ export function getCookies({ chromeDir, profile, domain, url, decrypt = true, limit = 100 } = {}) {
35
97
  const profiles = resolveTargetProfiles({ chromeDir, profile });
36
98
  const derivedKey = decrypt ? deriveEncryptionKey() : null;
37
99
  const results = [];
100
+ const parsedUrl = parseURL(url);
38
101
 
39
102
  for (const p of profiles) {
40
103
  const dbPath = join(p.path, "Cookies");
@@ -51,19 +114,30 @@ export function getCookies({ chromeDir, profile, domain, decrypt = true, limit =
51
114
  if (domain) {
52
115
  conditions.push(`host_key LIKE ?`);
53
116
  params.push(`%${domain}%`);
117
+ } else if (parsedUrl) {
118
+ const hostKeys = getPossibleHostKeys(parsedUrl.hostname);
119
+ const placeholders = hostKeys.map(() => "?").join(", ");
120
+ conditions.push(`host_key IN (${placeholders})`);
121
+ params.push(...hostKeys);
54
122
  }
55
123
 
56
124
  if (conditions.length > 0) {
57
125
  sql += ` WHERE ${conditions.join(" AND ")}`;
58
126
  }
59
127
  sql += ` ORDER BY host_key, name`;
60
- if (limit) {
128
+ if (limit && !url) {
61
129
  sql += ` LIMIT ${parseInt(limit, 10)}`;
62
130
  }
63
131
 
64
132
  const rows = queryDatabase(dbPath, sql, params);
65
133
 
66
134
  for (const row of rows) {
135
+ if (parsedUrl) {
136
+ if (!domainMatch(parsedUrl.hostname, row.host_key || "")) continue;
137
+ if (!pathMatch(parsedUrl.pathname || "/", row.path || "/")) continue;
138
+ if (row.is_secure && parsedUrl.protocol !== "https:") continue;
139
+ }
140
+
67
141
  let cookieValue = row.value || "";
68
142
  if (!cookieValue && row.encrypted_value && derivedKey) {
69
143
  cookieValue = decodeCookieValue(row.encrypted_value, derivedKey, row.host_key);
@@ -88,5 +162,17 @@ export function getCookies({ chromeDir, profile, domain, decrypt = true, limit =
88
162
  }
89
163
  }
90
164
 
165
+ if (limit && url) {
166
+ return results.slice(0, parseInt(limit, 10));
167
+ }
91
168
  return results;
92
169
  }
170
+
171
+ export const _test = {
172
+ parseURL,
173
+ getPossibleHostKeys,
174
+ domainMatch,
175
+ pathMatch,
176
+ };
177
+
178
+