# Passchmop A Firefox password manager whose vault is a plain git repository. Every entry is a client-side-encrypted blob; the remote only ever sees ciphertext. Multiple devices sync by pushing to the same repo — conflicts resolve deterministically on-device. No server. No cloud account. You bring the repo. ## Features - **Your repo is the backend.** Any HTTPS-accessible git remote works — GitHub, GitLab, Gitea, Forgejo, self-hosted. One encrypted file per entry under `entries/.enc`. - **Your device holds the key.** Master password → PBKDF2-SHA256 (600 k iterations, 16-byte random salt) → AES-GCM-256. The remote sees only ciphertext; no verifier for the password is ever transmitted in the clear. - **Cross-device sync.** Sync on popup open, on every add/edit/delete, and on a 2-minute background timer. The vault stays unlocked for 5 minutes of inactivity (timer resets on use). - **Deterministic conflict resolution.** Edit/edit produces a `.conflict-.enc` sidecar the user resolves in the UI. Edit/delete always keeps the edit (no silent data loss). Add/add is impossible because entry IDs are random UUIDs. - **Origin-aware list.** Entries matching the active tab's origin surface to the top; URL prefill on "+ Add" uses just the origin. - **Autofill button.** Click ✎ Autofill on an expanded entry — the popup injects a small form-heuristic script into the active tab via `browser.scripting.executeScript`, finds the first visible `input[type=password]`, walks back for the nearest username-ish input in the same form, and fills both using the prototype setter so React/Vue/Angular controlled inputs observe the change. - **Clipboard auto-clear.** Copied credentials blank out of the clipboard after 30 s. ## Install from source ``` git clone …/passchmop.git cd passchmop npm install npm run build npx web-ext run ``` `web-ext run` launches a temporary Firefox instance with the extension loaded. For permanent install, package with `npm run package` and install the resulting `.xpi` via `about:debugging → This Firefox → Load Temporary Add-on` (or sign it for proper installation). **Firefox 115+ required** — we rely on Manifest V3 features including `browser.storage.session`. ## First-run setup 1. Click the Passchmop toolbar icon → "Open setup". 2. Enter your git repo URL (HTTPS), a username, and a personal access token with write access to the repo. 3. Pick **Create new vault** (empty repo, or a repo that only contains an auto-README) or **Join existing vault** (a repo that was set up by another Passchmop device). 4. Choose a master password. It never leaves this device. 5. The extension commits `vault-meta.json` and an empty `entries/` directory, then pushes. On a repo with an existing non-Passchmop commit (e.g. an auto-created README) it rebases the init commit on top; on a repo that already contains a vault it refuses and directs you to "Join existing". **Your master password is unrecoverable.** There is no reset flow on the server side. Lose it and the vault is ciphertext bricks. ## Dev workflow ``` npm run build # one-shot bundle into build/ npm run watch # rebuild on file change npm run typecheck # tsc --noEmit (strict + noUncheckedIndexedAccess) npx web-ext run # launch Firefox dev instance npx web-ext lint # sanity check the manifest + bundle ``` - `src/background/` — the event page (vault state, git sync, crypto, message router). - `src/popup/` — toolbar popup UI. - `src/options/` — first-run setup and settings. - `src/common/` — shared types + typed message API. - Build output lands in `build/` (gitignored). Architecture and non-obvious invariants are documented in `CLAUDE.md`. ## Vault layout on disk ``` your-repo/ vault-meta.json # KDF params + verifier. Not secret. entries/ .enc # iv(12B) || ciphertext || gcm-tag(16B) .conflict-.enc # conflict sidecar, if any ``` `vault-meta.json`: ```json { "version": 1, "kdf": { "name": "PBKDF2", "hash": "SHA-256", "iterations": 600000, "salt": "" }, "verifier": "" } ``` Each entry's plaintext is UTF-8 JSON: `{ id, title, url, username, password, notes, created_at, modified_at, device_id }`. ## Security model - The master password derives the AES-GCM key. The derived key is held in memory while the vault is unlocked; its raw bytes are also mirrored into `browser.storage.session` so the key survives Firefox MV3's event-page suspensions. `storage.session` is in-memory only and is wiped on browser restart. - The idle-lock alarm clears both the in-memory key and the session copy after 5 minutes of inactivity, or immediately when you click Lock. - Repo credentials (username + PAT) are encrypted with the master key before hitting `storage.local`. Without the master password, a compromised extension profile yields only ciphertext + KDF parameters. - The remote is assumed hostile-adjacent: it sees the number of entries, their sizes, and commit timing, but not their contents. Commit author is `passchmop-@passchmop.local`, not your real identity. - `host_permissions: ["https://*/*"]` is broad. It's needed because the background script's `fetch` must bypass CORS against arbitrary git hosts (none of them set CORS headers on smart-HTTP endpoints). Narrow it after first-run configuration if you prefer — requires an extension reload. ## Conflict resolution, concretely - Each PUT op carries a `baseModifiedAt` — the `modified_at` of the version you started editing from. On sync, if `remote.modified_at === baseModifiedAt`, it's a linear update; the remote is overwritten with no sidecar. Anything else is genuine divergence. - Divergence → the newer version becomes `.enc`, the loser is written as `.conflict-.enc`. Both show up in the list (the sidecar is flagged with a `conflict` badge). - Resolve a conflict by picking which entry to keep and deleting the other. - Edit vs delete → edit wins. Delete vs delete → clean no-op. ## Known limitations - No SSH transport. Browser fetch can't speak ssh; HTTPS + PAT is the only option. - No content-script autofill on page load. Autofill is explicit (you click the button), by design. - No password generator UI beyond the `↻ gen` button on the edit form. - No TOTP / secure notes / attachments in v1. The free-text `notes` field can hold whatever. - Background sync is best-effort: Firefox MV3 event pages can be suspended. Sync always runs when you open the popup, so incoming changes from other devices appear then. - Salt is fixed at vault creation; rotation would require re-encrypting every entry (not in v1). - Chrome / other-browser support is untested. The manifest is MV3 and mostly portable, but `browser.*` globals and some Firefox specifics would need work. ## License TBD.