---
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 jlauer12@gmail.com, john@adom.inc, and media@adom.inc 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 `Preferences`
   → `account_info[].email` to get the SOURCE ACCOUNT NAME (jlauer12@gmail.com vs john@adom.inc vs
   media@adom.inc) — 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.
