The Hypothesis
I have a self-hosted video merging service — Merge Video. It downloads YouTube videos with yt-dlp, merges them with ffmpeg, and uploads the result back to YouTube.
On localhost everything worked. On a cloud server:
ERROR: Sign in to confirm you're not a bot.
Use --cookies-from-browser or --cookies for authentication.
YouTube detected the datacenter IP and blocked downloads. The fix seemed obvious: extract cookies from Chrome, upload to server, done.
My hypothesis: extracting cookies is a solved problem. Dozens of tools exist. Should take 10 minutes.
Reality: I spent 3 hours trying 6 different methods. Only one worked — and it wasn't Chrome.
What Debugging Cookies Taught Me About Strategy
I run strategic sessions — 6-to-10-hour workshops where teams map complex systems before making decisions. We draw the object of management, analyze its place in a larger system, do hindsight and foresight, and only then build a plan. Over 150 sessions so far.
The cookie debugging was the exact opposite of this process.
I had an AI coding assistant — Antigravity by Google DeepMind — writing scripts, testing APIs, trying approaches. It's incredibly fast. But here's what happened: we burned through 6 attempts in 3 hours because neither of us stopped to map the system first.
The moment I paused and asked: "What's the plan? Pros, cons, risks, options?" — the answer became obvious in minutes:
| Without strategy | With strategy |
|---|---|
| Try DPAPI → fail | Map the landscape: Chrome encrypts, Firefox doesn't |
| Try rookiepy → fail | Identify constraints: app-bound encryption since July 2024 |
| Try CDP → fail | Evaluate options: only 2 viable paths (Firefox or proxies) |
| Try OAuth → fail | Choose: Firefox = free, proxies = $1.50/GB |
| Try Bearer token → fail | Execute: 10 lines of Python, done |
| Try Firefox → works | |
| 6 attempts, 3 hours | 1 attempt, 5 minutes |
This is the pattern I see in every strategic session: teams that jump to solutions before mapping the system waste time on dead ends. Teams that invest 30 minutes in analysis often find the answer without trying a single wrong approach.
AI tools are powerful executors. They'll write any script you ask for in seconds. But they'll also happily execute 5 wrong approaches before someone asks: "Wait — what are we actually dealing with?"
The human's job isn't to write code. It's to bring strategy — to ask the right questions before the first line of code is written.
⚠️ First, a Security Warning
Before you Google "export Chrome cookies" — read this.
When I first asked Antigravity to help extract cookies, its very first suggestion was to install a popular Chrome extension — Get cookies.txt. Quick, easy, thousands of users. I didn't install it — something felt off about giving a third-party extension access to all my browser sessions.
Good instinct. That extension turned out to be malware. It was silently sending all your cookies — login sessions, banking tokens, everything — to its developer. Google removed it from the Chrome Web Store and flagged it as malware.
This isn't an isolated case. Any browser extension with cookie access permissions can steal your sessions. Cookie-stealing malware like Raccoon, RedLine, and Vidar has been doing exactly this for years.
The rule: never install a third-party extension to export cookies. Use built-in tools or read the database directly.
Attempt #1: DPAPI Decryption Script
Chrome encrypts cookies with DPAPI + AES-256-GCM on Windows. I wrote a Python script to call CryptUnprotectData via ctypes:
class DATA_BLOB(ctypes.Structure):
_fields_ = [("cbData", ctypes.wintypes.DWORD),
("pbData", ctypes.POINTER(ctypes.c_char))]
result = ctypes.windll.crypt32.CryptUnprotectData(
ctypes.byref(input_blob), None, None, None, None, 0,
ctypes.byref(output_blob)
)
Result: CryptUnprotectData failed
Why: Chrome 127+ (July 2024) introduced app-bound encryption. The decryption key is now bound to the Chrome binary itself. External programs — even running as the same user — can't decrypt cookies anymore.
Attempt #2: rookiepy
rookiepy is a Rust-based Python library built specifically for extracting browser cookies. It handles modern Chrome encryption:
import rookiepy
cookies = rookiepy.chrome([".youtube.com", ".google.com"])
Ran it as Administrator.
Result: RuntimeError: decrypt_encrypted_value failed
Why: Same app-bound encryption. Even with admin privileges, no external process can access Chrome's cookie encryption key on Chrome 127+.
Attempt #3: Chrome DevTools Protocol (CDP)
If you can't decrypt cookies externally, maybe ask Chrome directly. Chrome's DevTools Protocol has Network.getAllCookies:
ws.send(json.dumps({"id": 1, "method": "Network.getAllCookies"}))
cookies = json.loads(ws.recv())["result"]["cookies"]
This requires starting Chrome with --remote-debugging-port=9222.
Result: TCP connect to (127.0.0.1 : 9222) failed
Why: On Windows, if Chrome is already running, a new instance with the debug flag opens as a window in the existing process — without the debugging port. I killed Chrome, restarted with the flag. Chrome still wouldn't bind to port 9222. Possibly Windows Defender or a Chrome policy blocking it.
Attempt #4: yt-dlp Native OAuth2
yt-dlp added native OAuth2 support in 2024. It uses YouTube TV's device code flow:
yt-dlp --username oauth2 --password '' https://youtu.be/...
# Go to google.com/device and enter code: XXXX-XXXX
Result: Login with OAuth is no longer supported
Why: YouTube deprecated this authentication method. It worked for a few months, then Google killed it. The yt-dlp wiki now says: "Use --cookies instead."
Attempt #5: Per-User OAuth Bearer Token
My app already had Google OAuth for YouTube uploads. Users sign in, I get their access_token. Why not pass it to yt-dlp?
cmd.extend(["--add-header", f"Authorization:Bearer {access_token}"])
Result: Request had insufficient authentication scopes
Why: yt-dlp uses YouTube's InnerTube API internally, not the YouTube Data API v3. They're completely different authentication systems:
| API | Authentication | Used by |
|---|---|---|
| YouTube Data API v3 | OAuth2 Bearer token | App uploads |
| YouTube InnerTube API | Session cookies | yt-dlp downloads |
A Data API token simply can't authenticate InnerTube requests.
Attempt #6: Firefox 🏆
After 5 failures with Chrome, I tried Firefox.
Firefox doesn't encrypt cookies.
Firefox stores cookies in a plain SQLite database at %APPDATA%/Mozilla/Firefox/Profiles/*/cookies.sqlite. No DPAPI. No AES-GCM. No app-bound encryption. Just SQL:
import sqlite3, shutil, glob, os
profiles = os.path.join(os.environ["APPDATA"], "Mozilla", "Firefox", "Profiles")
db = max(glob.glob(f"{profiles}/*/cookies.sqlite"), key=os.path.getsize)
shutil.copy2(db, "tmp.sqlite") # Copy — Firefox locks the file
conn = sqlite3.connect("tmp.sqlite")
rows = conn.execute(
"SELECT host, name, value, path, expiry, isSecure "
"FROM moz_cookies WHERE host LIKE '%youtube%' OR host LIKE '%google%'"
).fetchall()
print(f"✅ {len(rows)} cookies extracted")
Result: ✅ 67 cookies saved
10 lines of Python. No admin rights. No encryption libraries. No fighting the browser.
The Scoreboard
| # | Method | Blocked by | Time |
|---|---|---|---|
| 1 | DPAPI + AES-GCM | Chrome 127+ app-bound encryption | 30 min |
| 2 | rookiepy (Rust lib) | Same encryption, even as admin | 15 min |
| 3 | Chrome DevTools Protocol | Port 9222 wouldn't bind | 40 min |
| 4 | yt-dlp OAuth2 | Deprecated by YouTube | 20 min |
| 5 | Per-User Bearer token | Wrong API (InnerTube ≠ Data API) | 30 min |
| 6 | Firefox SQLite | Nothing | 5 min |
Hypothesis vs Reality
| What I expected | What actually happened |
|---|---|
| Cookie extraction is a solved problem | Chrome made it unsolvable in July 2024 |
| Admin rights bypass any protection | App-bound encryption ignores admin |
| OAuth tokens work across Google APIs | InnerTube ≠ Data API |
| yt-dlp has built-in auth | YouTube deprecated it |
| Chrome extensions export cookies safely | The most popular one was malware |
| It should take 10 minutes | It took 3 hours |
Why Chrome Does This
Chrome's app-bound encryption exists for a good reason: protecting users from cookie-stealing malware. Before Chrome 127, any program running as the same Windows user could read all Chrome cookies — login sessions, banking tokens, everything.
Google's fix: bind the decryption key to the Chrome binary. Only Chrome's own process can decrypt cookies. This blocks Raccoon, RedLine, Vidar, and all similar stealers.
The collateral damage: legitimate tools like yt-dlp, password managers, and cookie exporters also break.
Firefox took a different approach: cookies are plain SQLite, and security relies on OS-level protections — file permissions and user isolation. Neither approach is objectively better. Chrome prioritizes defense against same-user malware. Firefox prioritizes interoperability.
The Deployment
After extracting cookies, deployment was straightforward:
# Extract on local machine (Windows)
python extract_cookies.py
# → cookies.txt (67 YouTube/Google cookies, Netscape format)
# Upload to server
scp cookies.txt user@server:/opt/app/backend/
yt-dlp uses them automatically:
COOKIES_FILE = Path(__file__).parent / "cookies.txt"
cmd = ["yt-dlp", "-f", "bestvideo+bestaudio", ...]
if COOKIES_FILE.exists():
cmd.extend(["--cookies", str(COOKIES_FILE)])
Trade-off: Cookies expire in ~2 weeks. When they do: open Firefox, visit YouTube, close Firefox, re-run the script. 60 seconds.
Key Takeaways
Start with Firefox. Chrome's encryption makes external cookie extraction impossible since July 2024.
Never install cookie-export extensions. The popular "Get cookies.txt" was literal malware. Read the SQLite database directly.
Check the timeline. Any cookie extraction tutorial from before Chrome 127 (mid-2024) won't work on modern Chrome.
InnerTube ≠ Data API. If you're building around yt-dlp, Google OAuth tokens from your app won't help — yt-dlp needs browser cookies, not API tokens.
Don't fight the browser. When a browser actively resists external access, use a browser that doesn't.
Where It Stands Now
| Component | Status |
|---|---|
| Cookie extraction (Firefox) | ✅ Working |
| yt-dlp downloads with cookies | ✅ Working |
| Server deployment | ✅ Deployed |
| YouTube upload after merge | ✅ OAuth2 |
| Cookie auto-refresh | ❌ Manual (every ~2 weeks) |
The extraction script and the full project are open source: github.com/maximosovsky/merge-video
This is part of a series about building Merge Video — a self-hosted video merging service. Previous: I Tried to Merge 52 Video Files Automatically.
Building in public, one utility at a time. Follow the journey: LinkedIn · GitHub

Top comments (0)