carlos / contra Public

web/7 — 1PUX import, entirely client-side Feature Request open

Depends on web/6.

The off-ramp from 1Password, and the reason most first users will show up. Spec: AGENTS.md decision 7.

Deliverable: parse 1Password's .1pux export in the browser and seal every item before anything is uploaded. Items, types, TOTP seeds, attachments and vault structure. Chosen over CSV because CSV loses nearly all of it.

The hard constraint: the export file never reaches the server in any form. An unsealed export touching the instance would break the one rule on day one — and it would do so with the single most sensitive file a user will ever hand us.

Passkeys need honest handling, not a silent drop. 1Password does not let passkeys out, so no importer can bring them across. Detect them, count them, and produce a checklist of exactly which sites need re-creating that survives the import session. See discussion 3 for the open product question on where that checklist lives — answer it there before building this part.

Done when: a real 1PUX export imports on the canary, the greps pass over the imported data, and passkey items are surfaced rather than lost.

Trap: test fixtures must be generated, never a real export. No real secret ever enters a fixture, a prompt, or a commit — this repo publishes its prompt ledger.

Branch: web/import-1pux.

🤖 Claiming this.

Branch web/import-1pux, off main — which now exists. The nine-branch stack was fast-forwarded onto it at 57d8b44 and deployed, so this is the first item to start from trunk rather than from the tip of a chain.

Starting with the parser and the sealing path, which do not depend on #3 open question. Putting that question to Paul now rather than deciding it quietly, because it changes where the checklist is stored, not just how it looks — and in an A1 product a list of the sites you hold passkeys for is content, so it has to be sealed either way.

🤖 Blocker found in the spec, before writing the passkey half: a desktop .1pux does not contain passkeys at all, and contains no marker saying an item had one.

1Password only exports passkeys from iOS and Android, and not into .1pux — they go out through the Credential Exchange standard, a different format. The desktop export this item is built around simply omits them, and 1Password's own export documentation tells the user to "create new passkeys on each website where you saved a passkey in 1Password", which is what you tell people when the file cannot tell them itself.

There is an open 1Password community request asking them to start recording passkey data in .1pux. That request existing is the proof that today it is not there.

So this item's deliverable — "detect them, count them, and produce a checklist of exactly which sites need re-creating" — cannot be built from a .1pux. An item that had a passkey exports as an ordinary Login with the passkey absent and nothing to distinguish it from a Login that never had one. No parser can recover that; it is not in the bytes.

Escalating to Paul rather than shipping a checklist that would be silently empty and read as "you have no passkeys to re-create" — which is the exact class of quiet lie the house rules forbid, and worse than saying nothing.

The parser, the sealing path and the vault/item mapping are unaffected and are proceeding.

---

Second finding, and it changes the parser's design. KeePassXC has an open bug (keepassxreboot/keepassxc#12800) where its 1pux importer silently drops passwords: a 1Password Password-category item stores its value at details.password, while Login items use details.loginFields[] with designation: "password". Their importer read only the second location and dropped the first without a warning.

That is the failure mode to design against, so this importer will not use a whitelist that skips what it does not recognise. It will reconcile: every value in the export either lands in an item or appears in a list of what did not come across. An import that cannot account for a field says so.