How gstack Handles Cookie Security and Keychain Access: Cross-Platform Chromium Cookie Import
gstack implements a self-contained, cross-platform Chromium cookie importer that isolates credential handling by querying the macOS Keychain or Linux libsecret via audited shell commands, caching derived AES keys in memory only, and ensuring raw cookie values never appear in the user interface.
The garrytan/gstack repository provides a CLI tool for importing browser cookies into Playwright-compatible formats. Its architecture prioritizes cookie security by separating the decryption logic from the main application and strictly controlling how credentials are retrieved from system secret stores.
The Chromium Cookie Import Pipeline
The import process in browse/src/cookie-import-browser.ts follows a five-stage pipeline that minimizes exposure of sensitive material.
Profile Discovery Without User Input
The system locates browser profiles through findInstalledBrowsers(), listProfiles(), and getBrowserMatch(). These functions traverse standard data directories for Chromium-based browsers—including Chrome, Chromium, Arc, Brave, Edge, and Comet—using the hard-coded BROWSER_REGISTRY array. This design deliberately rejects user input for browser identification, preventing path traversal attacks.
Read-Only Database Access
The openDb() function opens the Cookies SQLite database in read-only mode on macOS and Linux. On Windows, it creates a temporary copy to avoid Write-Ahead Logging (WAL) lock contention, ensuring the original database remains unmodified.
Key Derivation from macOS Keychain and Linux libsecret
The most security-critical phase derives the AES-128-CBC decryption key using platform-specific secret stores:
-
macOS (v10 cookies) – Executes
security find-generic-password -s <service>to retrieve the password from the user's Keychain. The service name (e.g., "Chrome Safe Storage") originates from the hard-codedkeychainServicefield, never from user input. -
Linux (v10) – Uses the static string
"peanuts"as the password, consistent with Chromium's legacy implementation. -
Linux (v11) – Queries libsecret via
secret-tool lookup(or XDG schema variants) to obtain the stored password. -
Windows – Leverages DPAPI through Node/Bun crypto APIs.
The implementation performs PBKDF2 key derivation using 1,003 iterations on macOS and 1 iteration on Linux. The resulting key is cached in a module-level keyCache (a Map<string, Buffer>) keyed by platform and browser service name, ensuring expensive Keychain or secret-tool invocations occur only once per session.
AES Decryption in Memory
The decryptCookieValue() function processes only cookies with encrypted_value headers matching v10 or v11. It constructs the AES-128-CBC parameters using an initialization vector of 16 bytes set to 0x20, decrypts the ciphertext (bytes following the version prefix), and strips the 32-byte Chromium metadata before returning the plaintext. This operation occurs entirely in memory without intermediate disk storage.
Sanitized Output to UI
The final ImportResult object contains Playwright-compatible cookie objects and per-domain statistics, but never the raw key material. The setup-browser-cookies skill documentation confirms the UI displays only domain names and counts, with an explicit guarantee that no cookie values are shown.
Security-Focused Implementation Details
Command Injection Prevention
All shell invocations use static string templates. The source code contains an explicit comment enforcing: "Hardcoded — NEVER interpolate user input into shell commands." This applies to the security CLI on macOS and secret-tool on Linux, eliminating injection vectors.
Credential Lifecycle Management
Passwords retrieved from the macOS Keychain or Linux libsecret exist only in volatile memory during the decryption session. The implementation guarantees these values are never logged to stdout, written to disk, or included in error traces.
Platform-Specific Secret Handling
| Platform | Mechanism | Implementation |
|---|---|---|
| macOS | Keychain | security find-generic-password CLI |
| Linux v11 | libsecret | secret-tool lookup or XDG schema queries |
| Windows | DPAPI | Native Node/Bun crypto APIs |
Key Reuse and Caching
The keyCache Map prevents repeated authentication prompts by storing derived AES keys in memory. Each entry is keyed by a composite string including the platform identifier and browser service name, balancing performance with the security requirement to minimize Keychain access frequency.
Practical Usage Examples
# Import all cookies from the default Chrome profile
$B cookie-import-browser
# Import only github.com cookies from Brave's Default profile
$B browser-import-browser brave --domain github.com --profile "Default"
# Skip interactive picker and import from Comet's Profile 2
$B cookie-import-browser comet --profile "Profile 2" --domain example.com
These commands invoke the cookie-import-browser binary, which internally executes the secure pipeline described above without exposing credentials in process lists or logs.
Key Source Files
-
browse/src/cookie-import-browser.ts– Core implementation containingBROWSER_REGISTRY,openDb(), and the Keychain/libsecret integration logic. -
setup-browser-cookies/SKILL.md– User-facing documentation defining the skill interface, explaining the Linuxsecret-toolprerequisite, and confirming that cookie values remain hidden from the UI. -
browse/test/cookie-import-browser.test.ts– Test suite validating the macOS Keychain and Linux secret-tool pathways through mocks, ensuring the security logic functions correctly across platforms.
Summary
- gstack isolates cookie decryption in
browse/src/cookie-import-browser.ts, separate from Playwright and HTTP handling code. - macOS Keychain access uses the hardened
securityCLI with hardcoded service names, while Linux libsecret integration relies onsecret-toolfor v11 cookies. - Derived AES keys are cached in a
keyCacheMap to minimize authentication prompts, but credentials never persist to disk or logs. - The UI layer in
setup-browser-cookies/SKILL.mddisplays only domain counts, guaranteeing cookie values remain confidential. - All shell commands use static strings to prevent command injection, with explicit guards against user input interpolation.
Frequently Asked Questions
How does gstack access the macOS Keychain without prompting the user repeatedly?
According to the source code in browse/src/cookie-import-browser.ts, gstack caches derived AES keys in a module-level keyCache Map keyed by platform and browser service name. This ensures the security find-generic-password command runs only once per session, after which the derived key resides in memory for subsequent decryptions.
Why does gstack require secret-tool on Linux but not macOS?
Linux v11 cookies use libsecret (via secret-tool or XDG schema lookups) to retrieve the master password stored by Chromium, whereas macOS v10 cookies use the native Keychain. The Linux v10 implementation uses a static derivation string ("peanuts"), but modern Chromium versions on Linux require the dynamic secret lookup that libsecret provides.
Are imported cookie values ever written to disk or displayed in the terminal?
No. The decryptCookieValue() function processes cookies entirely in memory, and the setup-browser-cookies skill explicitly documents that the UI shows only domain names and counts. Raw cookie values and Keychain passwords are never logged, written to temporary files, or exposed in error messages.
What prevents malicious input from injecting commands during cookie import?
The implementation uses a hard-coded BROWSER_REGISTRY array to identify browsers, and all shell commands (including security and secret-tool invocations) are static strings. The source code contains an explicit comment prohibiting user input interpolation into command arguments, eliminating command injection vulnerabilities.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →