browser-cookie-bridge 1.5.1 → 1.5.2
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 +12 -12
- package/extension-template/manifest.json +1 -1
- package/macos-app/Info.plist +2 -2
- package/macos-app/Sources/BraveCodexSyncApp/BraveCodexSyncApp.swift +6 -5
- package/macos-app/Sources/BraveCodexSyncApp/SyncModel.swift +1 -4
- package/package.json +1 -1
- package/src/cli.js +0 -1
- package/src/grok-bot-export.js +10 -4
- package/src/grok-bot-importer.mjs +27 -3
package/README.md
CHANGED
|
@@ -48,13 +48,13 @@
|
|
|
48
48
|
|
|
49
49
|
---
|
|
50
50
|
|
|
51
|
-
> **Version 1.5.1
|
|
51
|
+
> **Version 1.5.2:** Grok Bot `.bcbx` bundles embed the decryption key, so import needs no passphrase—treat the file as credentials because anyone with it can decrypt the cookies. Version 1.5.1 restored the [Product Hunt](https://www.producthunt.com/products/browser-cookie-bridge-2) launch link. Version 1.5.0 added experimental Cursor browser import and encrypted Grok Bot `.bcbx` export. Cursor is destination-only and currently imports cookie sessions into its dedicated browser partition. Attach the `.bcbx` file to any Grok Bot and paste the prompt; the bundled importer decrypts automatically. Cookie values must never be logged or pasted into chat. The optional Codex restart flow from 1.4.0 is unchanged and remains off by default.
|
|
52
52
|
|
|
53
53
|
## Why Browser Cookie Bridge
|
|
54
54
|
|
|
55
55
|
Signing into the same sites across several browsers is repetitive. Export files are awkward, password managers do not move active sessions, and embedded app browsers do not always offer an import button.
|
|
56
56
|
|
|
57
|
-
Browser Cookie Bridge gives those browser profiles a small, native control panel. Local browser, Codex, and Cursor transfers stay on the Mac. Grok Bot uses
|
|
57
|
+
Browser Cookie Bridge gives those browser profiles a small, native control panel. Local browser, Codex, and Cursor transfers stay on the Mac. Grok Bot uses an encrypted transfer file with an embedded decryption key that you attach yourself. If you explicitly select Browserless Cloud and click Upload, the app can instead send authenticated profile state to your own Browserless account.
|
|
58
58
|
|
|
59
59
|
## Windows waitlist
|
|
60
60
|
|
|
@@ -66,7 +66,7 @@ Browser Cookie Bridge currently ships for macOS. A separate Windows app is being
|
|
|
66
66
|
- 🗃️ **Optional full site data for Codex** — replace Codex's compatible Local Storage, IndexedDB, Session Storage, service-worker, and related origin stores from a closed source profile, with backup and rollback. Off by default.
|
|
67
67
|
- 🌐 **Seven Chromium browsers** — Brave, Chrome, Edge, Arc, Vivaldi, Opera, and Perplexity Comet can be sources or destinations.
|
|
68
68
|
- ✨ **ChatGPT Codex and experimental Cursor import** — merge cookie sessions into either app's built-in browser; Codex also supports optional history and full site data. Both are destination-only. Cursor import is experimental: it writes only to Cursor's dedicated `Partitions/cursor-browser` profile and refuses unknown schemas.
|
|
69
|
-
- 🤖 **Encrypted Grok Bot transfer (`.bcbx`)** — export cookie sessions into a local encrypted bundle with
|
|
69
|
+
- 🤖 **Encrypted Grok Bot transfer (`.bcbx`)** — export cookie sessions into a local encrypted bundle with an embedded decryption key and bundled importer. The destination tile uses the official Grok Bot app icon. Attach the file to any Grok Bot; the importer runs on the cloud computer only. Manual only.
|
|
70
70
|
- 🔁 **Optional Codex restart** — manual Sync can force quit a running Codex instance and reopen it only after a successful local transfer; off by default.
|
|
71
71
|
- 🔄 **Optional two-app restart** — Full site data can force quit the selected source browser and Codex, then reopen only the apps that were running after a successful transfer; off by default.
|
|
72
72
|
- ☁️ **Optional Browserless upload** — create or refresh a Browserless authenticated profile with cookies, local storage, and IndexedDB; see a local size preflight, live progress, cancellation, and post-upload verification.
|
|
@@ -164,21 +164,21 @@ An open destination app blocks the transfer. The optional **Restart Codex automa
|
|
|
164
164
|
|
|
165
165
|
No browser extension is needed for this path.
|
|
166
166
|
|
|
167
|
-
Grok Bot cannot receive a local database merge. The app writes
|
|
167
|
+
Grok Bot cannot receive a local database merge. The app writes an encrypted transfer file that you attach to any Grok Bot.
|
|
168
168
|
|
|
169
169
|
1. Select a source browser and **Grok Bot** as the destination. The tile uses the official Grok Bot app icon.
|
|
170
170
|
2. Keep **Cookies** on. History URLs and Full site data are excluded from Grok Bot transfer files.
|
|
171
171
|
3. Optionally limit the export with **Only these domains**. Leave it blank to include every readable cookie.
|
|
172
172
|
4. Press **Create transfer file** and choose where to save `GrokBot-Import.bcbx`.
|
|
173
|
-
5. The result sheet shows
|
|
174
|
-
6. Attach the `.bcbx` file to any Grok Bot
|
|
173
|
+
5. The result sheet shows a sensitivity warning and the prompt to paste. Use **Reveal file** or **Copy prompt**. Do not paste cookie values into chat.
|
|
174
|
+
6. Attach the `.bcbx` file to any Grok Bot and paste the prompt. The bundled importer decrypts automatically—no separate key entry.
|
|
175
175
|
|
|
176
|
-
The bundle is a zip-compatible `.bcbx` file containing `manifest.json`, encrypted `payload.enc`, `import.mjs`, and `PROMPT.txt`. Cookies are encrypted with AES-256-GCM using a scrypt-derived key. The file is written with user-only permissions (`0600`). Daily sync and Sync at login never create Grok Bot bundles.
|
|
176
|
+
The bundle is a zip-compatible `.bcbx` file containing `manifest.json`, encrypted `payload.enc`, `decryption.key`, `import.mjs`, and `PROMPT.txt`. Cookies are encrypted with AES-256-GCM using a scrypt-derived key; the same key is stored in `decryption.key` inside the bundle so the importer can decrypt without user input. Treat the file as credentials—anyone with the bundle can read the cookies. The file is written with user-only permissions (`0600`). Daily sync and Sync at login never create Grok Bot bundles.
|
|
177
177
|
|
|
178
178
|
On the Grok Bot cloud computer the bundled importer:
|
|
179
179
|
|
|
180
180
|
1. Expects you to unzip `GrokBot-Import.bcbx` and run `node import.mjs`.
|
|
181
|
-
2.
|
|
181
|
+
2. Reads the embedded decryption key and decrypts the payload automatically. Do not print cookie names or values.
|
|
182
182
|
3. Injects cookies into the cloud browser and reports only how many cookies were imported.
|
|
183
183
|
4. Deletes the unzipped files and the bundle copy.
|
|
184
184
|
|
|
@@ -243,7 +243,7 @@ browser-cookie-bridge disable-app-login
|
|
|
243
243
|
browser-cookie-bridge remove-schedule
|
|
244
244
|
```
|
|
245
245
|
|
|
246
|
-
Supported source IDs are `brave`, `chrome`, `edge`, `arc`, `vivaldi`, `opera`, and `comet`. Target IDs are the same plus `codex`, `cursor`, `browserless`, and `grok-bot`. The same browser cannot be both endpoints. Cursor import currently supports cookies only. Grok Bot export requires `--output /path/GrokBot-Import.bcbx` and writes cookies only; `--grok-bot-domains` optionally limits the bundle. The
|
|
246
|
+
Supported source IDs are `brave`, `chrome`, `edge`, `arc`, `vivaldi`, `opera`, and `comet`. Target IDs are the same plus `codex`, `cursor`, `browserless`, and `grok-bot`. The same browser cannot be both endpoints. Cursor import currently supports cookies only. Grok Bot export requires `--output /path/GrokBot-Import.bcbx` and writes cookies only; `--grok-bot-domains` optionally limits the bundle. The bundle includes an embedded decryption key—treat it as credentials and never paste cookie values into chat. Browserless requires `BROWSERLESS_TOKEN` and the explicit `--allow-cloud-upload` flag; the native app supplies the token from Keychain without placing it in the OS command line or app configuration.
|
|
247
247
|
|
|
248
248
|
## How it works
|
|
249
249
|
|
|
@@ -251,16 +251,16 @@ Supported source IDs are `brave`, `chrome`, `edge`, `arc`, `vivaldi`, `opera`, a
|
|
|
251
251
|
|---|---|
|
|
252
252
|
| **Browser → browser** | Unpacked extensions connect to a short-lived broker on IPv4 loopback. Selected data stays in memory and is never written to logs. |
|
|
253
253
|
| **Browser → Codex / Cursor** | The app reads the selected local Chromium profile, creates a consistent destination SQLite backup, merges cookies into a working copy, validates it, then replaces only the embedded browser's cookie database atomically. Codex also supports optional history and full site data. Cursor import is experimental and limited to the dedicated browser-partition cookie store. |
|
|
254
|
-
| **Browser → Grok Bot** | The app reads the selected local Chromium profile, encrypts cookie sessions into a `.bcbx` bundle with
|
|
254
|
+
| **Browser → Grok Bot** | The app reads the selected local Chromium profile, encrypts cookie sessions into a `.bcbx` bundle with an embedded decryption key, and writes that file locally. You attach it to a Grok Bot; the bundled importer decrypts and injects cookies on the cloud computer only. |
|
|
255
255
|
| **Browser → Browserless** | The bundled official Browserless CLI copies a closed local profile, captures cookies/local storage/IndexedDB, and uploads it directly to the selected Browserless region. |
|
|
256
256
|
|
|
257
257
|
The broker validates a random token and extension origin, limits payload size, and normally exits after five minutes. Only the endpoints selected in the app respond to a transfer.
|
|
258
258
|
|
|
259
259
|
## Security & privacy
|
|
260
260
|
|
|
261
|
-
- Cookie values and history URLs are never logged. Never paste cookie values
|
|
261
|
+
- Cookie values and history URLs are never logged. Never paste cookie values into chat.
|
|
262
262
|
- Browser-to-browser data is held only in broker memory.
|
|
263
|
-
- Grok Bot `.bcbx` files
|
|
263
|
+
- Grok Bot `.bcbx` files encrypt cookie payloads with AES-256-GCM but include the decryption key inside the bundle (`decryption.key`). Treat the entire file as credentials—anyone with the bundle can read the cookies. Do not share it or paste cookie values into chat. The bundled importer decrypts automatically, reports only import counts, then deletes the unzipped files and the bundle copy. Legacy v1 bundles without an embedded key still prompt for a separate passphrase.
|
|
264
264
|
- Direct-import backups are stored with user-only permissions under `~/Library/Application Support/BraveCodexCookieSync/backups/codex` or `backups/cursor`; the newest 14 per destination are retained.
|
|
265
265
|
- Full site-data import is opt-in for Codex and requires both Codex and the source browser to be closed. It replaces compatible origin-storage directories rather than attempting an unsafe LevelDB merge; the previous directories are included in the same backup.
|
|
266
266
|
- The app refuses unknown destination database schemas instead of guessing and writes only to Cursor's dedicated `Partitions/cursor-browser` profile, never Cursor's main application cookie store.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"manifest_version": 3,
|
|
3
3
|
"name": "Browser Cookie Bridge",
|
|
4
|
-
"version": "1.5.
|
|
4
|
+
"version": "1.5.2",
|
|
5
5
|
"description": "Transfers selected browser data locally between supported browsers and embedded app browsers.",
|
|
6
6
|
"key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA5o6lDkXzU/23Jm2QRk4mfuxNnr3VlEIjRHcZr93Gdo7PI7asHJQhigaMGT6UOz0lkaTn1F+TGIPlsJo9GNBSsxbCfxWoBSXcFQFrPolY5LEfKaMtZQQ6eb4UAnp+bbUwb1den4On0piWU47Yzl6i0Jcdg4EG5H1kCSF/6nJsbKgF7OTaR/3drULu40yRvflzSXRhly3UDO7c5mgHJsaMwVV+VILsxbcPOoRINjuo3zDC737HFs67dpf38xF3tjSVL0HURAWKKobMvfABiDfVEm5O0Lvf+57d1Ib12QLT2qtVXNDy3iYwX+w2S0wH2vIMsfvoFPlRdmeqft70kZOqXwIDAQAB",
|
|
7
7
|
"permissions": ["alarms", "cookies", "history"],
|
package/macos-app/Info.plist
CHANGED
|
@@ -10,8 +10,8 @@
|
|
|
10
10
|
<key>CFBundleInfoDictionaryVersion</key><string>6.0</string>
|
|
11
11
|
<key>CFBundleName</key><string>Browser Cookie Bridge</string>
|
|
12
12
|
<key>CFBundlePackageType</key><string>APPL</string>
|
|
13
|
-
<key>CFBundleShortVersionString</key><string>1.5.
|
|
14
|
-
<key>CFBundleVersion</key><string>
|
|
13
|
+
<key>CFBundleShortVersionString</key><string>1.5.2</string>
|
|
14
|
+
<key>CFBundleVersion</key><string>39</string>
|
|
15
15
|
<key>LSMinimumSystemVersion</key><string>13.5</string>
|
|
16
16
|
<key>NSHighResolutionCapable</key><true/>
|
|
17
17
|
</dict>
|
|
@@ -949,7 +949,7 @@ struct ExtensionSetupSheet: View {
|
|
|
949
949
|
Text(model.isDirectTarget
|
|
950
950
|
? "\(model.targetName) uses a direct local merge, so no extension is required. Quit \(model.targetName) before syncing. Password access is never requested."
|
|
951
951
|
: model.isGrokBotTarget
|
|
952
|
-
? "Grok Bot uses
|
|
952
|
+
? "Grok Bot uses an encrypted transfer file with an embedded decryption key, so no extension is required. Create the .bcbx bundle, attach it to any Grok Bot, and paste the prompt."
|
|
953
953
|
: "In both endpoints, enable Developer mode and choose Load unpacked. Password access is never requested.")
|
|
954
954
|
.font(.system(size: 10.5))
|
|
955
955
|
.foregroundStyle(.secondary)
|
|
@@ -1188,12 +1188,13 @@ struct GrokBotResultSheet: View {
|
|
|
1188
1188
|
}
|
|
1189
1189
|
|
|
1190
1190
|
VStack(alignment: .leading, spacing: 8) {
|
|
1191
|
-
Text("
|
|
1191
|
+
Text("Sensitive transfer file")
|
|
1192
1192
|
.font(.system(size: 11, weight: .semibold))
|
|
1193
1193
|
.foregroundStyle(.secondary)
|
|
1194
|
-
Text(
|
|
1195
|
-
.font(.system(size:
|
|
1196
|
-
.
|
|
1194
|
+
Text("The .bcbx bundle includes the decryption key. Anyone with the file can read your cookies—treat it like a password and do not share it.")
|
|
1195
|
+
.font(.system(size: 11))
|
|
1196
|
+
.foregroundStyle(.secondary)
|
|
1197
|
+
.fixedSize(horizontal: false, vertical: true)
|
|
1197
1198
|
.padding(10)
|
|
1198
1199
|
.frame(maxWidth: .infinity, alignment: .leading)
|
|
1199
1200
|
.background(Color.primary.opacity(0.04), in: RoundedRectangle(cornerRadius: 10, style: .continuous))
|
|
@@ -100,7 +100,6 @@ final class SyncModel: ObservableObject {
|
|
|
100
100
|
@Published var grokBotOnlyDomains = ""
|
|
101
101
|
@Published var showingBrowserlessSetup = false
|
|
102
102
|
@Published var showingGrokBotResult = false
|
|
103
|
-
@Published var grokBotPassphrase = ""
|
|
104
103
|
@Published var grokBotPrompt = ""
|
|
105
104
|
@Published var grokBotOutputPath = ""
|
|
106
105
|
@Published var browserlessAssessment: BrowserlessProfileAssessment?
|
|
@@ -754,7 +753,6 @@ final class SyncModel: ObservableObject {
|
|
|
754
753
|
} else if success {
|
|
755
754
|
self.state = partial ? .warning : .success
|
|
756
755
|
if self.isGrokBotTarget, let result = self.parseGrokBotResult(from: output) {
|
|
757
|
-
self.grokBotPassphrase = result.passphrase
|
|
758
756
|
self.grokBotPrompt = result.prompt
|
|
759
757
|
self.grokBotOutputPath = result.outputPath
|
|
760
758
|
self.showingGrokBotResult = true
|
|
@@ -1167,7 +1165,6 @@ final class SyncModel: ObservableObject {
|
|
|
1167
1165
|
|
|
1168
1166
|
private struct GrokBotResultPayload: Decodable {
|
|
1169
1167
|
let outputPath: String
|
|
1170
|
-
let passphrase: String
|
|
1171
1168
|
let prompt: String
|
|
1172
1169
|
}
|
|
1173
1170
|
|
|
@@ -1230,7 +1227,7 @@ final class SyncModel: ObservableObject {
|
|
|
1230
1227
|
} else {
|
|
1231
1228
|
state = .ready
|
|
1232
1229
|
primaryStatus = "Ready to create a Grok Bot transfer file"
|
|
1233
|
-
secondaryStatus = "Creates an encrypted .bcbx bundle with
|
|
1230
|
+
secondaryStatus = "Creates an encrypted .bcbx bundle with an embedded decryption key and bundled importer"
|
|
1234
1231
|
}
|
|
1235
1232
|
} else if isBrowserlessTarget {
|
|
1236
1233
|
if selectedSourceID == "comet" {
|
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -312,7 +312,6 @@ async function sync(args, { signal } = {}) {
|
|
|
312
312
|
console.log(grokBotSummary(result));
|
|
313
313
|
console.log(`BCB_GROK_RESULT ${JSON.stringify({
|
|
314
314
|
outputPath: result.outputPath,
|
|
315
|
-
passphrase: result.passphrase,
|
|
316
315
|
cookieCount: result.cookieCount,
|
|
317
316
|
domainCount: result.domainCount,
|
|
318
317
|
domains: result.domains,
|
package/src/grok-bot-export.js
CHANGED
|
@@ -7,13 +7,15 @@ import { createZipArchive, readZipArchive } from "./zip-store.js";
|
|
|
7
7
|
|
|
8
8
|
export const BUNDLE_EXTENSION = "bcbx";
|
|
9
9
|
export const DEFAULT_BUNDLE_NAME = "GrokBot-Import.bcbx";
|
|
10
|
+
export const BUNDLE_FORMAT_VERSION = 2;
|
|
11
|
+
export const EMBEDDED_KEY_FILENAME = "decryption.key";
|
|
12
|
+
|
|
10
13
|
export const GROK_BOT_PROMPT = `On your Grok Bot cloud computer only — do not access my local Mac and do not print cookie values.
|
|
11
14
|
|
|
12
15
|
1. Save the attached GrokBot-Import.bcbx to the cloud computer.
|
|
13
16
|
2. Unzip it: unzip -o GrokBot-Import.bcbx -d bcb-import && cd bcb-import
|
|
14
17
|
3. Run: node import.mjs
|
|
15
|
-
4.
|
|
16
|
-
5. Report only how many cookies were imported per domain, then delete the bcb-import folder and any copies of the bundle.`;
|
|
18
|
+
4. Report only how many cookies were imported per domain, then delete the bcb-import folder and any copies of the bundle.`;
|
|
17
19
|
|
|
18
20
|
const KDF = { name: "scrypt", N: 16384, r: 8, p: 1, keyLength: 32 };
|
|
19
21
|
const SAME_SITE = {
|
|
@@ -50,7 +52,9 @@ export function buildGrokBotBundle({
|
|
|
50
52
|
const domains = uniqueDomains(exportCookies);
|
|
51
53
|
const manifest = {
|
|
52
54
|
format: "browser-cookie-bridge-grok-bot",
|
|
53
|
-
version:
|
|
55
|
+
version: BUNDLE_FORMAT_VERSION,
|
|
56
|
+
keyDelivery: "embedded",
|
|
57
|
+
keyFile: EMBEDDED_KEY_FILENAME,
|
|
54
58
|
createdAt: now.toISOString(),
|
|
55
59
|
sourceBrowser,
|
|
56
60
|
cookieCount: exportCookies.length,
|
|
@@ -66,6 +70,7 @@ export function buildGrokBotBundle({
|
|
|
66
70
|
const archive = createZipArchive([
|
|
67
71
|
{ name: "manifest.json", data: Buffer.from(`${JSON.stringify(manifest, null, 2)}\n`, "utf8") },
|
|
68
72
|
{ name: "payload.enc", data: payloadBody },
|
|
73
|
+
{ name: EMBEDDED_KEY_FILENAME, data: Buffer.from(`${passphrase}\n`, "utf8") },
|
|
69
74
|
{ name: "import.mjs", data: Buffer.from(importerSource, "utf8") },
|
|
70
75
|
{ name: "PROMPT.txt", data: Buffer.from(`${GROK_BOT_PROMPT}\n`, "utf8") },
|
|
71
76
|
]);
|
|
@@ -138,6 +143,7 @@ export function parseGrokBotBundle(buffer) {
|
|
|
138
143
|
return {
|
|
139
144
|
manifest,
|
|
140
145
|
payload: entries.get("payload.enc"),
|
|
146
|
+
embeddedKey: entries.get(EMBEDDED_KEY_FILENAME)?.toString("utf8").trim() || "",
|
|
141
147
|
importer: entries.get("import.mjs")?.toString("utf8") || "",
|
|
142
148
|
prompt: entries.get("PROMPT.txt")?.toString("utf8") || "",
|
|
143
149
|
};
|
|
@@ -148,7 +154,7 @@ export function grokBotSummary(result) {
|
|
|
148
154
|
const skipped = result.sourceCookieSkipped
|
|
149
155
|
? ` ${result.sourceCookieSkipped} source cookie${result.sourceCookieSkipped === 1 ? " was" : "s were"} unreadable or unsupported.`
|
|
150
156
|
: "";
|
|
151
|
-
return `Grok Bot transfer file created: ${result.cookieCount} cookies across ${domainNote} from ${result.sourceBrowser}.${skipped} Attach ${path.basename(result.outputPath)} to any Grok Bot
|
|
157
|
+
return `Grok Bot transfer file created: ${result.cookieCount} cookies across ${domainNote} from ${result.sourceBrowser}.${skipped} Attach ${path.basename(result.outputPath)} to any Grok Bot and paste the prompt. The bundle includes the decryption key—treat the file as credentials and do not share it.`;
|
|
152
158
|
}
|
|
153
159
|
|
|
154
160
|
export function filterCookies(cookies, onlyDomains = []) {
|
|
@@ -11,6 +11,7 @@ import { fileURLToPath } from "node:url";
|
|
|
11
11
|
import { stdin as input, stdout as output } from "node:process";
|
|
12
12
|
|
|
13
13
|
const CDP_PORTS = [9222, 9223, 9224, 9228, 9229, 9400];
|
|
14
|
+
const EMBEDDED_KEY_FILENAME = "decryption.key";
|
|
14
15
|
const SAME_SITE = {
|
|
15
16
|
unspecified: "Lax",
|
|
16
17
|
no_restriction: "None",
|
|
@@ -29,7 +30,7 @@ export async function main(argv = process.argv.slice(2)) {
|
|
|
29
30
|
|
|
30
31
|
const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf8"));
|
|
31
32
|
validateManifest(manifest);
|
|
32
|
-
const passphrase = await
|
|
33
|
+
const passphrase = await resolveDecryptionKey({ manifest, bundleDir });
|
|
33
34
|
const cookies = decryptPayload({
|
|
34
35
|
manifest,
|
|
35
36
|
encrypted: fs.readFileSync(payloadPath),
|
|
@@ -42,9 +43,12 @@ export async function main(argv = process.argv.slice(2)) {
|
|
|
42
43
|
}
|
|
43
44
|
|
|
44
45
|
export function validateManifest(manifest) {
|
|
45
|
-
if (manifest?.format !== "browser-cookie-bridge-grok-bot"
|
|
46
|
+
if (manifest?.format !== "browser-cookie-bridge-grok-bot") {
|
|
46
47
|
throw new Error("Unsupported Browser Cookie Bridge Grok Bot bundle.");
|
|
47
48
|
}
|
|
49
|
+
if (manifest?.version !== 1 && manifest?.version !== 2) {
|
|
50
|
+
throw new Error("Unsupported Browser Cookie Bridge Grok Bot bundle version.");
|
|
51
|
+
}
|
|
48
52
|
for (const key of ["salt", "iv", "authTag"]) {
|
|
49
53
|
if (typeof manifest[key] !== "string" || !manifest[key]) {
|
|
50
54
|
throw new Error(`Bundle manifest is missing ${key}.`);
|
|
@@ -52,6 +56,26 @@ export function validateManifest(manifest) {
|
|
|
52
56
|
}
|
|
53
57
|
}
|
|
54
58
|
|
|
59
|
+
export async function resolveDecryptionKey({ manifest, bundleDir }) {
|
|
60
|
+
if (manifest.version === 2) {
|
|
61
|
+
if (manifest.keyDelivery !== "embedded") {
|
|
62
|
+
throw new Error("Unsupported Grok Bot bundle key delivery method.");
|
|
63
|
+
}
|
|
64
|
+
const keyFile = typeof manifest.keyFile === "string" && manifest.keyFile
|
|
65
|
+
? manifest.keyFile
|
|
66
|
+
: EMBEDDED_KEY_FILENAME;
|
|
67
|
+
const keyPath = path.join(bundleDir, keyFile);
|
|
68
|
+
if (!fs.existsSync(keyPath)) {
|
|
69
|
+
throw new Error(`Embedded decryption key file is missing: ${keyFile}`);
|
|
70
|
+
}
|
|
71
|
+
const key = fs.readFileSync(keyPath, "utf8").trim();
|
|
72
|
+
if (!key) throw new Error("Embedded decryption key file is empty.");
|
|
73
|
+
return key;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return readPassphrase();
|
|
77
|
+
}
|
|
78
|
+
|
|
55
79
|
export function decryptPayload({ manifest, encrypted, passphrase }) {
|
|
56
80
|
const key = crypto.scryptSync(
|
|
57
81
|
passphrase,
|
|
@@ -164,7 +188,7 @@ function reportSummary(result, domains) {
|
|
|
164
188
|
}
|
|
165
189
|
|
|
166
190
|
function cleanup(bundleDir, bundlePath) {
|
|
167
|
-
for (const name of ["manifest.json", "payload.enc", "import.mjs", "PROMPT.txt"]) {
|
|
191
|
+
for (const name of ["manifest.json", "payload.enc", EMBEDDED_KEY_FILENAME, "import.mjs", "PROMPT.txt"]) {
|
|
168
192
|
fs.rmSync(path.join(bundleDir, name), { force: true });
|
|
169
193
|
}
|
|
170
194
|
fs.rmSync(bundlePath, { force: true });
|