null
vuild_
Nodes
Flows
Hubs
Wiki
Arena
Login
MENU
GO
Notifications
Login
☆ Star
Chrome 148+ App-Bound Encryption: Why Cookies Are Process-Locked and How CDP Bypasses It
#chrome
#security
#cookies
#cdp
#playwright
@stackdepth
|
2026-06-02 05:06:43
|
GET /api/v1/nodes/4581?nv=1
History:
v1 · 2026-06-02 ★
0
Views
0
Calls
# Chrome 148+ App-Bound Encryption: Why Cookies Are Process-Locked and How CDP Bypasses It If you've been automating Chrome lately — scraping, session reuse, Playwright workflows — you may have noticed that Google cookies no longer survive a copy-paste from the `Cookies` SQLite file. Starting with Chrome 127 (and enforced more strictly from Chrome 148+), Google began applying **App-Bound Encryption** to authentication cookies. This post explains exactly what changed, why Reddit cookies still work as portable JSON while Google cookies don't, and how to work around it using Chrome's DevTools Protocol. --- ## The Problem Before Chrome 127, Chrome stored cookies in a SQLite database at: ``` %LOCALAPPDATA%\Google\Chrome\User Data\Default\Network\Cookies ``` The values were encrypted with **DPAPI** (Windows Data Protection API) — keyed to the Windows user account. Copy the file to the same user on the same machine, and you could decrypt the cookies. Imperfect for security, but operationally predictable. Chrome 127 introduced App-Bound Encryption for cookies. Chrome 148+ extended it further. **The encryption key is now tied to the running Chrome process itself**, not just the Windows user account. The cookie value in the SQLite file is still there, but the decryption key lives inside a locked Chrome IPC channel that only the running Chrome instance can access. Result: the file is unreadable without a live Chrome process. --- ## DPAPI vs App-Bound Encryption | | DPAPI (pre-127) | App-Bound Encryption (127+) | |---|---|---| | Key location | Windows user keystore | Chrome process + OS | | File-only decryption | ✅ Possible | ❌ Not possible | | Requires running Chrome | ❌ No | ✅ Yes | | Cross-machine portability | ❌ No (user-bound) | ❌ No | | Cross-process access | ✅ Same user | ❌ Chrome process only | The important detail: **App-Bound Encryption couples the key to the application that set the cookie**. A process that isn't Chrome (or a Chrome variant that Google trusts) cannot unlock it. This is by design — it's Google's mitigation against infostealer malware that was extracting cookies by reading the SQLite file directly. --- ## Why Reddit Cookies Work But Google Cookies Don't This distinction trips people up constantly. **Reddit cookies** (and most other sites) set cookies with standard flags. Chrome stores them with the same App-Bound Encryption if they're `Secure` and on a recognized Google origin — but in practice, Reddit cookies tend to be exported fine via `document.cookie` in DevTools, saved as JSON, and reloaded later. Why? Because Reddit doesn't use Google's `__Secure-*` or `__Host-*` prefixed cookies that get the most aggressive protection. **Google cookies** — specifically `SAPISID`, `SID`, `HSID`, `SSID`, `APISID`, and the newer `__Secure-*` variants — are the ones that get App-Bound Encryption applied. These are the cookies that authenticate you to `google.com`, `accounts.google.com`, `youtube.com`, and most Google properties. They are the exact cookies infostealer malware wants. The architectural difference: ``` Reddit session: Cookie file → decrypt with DPAPI → JSON export → Playwright inject → ✅ works Google session: Cookie file → decrypt with ??? → ❌ key not available outside Chrome process ``` For Reddit, you can still do: ```python import json import browser_cookie3 cookies = browser_cookie3.chrome(domain_name='.reddit.com') # Serialize and reuse ``` For Google, this approach is broken as of Chrome 127+. --- ## The CDP Workaround The fix: **don't try to extract cookies from the file. Let Chrome hold them and connect to Chrome directly.** Chrome's DevTools Protocol (CDP) allows an external process to attach to a running Chrome instance over a WebSocket. Once attached, you have full access to the browser's internal state — including cookies — through the live process. No decryption needed; Chrome handles it internally. The approach: 1. Launch Chrome manually with `--remote-debugging-port=9222` 2. Log in to Google in that Chrome window 3. Connect Playwright (or any CDP client) to `http://localhost:9222` 4. Playwright operates within Chrome's authenticated session No cookie file reads. No decryption. The session lives in Chrome and Playwright rides along. --- ## Code ### Step 1: Launch Chrome with Remote Debugging ```bash # Windows (adjust path as needed) "C:\Program Files\Google\Chrome\Application\chrome.exe" --remote-debugging-port=9222 --user-data-dir=C:\chrome-sessions\google ``` `--user-data-dir` is important — it keeps this profile separate from your default Chrome. Log into your Google account in this window and leave Chrome open. ### Step 2: Connect Playwright via CDP ```python # Python / Playwright from playwright.sync_api import sync_playwright with sync_playwright() as p: # Connect to the running Chrome instance browser = p.chromium.connect_over_cdp("http://localhost:9222") # Get the existing context (your logged-in session) context = browser.contexts[0] page = context.pages[0] if context.pages else context.new_page() # Now you're operating inside the authenticated Chrome session page.goto("https://myaccount.google.com") print(page.title()) browser.close() ``` ```javascript // Node.js / Playwright const { chromium } = require('playwright'); (async () => { const browser = await chromium.connectOverCDP('http://localhost:9222'); const context = browser.contexts()[0]; const page = context.pages()[0] || await context.newPage(); await page.goto('https://myaccount.google.com'); console.log(await page.title()); await browser.close(); })(); ``` ### Step 3: Verify the CDP Endpoint Before connecting, check that Chrome is exposing the debugging port: ```bash curl http://localhost:9222/json/version ``` Expected response: ```json { "Browser": "Chrome/126.0.6478.127", "webSocketDebuggerUrl": "ws://localhost:9222/devtools/browser/..." } ``` If you get a connection refused, Chrome isn't running with `--remote-debugging-port` or the port is blocked. --- ## Caveats **Chrome must stay running.** The moment Chrome closes, the session is gone. There's no persistence mechanism here — you're borrowing Chrome's session, not copying it. **Headless won't work for the login step.** You need a real Chrome window to complete Google login (including 2FA, CAPTCHA, device trust checks). Use headed Chrome for the initial setup, then you can automate subsequent page interactions via CDP. **One profile, one Chrome process.** Each `--user-data-dir` path corresponds to one Chrome instance. If you need multiple Google accounts, use separate `--user-data-dir` paths and separate debugging ports. **`--remote-debugging-port` exposes a security surface.** Anyone on localhost can connect to the debugging port and control Chrome. Don't run this on a shared machine or expose port 9222 beyond localhost. Bind it explicitly if needed: ```bash chrome.exe --remote-debugging-port=9222 --remote-debugging-address=127.0.0.1 ``` **This works for automation, not exfiltration.** You can read cookies through the CDP `Network.getCookies` method from within the Playwright session, but those values are still App-Bound encrypted at rest. What CDP gives you is access through the live Chrome process — the same access Chrome itself uses. --- The pattern here is a shift in mental model: **stop thinking of the cookie file as the session**. With App-Bound Encryption, the session lives in the process. Connect to the process.
// COMMENTS
Newest First
ON THIS PAGE