name: pup-browser-import description: How to import a user's existing Chrome logins (passwords, cookies/sessions) into pup's profiles to seed the adom-you profile - the App-Bound Encryption (ABE) wall, the admin/SYSTEM path that defeats it, and the hard-measured reality of what actually imports vs what is blocked. Includes the reverse-engineered playbook from OpenAI Codex Desktop's "Import from Chrome" flow. READ BEFORE building or debugging any Chrome-data import, credential-vault seeding, or "get the user's logins into pup" feature.

Importing the user's Chrome logins into pup (the adom-you seed)

✅ NOW LIVE (2026-08-04) — AD shipped the decrypt helper

The elevated decrypt (the whole blocker below) landed in Bridge v1.9.220 as desktop_decrypt_pup_credentials (this was adom/adom-bridge #51). The pup side is wired: the dashboard Settings → "Import logins from Chrome / Edge" picker (from pup_import_scan) → Import button → import-run action → chrome.adCommand('desktop_decrypt_pup_credentials', …)credentialVault.mergeImportedCredentials(rows).

THE ONE RULE THAT MATTERS: call it LOCALLY, never over the relay. It is a loopback/bridge-only verb. chrome.adCommand already routes over AD's direct API (127.0.0.1:47200 from ~/.adom/direct-api-port), which is the correct local path — so calling it from the bridge is right. A relayed/cloud call (e.g. adom-bridge-cli --target … desktop_decrypt_pup_credentials from the container) is REFUSED with errorCode: relay_forbidden and pops NO UAC. AD fires its OWN consent dialog (names the caller) + a UAC prompt — that UAC is the user's permission for the decrypt.

Call shape: { browsers:['chrome','edge'], include:['passwords'] } (add 'cookies' for best-effort cookies). Returns { ok, passwords[], cookies[], stats{profilesRead,passwordsOk,passwordsFailed, cookiesOk,cookiesFailed} }. Each password row: {origin, username, password, sourceBrowser, sourceProfile, sourceAccount}. errorCodes to handle: relay_forbidden (you called it wrong — call locally), consent_declined (user said no at AD dialog), elevation_declined (user cancelled UAC), helper_missing (broken install). Per-item decrypt failures land in stats.*Failed and are NEVER fatal — newest Chrome v20 blobs are best-effort if Google rotated the elevation-service key; legacy v10/v11 always decrypt.

Plaintext discipline (do this exactly): the plaintext comes back ONCE over loopback and AD never logs it. pup must not either: hand rows straight to mergeImportedCredentials (keytar), then ZERO the password fields; NEVER log a password, NEVER echo it into another verb's args or the dashboard response (the import-run handler returns COUNTS ONLY). The vault stores each password in the OS keychain under a per-entry composite account and keeps only host+username+identity tags in the index.

The pup core goal (see the root SKILL.md) is to build up an adom-you profile: the pup browser accretes the user's real logins so the agent can act as them. The fastest seed for that is to import the logins the user ALREADY has in their everyday Chrome. This file is the hard-won map of what is actually possible on a modern (2024+) Windows Chrome, so the next maintainer does not re-derive it or over-promise a silent import that Windows blocks by design.

Most of what is below was learned on 2026-08-02/03 by watching OpenAI's Codex Desktop run its own "Import from Chrome" flow on John's machine, side by side with our own probes. John's framing: "the info about Codex's approach is gold." It is - it told us exactly which walls are real.

The core obstacle: Chrome App-Bound Encryption (ABE)

Since Chrome 127 (Aug 2024) on Windows, Chrome no longer protects your passwords/cookies with a key a normal user process can unwrap. Instead:

  • Chrome ships elevation_service.exe, a helper that runs as SYSTEM. It owns the master key that decrypts cookies and passwords, and it stores that key double-wrapped with DPAPI - the outer layer under the SYSTEM account, not the user. In Local State this is the os_crypt.app_bound_encrypted_key field (base64, APPB prefix).
  • A normal user-context app (our Chrome for Testing, a plain PowerShell/DPAPI helper, Codex's non-elevated code) runs as the user, so it cannot unwrap the SYSTEM layer. This is the whole point of ABE: it assumes malware runs as a normal user, so it puts the key one privilege level up.
  • Encrypted blob versions tell you which scheme protects a value:
    • v10 / v11 = legacy os_crypt (user-DPAPI key). A same-user app CAN decrypt these.
    • v20 = App-Bound. Needs the SYSTEM-level app-bound key. A user app canNOT decrypt these.

Measured reality on John's Chrome 150 (2026-08-03)

Probed via adom-bridge-cli shell_execute (PowerShell + the desktop's Python, which parsed the actual Login Data SQLite):

  • chrome_version = 150.0.7871.187
  • Local State has BOTH app_bound_encrypted_key (ABE) AND the legacy encrypted_key.
  • 4 profiles: Default, Profile 1 (main - 873 logins), Profile 2, Profile.
  • Profile 1 Login Data: 873 saved passwords, 829 non-empty, and EVERY non-empty blob is v20. ZERO v10/v11. (A naive byte-scan first reported "784 v10" - that was pure binary noise; parsing the SQLite logins table showed the truth: all v20.)

Lesson: do not byte-scan for v10/v20 counts. Parse the SQLite logins.password_value blobs. The desktop has python on PATH (sqlite3 stdlib) - use it, don't hand-roll SQLite in PowerShell.

Consequence: a DPAPI / copy-the-file import gets ZERO passwords and ZERO cookies on this machine. Everything is v20. That is why our first plan (silent same-user DPAPI import) was dead on arrival.

The Codex Desktop playbook (reverse-engineered from its UI)

Codex's "Import from Chrome" is the reference implementation of doing this RIGHT. What we observed:

  1. Import prompt on first run ("Import data from Chrome - bring over your passwords and cookies to the built-in browser", with an Import button). So they seed on first run - mirror this.
  2. Import dialog with a source-profile picker ("From: Google Chrome adom.inc"), separate Passwords and Cookies toggles (both default ON), and a required consent line:

    "Administrator approval required. Windows protects Chrome cookies and passwords with App-Bound Encryption, so ChatGPT also needs administrator approval." + an "I understand the app will request administrator approval" checkbox. That admin approval is a UAC elevation - it is how they reach SYSTEM to unwrap the ABE key. There is no way around the prompt; ABE is specifically designed to force it.

  3. Result dialog ("Import complete"):
    • Passwords: succeeded (checkmark). So the admin/SYSTEM path DID decrypt the v20 passwords.
    • Cookies: FAILED - "ChatGPT couldn't access this profile's cookies. A device security policy may be blocking access."

What the result teaches us (the gold)

  • Passwords ARE recoverable from a modern ABE Chrome - but only with administrator/SYSTEM. The admin helper becomes SYSTEM, does the same DPAPI unwrap elevation_service.exe does, recovers the master key, and decrypts v20. Any earlier claim that "even passwords are locked" is WRONG without the admin qualifier: locked to a user process, open to a SYSTEM one.
  • Cookies can be blocked EVEN WITH admin, by a device/enterprise security policy. John's Chrome profile is adom.inc - an enterprise-managed profile. The cookie unwrap failed where the password unwrap succeeded, so the block is cookie-specific and policy-driven (candidates: Intune/MDM device policy, a Chrome enterprise policy, DLP, or an ASR rule that specifically guards the cookie store / the elevation-service cookie path). It is NOT a generic "ABE is unbreakable" wall - the passwords proved the ABE break works on this box; something extra guards the cookies.
  • Net: on a managed device, plan for passwords to import and cookies to be refused. Do not build the feature such that a cookie failure blocks the password import (Codex reports them independently - do the same).

Implications for pup's import feature (the plan — COMPREHENSIVE, all profiles, LOCKED 2026-08-04)

John (2026-08-04): "i have [email protected], [email protected], and [email protected] profiles... most users will have lots of profiles... you should import on ALL of the user's profiles to make a comprehensive adom-you profile that pup can use. that gives the AI the most power." The earlier "Profile 1 only" plan was too narrow. Measured on John's box: 4 Chrome profiles (Default, Profile, Profile 1 [819KB], Profile 2) + 1 Edge profile (Default), ALL ABE=True. So the feature is ALL profiles across ALL Chromium browsers.

STRUCTURAL FACT (verified): the ABE master key lives in each browser's SHARED User Data/Local State (one key per BROWSER), while each PROFILE has its own Login Data. So per browser: unwrap the key ONCE, decrypt EVERY profile with it.

  1. Discover everything. Enumerate every profile of every Chromium browser (Chrome, Edge, Brave if present), reusing HD's existing browser-profile-scan logic. For each profile read Preferencesaccount_info[].email to get the SOURCE ACCOUNT NAME ([email protected] vs [email protected] vs [email protected]) — the identity tag is the whole point.
  2. Passwords via ONE admin-elevated SYSTEM helper, ALL profiles. AD-side (the container can't touch Windows DPAPI or elevate — this half is AD-CORE work, file it there): one UAC → SYSTEM token → for EACH browser, unwrap os_crypt.app_bound_encrypted_key (strip APPB, SYSTEM-DPAPI unwrap, then user-DPAPI unwrap) to the AES-256-GCM master key → for EACH profile of that browser, copy Login Data, decrypt every v20 password_value (3-byte version prefix, 12-byte nonce, ciphertext, 16-byte GCM tag). Returns rows {origin, username, password, sourceBrowser, sourceProfile, sourceAccount, lastUsed}. ONE elevation covers Chrome + Edge + all profiles.
  3. Merge into ONE identity-tagged adom-you vault (src/credential_vault.js, pup-side, ours). DECISION 1 (locked): keep BOTH on a (origin,username) conflict, tagged by identity — do not collapse to newest. A site saved under personal AND work stays as two tagged entries so the agent can act as the RIGHT persona; pick by context (the pup window's owner/profile/thread), fall back to most-recently-used. This is the "most power" John wants.
  4. Feed the EXISTING autoLogin so the agent logs into any site with the matching tagged credential and builds FRESH sessions. DECISION 2 (locked): import passwords + bookmarks + autofill (bookmarks/autofill are DPAPI-cheap and ride the same pass); cookies stay best-effort (blocked by the managed-profile device policy — see below). Never write a plaintext CSV/file. Decrypt in memory, hand to the vault, zero the buffers.
  5. DECISION 3 (locked) — ownership: the elevated SYSTEM decrypt helper is AD-core (file against adom/adom-bridge); pup owns discovery-surfacing, the merge, the vault, and the UX.
  6. Cookies: attempt, but treat failure as normal. Same SYSTEM helper can try each profile's Cookies store (Network\Cookies, v20, same per-browser master key). On a managed profile expect the "device security policy" refusal - surface it honestly per-item (Codex-style), do not fail the run. Passwords are the deliverable; sessions come from pup logging in with them.
  7. First-run prompt + a Settings "Import logins from Chrome/Edge" section (John's explicit ask). Lists EVERY discovered profile across ALL browsers with its account name + saved-password count and a checkbox each (DEFAULT: all checked). One Import button → ONE UAC prompt → the whole SYSTEM pass → merged into the tagged vault. Show the honest consent about the admin prompt (it's the user's own data, own machine, own elevation — same as Codex/any password-manager import).

Passkeys and camera (asked about, decided)

  • Passkeys canNOT be imported by anyone - not Codex, not us. The private key is TPM/Windows-Hello bound and non-exportable BY DESIGN. Codex "enumerating passkeys" (its Windows-auth escalation window) = listing/using platform credentials via the Windows WebAuthn API, not copying them. pup's play is to let a pup window USE the user's Windows Hello passkeys during a WebAuthn sign-in (a real CfT window on the desktop can invoke the platform authenticator like normal Chrome - VERIFY this fires before promising it). Never claim passkey "import".
  • Camera: not needed. Codex asked for it; pup has no reason to. Do not request it.

Ethics / framing (so this does not get mis-read as an attack tool)

This is the user decrypting their OWN browser data, on their OWN machine, with an explicit UAC admin prompt - exactly what every password-manager "import from Chrome" and Codex/ChatGPT Desktop itself does. It is legitimate. It is ALSO defeating a security control, so:

  • Always require the real UAC elevation - never try to hide it or auto-approve it.
  • Never build a path that reads another user's profile or a profile without the live consenting user. Same-machine, same-user, admin-consented only.
  • Get the user's explicit go before shipping the elevated helper (John gave it for the design; get it again before the elevated code lands).

Open questions to verify before shipping

  • Exactly which policy blocks cookies on the adom.inc managed profile (Intune? Chrome enterprise policy? ASR?) - and whether an UNmanaged profile on the same box imports cookies fine. That tells us whether cookie import is ever worth offering or should be quietly omitted on managed devices.
  • Whether AD already has an elevate-to-SYSTEM primitive or that helper is net-new AD-core work (this is an AD-boundary question - see adom-desktop-bridge-ownership; the elevation primitive is probably AD-core, the Chrome-blob decryption + vault import is ours).
  • Whether a pup CfT window actually triggers Windows Hello for a passkey WebAuthn ceremony, or whether automated Chrome silently routes to a virtual authenticator and needs a flag.

One-line summary

Modern Chrome (127+) locks passwords AND cookies with App-Bound Encryption; a user process gets nothing (v20 everywhere). Admin -> SYSTEM -> DPAPI-unwrap recovers the master key and decrypts passwords (proven by Codex on John's box); cookies can still be refused by an enterprise/device security policy. Build the password import on the admin/SYSTEM helper, feed the credential vault + autoLogin, treat cookies as best-effort, and never pretend to import passkeys.