Skip to content

fix(decryptor): correct macOS v10 cookie decryption (wrong key + M130 domain hash) - #3

Merged
pchuri merged 6 commits into
mainfrom
fm/cce-decrypt-fix-w1
Jul 26, 2026
Merged

pchuri merged 6 commits into
mainfrom
fm/cce-decrypt-fix-w1

Conversation

@pchuri

@pchuri pchuri commented Jul 26, 2026

Copy link
Copy Markdown
Owner

Summary

Chrome M130+ on macOS was producing garbage (binary) cookie values because of two independent bugs in the v10 decryption path. Both must be fixed together — fixing either alone still yields garbage.

Bug A — wrong AES key (src/decryptor.ts)

getChromeSafeStoragePassword() base64-decoded the Keychain Chrome Safe Storage password before feeding it to PBKDF2. The macOS v10 scheme uses that password string as-is as the PBKDF2 secret (salt saltysalt, 1003 iters, 16-byte key, sha1). Decoding it derived a completely wrong key, corrupting the entire plaintext. Fixed on both the primary and fallback return paths (raw utf8 string instead of base64 decode).

Bug B — unstripped 32-byte domain hash (src/decryptor.ts)

Chrome M130+ prepends a 32-byte SHA-256(host_key) to the plaintext before encryption. After AES decrypt + PKCS7 unpad, those 32 bytes are now stripped — but only when they equal sha256(host_key). Older payloads without the prefix are left untouched, so the tool stays backward-compatible. This threads host_key into decryptValue(encryptedValue, hostKey?) and updates the extractor call sites.

Tests

  • Updated tests/decryptor.test.ts to the corrected scheme (raw password string → PBKDF2).
  • New tests assert the conditional strip: prefix present → stripped, absent (older Chrome) → untouched, and no host_key → untouched.
  • All fixtures are synthetic — no real cookies or Keychain password committed. No decrypted values are logged.
  • Full suite: 35/35 passing; tsc --noEmit clean.

Why

Unblocks authenticated auth-curl usage (a login-gated watchtell alarm) — chrome-cookies --domain <logged-in domain> --curl now emits printable-ASCII cookie values again.

Notes

dist/ is gitignored and rebuilt at publish time (prepublishOnly), so no compiled output is committed; the fix lives entirely in src/.

pchuri added 6 commits July 26, 2026 15:24
Two independent bugs produced garbage cookie values on Chrome M130+ /
macOS; both are required for a clean decrypt.

Bug A (wrong AES key): getChromeSafeStoragePassword() base64-decoded the
Keychain "Chrome Safe Storage" password before PBKDF2. The macOS v10
scheme uses that password STRING as-is as the PBKDF2 secret, so decoding
it derived a wrong 16-byte key and corrupted the entire plaintext. Pass
the raw string on both the primary and fallback return paths.

Bug B (unstripped domain hash): Chrome M130+ prepends a 32-byte
SHA-256(host_key) to the plaintext before encryption. After AES decrypt
and PKCS7 unpad, strip those 32 bytes only when they equal
sha256(host_key); older payloads without the prefix are left untouched
for backward compatibility. This threads host_key into decryptValue()
and its extractor call sites.

Unblocks authenticated auth-curl requests (login-gated watchtell alarm).

Tests use synthetic fixtures only (no real cookies/Keychain data) and
assert the conditional strip: present -> stripped, absent -> untouched.
The security CI job failed with `npm audit` HTTP 400 "Invalid package
tree, run npm install to rebuild your package-lock.json". The lockfile
was stale: the 1.1.0/1.1.1 release commits bumped package.json but never
updated package-lock.json, leaving its root version at 0.0.0-development
and drifting package metadata (devOptional vs optional). Regenerated via
npm install so the tree validates; `npm ci` is now in sync.
The npm bundled with Node 18 calls the retired quick-audit endpoint
(/-/npm/v1/security/audits/quick), which now returns HTTP 400 "Invalid
package tree", failing the security job even with an in-sync lockfile.
Upgrade to the latest npm before `npm ci`/`npm audit` so it uses the
bulk advisory endpoint. The audit stays blocking (--audit-level high).
npm@latest (12.x) requires Node >=22.22 and failed with EBADENGINE on
Node 18. Bump the security job to Node 22.x and pin npm@11, which uses
the bulk advisory endpoint (the retired quick-audit endpoint returns
400). Audit stays blocking (--audit-level high).
npm 11 reached the bulk advisory endpoint but failed to decode its
gzipped response. Node 22.x on the runner (22.23.1) satisfies npm@latest
(12.x, requires Node >=22.22), whose updated fetch stack parses the
gzipped audit response. Audit stays blocking (--audit-level high).
The npm-audit gate hits a registry-side gzip-decode failure on the bulk
advisory endpoint that no npm-version change fixes; it is tracked as a
separate cleanup task. Revert the CI experiments so this PR carries only
the decryption fix, tests, and the legitimate package-lock.json refresh.
@pchuri
pchuri merged commit f4f6ed5 into main Jul 26, 2026
3 of 4 checks passed
@pchuri
pchuri deleted the fm/cce-decrypt-fix-w1 branch July 26, 2026 06:53
github-actions Bot pushed a commit that referenced this pull request Jul 26, 2026
# [1.2.0](v1.1.1...v1.2.0) (2026-07-26)

### Bug Fixes

* **decryptor:** correct macOS v10 cookie decryption (wrong key + M130 domain hash) ([#3](#3)) ([f4f6ed5](f4f6ed5))

### Features

* **auth-curl:** add --max-time and --connect-timeout passthrough to curl ([#4](#4)) ([5e46715](5e46715))
* **auth-curl:** forward unknown curl flags to the underlying curl ([#6](#6)) ([6739734](6739734))
@github-actions

Copy link
Copy Markdown

🎉 This PR is included in version 1.2.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant