chrome-tools 1.0.5 → 1.0.6
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/bin/cli.d.ts +8 -0
- package/dist/bin/cli.js +543 -0
- package/dist/src/core/chrome.d.ts +77 -0
- package/dist/src/core/chrome.js +159 -0
- package/dist/src/core/crypto.d.ts +26 -0
- package/dist/src/core/crypto.js +69 -0
- package/dist/src/core/db.d.ts +19 -0
- package/dist/src/core/db.js +78 -0
- package/dist/src/core/keychain.d.ts +20 -0
- package/dist/src/core/keychain.js +53 -0
- package/dist/src/index.d.ts +25 -0
- package/{src → dist/src}/index.js +4 -26
- package/dist/src/modules/autofill.d.ts +72 -0
- package/dist/src/modules/autofill.js +178 -0
- package/dist/src/modules/bookmarks.d.ts +23 -0
- package/dist/src/modules/bookmarks.js +92 -0
- package/dist/src/modules/cookies.d.ts +40 -0
- package/dist/src/modules/cookies.js +159 -0
- package/dist/src/modules/downloads.d.ts +28 -0
- package/dist/src/modules/downloads.js +78 -0
- package/dist/src/modules/extensions.d.ts +25 -0
- package/dist/src/modules/extensions.js +70 -0
- package/dist/src/modules/history.d.ts +37 -0
- package/dist/src/modules/history.js +84 -0
- package/dist/src/modules/passwords.d.ts +20 -0
- package/dist/src/modules/passwords.js +50 -0
- package/dist/src/modules/search-engines.d.ts +22 -0
- package/dist/src/modules/search-engines.js +40 -0
- package/dist/src/modules/top-sites.d.ts +18 -0
- package/dist/src/modules/top-sites.js +35 -0
- package/package.json +24 -18
- package/bin/cli.js +0 -629
- package/src/core/chrome.js +0 -178
- package/src/core/crypto.js +0 -75
- package/src/core/db.js +0 -69
- package/src/core/keychain.js +0 -68
- package/src/modules/autofill.js +0 -213
- package/src/modules/bookmarks.js +0 -109
- package/src/modules/cookies.js +0 -178
- package/src/modules/downloads.js +0 -91
- package/src/modules/extensions.js +0 -80
- package/src/modules/history.js +0 -102
- package/src/modules/passwords.js +0 -61
- package/src/modules/search-engines.js +0 -48
- package/src/modules/top-sites.js +0 -43
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
/** Known database/data files that indicate a valid Chrome profile directory. */
|
|
5
|
+
const PROFILE_INDICATORS = [
|
|
6
|
+
"Login Data",
|
|
7
|
+
"History",
|
|
8
|
+
"Bookmarks",
|
|
9
|
+
"Cookies",
|
|
10
|
+
"Web Data",
|
|
11
|
+
"Preferences",
|
|
12
|
+
];
|
|
13
|
+
/**
|
|
14
|
+
* Returns the default Chrome User Data directory for macOS.
|
|
15
|
+
* @returns The absolute path to Chrome's User Data.
|
|
16
|
+
*/
|
|
17
|
+
export function getDefaultChromeDir() {
|
|
18
|
+
return join(homedir(), "Library", "Application Support", "Google", "Chrome");
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Scans the Chrome User Data directory for profile subdirectories.
|
|
22
|
+
* A directory is considered a profile if it contains at least one known data file.
|
|
23
|
+
*
|
|
24
|
+
* @param chromeDir - Chrome User Data path. Defaults to standard macOS location.
|
|
25
|
+
* @returns Discovered profiles with name and absolute path.
|
|
26
|
+
*/
|
|
27
|
+
export function discoverProfiles(chromeDir) {
|
|
28
|
+
const dir = chromeDir || getDefaultChromeDir();
|
|
29
|
+
if (!dir || !existsSync(dir)) {
|
|
30
|
+
return [];
|
|
31
|
+
}
|
|
32
|
+
try {
|
|
33
|
+
const entries = readdirSync(dir, { withFileTypes: true });
|
|
34
|
+
const profiles = [];
|
|
35
|
+
for (const entry of entries) {
|
|
36
|
+
if (!entry.isDirectory())
|
|
37
|
+
continue;
|
|
38
|
+
const profilePath = join(dir, entry.name);
|
|
39
|
+
const hasIndicator = PROFILE_INDICATORS.some((file) => existsSync(join(profilePath, file)));
|
|
40
|
+
if (hasIndicator) {
|
|
41
|
+
profiles.push({ name: entry.name, path: profilePath });
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return profiles;
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
return [];
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Parses the Chrome "Local State" JSON file to extract profile metadata.
|
|
52
|
+
* Returns enriched profile info including display name, email, avatar, etc.
|
|
53
|
+
*
|
|
54
|
+
* @param chromeDir - Chrome User Data path.
|
|
55
|
+
* @returns Profile metadata objects.
|
|
56
|
+
*/
|
|
57
|
+
export function getProfileMetadata(chromeDir) {
|
|
58
|
+
const dir = chromeDir || getDefaultChromeDir();
|
|
59
|
+
const localStatePath = join(dir, "Local State");
|
|
60
|
+
if (!existsSync(localStatePath)) {
|
|
61
|
+
return [];
|
|
62
|
+
}
|
|
63
|
+
try {
|
|
64
|
+
const raw = readFileSync(localStatePath, "utf-8");
|
|
65
|
+
const localState = JSON.parse(raw);
|
|
66
|
+
const infoCache = localState?.profile?.info_cache;
|
|
67
|
+
if (!infoCache || typeof infoCache !== "object") {
|
|
68
|
+
return [];
|
|
69
|
+
}
|
|
70
|
+
return Object.entries(infoCache).map(([dirName, infoAny]) => {
|
|
71
|
+
const info = infoAny;
|
|
72
|
+
return {
|
|
73
|
+
directory: dirName,
|
|
74
|
+
name: info.name || dirName,
|
|
75
|
+
gaiaName: info.gaia_name || "",
|
|
76
|
+
userName: info.user_name || "",
|
|
77
|
+
email: info.user_name || "",
|
|
78
|
+
avatarIcon: info.avatar_icon || "",
|
|
79
|
+
isUsingDefaultName: info.is_using_default_name ?? true,
|
|
80
|
+
isEphemeral: info.is_ephemeral ?? false,
|
|
81
|
+
activeTime: info.active_time || 0,
|
|
82
|
+
};
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
return [];
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Combines filesystem profile discovery with Local State metadata
|
|
91
|
+
* to provide a comprehensive view of each profile's state.
|
|
92
|
+
*
|
|
93
|
+
* @param chromeDir - Chrome User Data path.
|
|
94
|
+
* @returns Enriched profile state objects.
|
|
95
|
+
*/
|
|
96
|
+
export function getProfileStates(chromeDir) {
|
|
97
|
+
const dir = chromeDir || getDefaultChromeDir();
|
|
98
|
+
const discovered = discoverProfiles(dir);
|
|
99
|
+
const metadata = getProfileMetadata(dir);
|
|
100
|
+
const metaMap = new Map();
|
|
101
|
+
for (const m of metadata) {
|
|
102
|
+
metaMap.set(m.directory, m);
|
|
103
|
+
}
|
|
104
|
+
return discovered.map((profile) => {
|
|
105
|
+
const meta = metaMap.get(profile.name);
|
|
106
|
+
const profilePath = profile.path;
|
|
107
|
+
// Check which data files exist
|
|
108
|
+
const dataFiles = {};
|
|
109
|
+
for (const file of PROFILE_INDICATORS) {
|
|
110
|
+
dataFiles[file.replace(/\s+/g, "_").toLowerCase()] = existsSync(join(profilePath, file));
|
|
111
|
+
}
|
|
112
|
+
// Additional files
|
|
113
|
+
dataFiles.top_sites = existsSync(join(profilePath, "Top Sites"));
|
|
114
|
+
dataFiles.extensions = existsSync(join(profilePath, "Extensions"));
|
|
115
|
+
return {
|
|
116
|
+
directory: profile.name,
|
|
117
|
+
path: profilePath,
|
|
118
|
+
displayName: meta?.name || profile.name,
|
|
119
|
+
gaiaName: meta?.gaiaName || "",
|
|
120
|
+
email: meta?.email || "",
|
|
121
|
+
avatarIcon: meta?.avatarIcon || "",
|
|
122
|
+
isEphemeral: meta?.isEphemeral ?? false,
|
|
123
|
+
activeTime: meta?.activeTime || 0,
|
|
124
|
+
dataFiles,
|
|
125
|
+
};
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Resolves a profile name to its directory path.
|
|
130
|
+
*
|
|
131
|
+
* @param chromeDir - Chrome User Data path.
|
|
132
|
+
* @param profileName - Profile directory name (e.g., "Default", "Profile 1").
|
|
133
|
+
* @returns Absolute path to profile directory, or null if not found.
|
|
134
|
+
*/
|
|
135
|
+
export function resolveProfilePath(chromeDir, profileName) {
|
|
136
|
+
const dir = chromeDir || getDefaultChromeDir();
|
|
137
|
+
const profilePath = join(dir, profileName);
|
|
138
|
+
return existsSync(profilePath) ? profilePath : null;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Helper to get the profiles to operate on based on options.
|
|
142
|
+
* If a specific profile is specified, returns only that one.
|
|
143
|
+
* Otherwise returns all discovered profiles.
|
|
144
|
+
*
|
|
145
|
+
* @param options - Resolution options.
|
|
146
|
+
* @returns Array of targeted profiles.
|
|
147
|
+
*/
|
|
148
|
+
export function resolveTargetProfiles({ profilePath, chromeDir, profile, } = {}) {
|
|
149
|
+
const dir = profilePath || chromeDir || getDefaultChromeDir();
|
|
150
|
+
const allProfiles = discoverProfiles(dir);
|
|
151
|
+
if (profile) {
|
|
152
|
+
const match = allProfiles.filter((p) => p.name === profile);
|
|
153
|
+
if (match.length === 0) {
|
|
154
|
+
throw new Error(`Profile "${profile}" not found in ${dir}`);
|
|
155
|
+
}
|
|
156
|
+
return match;
|
|
157
|
+
}
|
|
158
|
+
return allProfiles;
|
|
159
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Decrypts a password/value buffer encrypted by Chrome on macOS.
|
|
3
|
+
* Chrome v10+ on macOS prefixes the encrypted blob with "v10" or "v11",
|
|
4
|
+
* followed by AES-128-CBC ciphertext.
|
|
5
|
+
*
|
|
6
|
+
* @param encryptedBlob - The encrypted blob from the database.
|
|
7
|
+
* @param derivedKey - The derived 16-byte key.
|
|
8
|
+
* @returns The decrypted plaintext, or an error placeholder.
|
|
9
|
+
*/
|
|
10
|
+
export declare function decryptValue(encryptedBlob: Buffer | Uint8Array | null | undefined, derivedKey: Buffer): string;
|
|
11
|
+
/**
|
|
12
|
+
* Decrypts a Chrome encrypted blob and returns the raw plaintext bytes.
|
|
13
|
+
*
|
|
14
|
+
* @param encryptedBlob - The encrypted blob from the database.
|
|
15
|
+
* @param derivedKey - The derived 16-byte key.
|
|
16
|
+
* @returns The decrypted plaintext bytes, or an error placeholder.
|
|
17
|
+
*/
|
|
18
|
+
export declare function decryptValueBuffer(encryptedBlob: Buffer | Uint8Array | null | undefined, derivedKey: Buffer): Buffer | string;
|
|
19
|
+
/**
|
|
20
|
+
* Converts a Chrome/Webkit timestamp (microseconds since 1601-01-01)
|
|
21
|
+
* to a readable local Date string.
|
|
22
|
+
*
|
|
23
|
+
* @param chromeTimestamp - Timestamp in microseconds.
|
|
24
|
+
* @returns Human-readable date-time string, or "N/A".
|
|
25
|
+
*/
|
|
26
|
+
export declare function chromeTimeToDate(chromeTimestamp: bigint | number | null | undefined): string;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { createDecipheriv } from "node:crypto";
|
|
2
|
+
const AES_IV = Buffer.alloc(16, " "); // 16 spaces (0x20)
|
|
3
|
+
/**
|
|
4
|
+
* Decrypts a password/value buffer encrypted by Chrome on macOS.
|
|
5
|
+
* Chrome v10+ on macOS prefixes the encrypted blob with "v10" or "v11",
|
|
6
|
+
* followed by AES-128-CBC ciphertext.
|
|
7
|
+
*
|
|
8
|
+
* @param encryptedBlob - The encrypted blob from the database.
|
|
9
|
+
* @param derivedKey - The derived 16-byte key.
|
|
10
|
+
* @returns The decrypted plaintext, or an error placeholder.
|
|
11
|
+
*/
|
|
12
|
+
export function decryptValue(encryptedBlob, derivedKey) {
|
|
13
|
+
const decrypted = decryptValueBuffer(encryptedBlob, derivedKey);
|
|
14
|
+
return Buffer.isBuffer(decrypted) ? decrypted.toString("utf-8") : decrypted;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Decrypts a Chrome encrypted blob and returns the raw plaintext bytes.
|
|
18
|
+
*
|
|
19
|
+
* @param encryptedBlob - The encrypted blob from the database.
|
|
20
|
+
* @param derivedKey - The derived 16-byte key.
|
|
21
|
+
* @returns The decrypted plaintext bytes, or an error placeholder.
|
|
22
|
+
*/
|
|
23
|
+
export function decryptValueBuffer(encryptedBlob, derivedKey) {
|
|
24
|
+
if (!encryptedBlob || encryptedBlob.length === 0) {
|
|
25
|
+
return Buffer.alloc(0);
|
|
26
|
+
}
|
|
27
|
+
const blob = Buffer.isBuffer(encryptedBlob)
|
|
28
|
+
? encryptedBlob
|
|
29
|
+
: Buffer.from(encryptedBlob);
|
|
30
|
+
const prefix = blob.subarray(0, 3).toString("utf-8");
|
|
31
|
+
if (prefix !== "v10" && prefix !== "v11") {
|
|
32
|
+
return "<unsupported encryption format>";
|
|
33
|
+
}
|
|
34
|
+
const ciphertext = blob.subarray(3);
|
|
35
|
+
try {
|
|
36
|
+
const decipher = createDecipheriv("aes-128-cbc", derivedKey, AES_IV);
|
|
37
|
+
let decrypted = decipher.update(ciphertext);
|
|
38
|
+
decrypted = Buffer.concat([decrypted, decipher.final()]);
|
|
39
|
+
return decrypted;
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
return "<decryption failed>";
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Converts a Chrome/Webkit timestamp (microseconds since 1601-01-01)
|
|
47
|
+
* to a readable local Date string.
|
|
48
|
+
*
|
|
49
|
+
* @param chromeTimestamp - Timestamp in microseconds.
|
|
50
|
+
* @returns Human-readable date-time string, or "N/A".
|
|
51
|
+
*/
|
|
52
|
+
export function chromeTimeToDate(chromeTimestamp) {
|
|
53
|
+
if (!chromeTimestamp || chromeTimestamp === 0 || chromeTimestamp === 0n) {
|
|
54
|
+
return "N/A";
|
|
55
|
+
}
|
|
56
|
+
try {
|
|
57
|
+
const timestampBigInt = BigInt(chromeTimestamp);
|
|
58
|
+
const unixMicroseconds = timestampBigInt - 11644473600000000n;
|
|
59
|
+
const ms = Number(unixMicroseconds / 1000n);
|
|
60
|
+
const date = new Date(ms);
|
|
61
|
+
if (isNaN(date.getTime())) {
|
|
62
|
+
return "N/A";
|
|
63
|
+
}
|
|
64
|
+
return date.toLocaleString();
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
return "N/A";
|
|
68
|
+
}
|
|
69
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copies a Chrome SQLite database to a temporary location, executes a query,
|
|
3
|
+
* and cleans up. This avoids SQLite locking issues when Chrome is running.
|
|
4
|
+
*
|
|
5
|
+
* @param dbFilePath - Absolute path to the Chrome database file.
|
|
6
|
+
* @param sql - SQL query to execute.
|
|
7
|
+
* @param params - Bound parameters for the query.
|
|
8
|
+
* @returns Query result rows.
|
|
9
|
+
* @throws If the file doesn't exist or query fails.
|
|
10
|
+
*/
|
|
11
|
+
export declare function queryDatabase(dbFilePath: string, sql: string, params?: unknown[]): any[];
|
|
12
|
+
/**
|
|
13
|
+
* Checks whether a specific table exists in the given database file.
|
|
14
|
+
*
|
|
15
|
+
* @param dbFilePath - Absolute path to the database file.
|
|
16
|
+
* @param tableName - Name of the table to check.
|
|
17
|
+
* @returns True if the table exists.
|
|
18
|
+
*/
|
|
19
|
+
export declare function tableExists(dbFilePath: string, tableName: string): boolean;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { existsSync, copyFileSync, mkdtempSync, unlinkSync, rmSync } from "node:fs";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import { join, basename } from "node:path";
|
|
4
|
+
import Database from "better-sqlite3";
|
|
5
|
+
/**
|
|
6
|
+
* Copies a Chrome SQLite database to a temporary location, executes a query,
|
|
7
|
+
* and cleans up. This avoids SQLite locking issues when Chrome is running.
|
|
8
|
+
*
|
|
9
|
+
* @param dbFilePath - Absolute path to the Chrome database file.
|
|
10
|
+
* @param sql - SQL query to execute.
|
|
11
|
+
* @param params - Bound parameters for the query.
|
|
12
|
+
* @returns Query result rows.
|
|
13
|
+
* @throws If the file doesn't exist or query fails.
|
|
14
|
+
*/
|
|
15
|
+
export function queryDatabase(dbFilePath, sql, params = []) {
|
|
16
|
+
if (!existsSync(dbFilePath)) {
|
|
17
|
+
throw new Error(`Database file not found: ${dbFilePath}`);
|
|
18
|
+
}
|
|
19
|
+
const tmpDir = mkdtempSync(join(tmpdir(), "chrome-tools-"));
|
|
20
|
+
const tmpDb = join(tmpDir, basename(dbFilePath));
|
|
21
|
+
try {
|
|
22
|
+
copyFileSync(dbFilePath, tmpDb);
|
|
23
|
+
}
|
|
24
|
+
catch (err) {
|
|
25
|
+
cleanupTmp(tmpDb, tmpDir);
|
|
26
|
+
throw new Error(`Failed to copy database file '${basename(dbFilePath)}': ${err.message}`);
|
|
27
|
+
}
|
|
28
|
+
let db;
|
|
29
|
+
try {
|
|
30
|
+
db = new Database(tmpDb, { readonly: true, timeout: 5000 });
|
|
31
|
+
const stmt = db.prepare(sql);
|
|
32
|
+
return params.length > 0 ? stmt.all(...params) : stmt.all();
|
|
33
|
+
}
|
|
34
|
+
catch (err) {
|
|
35
|
+
throw new Error(`Database query failed on '${basename(dbFilePath)}': ${err.message}`);
|
|
36
|
+
}
|
|
37
|
+
finally {
|
|
38
|
+
if (db) {
|
|
39
|
+
try {
|
|
40
|
+
db.close();
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
/* ignore */
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
cleanupTmp(tmpDb, tmpDir);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Checks whether a specific table exists in the given database file.
|
|
51
|
+
*
|
|
52
|
+
* @param dbFilePath - Absolute path to the database file.
|
|
53
|
+
* @param tableName - Name of the table to check.
|
|
54
|
+
* @returns True if the table exists.
|
|
55
|
+
*/
|
|
56
|
+
export function tableExists(dbFilePath, tableName) {
|
|
57
|
+
try {
|
|
58
|
+
const rows = queryDatabase(dbFilePath, `SELECT name FROM sqlite_master WHERE type='table' AND name=?`, [tableName]);
|
|
59
|
+
return rows.length > 0;
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
function cleanupTmp(tmpDb, tmpDir) {
|
|
66
|
+
try {
|
|
67
|
+
unlinkSync(tmpDb);
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
/* ignore */
|
|
71
|
+
}
|
|
72
|
+
try {
|
|
73
|
+
rmSync(tmpDir, { recursive: true, force: true });
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
/* ignore */
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Retrieves the "Chrome Safe Storage" password from the macOS Keychain.
|
|
3
|
+
* Caches the result for subsequent calls within the same process.
|
|
4
|
+
*
|
|
5
|
+
* @returns The raw keychain password.
|
|
6
|
+
* @throws If the password could not be retrieved.
|
|
7
|
+
*/
|
|
8
|
+
export declare function getChromeKeychainPassword(): string;
|
|
9
|
+
/**
|
|
10
|
+
* Derives the AES-128-CBC key from the Keychain password using PBKDF2.
|
|
11
|
+
* Caches the result for subsequent calls.
|
|
12
|
+
*
|
|
13
|
+
* @param keychainPassword - The raw password. If omitted, fetched from Keychain.
|
|
14
|
+
* @returns The derived 16-byte key.
|
|
15
|
+
*/
|
|
16
|
+
export declare function deriveEncryptionKey(keychainPassword?: string): Buffer;
|
|
17
|
+
/**
|
|
18
|
+
* Clears cached keychain data. Useful for testing or re-authentication.
|
|
19
|
+
*/
|
|
20
|
+
export declare function clearKeychainCache(): void;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { execSync } from "node:child_process";
|
|
2
|
+
import { pbkdf2Sync } from "node:crypto";
|
|
3
|
+
const PBKDF2_ITERATIONS = 1003;
|
|
4
|
+
const PBKDF2_KEY_LENGTH = 16;
|
|
5
|
+
const PBKDF2_SALT = Buffer.from("saltysalt");
|
|
6
|
+
let _cachedKeychainPassword = null;
|
|
7
|
+
let _cachedDerivedKey = null;
|
|
8
|
+
/**
|
|
9
|
+
* Retrieves the "Chrome Safe Storage" password from the macOS Keychain.
|
|
10
|
+
* Caches the result for subsequent calls within the same process.
|
|
11
|
+
*
|
|
12
|
+
* @returns The raw keychain password.
|
|
13
|
+
* @throws If the password could not be retrieved.
|
|
14
|
+
*/
|
|
15
|
+
export function getChromeKeychainPassword() {
|
|
16
|
+
if (_cachedKeychainPassword !== null) {
|
|
17
|
+
return _cachedKeychainPassword;
|
|
18
|
+
}
|
|
19
|
+
try {
|
|
20
|
+
const password = execSync('security find-generic-password -w -s "Chrome Safe Storage"', { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
21
|
+
_cachedKeychainPassword = password;
|
|
22
|
+
return password;
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
throw new Error("Could not retrieve Chrome Safe Storage key from macOS Keychain.\n" +
|
|
26
|
+
"Ensure Google Chrome has been run at least once and you have granted Keychain access.");
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Derives the AES-128-CBC key from the Keychain password using PBKDF2.
|
|
31
|
+
* Caches the result for subsequent calls.
|
|
32
|
+
*
|
|
33
|
+
* @param keychainPassword - The raw password. If omitted, fetched from Keychain.
|
|
34
|
+
* @returns The derived 16-byte key.
|
|
35
|
+
*/
|
|
36
|
+
export function deriveEncryptionKey(keychainPassword) {
|
|
37
|
+
if (_cachedDerivedKey !== null) {
|
|
38
|
+
return _cachedDerivedKey;
|
|
39
|
+
}
|
|
40
|
+
const password = keychainPassword || getChromeKeychainPassword();
|
|
41
|
+
if (!password) {
|
|
42
|
+
throw new Error("Keychain password cannot be empty for key derivation.");
|
|
43
|
+
}
|
|
44
|
+
_cachedDerivedKey = pbkdf2Sync(password, PBKDF2_SALT, PBKDF2_ITERATIONS, PBKDF2_KEY_LENGTH, "sha1");
|
|
45
|
+
return _cachedDerivedKey;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Clears cached keychain data. Useful for testing or re-authentication.
|
|
49
|
+
*/
|
|
50
|
+
export function clearKeychainCache() {
|
|
51
|
+
_cachedKeychainPassword = null;
|
|
52
|
+
_cachedDerivedKey = null;
|
|
53
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* chrome-tools — Public API
|
|
3
|
+
*
|
|
4
|
+
* This module re-exports all feature modules for use as a Node.js library.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* import { getPasswords, getBookmarks, discoverProfiles } from 'chrome-tools';
|
|
8
|
+
*
|
|
9
|
+
* const passwords = getPasswords({ profile: 'Default' });
|
|
10
|
+
* const bookmarks = getBookmarks({ search: 'github' });
|
|
11
|
+
* const profiles = discoverProfiles();
|
|
12
|
+
*/
|
|
13
|
+
export { getDefaultChromeDir, discoverProfiles, getProfileMetadata, getProfileStates, resolveProfilePath, resolveTargetProfiles, type DiscoveredProfile, type ProfileMetadata, type ProfileState, type ResolveProfilesOptions, } from "./core/chrome.js";
|
|
14
|
+
export { getChromeKeychainPassword, deriveEncryptionKey, clearKeychainCache, } from "./core/keychain.js";
|
|
15
|
+
export { decryptValue, chromeTimeToDate, } from "./core/crypto.js";
|
|
16
|
+
export { queryDatabase, tableExists, } from "./core/db.js";
|
|
17
|
+
export { getPasswords } from "./modules/passwords.js";
|
|
18
|
+
export { getBookmarks } from "./modules/bookmarks.js";
|
|
19
|
+
export { getHistory, getSearchTerms } from "./modules/history.js";
|
|
20
|
+
export { getCookies, type CookieEntry, type CookieOptions } from "./modules/cookies.js";
|
|
21
|
+
export { getAutofillEntries, getCreditCards, getAddresses } from "./modules/autofill.js";
|
|
22
|
+
export { getDownloads } from "./modules/downloads.js";
|
|
23
|
+
export { getExtensions } from "./modules/extensions.js";
|
|
24
|
+
export { getSearchEngines } from "./modules/search-engines.js";
|
|
25
|
+
export { getTopSites } from "./modules/top-sites.js";
|
|
@@ -10,33 +10,11 @@
|
|
|
10
10
|
* const bookmarks = getBookmarks({ search: 'github' });
|
|
11
11
|
* const profiles = discoverProfiles();
|
|
12
12
|
*/
|
|
13
|
-
|
|
14
13
|
// Core utilities
|
|
15
|
-
export {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
getProfileStates,
|
|
20
|
-
resolveProfilePath,
|
|
21
|
-
resolveTargetProfiles,
|
|
22
|
-
} from "./core/chrome.js";
|
|
23
|
-
|
|
24
|
-
export {
|
|
25
|
-
getChromeKeychainPassword,
|
|
26
|
-
deriveEncryptionKey,
|
|
27
|
-
clearKeychainCache,
|
|
28
|
-
} from "./core/keychain.js";
|
|
29
|
-
|
|
30
|
-
export {
|
|
31
|
-
decryptValue,
|
|
32
|
-
chromeTimeToDate,
|
|
33
|
-
} from "./core/crypto.js";
|
|
34
|
-
|
|
35
|
-
export {
|
|
36
|
-
queryDatabase,
|
|
37
|
-
tableExists,
|
|
38
|
-
} from "./core/db.js";
|
|
39
|
-
|
|
14
|
+
export { getDefaultChromeDir, discoverProfiles, getProfileMetadata, getProfileStates, resolveProfilePath, resolveTargetProfiles, } from "./core/chrome.js";
|
|
15
|
+
export { getChromeKeychainPassword, deriveEncryptionKey, clearKeychainCache, } from "./core/keychain.js";
|
|
16
|
+
export { decryptValue, chromeTimeToDate, } from "./core/crypto.js";
|
|
17
|
+
export { queryDatabase, tableExists, } from "./core/db.js";
|
|
40
18
|
// Feature modules
|
|
41
19
|
export { getPasswords } from "./modules/passwords.js";
|
|
42
20
|
export { getBookmarks } from "./modules/bookmarks.js";
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
export interface AutofillOptions {
|
|
2
|
+
chromeDir?: string;
|
|
3
|
+
profile?: string;
|
|
4
|
+
search?: string;
|
|
5
|
+
limit?: number;
|
|
6
|
+
}
|
|
7
|
+
export interface AutofillEntry {
|
|
8
|
+
profile: string;
|
|
9
|
+
name: string;
|
|
10
|
+
value: string;
|
|
11
|
+
dateCreated: string;
|
|
12
|
+
dateLastUsed: string;
|
|
13
|
+
count: number;
|
|
14
|
+
}
|
|
15
|
+
export interface CreditCardOptions {
|
|
16
|
+
chromeDir?: string;
|
|
17
|
+
profile?: string;
|
|
18
|
+
}
|
|
19
|
+
export interface CreditCardEntry {
|
|
20
|
+
profile: string;
|
|
21
|
+
guid: string;
|
|
22
|
+
nameOnCard: string;
|
|
23
|
+
expirationMonth: number;
|
|
24
|
+
expirationYear: number;
|
|
25
|
+
cardNumber: string;
|
|
26
|
+
nickname: string;
|
|
27
|
+
origin: string;
|
|
28
|
+
useCount: number;
|
|
29
|
+
dateModified: string;
|
|
30
|
+
}
|
|
31
|
+
export interface AddressOptions {
|
|
32
|
+
chromeDir?: string;
|
|
33
|
+
profile?: string;
|
|
34
|
+
}
|
|
35
|
+
export interface AddressEntry {
|
|
36
|
+
profile: string;
|
|
37
|
+
guid: string;
|
|
38
|
+
fullName: string;
|
|
39
|
+
email: string;
|
|
40
|
+
phone: string;
|
|
41
|
+
companyName: string;
|
|
42
|
+
streetAddress: string;
|
|
43
|
+
city: string;
|
|
44
|
+
state: string;
|
|
45
|
+
zipcode: string;
|
|
46
|
+
countryCode: string;
|
|
47
|
+
origin: string;
|
|
48
|
+
useCount: number;
|
|
49
|
+
dateModified: string;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Retrieves autofill entries from Chrome's Web Data database.
|
|
53
|
+
*
|
|
54
|
+
* @param options - Filtering and targeting options.
|
|
55
|
+
* @returns Array of autofill entries.
|
|
56
|
+
*/
|
|
57
|
+
export declare function getAutofillEntries({ chromeDir, profile, search, limit, }?: AutofillOptions): AutofillEntry[];
|
|
58
|
+
/**
|
|
59
|
+
* Retrieves saved credit cards from Chrome's Web Data database.
|
|
60
|
+
* Card numbers are encrypted and will be decrypted.
|
|
61
|
+
*
|
|
62
|
+
* @param options - Targeting options.
|
|
63
|
+
* @returns Array of credit card entries.
|
|
64
|
+
*/
|
|
65
|
+
export declare function getCreditCards({ chromeDir, profile }?: CreditCardOptions): CreditCardEntry[];
|
|
66
|
+
/**
|
|
67
|
+
* Retrieves saved addresses from Chrome's Web Data database.
|
|
68
|
+
*
|
|
69
|
+
* @param options - Targeting options.
|
|
70
|
+
* @returns Array of address entries.
|
|
71
|
+
*/
|
|
72
|
+
export declare function getAddresses({ chromeDir, profile }?: AddressOptions): AddressEntry[];
|