Vibecode 1Password
track this build5 phases, 15 steps, beginner friendly0%A basic encrypted vault is buildable, but browser autofill, mobile apps, passkeys, sharing, recovery, audits, and trust make this a dangerous thing to replace casually.
You are building a lean indie version of 1Password. Create the following project files first, then implement the application by following them. Keep the files updated as decisions change. Do not collapse this into a single README or prompt. ===== README.md ===== # 1Password · indie build A KDBX 4 vault created and driven through KeePassXC and its command line, with a fast search and entry workflow of your own, honest entropy reporting on generated secrets, TOTP codes, and encrypted sync over a cloud drive with conflict detection that does not silently lose an edit. Estimated effort: **multi-day**. Work `BUILD_PLAN.md` top to bottom · every phase ends in a check that has to pass before the next one starts. ## Stack | Part | Choice | Why | | --- | --- | --- | | Vault format | KDBX 4 with the Argon2id key derivation function | memory-hard derivation is what makes a stolen vault file expensive rather than free to crack | | Vault engine | KeePassXC and keepassxc-cli | audited, actively maintained, and it means no line of cryptography is yours | | Your layer | Python 3.12 with a small command line and a Textual terminal interface | the part worth building is search and workflow, and that is where a scripting language pays | | Sync | Any file-sync drive you already have, holding the encrypted .kdbx | the file is already ciphertext, so the sync provider never sees a secret | | Autofill | KeePassXC-Browser, the official extension | browser integration is a native messaging protocol with a security model you should not reimplement | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **KeePassXC with its command line tool** · free - Why: It is the vault engine. Every cryptographic operation in this build happens inside it. - Get it: Download from keepassxc.org, or install with brew install --cask keepassxc, or your Linux package manager. On some distributions the command line tool is a separate keepassxc-cli package. - Verify: keepassxc-cli --version prints a version - [ ] **Python 3.12 or newer** · free - Why: Runs your search, generation and sync layer. - Get it: Install from python.org or with your package manager, then create a virtual environment in the project folder. - Verify: python3 --version prints 3.12 or higher - [ ] **A master password you have not used anywhere else** · free - Why: It is the only thing standing between a stolen vault file and every account you own, and there is no reset. - Get it: Generate a passphrase of at least five words from a large word list and commit it to memory before you put anything real in the vault. Write it on paper and store that somewhere physical until you are sure. - Verify: You can type it from memory three days running - [ ] **A backup destination that is not the sync folder** · varies, and free is possible with an external drive you already own - Why: Sync propagates a corrupted or emptied vault as eagerly as a good one. A second, separate copy is what makes recovery possible. - Get it: An external drive, a second cloud account, or an encrypted archive somewhere else. Decide before Phase 5, because sync makes mistakes travel fast. - [ ] **KeePassXC-Browser extension** (optional) · free - Why: Product phase only. Autofill and passkeys both live in the extension, not in the command line. - Get it: Install from your browser's extension store, then enable browser integration in KeePassXC settings for that specific browser. - Verify: The extension connects and shows the database name ## Quick start ```sh keepassxc-cli --version ``` Then copy `.env.example` to `.env` and fill in the values it documents. ## Honest limits This build deliberately does not replace: - Secure sharing with other people, which needs a server and a key exchange. - Account recovery of any kind. Forgetting the master password is final. - Mobile autofill through the platform providers on iOS and Android. - Breach monitoring, travel mode and the audits that come with a paid account. - Any cryptography written by you, which is the point rather than a gap. - browser/mobile autofill - passkey support - secure sharing - recovery - audits - watchtower alerts - family/team management If one of those is essential to you, that is the reason to keep paying for 1Password, and the README should say so rather than pretend. ===== BRIEF.md ===== # Build brief · 1Password The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me a local encrypted password vault with generation, search, TOTP and sync you control to replace 1Password. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Rule zero Do not write encryption. Do not invent a vault format, do not implement a key derivation function, and do not roll your own file format with AES bolted on. Use KDBX 4 through KeePassXC, which is audited, cross-platform and already read by clients on every operating system. Your job is the workflow around a format that already exists. If a phase below tempts you into writing a cipher, you have misread it. ### Stack (fixed, do not substitute) - Vault format: KDBX 4 with the Argon2id key derivation function · memory-hard derivation is what makes a stolen vault file expensive rather than free to crack - Vault engine: KeePassXC and keepassxc-cli · audited, actively maintained, and it means no line of cryptography is yours - Your layer: Python 3.12 with a small command line and a Textual terminal interface · the part worth building is search and workflow, and that is where a scripting language pays - Sync: Any file-sync drive you already have, holding the encrypted .kdbx · the file is already ciphertext, so the sync provider never sees a secret - Autofill: KeePassXC-Browser, the official extension · browser integration is a native messaging protocol with a security model you should not reimplement ### Data model (create this before Phase 1) - The vault itself is a single .kdbx file. It is the only durable state and it is encrypted at rest. - Groups and entries live inside the vault: title, username, password, url, notes, TOTP seed, tags. - index.json (local, unencrypted, derived) · title, username, url and tags only, never a secret, rebuilt on unlock to make search instant - sync_state.json · last known file hash, last sync time, and the path of any conflict copy - Never write a decrypted secret to disk. The index exists precisely because it holds nothing worth stealing. ### Phase 1 · A vault with the right parameters Build: Create the vault with keepassxc-cli. Set the key derivation function to Argon2id and raise the memory and iteration parameters above the defaults deliberately. Write a script that reports the vault format and KDF so the setting is verifiable, not folklore. Decide now whether you also use a key file, and record that decision in the README. Trap: A vault created by an old tool may still be KDBX 3.1 with AES-KDF, which is much cheaper to attack, and nothing in the interface shouts about it. Check the format explicitly rather than trusting the default of whatever created the file. Done when: A command you can re-run reports the vault as KDBX 4 with Argon2id and prints the memory and iteration parameters. Opening the vault with a wrong password fails without revealing how many entries it contains or how large it is. Do not build yet: No search, no TOTP, no sync. One correctly configured file. ### Phase 2 · Entries and instant search Build: Add, edit, list and remove entries through keepassxc-cli, never by parsing the file. On unlock, build index.json containing titles, usernames, urls and tags only. Search that index. Fetch the password from the vault only at the moment of copying, and clear the clipboard after a timeout. Trap: Putting the password in the index to make search feel snappier turns a plaintext file into the weakest link in a chain whose whole point was strength. If you catch yourself doing it for performance, measure first: the index without secrets is already fast. Done when: Searching a vault of a thousand entries returns matches without a perceptible pause. Grepping index.json for any known password finds nothing. A copied password is gone from the clipboard after the configured timeout, verified by pasting after it expires. Do not build yet: No TOTP and no sync yet. ### Phase 3 · Generation with honest entropy Build: A generator for random character passwords and for word passphrases, using the operating system random source. Report entropy in bits, computed from the alphabet size and length, or from the word list size and word count. Show the number in the interface next to what it generated. Trap: Entropy comes from the process that generated the secret, not from the string itself. A passphrase drawn from a 7776 word list at four words is about 51.7 bits whatever the words turn out to be, and a strength meter that scores the resulting string instead will confidently disagree. Compute from the generator's own parameters. Done when: A four word passphrase from a 7776 word list reports approximately 51.7 bits, and five words reports approximately 64.6 bits. A sixteen character password from a 94 character alphabet reports approximately 104.9 bits. Generating ten thousand passphrases produces no duplicate and uses the operating system random source, not the language default random module. Do not build yet: No TOTP and no sync yet. ### Phase 4 · TOTP Build: Store TOTP seeds in the entry's dedicated field and generate codes with keepassxc-cli. Display the remaining seconds in the period. Handle the seed formats real services hand out: bare base32, and otpauth:// URLs with period and digit parameters. Trap: Not every issuer uses thirty second periods and six digits, and the parameters ride in the otpauth URL rather than in the seed. Parse the URL and honour period, digits and algorithm, or you will produce confidently wrong codes for a minority of services and never work out why. Done when: A code generated for a known test seed matches a second independent implementation for the same timestamp. A seed supplied as an otpauth URL with a sixty second period produces a code that changes every sixty seconds, not every thirty. The countdown reaches zero exactly when the code changes. Do not build yet: No sync and no browser integration yet. ### Phase 5 · Sync with conflict detection Build: Keep the .kdbx in a synced folder. Record the file hash and modification time in sync_state.json before and after every local change. Before writing, compare the current hash to the last one you recorded. If it moved, someone else wrote, so keep a conflict copy and merge with KeePassXC's own merge rather than choosing a winner. Trap: File sync services resolve simultaneous edits by picking one file and, at best, leaving the other beside it with a renamed copy nobody looks at. The whole point of this phase is to notice before that happens. Hash before you write, every time, with no fast path that skips the check. Done when: Editing the vault on machine A and then, before it syncs, editing on machine B, produces a detected conflict and a preserved conflict copy rather than a silent overwrite. Running the merge afterwards leaves entries from both machines present. A normal single-machine edit produces no conflict copy. Do not build yet: No browser autofill or passkeys yet; those are the product phase. ### Phase 6 · Browser autofill, passkeys and recovery Build: Connect KeePassXC-Browser through native messaging, turn on passkey support in the extension settings, and write and then actually perform a recovery drill: restore the vault from backup onto a machine that has never seen it and unlock it. Trap: Passkeys in KeePassXC are handled by the browser extension, not by the command line, so a passkey created in the browser is invisible to a script-only workflow. Decide which credentials live as passkeys before you spread them across two interfaces you have to remember. Done when: The browser extension connects and fills a login on a real site. A passkey created through the extension survives a browser restart and authenticates again. The recovery drill has been performed on a second machine, and the date it was performed is written in the README. Do not build yet: Nothing after this. This is the last phase. ### Out of scope (and why) - Any cryptography of your own. This is the one rule that is not negotiable, and the reason the vault format is someone else's. - Secure sharing with other people. 1Password's sharing is a key exchange with a server, an account model and a revocation story, and a link with a password in it is not the same thing. - Account recovery. 1Password can help a family member back in; a file encrypted with a password you forgot is gone, and your README must say so plainly. - Mobile autofill. The system autofill providers on iOS and Android are platform integrations with their own review, and reading a KDBX file on a phone is the easy part of that problem. - Breach monitoring and travel mode, both of which are services rather than features. ### README must contain - That the vault is KDBX 4 with Argon2id and how to verify that yourself - That there is no recovery: forgetting the master password loses the vault - Where the vault file lives, where backups go, and the date of the last successful restore drill - That the local index contains no secrets and can be deleted safely - Which clients can open the vault, including KeePassXC on desktop and the mobile clients that read KDBX ===== AGENTS.md ===== # Agent instructions · 1Password indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: KDBX 4 with the Argon2id key derivation function, KeePassXC and keepassxc-cli, Python 3.12 with a small command line and a Textual terminal interface, Any file-sync drive you already have, holding the encrypted .kdbx, KeePassXC-Browser, the official extension. Do not substitute. - Work one phase at a time, in order. Do not start a phase until every "Done when" item of the previous one passes. - Prefer the fewest moving parts that satisfy the step. No frameworks, services or dependencies the plan does not name. - Secrets live in `.env`, never in source or logs. Keep `.env.example` current when a variable is introduced. - Do not invent cryptography, security guarantees, APIs or compliance claims. - Add a focused test for every destructive, security-sensitive or data-loss path the plan names. - Run the project checks before declaring a phase complete, and record any deliberate shortcut in the README under "Tradeoffs". ## Known traps - A vault created by an old tool may still be KDBX 3.1 with AES-KDF, which is much cheaper to attack, and nothing in the interface shouts about it. Check the format explicitly rather than trusting the default of whatever created the file. - Putting the password in the index to make search feel snappier turns a plaintext file into the weakest link in a chain whose whole point was strength. If you catch yourself doing it for performance, measure first: the index without secrets is already fast. - Entropy comes from the process that generated the secret, not from the string itself. A passphrase drawn from a 7776 word list at four words is about 51.7 bits whatever the words turn out to be, and a strength meter that scores the resulting string instead will confidently disagree. Compute from the generator's own parameters. - Not every issuer uses thirty second periods and six digits, and the parameters ride in the otpauth URL rather than in the seed. Parse the URL and honour period, digits and algorithm, or you will produce confidently wrong codes for a minority of services and never work out why. - File sync services resolve simultaneous edits by picking one file and, at best, leaving the other beside it with a renamed copy nobody looks at. The whole point of this phase is to notice before that happens. Hash before you write, every time, with no fast path that skips the check. ===== BUILD_PLAN.md ===== # Build plan · 1Password A KDBX 4 vault created and driven through KeePassXC and its command line, with a fast search and entry workflow of your own, honest entropy reporting on generated secrets, TOTP codes, and encrypted sync over a cloud drive with conflict detection that does not silently lose an edit. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · A vault with the right parameters A KDBX 4 vault exists, uses Argon2id, and you have proved it does rather than assumed it. ### Steps 1. Install KeePassXC and confirm the command line is present The command line tool ships with the desktop application on macOS and Windows and is often a separate package on Linux. Confirm it before building anything on top of it. ```sh keepassxc-cli --version ``` 2. Create the vault with explicit KDF settings Create the database, then set the key derivation function and parameters rather than accepting whatever the version default is. Higher memory costs you a second on unlock and costs an attacker far more. Files: `vault.kdbx` ```sh keepassxc-cli db-create vault.kdbx --set-password keepassxc-cli db-info vault.kdbx ``` 3. Write the verification script A short script that runs db-info and asserts the format is KDBX 4 and the KDF is Argon2id, exiting non-zero otherwise. Run it in your test suite so a future migration cannot quietly downgrade you. Files: `scripts/verify_vault.py` ### Done when - [ ] The verification script reports KDBX 4 and Argon2id and exits zero - [ ] The same script exits non-zero when pointed at a deliberately created KDBX 3.1 vault - [ ] A wrong password is rejected without disclosing entry count or structure - [ ] The chosen memory parameter makes unlock take a noticeable but tolerable moment ### Watch out - A vault created by an old tool may still be KDBX 3.1 with AES-KDF, which is much cheaper to attack, and nothing in the interface shouts about it. Check the format explicitly rather than trusting the default of whatever created the file. ## Phase 2 · Entries and instant search Finding and copying a credential is faster than opening a browser, and the search index never holds a secret. ### Steps 1. Wrap the command line rather than the file One module that shells out for show, add, edit and rm, passing the password on stdin and never as an argument, because arguments are visible in the process list. Files: `vault.py` 2. Build the secret-free index on unlock Enumerate entries and write index.json with title, username, url and tags. Add a test that asserts no field of a seeded entry's password appears anywhere in the file. Files: `index.py`, `tests/test_index.py` 3. Copy with a clipboard timeout Copy the secret, then clear the clipboard after a set number of seconds, but only if the clipboard still holds what you put there. Clearing unconditionally destroys whatever the user copied in between. Files: `clip.py` ### Done when - [ ] Search across a thousand seeded entries returns results without a visible pause - [ ] A known password from a seeded entry cannot be found anywhere in index.json - [ ] The clipboard is cleared after the timeout and only if it still holds the secret - [ ] Passwords are never passed as command line arguments, verified by inspecting the process list during a copy ### Watch out - Putting the password in the index to make search feel snappier turns a plaintext file into the weakest link in a chain whose whole point was strength. If you catch yourself doing it for performance, measure first: the index without secrets is already fast. ## Phase 3 · Generation with honest entropy Every generated secret comes with a number saying how strong it is, and the number is right. ### Steps 1. Use the cryptographic random source Python's secrets module, not random. The random module is seeded predictably and is documented as unsuitable for security purposes. Files: `generate.py` 2. Ship a word list and compute passphrase entropy Bundle a diceware style list and record its exact length. Entropy is words multiplied by the base two logarithm of the list length. Files: `wordlist.txt`, `generate.py` 3. Show the number where the secret appears Print bits next to every generated secret, and refuse to generate below a floor you choose, explaining why rather than silently upgrading. Files: `cli.py` ### Done when - [ ] Four words from a 7776 word list reports about 51.7 bits - [ ] Sixteen characters from a 94 character alphabet reports about 104.9 bits - [ ] Ten thousand generated passphrases contain no duplicate - [ ] The code imports secrets and never random for anything that becomes a credential ### Watch out - Entropy comes from the process that generated the secret, not from the string itself. A passphrase drawn from a 7776 word list at four words is about 51.7 bits whatever the words turn out to be, and a strength meter that scores the resulting string instead will confidently disagree. Compute from the generator's own parameters. ## Phase 4 · TOTP Six digit codes match what your phone shows, including across a clock that is slightly wrong. ### Steps 1. Store seeds in the TOTP field, not in notes KDBX has a dedicated attribute for this and KeePassXC and its browser extension both read it. Putting the seed in notes works only in your own tool. Files: `vault.py` 2. Parse otpauth URLs properly Extract secret, period, digits and algorithm. Default to thirty seconds and six digits only when the URL does not say otherwise. Files: `totp.py` 3. Verify against a known vector Use a published test seed and timestamp, assert the expected code, and keep that as a test so a refactor cannot break code generation silently. Files: `tests/test_totp.py` ### Done when - [ ] A known test seed and timestamp produce the documented expected code - [ ] An otpauth URL with a sixty second period changes the code every sixty seconds - [ ] The countdown hits zero at the moment the code rolls over - [ ] A seed stored in the TOTP field is also readable by KeePassXC itself ### Watch out - Not every issuer uses thirty second periods and six digits, and the parameters ride in the otpauth URL rather than in the seed. Parse the URL and honour period, digits and algorithm, or you will produce confidently wrong codes for a minority of services and never work out why. ## Phase 5 · Sync with conflict detection Two machines can edit the vault and you will be told when that happened, instead of losing one side quietly. ### Steps 1. Record hash and modification time around every write Hash the whole file. Store it with a timestamp in sync_state.json immediately after a successful write. Files: `sync.py` 2. Detect divergence before writing Re-hash the file on disk and compare to the recorded hash. A mismatch means a remote write landed and you must not overwrite it. Files: `sync.py` 3. Merge with the vault engine, never by hand keepassxc-cli merge understands entry history and timestamps. Your own merge would not. ```sh keepassxc-cli merge vault.kdbx vault-conflict.kdbx ``` ### Done when - [ ] A simulated concurrent edit produces a conflict copy and a warning, not a silent overwrite - [ ] Merging the conflict copy leaves entries from both sides present - [ ] A normal edit on one machine produces no conflict copy - [ ] sync_state.json never contains anything derived from a password ### Watch out - File sync services resolve simultaneous edits by picking one file and, at best, leaving the other beside it with a renamed copy nobody looks at. The whole point of this phase is to notice before that happens. Hash before you write, every time, with no fast path that skips the check. ## Not in this build - Secure sharing with other people, which needs a server and a key exchange. - Account recovery of any kind. Forgetting the master password is final. - Mobile autofill through the platform providers on iOS and Android. - Breach monitoring, travel mode and the audits that come with a paid account. - Any cryptography written by you, which is the point rather than a gap. ## After v1, if you want it - A read-only mobile workflow using one of the existing KDBX clients on your phone - Automatic backup verification that opens the newest dated copy and counts entries - Tagging and per-tag clipboard timeouts, so a banking credential clears faster ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required. The .kdbx file. Put it inside the folder your sync tool watches. VAULT_PATH=~/Sync/vault.kdbx # Optional · secret. Optional second factor for the vault. If you use one, it must never travel in the same sync folder as the vault. VAULT_KEYFILE=~/.config/vault/vault.key # Required. The secret-free search index. Safe to delete; it rebuilds on unlock. INDEX_PATH=~/.cache/vault/index.json # Required. How long a copied secret stays on the clipboard. Chosen by you. CLIPBOARD_CLEAR_SECONDS=20 # Required. Where dated vault copies are written. Deliberately not inside the sync folder. BACKUP_DIR=/Volumes/Backup/vault
You are building a lean indie version of 1Password. Create the following project files first, then implement the application by following them. Keep the files updated as decisions change. Do not collapse this into a single README or prompt. ===== README.md ===== # 1Password · indie build A KDBX 4 vault created and driven through KeePassXC and its command line, with a fast search and entry workflow of your own, honest entropy reporting on generated secrets, TOTP codes, and encrypted sync over a cloud drive with conflict detection that does not silently lose an edit. Estimated effort: **multi-day**. Work `BUILD_PLAN.md` top to bottom · every phase ends in a check that has to pass before the next one starts. ## Stack | Part | Choice | Why | | --- | --- | --- | | Vault format | KDBX 4 with the Argon2id key derivation function | memory-hard derivation is what makes a stolen vault file expensive rather than free to crack | | Vault engine | KeePassXC and keepassxc-cli | audited, actively maintained, and it means no line of cryptography is yours | | Your layer | Python 3.12 with a small command line and a Textual terminal interface | the part worth building is search and workflow, and that is where a scripting language pays | | Sync | Any file-sync drive you already have, holding the encrypted .kdbx | the file is already ciphertext, so the sync provider never sees a secret | | Autofill | KeePassXC-Browser, the official extension | browser integration is a native messaging protocol with a security model you should not reimplement | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **KeePassXC with its command line tool** · free - Why: It is the vault engine. Every cryptographic operation in this build happens inside it. - Get it: Download from keepassxc.org, or install with brew install --cask keepassxc, or your Linux package manager. On some distributions the command line tool is a separate keepassxc-cli package. - Verify: keepassxc-cli --version prints a version - [ ] **Python 3.12 or newer** · free - Why: Runs your search, generation and sync layer. - Get it: Install from python.org or with your package manager, then create a virtual environment in the project folder. - Verify: python3 --version prints 3.12 or higher - [ ] **A master password you have not used anywhere else** · free - Why: It is the only thing standing between a stolen vault file and every account you own, and there is no reset. - Get it: Generate a passphrase of at least five words from a large word list and commit it to memory before you put anything real in the vault. Write it on paper and store that somewhere physical until you are sure. - Verify: You can type it from memory three days running - [ ] **A backup destination that is not the sync folder** · varies, and free is possible with an external drive you already own - Why: Sync propagates a corrupted or emptied vault as eagerly as a good one. A second, separate copy is what makes recovery possible. - Get it: An external drive, a second cloud account, or an encrypted archive somewhere else. Decide before Phase 5, because sync makes mistakes travel fast. - [ ] **KeePassXC-Browser extension** (optional) · free - Why: Product phase only. Autofill and passkeys both live in the extension, not in the command line. - Get it: Install from your browser's extension store, then enable browser integration in KeePassXC settings for that specific browser. - Verify: The extension connects and shows the database name ## Quick start ```sh keepassxc-cli --version ``` Then copy `.env.example` to `.env` and fill in the values it documents. ## Honest limits This build deliberately does not replace: - Secure sharing with other people, which needs a server and a key exchange. - Account recovery of any kind. Forgetting the master password is final. - Mobile autofill through the platform providers on iOS and Android. - Breach monitoring, travel mode and the audits that come with a paid account. - Any cryptography written by you, which is the point rather than a gap. - browser/mobile autofill - passkey support - secure sharing - recovery - audits - watchtower alerts - family/team management If one of those is essential to you, that is the reason to keep paying for 1Password, and the README should say so rather than pretend. ===== BRIEF.md ===== # Build brief · 1Password The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me a local encrypted password vault with generation, search, TOTP and sync you control to replace 1Password. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Rule zero Do not write encryption. Do not invent a vault format, do not implement a key derivation function, and do not roll your own file format with AES bolted on. Use KDBX 4 through KeePassXC, which is audited, cross-platform and already read by clients on every operating system. Your job is the workflow around a format that already exists. If a phase below tempts you into writing a cipher, you have misread it. ### Stack (fixed, do not substitute) - Vault format: KDBX 4 with the Argon2id key derivation function · memory-hard derivation is what makes a stolen vault file expensive rather than free to crack - Vault engine: KeePassXC and keepassxc-cli · audited, actively maintained, and it means no line of cryptography is yours - Your layer: Python 3.12 with a small command line and a Textual terminal interface · the part worth building is search and workflow, and that is where a scripting language pays - Sync: Any file-sync drive you already have, holding the encrypted .kdbx · the file is already ciphertext, so the sync provider never sees a secret - Autofill: KeePassXC-Browser, the official extension · browser integration is a native messaging protocol with a security model you should not reimplement ### Data model (create this before Phase 1) - The vault itself is a single .kdbx file. It is the only durable state and it is encrypted at rest. - Groups and entries live inside the vault: title, username, password, url, notes, TOTP seed, tags. - index.json (local, unencrypted, derived) · title, username, url and tags only, never a secret, rebuilt on unlock to make search instant - sync_state.json · last known file hash, last sync time, and the path of any conflict copy - Never write a decrypted secret to disk. The index exists precisely because it holds nothing worth stealing. ### Phase 1 · A vault with the right parameters Build: Create the vault with keepassxc-cli. Set the key derivation function to Argon2id and raise the memory and iteration parameters above the defaults deliberately. Write a script that reports the vault format and KDF so the setting is verifiable, not folklore. Decide now whether you also use a key file, and record that decision in the README. Trap: A vault created by an old tool may still be KDBX 3.1 with AES-KDF, which is much cheaper to attack, and nothing in the interface shouts about it. Check the format explicitly rather than trusting the default of whatever created the file. Done when: A command you can re-run reports the vault as KDBX 4 with Argon2id and prints the memory and iteration parameters. Opening the vault with a wrong password fails without revealing how many entries it contains or how large it is. Do not build yet: No search, no TOTP, no sync. One correctly configured file. ### Phase 2 · Entries and instant search Build: Add, edit, list and remove entries through keepassxc-cli, never by parsing the file. On unlock, build index.json containing titles, usernames, urls and tags only. Search that index. Fetch the password from the vault only at the moment of copying, and clear the clipboard after a timeout. Trap: Putting the password in the index to make search feel snappier turns a plaintext file into the weakest link in a chain whose whole point was strength. If you catch yourself doing it for performance, measure first: the index without secrets is already fast. Done when: Searching a vault of a thousand entries returns matches without a perceptible pause. Grepping index.json for any known password finds nothing. A copied password is gone from the clipboard after the configured timeout, verified by pasting after it expires. Do not build yet: No TOTP and no sync yet. ### Phase 3 · Generation with honest entropy Build: A generator for random character passwords and for word passphrases, using the operating system random source. Report entropy in bits, computed from the alphabet size and length, or from the word list size and word count. Show the number in the interface next to what it generated. Trap: Entropy comes from the process that generated the secret, not from the string itself. A passphrase drawn from a 7776 word list at four words is about 51.7 bits whatever the words turn out to be, and a strength meter that scores the resulting string instead will confidently disagree. Compute from the generator's own parameters. Done when: A four word passphrase from a 7776 word list reports approximately 51.7 bits, and five words reports approximately 64.6 bits. A sixteen character password from a 94 character alphabet reports approximately 104.9 bits. Generating ten thousand passphrases produces no duplicate and uses the operating system random source, not the language default random module. Do not build yet: No TOTP and no sync yet. ### Phase 4 · TOTP Build: Store TOTP seeds in the entry's dedicated field and generate codes with keepassxc-cli. Display the remaining seconds in the period. Handle the seed formats real services hand out: bare base32, and otpauth:// URLs with period and digit parameters. Trap: Not every issuer uses thirty second periods and six digits, and the parameters ride in the otpauth URL rather than in the seed. Parse the URL and honour period, digits and algorithm, or you will produce confidently wrong codes for a minority of services and never work out why. Done when: A code generated for a known test seed matches a second independent implementation for the same timestamp. A seed supplied as an otpauth URL with a sixty second period produces a code that changes every sixty seconds, not every thirty. The countdown reaches zero exactly when the code changes. Do not build yet: No sync and no browser integration yet. ### Phase 5 · Sync with conflict detection Build: Keep the .kdbx in a synced folder. Record the file hash and modification time in sync_state.json before and after every local change. Before writing, compare the current hash to the last one you recorded. If it moved, someone else wrote, so keep a conflict copy and merge with KeePassXC's own merge rather than choosing a winner. Trap: File sync services resolve simultaneous edits by picking one file and, at best, leaving the other beside it with a renamed copy nobody looks at. The whole point of this phase is to notice before that happens. Hash before you write, every time, with no fast path that skips the check. Done when: Editing the vault on machine A and then, before it syncs, editing on machine B, produces a detected conflict and a preserved conflict copy rather than a silent overwrite. Running the merge afterwards leaves entries from both machines present. A normal single-machine edit produces no conflict copy. Do not build yet: No browser autofill or passkeys yet; those are the product phase. ### Phase 6 · Browser autofill, passkeys and recovery Build: Connect KeePassXC-Browser through native messaging, turn on passkey support in the extension settings, and write and then actually perform a recovery drill: restore the vault from backup onto a machine that has never seen it and unlock it. Trap: Passkeys in KeePassXC are handled by the browser extension, not by the command line, so a passkey created in the browser is invisible to a script-only workflow. Decide which credentials live as passkeys before you spread them across two interfaces you have to remember. Done when: The browser extension connects and fills a login on a real site. A passkey created through the extension survives a browser restart and authenticates again. The recovery drill has been performed on a second machine, and the date it was performed is written in the README. Do not build yet: Nothing after this. This is the last phase. ### Out of scope (and why) - Any cryptography of your own. This is the one rule that is not negotiable, and the reason the vault format is someone else's. - Secure sharing with other people. 1Password's sharing is a key exchange with a server, an account model and a revocation story, and a link with a password in it is not the same thing. - Account recovery. 1Password can help a family member back in; a file encrypted with a password you forgot is gone, and your README must say so plainly. - Mobile autofill. The system autofill providers on iOS and Android are platform integrations with their own review, and reading a KDBX file on a phone is the easy part of that problem. - Breach monitoring and travel mode, both of which are services rather than features. ### README must contain - That the vault is KDBX 4 with Argon2id and how to verify that yourself - That there is no recovery: forgetting the master password loses the vault - Where the vault file lives, where backups go, and the date of the last successful restore drill - That the local index contains no secrets and can be deleted safely - Which clients can open the vault, including KeePassXC on desktop and the mobile clients that read KDBX ===== AGENTS.md ===== # Agent instructions · 1Password indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: KDBX 4 with the Argon2id key derivation function, KeePassXC and keepassxc-cli, Python 3.12 with a small command line and a Textual terminal interface, Any file-sync drive you already have, holding the encrypted .kdbx, KeePassXC-Browser, the official extension. Do not substitute. - Work one phase at a time, in order. Do not start a phase until every "Done when" item of the previous one passes. - Prefer the fewest moving parts that satisfy the step. No frameworks, services or dependencies the plan does not name. - Secrets live in `.env`, never in source or logs. Keep `.env.example` current when a variable is introduced. - Do not invent cryptography, security guarantees, APIs or compliance claims. - Add a focused test for every destructive, security-sensitive or data-loss path the plan names. - Run the project checks before declaring a phase complete, and record any deliberate shortcut in the README under "Tradeoffs". ## Known traps - A vault created by an old tool may still be KDBX 3.1 with AES-KDF, which is much cheaper to attack, and nothing in the interface shouts about it. Check the format explicitly rather than trusting the default of whatever created the file. - Putting the password in the index to make search feel snappier turns a plaintext file into the weakest link in a chain whose whole point was strength. If you catch yourself doing it for performance, measure first: the index without secrets is already fast. - Entropy comes from the process that generated the secret, not from the string itself. A passphrase drawn from a 7776 word list at four words is about 51.7 bits whatever the words turn out to be, and a strength meter that scores the resulting string instead will confidently disagree. Compute from the generator's own parameters. - Not every issuer uses thirty second periods and six digits, and the parameters ride in the otpauth URL rather than in the seed. Parse the URL and honour period, digits and algorithm, or you will produce confidently wrong codes for a minority of services and never work out why. - File sync services resolve simultaneous edits by picking one file and, at best, leaving the other beside it with a renamed copy nobody looks at. The whole point of this phase is to notice before that happens. Hash before you write, every time, with no fast path that skips the check. ===== BUILD_PLAN.md ===== # Build plan · 1Password A KDBX 4 vault created and driven through KeePassXC and its command line, with a fast search and entry workflow of your own, honest entropy reporting on generated secrets, TOTP codes, and encrypted sync over a cloud drive with conflict detection that does not silently lose an edit. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · A vault with the right parameters A KDBX 4 vault exists, uses Argon2id, and you have proved it does rather than assumed it. ### Steps 1. Install KeePassXC and confirm the command line is present The command line tool ships with the desktop application on macOS and Windows and is often a separate package on Linux. Confirm it before building anything on top of it. ```sh keepassxc-cli --version ``` 2. Create the vault with explicit KDF settings Create the database, then set the key derivation function and parameters rather than accepting whatever the version default is. Higher memory costs you a second on unlock and costs an attacker far more. Files: `vault.kdbx` ```sh keepassxc-cli db-create vault.kdbx --set-password keepassxc-cli db-info vault.kdbx ``` 3. Write the verification script A short script that runs db-info and asserts the format is KDBX 4 and the KDF is Argon2id, exiting non-zero otherwise. Run it in your test suite so a future migration cannot quietly downgrade you. Files: `scripts/verify_vault.py` ### Done when - [ ] The verification script reports KDBX 4 and Argon2id and exits zero - [ ] The same script exits non-zero when pointed at a deliberately created KDBX 3.1 vault - [ ] A wrong password is rejected without disclosing entry count or structure - [ ] The chosen memory parameter makes unlock take a noticeable but tolerable moment ### Watch out - A vault created by an old tool may still be KDBX 3.1 with AES-KDF, which is much cheaper to attack, and nothing in the interface shouts about it. Check the format explicitly rather than trusting the default of whatever created the file. ## Phase 2 · Entries and instant search Finding and copying a credential is faster than opening a browser, and the search index never holds a secret. ### Steps 1. Wrap the command line rather than the file One module that shells out for show, add, edit and rm, passing the password on stdin and never as an argument, because arguments are visible in the process list. Files: `vault.py` 2. Build the secret-free index on unlock Enumerate entries and write index.json with title, username, url and tags. Add a test that asserts no field of a seeded entry's password appears anywhere in the file. Files: `index.py`, `tests/test_index.py` 3. Copy with a clipboard timeout Copy the secret, then clear the clipboard after a set number of seconds, but only if the clipboard still holds what you put there. Clearing unconditionally destroys whatever the user copied in between. Files: `clip.py` ### Done when - [ ] Search across a thousand seeded entries returns results without a visible pause - [ ] A known password from a seeded entry cannot be found anywhere in index.json - [ ] The clipboard is cleared after the timeout and only if it still holds the secret - [ ] Passwords are never passed as command line arguments, verified by inspecting the process list during a copy ### Watch out - Putting the password in the index to make search feel snappier turns a plaintext file into the weakest link in a chain whose whole point was strength. If you catch yourself doing it for performance, measure first: the index without secrets is already fast. ## Phase 3 · Generation with honest entropy Every generated secret comes with a number saying how strong it is, and the number is right. ### Steps 1. Use the cryptographic random source Python's secrets module, not random. The random module is seeded predictably and is documented as unsuitable for security purposes. Files: `generate.py` 2. Ship a word list and compute passphrase entropy Bundle a diceware style list and record its exact length. Entropy is words multiplied by the base two logarithm of the list length. Files: `wordlist.txt`, `generate.py` 3. Show the number where the secret appears Print bits next to every generated secret, and refuse to generate below a floor you choose, explaining why rather than silently upgrading. Files: `cli.py` ### Done when - [ ] Four words from a 7776 word list reports about 51.7 bits - [ ] Sixteen characters from a 94 character alphabet reports about 104.9 bits - [ ] Ten thousand generated passphrases contain no duplicate - [ ] The code imports secrets and never random for anything that becomes a credential ### Watch out - Entropy comes from the process that generated the secret, not from the string itself. A passphrase drawn from a 7776 word list at four words is about 51.7 bits whatever the words turn out to be, and a strength meter that scores the resulting string instead will confidently disagree. Compute from the generator's own parameters. ## Phase 4 · TOTP Six digit codes match what your phone shows, including across a clock that is slightly wrong. ### Steps 1. Store seeds in the TOTP field, not in notes KDBX has a dedicated attribute for this and KeePassXC and its browser extension both read it. Putting the seed in notes works only in your own tool. Files: `vault.py` 2. Parse otpauth URLs properly Extract secret, period, digits and algorithm. Default to thirty seconds and six digits only when the URL does not say otherwise. Files: `totp.py` 3. Verify against a known vector Use a published test seed and timestamp, assert the expected code, and keep that as a test so a refactor cannot break code generation silently. Files: `tests/test_totp.py` ### Done when - [ ] A known test seed and timestamp produce the documented expected code - [ ] An otpauth URL with a sixty second period changes the code every sixty seconds - [ ] The countdown hits zero at the moment the code rolls over - [ ] A seed stored in the TOTP field is also readable by KeePassXC itself ### Watch out - Not every issuer uses thirty second periods and six digits, and the parameters ride in the otpauth URL rather than in the seed. Parse the URL and honour period, digits and algorithm, or you will produce confidently wrong codes for a minority of services and never work out why. ## Phase 5 · Sync with conflict detection Two machines can edit the vault and you will be told when that happened, instead of losing one side quietly. ### Steps 1. Record hash and modification time around every write Hash the whole file. Store it with a timestamp in sync_state.json immediately after a successful write. Files: `sync.py` 2. Detect divergence before writing Re-hash the file on disk and compare to the recorded hash. A mismatch means a remote write landed and you must not overwrite it. Files: `sync.py` 3. Merge with the vault engine, never by hand keepassxc-cli merge understands entry history and timestamps. Your own merge would not. ```sh keepassxc-cli merge vault.kdbx vault-conflict.kdbx ``` ### Done when - [ ] A simulated concurrent edit produces a conflict copy and a warning, not a silent overwrite - [ ] Merging the conflict copy leaves entries from both sides present - [ ] A normal edit on one machine produces no conflict copy - [ ] sync_state.json never contains anything derived from a password ### Watch out - File sync services resolve simultaneous edits by picking one file and, at best, leaving the other beside it with a renamed copy nobody looks at. The whole point of this phase is to notice before that happens. Hash before you write, every time, with no fast path that skips the check. ## Not in this build - Secure sharing with other people, which needs a server and a key exchange. - Account recovery of any kind. Forgetting the master password is final. - Mobile autofill through the platform providers on iOS and Android. - Breach monitoring, travel mode and the audits that come with a paid account. - Any cryptography written by you, which is the point rather than a gap. ## After v1, if you want it - A read-only mobile workflow using one of the existing KDBX clients on your phone - Automatic backup verification that opens the newest dated copy and counts entries - Tagging and per-tag clipboard timeouts, so a banking credential clears faster ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required. The .kdbx file. Put it inside the folder your sync tool watches. VAULT_PATH=~/Sync/vault.kdbx # Optional · secret. Optional second factor for the vault. If you use one, it must never travel in the same sync folder as the vault. VAULT_KEYFILE=~/.config/vault/vault.key # Required. The secret-free search index. Safe to delete; it rebuilds on unlock. INDEX_PATH=~/.cache/vault/index.json # Required. How long a copied secret stays on the clipboard. Chosen by you. CLIPBOARD_CLEAR_SECONDS=20 # Required. Where dated vault copies are written. Deliberately not inside the sync folder. BACKUP_DIR=/Volumes/Backup/vault
You are building a production product version of 1Password. Create the following project files first, then implement the application by following them. Keep the files updated as decisions change. Do not collapse this into a single README or prompt. ===== PRODUCT.md ===== # 1Password · product brief ## Problem A basic encrypted vault is buildable, but browser autofill, mobile apps, passkeys, sharing, recovery, audits, and trust make this a dangerous thing to replace casually. ## Product outcome A vault you trust because nothing about its security is your own invention, with a workflow fast enough that you stop reusing passwords, browser autofill and passkeys through the official extension, and a restore you have actually performed. ## Target user A builder who needs a maintainable product foundation, not a one-off demo. ## Required capabilities - strong encryption library - secure key derivation - browser extension if autofill matters - mobile/desktop storage - backup plan ## Explicit non-goals for v1 - Secure sharing with other people, which needs a server and a key exchange. - Account recovery of any kind. Forgetting the master password is final. - Mobile autofill through the platform providers on iOS and Android. - Breach monitoring, travel mode and the audits that come with a paid account. - Any cryptography written by you, which is the point rather than a gap. - browser/mobile autofill - passkey support - secure sharing - recovery - audits - watchtower alerts - family/team management ## Success criteria - The verification script confirms KDBX 4 with Argon2id - No secret appears in the index, proved by the test rather than by inspection - A restore onto a clean machine has been performed and dated in the README - A simulated concurrent edit produces a conflict copy rather than a silent loss - The clipboard timeout is confirmed to clear only the value it wrote ===== BRIEF.md ===== # Build brief · 1Password The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me a local encrypted password vault with generation, search, TOTP and sync you control to replace 1Password. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Rule zero Do not write encryption. Do not invent a vault format, do not implement a key derivation function, and do not roll your own file format with AES bolted on. Use KDBX 4 through KeePassXC, which is audited, cross-platform and already read by clients on every operating system. Your job is the workflow around a format that already exists. If a phase below tempts you into writing a cipher, you have misread it. ### Stack (fixed, do not substitute) - Vault format: KDBX 4 with the Argon2id key derivation function · memory-hard derivation is what makes a stolen vault file expensive rather than free to crack - Vault engine: KeePassXC and keepassxc-cli · audited, actively maintained, and it means no line of cryptography is yours - Your layer: Python 3.12 with a small command line and a Textual terminal interface · the part worth building is search and workflow, and that is where a scripting language pays - Sync: Any file-sync drive you already have, holding the encrypted .kdbx · the file is already ciphertext, so the sync provider never sees a secret - Autofill: KeePassXC-Browser, the official extension · browser integration is a native messaging protocol with a security model you should not reimplement ### Data model (create this before Phase 1) - The vault itself is a single .kdbx file. It is the only durable state and it is encrypted at rest. - Groups and entries live inside the vault: title, username, password, url, notes, TOTP seed, tags. - index.json (local, unencrypted, derived) · title, username, url and tags only, never a secret, rebuilt on unlock to make search instant - sync_state.json · last known file hash, last sync time, and the path of any conflict copy - Never write a decrypted secret to disk. The index exists precisely because it holds nothing worth stealing. ### Phase 1 · A vault with the right parameters Build: Create the vault with keepassxc-cli. Set the key derivation function to Argon2id and raise the memory and iteration parameters above the defaults deliberately. Write a script that reports the vault format and KDF so the setting is verifiable, not folklore. Decide now whether you also use a key file, and record that decision in the README. Trap: A vault created by an old tool may still be KDBX 3.1 with AES-KDF, which is much cheaper to attack, and nothing in the interface shouts about it. Check the format explicitly rather than trusting the default of whatever created the file. Done when: A command you can re-run reports the vault as KDBX 4 with Argon2id and prints the memory and iteration parameters. Opening the vault with a wrong password fails without revealing how many entries it contains or how large it is. Do not build yet: No search, no TOTP, no sync. One correctly configured file. ### Phase 2 · Entries and instant search Build: Add, edit, list and remove entries through keepassxc-cli, never by parsing the file. On unlock, build index.json containing titles, usernames, urls and tags only. Search that index. Fetch the password from the vault only at the moment of copying, and clear the clipboard after a timeout. Trap: Putting the password in the index to make search feel snappier turns a plaintext file into the weakest link in a chain whose whole point was strength. If you catch yourself doing it for performance, measure first: the index without secrets is already fast. Done when: Searching a vault of a thousand entries returns matches without a perceptible pause. Grepping index.json for any known password finds nothing. A copied password is gone from the clipboard after the configured timeout, verified by pasting after it expires. Do not build yet: No TOTP and no sync yet. ### Phase 3 · Generation with honest entropy Build: A generator for random character passwords and for word passphrases, using the operating system random source. Report entropy in bits, computed from the alphabet size and length, or from the word list size and word count. Show the number in the interface next to what it generated. Trap: Entropy comes from the process that generated the secret, not from the string itself. A passphrase drawn from a 7776 word list at four words is about 51.7 bits whatever the words turn out to be, and a strength meter that scores the resulting string instead will confidently disagree. Compute from the generator's own parameters. Done when: A four word passphrase from a 7776 word list reports approximately 51.7 bits, and five words reports approximately 64.6 bits. A sixteen character password from a 94 character alphabet reports approximately 104.9 bits. Generating ten thousand passphrases produces no duplicate and uses the operating system random source, not the language default random module. Do not build yet: No TOTP and no sync yet. ### Phase 4 · TOTP Build: Store TOTP seeds in the entry's dedicated field and generate codes with keepassxc-cli. Display the remaining seconds in the period. Handle the seed formats real services hand out: bare base32, and otpauth:// URLs with period and digit parameters. Trap: Not every issuer uses thirty second periods and six digits, and the parameters ride in the otpauth URL rather than in the seed. Parse the URL and honour period, digits and algorithm, or you will produce confidently wrong codes for a minority of services and never work out why. Done when: A code generated for a known test seed matches a second independent implementation for the same timestamp. A seed supplied as an otpauth URL with a sixty second period produces a code that changes every sixty seconds, not every thirty. The countdown reaches zero exactly when the code changes. Do not build yet: No sync and no browser integration yet. ### Phase 5 · Sync with conflict detection Build: Keep the .kdbx in a synced folder. Record the file hash and modification time in sync_state.json before and after every local change. Before writing, compare the current hash to the last one you recorded. If it moved, someone else wrote, so keep a conflict copy and merge with KeePassXC's own merge rather than choosing a winner. Trap: File sync services resolve simultaneous edits by picking one file and, at best, leaving the other beside it with a renamed copy nobody looks at. The whole point of this phase is to notice before that happens. Hash before you write, every time, with no fast path that skips the check. Done when: Editing the vault on machine A and then, before it syncs, editing on machine B, produces a detected conflict and a preserved conflict copy rather than a silent overwrite. Running the merge afterwards leaves entries from both machines present. A normal single-machine edit produces no conflict copy. Do not build yet: No browser autofill or passkeys yet; those are the product phase. ### Phase 6 · Browser autofill, passkeys and recovery Build: Connect KeePassXC-Browser through native messaging, turn on passkey support in the extension settings, and write and then actually perform a recovery drill: restore the vault from backup onto a machine that has never seen it and unlock it. Trap: Passkeys in KeePassXC are handled by the browser extension, not by the command line, so a passkey created in the browser is invisible to a script-only workflow. Decide which credentials live as passkeys before you spread them across two interfaces you have to remember. Done when: The browser extension connects and fills a login on a real site. A passkey created through the extension survives a browser restart and authenticates again. The recovery drill has been performed on a second machine, and the date it was performed is written in the README. Do not build yet: Nothing after this. This is the last phase. ### Out of scope (and why) - Any cryptography of your own. This is the one rule that is not negotiable, and the reason the vault format is someone else's. - Secure sharing with other people. 1Password's sharing is a key exchange with a server, an account model and a revocation story, and a link with a password in it is not the same thing. - Account recovery. 1Password can help a family member back in; a file encrypted with a password you forgot is gone, and your README must say so plainly. - Mobile autofill. The system autofill providers on iOS and Android are platform integrations with their own review, and reading a KDBX file on a phone is the easy part of that problem. - Breach monitoring and travel mode, both of which are services rather than features. ### README must contain - That the vault is KDBX 4 with Argon2id and how to verify that yourself - That there is no recovery: forgetting the master password loses the vault - Where the vault file lives, where backups go, and the date of the last successful restore drill - That the local index contains no secrets and can be deleted safely - Which clients can open the vault, including KeePassXC on desktop and the mobile clients that read KDBX ===== ARCHITECTURE.md ===== # Architecture · 1Password ## Stack | Part | Choice | Why | | --- | --- | --- | | Vault format | KDBX 4 with the Argon2id key derivation function | memory-hard derivation is what makes a stolen vault file expensive rather than free to crack | | Vault engine | KeePassXC and keepassxc-cli | audited, actively maintained, and it means no line of cryptography is yours | | Your layer | Python 3.12 with a small command line and a Textual terminal interface | the part worth building is search and workflow, and that is where a scripting language pays | | Sync | Any file-sync drive you already have, holding the encrypted .kdbx | the file is already ciphertext, so the sync provider never sees a secret | | Autofill | KeePassXC-Browser, the official extension | browser integration is a native messaging protocol with a security model you should not reimplement | ## Modules Each module has one owner concern and a documented way to replace it. | Module | Owns | How to replace it | | --- | --- | --- | | Vault engine | Encryption, key derivation, the file format and merging. | Not yours to swap and not yours to modify. Upgrading KeePassXC is the only change you make here. | | Index | Fast search over non-secret fields. | Replace JSON with SQLite full text search if a very large vault gets slow. The invariant that survives any swap is that no secret enters the index. | | Generator | Random secret creation and entropy reporting. | The word list is data, so a different list is a file change plus an updated expected-bits test. | | Sync | Hash tracking, conflict detection and invoking merge. | Any file transport works because the file is already ciphertext. Swapping the drive changes nothing but a path. | | Browser bridge | Autofill and passkeys. | Provided entirely by KeePassXC-Browser. If you find yourself writing native messaging code, stop. | ## Configuration Every runtime setting is an environment variable documented in `.env.example`, validated at startup, with a safe local default wherever one exists. - `VAULT_PATH` · required · The .kdbx file. Put it inside the folder your sync tool watches. - `VAULT_KEYFILE` · optional, secret · Optional second factor for the vault. If you use one, it must never travel in the same sync folder as the vault. - `INDEX_PATH` · required · The secret-free search index. Safe to delete; it rebuilds on unlock. - `CLIPBOARD_CLEAR_SECONDS` · required · How long a copied secret stays on the clipboard. Chosen by you. - `BACKUP_DIR` · required · Where dated vault copies are written. Deliberately not inside the sync folder. ## Production baseline - Security: least privilege, input validation at every boundary, secret redaction in logs, rate limits on abuse-prone paths, no invented security primitives. - Data: explicit schema and migrations, transactional writes where integrity matters, backup and restore procedures that have been exercised. - Integrations: adapters around third-party providers, idempotent webhook or job processing, bounded retries, timeouts. - Observability: structured logs with request or operation ids, an error-tracking hook, and health and readiness checks where a server exists. - Quality: unit tests for domain rules, integration tests at module boundaries, one end-to-end test of the critical path. ## Decision records For each dependency in the stack table, keep a short note: why it was chosen, its failure mode, and how it is replaced. Do not add infrastructure until a requirement in `PRODUCT.md` justifies it. ===== AGENTS.md ===== # Agent instructions · 1Password product build - Read `PRODUCT.md` and `ARCHITECTURE.md` before changing code. The stack is fixed: KDBX 4 with the Argon2id key derivation function, KeePassXC and keepassxc-cli, Python 3.12 with a small command line and a Textual terminal interface, Any file-sync drive you already have, holding the encrypted .kdbx, KeePassXC-Browser, the official extension. - Implement milestone by milestone from `MILESTONES.md`; keep each change reviewable and leave the application runnable at every commit. - Treat authentication, payments, encryption, imports, webhooks and destructive actions as high-risk boundaries when present. - Never invent cryptography or silently weaken a requirement to make a check pass. - Put every external service behind an interface with a deterministic fake for tests. - Add migrations and rollback or recovery notes for every persistent data change. - Log useful operational context without credentials, tokens, passwords or personal data. - Update documentation and run every check before completing a milestone. ## Known traps - A vault created by an old tool may still be KDBX 3.1 with AES-KDF, which is much cheaper to attack, and nothing in the interface shouts about it. Check the format explicitly rather than trusting the default of whatever created the file. - Putting the password in the index to make search feel snappier turns a plaintext file into the weakest link in a chain whose whole point was strength. If you catch yourself doing it for performance, measure first: the index without secrets is already fast. - Entropy comes from the process that generated the secret, not from the string itself. A passphrase drawn from a 7776 word list at four words is about 51.7 bits whatever the words turn out to be, and a strength meter that scores the resulting string instead will confidently disagree. Compute from the generator's own parameters. - Not every issuer uses thirty second periods and six digits, and the parameters ride in the otpauth URL rather than in the seed. Parse the URL and honour period, digits and algorithm, or you will produce confidently wrong codes for a minority of services and never work out why. - File sync services resolve simultaneous edits by picking one file and, at best, leaving the other beside it with a renamed copy nobody looks at. The whole point of this phase is to notice before that happens. Hash before you write, every time, with no fast path that skips the check. - Passkeys in KeePassXC are handled by the browser extension, not by the command line, so a passkey created in the browser is invisible to a script-only workflow. Decide which credentials live as passkeys before you spread them across two interfaces you have to remember. ===== MILESTONES.md ===== # Delivery milestones · 1Password Estimated effort: **multi-day** for the indie phases; the production-only milestones add the trust and operability layer. ## M1 · A vault with the right parameters A KDBX 4 vault exists, uses Argon2id, and you have proved it does rather than assumed it. ### Steps 1. Install KeePassXC and confirm the command line is present The command line tool ships with the desktop application on macOS and Windows and is often a separate package on Linux. Confirm it before building anything on top of it. ```sh keepassxc-cli --version ``` 2. Create the vault with explicit KDF settings Create the database, then set the key derivation function and parameters rather than accepting whatever the version default is. Higher memory costs you a second on unlock and costs an attacker far more. Files: `vault.kdbx` ```sh keepassxc-cli db-create vault.kdbx --set-password keepassxc-cli db-info vault.kdbx ``` 3. Write the verification script A short script that runs db-info and asserts the format is KDBX 4 and the KDF is Argon2id, exiting non-zero otherwise. Run it in your test suite so a future migration cannot quietly downgrade you. Files: `scripts/verify_vault.py` ### Done when - [ ] The verification script reports KDBX 4 and Argon2id and exits zero - [ ] The same script exits non-zero when pointed at a deliberately created KDBX 3.1 vault - [ ] A wrong password is rejected without disclosing entry count or structure - [ ] The chosen memory parameter makes unlock take a noticeable but tolerable moment ### Watch out - A vault created by an old tool may still be KDBX 3.1 with AES-KDF, which is much cheaper to attack, and nothing in the interface shouts about it. Check the format explicitly rather than trusting the default of whatever created the file. ## M2 · Entries and instant search Finding and copying a credential is faster than opening a browser, and the search index never holds a secret. ### Steps 1. Wrap the command line rather than the file One module that shells out for show, add, edit and rm, passing the password on stdin and never as an argument, because arguments are visible in the process list. Files: `vault.py` 2. Build the secret-free index on unlock Enumerate entries and write index.json with title, username, url and tags. Add a test that asserts no field of a seeded entry's password appears anywhere in the file. Files: `index.py`, `tests/test_index.py` 3. Copy with a clipboard timeout Copy the secret, then clear the clipboard after a set number of seconds, but only if the clipboard still holds what you put there. Clearing unconditionally destroys whatever the user copied in between. Files: `clip.py` ### Done when - [ ] Search across a thousand seeded entries returns results without a visible pause - [ ] A known password from a seeded entry cannot be found anywhere in index.json - [ ] The clipboard is cleared after the timeout and only if it still holds the secret - [ ] Passwords are never passed as command line arguments, verified by inspecting the process list during a copy ### Watch out - Putting the password in the index to make search feel snappier turns a plaintext file into the weakest link in a chain whose whole point was strength. If you catch yourself doing it for performance, measure first: the index without secrets is already fast. ## M3 · Generation with honest entropy Every generated secret comes with a number saying how strong it is, and the number is right. ### Steps 1. Use the cryptographic random source Python's secrets module, not random. The random module is seeded predictably and is documented as unsuitable for security purposes. Files: `generate.py` 2. Ship a word list and compute passphrase entropy Bundle a diceware style list and record its exact length. Entropy is words multiplied by the base two logarithm of the list length. Files: `wordlist.txt`, `generate.py` 3. Show the number where the secret appears Print bits next to every generated secret, and refuse to generate below a floor you choose, explaining why rather than silently upgrading. Files: `cli.py` ### Done when - [ ] Four words from a 7776 word list reports about 51.7 bits - [ ] Sixteen characters from a 94 character alphabet reports about 104.9 bits - [ ] Ten thousand generated passphrases contain no duplicate - [ ] The code imports secrets and never random for anything that becomes a credential ### Watch out - Entropy comes from the process that generated the secret, not from the string itself. A passphrase drawn from a 7776 word list at four words is about 51.7 bits whatever the words turn out to be, and a strength meter that scores the resulting string instead will confidently disagree. Compute from the generator's own parameters. ## M4 · TOTP Six digit codes match what your phone shows, including across a clock that is slightly wrong. ### Steps 1. Store seeds in the TOTP field, not in notes KDBX has a dedicated attribute for this and KeePassXC and its browser extension both read it. Putting the seed in notes works only in your own tool. Files: `vault.py` 2. Parse otpauth URLs properly Extract secret, period, digits and algorithm. Default to thirty seconds and six digits only when the URL does not say otherwise. Files: `totp.py` 3. Verify against a known vector Use a published test seed and timestamp, assert the expected code, and keep that as a test so a refactor cannot break code generation silently. Files: `tests/test_totp.py` ### Done when - [ ] A known test seed and timestamp produce the documented expected code - [ ] An otpauth URL with a sixty second period changes the code every sixty seconds - [ ] The countdown hits zero at the moment the code rolls over - [ ] A seed stored in the TOTP field is also readable by KeePassXC itself ### Watch out - Not every issuer uses thirty second periods and six digits, and the parameters ride in the otpauth URL rather than in the seed. Parse the URL and honour period, digits and algorithm, or you will produce confidently wrong codes for a minority of services and never work out why. ## M5 · Sync with conflict detection Two machines can edit the vault and you will be told when that happened, instead of losing one side quietly. ### Steps 1. Record hash and modification time around every write Hash the whole file. Store it with a timestamp in sync_state.json immediately after a successful write. Files: `sync.py` 2. Detect divergence before writing Re-hash the file on disk and compare to the recorded hash. A mismatch means a remote write landed and you must not overwrite it. Files: `sync.py` 3. Merge with the vault engine, never by hand keepassxc-cli merge understands entry history and timestamps. Your own merge would not. ```sh keepassxc-cli merge vault.kdbx vault-conflict.kdbx ``` ### Done when - [ ] A simulated concurrent edit produces a conflict copy and a warning, not a silent overwrite - [ ] Merging the conflict copy leaves entries from both sides present - [ ] A normal edit on one machine produces no conflict copy - [ ] sync_state.json never contains anything derived from a password ### Watch out - File sync services resolve simultaneous edits by picking one file and, at best, leaving the other beside it with a renamed copy nobody looks at. The whole point of this phase is to notice before that happens. Hash before you write, every time, with no fast path that skips the check. ## M6 · Browser autofill, passkeys and recovery (production only) The vault is usable in a browser, can hold passkeys, and you have proved you can get back in after losing a device. ### Steps 1. Connect the browser extension Install KeePassXC-Browser, enable browser integration in KeePassXC settings for your specific browser, and connect the database. Native messaging is what carries the connection. 2. Turn on passkeys and create one Enable the passkeys option in the extension settings, then register a passkey on a site that supports them and confirm it authenticates after a restart. 3. Perform the recovery drill and date it Copy the backup and the key file, if you use one, to a machine that has never had the vault. Unlock it there. Write the date in the README. A backup you have not restored is a rumour. Files: `README.md` ### Done when - [ ] The extension fills a login on a real site - [ ] A passkey created in the browser authenticates after a browser restart - [ ] The vault was restored and unlocked on a second machine and the date is recorded - [ ] Losing the local index does not lose data, because it rebuilds on unlock ### Watch out - Passkeys in KeePassXC are handled by the browser extension, not by the command line, so a passkey created in the browser is invisible to a script-only workflow. Decide which credentials live as passkeys before you spread them across two interfaces you have to remember. ===== OPERATIONS.md ===== # Operations · 1Password ## Backup A dated copy of the .kdbx to a destination outside the sync folder, on a schedule, keeping several generations. The file is encrypted, so the backup destination does not need to be trusted, but it does need to be separate. ## Restore Copy a dated vault and the key file, if any, to a machine with KeePassXC, and unlock. Do this on purpose at least once a year and write the date in the README, because an untested backup is not a backup. Do a restore drill before the first real user, and write the date here when it passes. ## Monitoring There is no service to watch. What you monitor is the backup: assert that a new dated copy appeared, that its size is plausible, and that the verification script still reports KDBX 4 with Argon2id. ## Incident checklist If the master password is lost the vault is gone and no amount of engineering changes that. If the file is corrupted, restore the newest dated backup and merge any newer conflict copy into it. If a device is stolen, change the master password and rotate the credentials that device had unlocked recently. 1. Contain the issue without destroying evidence or user data. 2. Record the timeline and affected scope. 3. Rotate exposed secrets and revoke compromised sessions or credentials. 4. Restore from a verified backup when needed. 5. Document the root cause, the remediation and the regression test. ## Release gate - [ ] The verification script confirms KDBX 4 with Argon2id - [ ] No secret appears in the index, proved by the test rather than by inspection - [ ] A restore onto a clean machine has been performed and dated in the README - [ ] A simulated concurrent edit produces a conflict copy rather than a silent loss - [ ] The clipboard timeout is confirmed to clear only the value it wrote ## Launch constraint Do not market omitted 1Password capabilities as implemented. The non-goals in `PRODUCT.md` remain user-visible limitations until they are deliberately delivered. ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required. The .kdbx file. Put it inside the folder your sync tool watches. VAULT_PATH=~/Sync/vault.kdbx # Optional · secret. Optional second factor for the vault. If you use one, it must never travel in the same sync folder as the vault. VAULT_KEYFILE=~/.config/vault/vault.key # Required. The secret-free search index. Safe to delete; it rebuilds on unlock. INDEX_PATH=~/.cache/vault/index.json # Required. How long a copied secret stays on the clipboard. Chosen by you. CLIPBOARD_CLEAR_SECONDS=20 # Required. Where dated vault copies are written. Deliberately not inside the sync folder. BACKUP_DIR=/Volumes/Backup/vault
# 1Password · indie build A KDBX 4 vault created and driven through KeePassXC and its command line, with a fast search and entry workflow of your own, honest entropy reporting on generated secrets, TOTP codes, and encrypted sync over a cloud drive with conflict detection that does not silently lose an edit. Estimated effort: **multi-day**. Work `BUILD_PLAN.md` top to bottom · every phase ends in a check that has to pass before the next one starts. ## Stack | Part | Choice | Why | | --- | --- | --- | | Vault format | KDBX 4 with the Argon2id key derivation function | memory-hard derivation is what makes a stolen vault file expensive rather than free to crack | | Vault engine | KeePassXC and keepassxc-cli | audited, actively maintained, and it means no line of cryptography is yours | | Your layer | Python 3.12 with a small command line and a Textual terminal interface | the part worth building is search and workflow, and that is where a scripting language pays | | Sync | Any file-sync drive you already have, holding the encrypted .kdbx | the file is already ciphertext, so the sync provider never sees a secret | | Autofill | KeePassXC-Browser, the official extension | browser integration is a native messaging protocol with a security model you should not reimplement | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **KeePassXC with its command line tool** · free - Why: It is the vault engine. Every cryptographic operation in this build happens inside it. - Get it: Download from keepassxc.org, or install with brew install --cask keepassxc, or your Linux package manager. On some distributions the command line tool is a separate keepassxc-cli package. - Verify: keepassxc-cli --version prints a version - [ ] **Python 3.12 or newer** · free - Why: Runs your search, generation and sync layer. - Get it: Install from python.org or with your package manager, then create a virtual environment in the project folder. - Verify: python3 --version prints 3.12 or higher - [ ] **A master password you have not used anywhere else** · free - Why: It is the only thing standing between a stolen vault file and every account you own, and there is no reset. - Get it: Generate a passphrase of at least five words from a large word list and commit it to memory before you put anything real in the vault. Write it on paper and store that somewhere physical until you are sure. - Verify: You can type it from memory three days running - [ ] **A backup destination that is not the sync folder** · varies, and free is possible with an external drive you already own - Why: Sync propagates a corrupted or emptied vault as eagerly as a good one. A second, separate copy is what makes recovery possible. - Get it: An external drive, a second cloud account, or an encrypted archive somewhere else. Decide before Phase 5, because sync makes mistakes travel fast. - [ ] **KeePassXC-Browser extension** (optional) · free - Why: Product phase only. Autofill and passkeys both live in the extension, not in the command line. - Get it: Install from your browser's extension store, then enable browser integration in KeePassXC settings for that specific browser. - Verify: The extension connects and shows the database name ## Quick start ```sh keepassxc-cli --version ``` Then copy `.env.example` to `.env` and fill in the values it documents. ## Honest limits This build deliberately does not replace: - Secure sharing with other people, which needs a server and a key exchange. - Account recovery of any kind. Forgetting the master password is final. - Mobile autofill through the platform providers on iOS and Android. - Breach monitoring, travel mode and the audits that come with a paid account. - Any cryptography written by you, which is the point rather than a gap. - browser/mobile autofill - passkey support - secure sharing - recovery - audits - watchtower alerts - family/team management If one of those is essential to you, that is the reason to keep paying for 1Password, and the README should say so rather than pretend.
# Build brief · 1Password The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me a local encrypted password vault with generation, search, TOTP and sync you control to replace 1Password. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Rule zero Do not write encryption. Do not invent a vault format, do not implement a key derivation function, and do not roll your own file format with AES bolted on. Use KDBX 4 through KeePassXC, which is audited, cross-platform and already read by clients on every operating system. Your job is the workflow around a format that already exists. If a phase below tempts you into writing a cipher, you have misread it. ### Stack (fixed, do not substitute) - Vault format: KDBX 4 with the Argon2id key derivation function · memory-hard derivation is what makes a stolen vault file expensive rather than free to crack - Vault engine: KeePassXC and keepassxc-cli · audited, actively maintained, and it means no line of cryptography is yours - Your layer: Python 3.12 with a small command line and a Textual terminal interface · the part worth building is search and workflow, and that is where a scripting language pays - Sync: Any file-sync drive you already have, holding the encrypted .kdbx · the file is already ciphertext, so the sync provider never sees a secret - Autofill: KeePassXC-Browser, the official extension · browser integration is a native messaging protocol with a security model you should not reimplement ### Data model (create this before Phase 1) - The vault itself is a single .kdbx file. It is the only durable state and it is encrypted at rest. - Groups and entries live inside the vault: title, username, password, url, notes, TOTP seed, tags. - index.json (local, unencrypted, derived) · title, username, url and tags only, never a secret, rebuilt on unlock to make search instant - sync_state.json · last known file hash, last sync time, and the path of any conflict copy - Never write a decrypted secret to disk. The index exists precisely because it holds nothing worth stealing. ### Phase 1 · A vault with the right parameters Build: Create the vault with keepassxc-cli. Set the key derivation function to Argon2id and raise the memory and iteration parameters above the defaults deliberately. Write a script that reports the vault format and KDF so the setting is verifiable, not folklore. Decide now whether you also use a key file, and record that decision in the README. Trap: A vault created by an old tool may still be KDBX 3.1 with AES-KDF, which is much cheaper to attack, and nothing in the interface shouts about it. Check the format explicitly rather than trusting the default of whatever created the file. Done when: A command you can re-run reports the vault as KDBX 4 with Argon2id and prints the memory and iteration parameters. Opening the vault with a wrong password fails without revealing how many entries it contains or how large it is. Do not build yet: No search, no TOTP, no sync. One correctly configured file. ### Phase 2 · Entries and instant search Build: Add, edit, list and remove entries through keepassxc-cli, never by parsing the file. On unlock, build index.json containing titles, usernames, urls and tags only. Search that index. Fetch the password from the vault only at the moment of copying, and clear the clipboard after a timeout. Trap: Putting the password in the index to make search feel snappier turns a plaintext file into the weakest link in a chain whose whole point was strength. If you catch yourself doing it for performance, measure first: the index without secrets is already fast. Done when: Searching a vault of a thousand entries returns matches without a perceptible pause. Grepping index.json for any known password finds nothing. A copied password is gone from the clipboard after the configured timeout, verified by pasting after it expires. Do not build yet: No TOTP and no sync yet. ### Phase 3 · Generation with honest entropy Build: A generator for random character passwords and for word passphrases, using the operating system random source. Report entropy in bits, computed from the alphabet size and length, or from the word list size and word count. Show the number in the interface next to what it generated. Trap: Entropy comes from the process that generated the secret, not from the string itself. A passphrase drawn from a 7776 word list at four words is about 51.7 bits whatever the words turn out to be, and a strength meter that scores the resulting string instead will confidently disagree. Compute from the generator's own parameters. Done when: A four word passphrase from a 7776 word list reports approximately 51.7 bits, and five words reports approximately 64.6 bits. A sixteen character password from a 94 character alphabet reports approximately 104.9 bits. Generating ten thousand passphrases produces no duplicate and uses the operating system random source, not the language default random module. Do not build yet: No TOTP and no sync yet. ### Phase 4 · TOTP Build: Store TOTP seeds in the entry's dedicated field and generate codes with keepassxc-cli. Display the remaining seconds in the period. Handle the seed formats real services hand out: bare base32, and otpauth:// URLs with period and digit parameters. Trap: Not every issuer uses thirty second periods and six digits, and the parameters ride in the otpauth URL rather than in the seed. Parse the URL and honour period, digits and algorithm, or you will produce confidently wrong codes for a minority of services and never work out why. Done when: A code generated for a known test seed matches a second independent implementation for the same timestamp. A seed supplied as an otpauth URL with a sixty second period produces a code that changes every sixty seconds, not every thirty. The countdown reaches zero exactly when the code changes. Do not build yet: No sync and no browser integration yet. ### Phase 5 · Sync with conflict detection Build: Keep the .kdbx in a synced folder. Record the file hash and modification time in sync_state.json before and after every local change. Before writing, compare the current hash to the last one you recorded. If it moved, someone else wrote, so keep a conflict copy and merge with KeePassXC's own merge rather than choosing a winner. Trap: File sync services resolve simultaneous edits by picking one file and, at best, leaving the other beside it with a renamed copy nobody looks at. The whole point of this phase is to notice before that happens. Hash before you write, every time, with no fast path that skips the check. Done when: Editing the vault on machine A and then, before it syncs, editing on machine B, produces a detected conflict and a preserved conflict copy rather than a silent overwrite. Running the merge afterwards leaves entries from both machines present. A normal single-machine edit produces no conflict copy. Do not build yet: No browser autofill or passkeys yet; those are the product phase. ### Phase 6 · Browser autofill, passkeys and recovery Build: Connect KeePassXC-Browser through native messaging, turn on passkey support in the extension settings, and write and then actually perform a recovery drill: restore the vault from backup onto a machine that has never seen it and unlock it. Trap: Passkeys in KeePassXC are handled by the browser extension, not by the command line, so a passkey created in the browser is invisible to a script-only workflow. Decide which credentials live as passkeys before you spread them across two interfaces you have to remember. Done when: The browser extension connects and fills a login on a real site. A passkey created through the extension survives a browser restart and authenticates again. The recovery drill has been performed on a second machine, and the date it was performed is written in the README. Do not build yet: Nothing after this. This is the last phase. ### Out of scope (and why) - Any cryptography of your own. This is the one rule that is not negotiable, and the reason the vault format is someone else's. - Secure sharing with other people. 1Password's sharing is a key exchange with a server, an account model and a revocation story, and a link with a password in it is not the same thing. - Account recovery. 1Password can help a family member back in; a file encrypted with a password you forgot is gone, and your README must say so plainly. - Mobile autofill. The system autofill providers on iOS and Android are platform integrations with their own review, and reading a KDBX file on a phone is the easy part of that problem. - Breach monitoring and travel mode, both of which are services rather than features. ### README must contain - That the vault is KDBX 4 with Argon2id and how to verify that yourself - That there is no recovery: forgetting the master password loses the vault - Where the vault file lives, where backups go, and the date of the last successful restore drill - That the local index contains no secrets and can be deleted safely - Which clients can open the vault, including KeePassXC on desktop and the mobile clients that read KDBX
# Agent instructions · 1Password indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: KDBX 4 with the Argon2id key derivation function, KeePassXC and keepassxc-cli, Python 3.12 with a small command line and a Textual terminal interface, Any file-sync drive you already have, holding the encrypted .kdbx, KeePassXC-Browser, the official extension. Do not substitute. - Work one phase at a time, in order. Do not start a phase until every "Done when" item of the previous one passes. - Prefer the fewest moving parts that satisfy the step. No frameworks, services or dependencies the plan does not name. - Secrets live in `.env`, never in source or logs. Keep `.env.example` current when a variable is introduced. - Do not invent cryptography, security guarantees, APIs or compliance claims. - Add a focused test for every destructive, security-sensitive or data-loss path the plan names. - Run the project checks before declaring a phase complete, and record any deliberate shortcut in the README under "Tradeoffs". ## Known traps - A vault created by an old tool may still be KDBX 3.1 with AES-KDF, which is much cheaper to attack, and nothing in the interface shouts about it. Check the format explicitly rather than trusting the default of whatever created the file. - Putting the password in the index to make search feel snappier turns a plaintext file into the weakest link in a chain whose whole point was strength. If you catch yourself doing it for performance, measure first: the index without secrets is already fast. - Entropy comes from the process that generated the secret, not from the string itself. A passphrase drawn from a 7776 word list at four words is about 51.7 bits whatever the words turn out to be, and a strength meter that scores the resulting string instead will confidently disagree. Compute from the generator's own parameters. - Not every issuer uses thirty second periods and six digits, and the parameters ride in the otpauth URL rather than in the seed. Parse the URL and honour period, digits and algorithm, or you will produce confidently wrong codes for a minority of services and never work out why. - File sync services resolve simultaneous edits by picking one file and, at best, leaving the other beside it with a renamed copy nobody looks at. The whole point of this phase is to notice before that happens. Hash before you write, every time, with no fast path that skips the check.
# Build plan · 1Password A KDBX 4 vault created and driven through KeePassXC and its command line, with a fast search and entry workflow of your own, honest entropy reporting on generated secrets, TOTP codes, and encrypted sync over a cloud drive with conflict detection that does not silently lose an edit. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · A vault with the right parameters A KDBX 4 vault exists, uses Argon2id, and you have proved it does rather than assumed it. ### Steps 1. Install KeePassXC and confirm the command line is present The command line tool ships with the desktop application on macOS and Windows and is often a separate package on Linux. Confirm it before building anything on top of it. ```sh keepassxc-cli --version ``` 2. Create the vault with explicit KDF settings Create the database, then set the key derivation function and parameters rather than accepting whatever the version default is. Higher memory costs you a second on unlock and costs an attacker far more. Files: `vault.kdbx` ```sh keepassxc-cli db-create vault.kdbx --set-password keepassxc-cli db-info vault.kdbx ``` 3. Write the verification script A short script that runs db-info and asserts the format is KDBX 4 and the KDF is Argon2id, exiting non-zero otherwise. Run it in your test suite so a future migration cannot quietly downgrade you. Files: `scripts/verify_vault.py` ### Done when - [ ] The verification script reports KDBX 4 and Argon2id and exits zero - [ ] The same script exits non-zero when pointed at a deliberately created KDBX 3.1 vault - [ ] A wrong password is rejected without disclosing entry count or structure - [ ] The chosen memory parameter makes unlock take a noticeable but tolerable moment ### Watch out - A vault created by an old tool may still be KDBX 3.1 with AES-KDF, which is much cheaper to attack, and nothing in the interface shouts about it. Check the format explicitly rather than trusting the default of whatever created the file. ## Phase 2 · Entries and instant search Finding and copying a credential is faster than opening a browser, and the search index never holds a secret. ### Steps 1. Wrap the command line rather than the file One module that shells out for show, add, edit and rm, passing the password on stdin and never as an argument, because arguments are visible in the process list. Files: `vault.py` 2. Build the secret-free index on unlock Enumerate entries and write index.json with title, username, url and tags. Add a test that asserts no field of a seeded entry's password appears anywhere in the file. Files: `index.py`, `tests/test_index.py` 3. Copy with a clipboard timeout Copy the secret, then clear the clipboard after a set number of seconds, but only if the clipboard still holds what you put there. Clearing unconditionally destroys whatever the user copied in between. Files: `clip.py` ### Done when - [ ] Search across a thousand seeded entries returns results without a visible pause - [ ] A known password from a seeded entry cannot be found anywhere in index.json - [ ] The clipboard is cleared after the timeout and only if it still holds the secret - [ ] Passwords are never passed as command line arguments, verified by inspecting the process list during a copy ### Watch out - Putting the password in the index to make search feel snappier turns a plaintext file into the weakest link in a chain whose whole point was strength. If you catch yourself doing it for performance, measure first: the index without secrets is already fast. ## Phase 3 · Generation with honest entropy Every generated secret comes with a number saying how strong it is, and the number is right. ### Steps 1. Use the cryptographic random source Python's secrets module, not random. The random module is seeded predictably and is documented as unsuitable for security purposes. Files: `generate.py` 2. Ship a word list and compute passphrase entropy Bundle a diceware style list and record its exact length. Entropy is words multiplied by the base two logarithm of the list length. Files: `wordlist.txt`, `generate.py` 3. Show the number where the secret appears Print bits next to every generated secret, and refuse to generate below a floor you choose, explaining why rather than silently upgrading. Files: `cli.py` ### Done when - [ ] Four words from a 7776 word list reports about 51.7 bits - [ ] Sixteen characters from a 94 character alphabet reports about 104.9 bits - [ ] Ten thousand generated passphrases contain no duplicate - [ ] The code imports secrets and never random for anything that becomes a credential ### Watch out - Entropy comes from the process that generated the secret, not from the string itself. A passphrase drawn from a 7776 word list at four words is about 51.7 bits whatever the words turn out to be, and a strength meter that scores the resulting string instead will confidently disagree. Compute from the generator's own parameters. ## Phase 4 · TOTP Six digit codes match what your phone shows, including across a clock that is slightly wrong. ### Steps 1. Store seeds in the TOTP field, not in notes KDBX has a dedicated attribute for this and KeePassXC and its browser extension both read it. Putting the seed in notes works only in your own tool. Files: `vault.py` 2. Parse otpauth URLs properly Extract secret, period, digits and algorithm. Default to thirty seconds and six digits only when the URL does not say otherwise. Files: `totp.py` 3. Verify against a known vector Use a published test seed and timestamp, assert the expected code, and keep that as a test so a refactor cannot break code generation silently. Files: `tests/test_totp.py` ### Done when - [ ] A known test seed and timestamp produce the documented expected code - [ ] An otpauth URL with a sixty second period changes the code every sixty seconds - [ ] The countdown hits zero at the moment the code rolls over - [ ] A seed stored in the TOTP field is also readable by KeePassXC itself ### Watch out - Not every issuer uses thirty second periods and six digits, and the parameters ride in the otpauth URL rather than in the seed. Parse the URL and honour period, digits and algorithm, or you will produce confidently wrong codes for a minority of services and never work out why. ## Phase 5 · Sync with conflict detection Two machines can edit the vault and you will be told when that happened, instead of losing one side quietly. ### Steps 1. Record hash and modification time around every write Hash the whole file. Store it with a timestamp in sync_state.json immediately after a successful write. Files: `sync.py` 2. Detect divergence before writing Re-hash the file on disk and compare to the recorded hash. A mismatch means a remote write landed and you must not overwrite it. Files: `sync.py` 3. Merge with the vault engine, never by hand keepassxc-cli merge understands entry history and timestamps. Your own merge would not. ```sh keepassxc-cli merge vault.kdbx vault-conflict.kdbx ``` ### Done when - [ ] A simulated concurrent edit produces a conflict copy and a warning, not a silent overwrite - [ ] Merging the conflict copy leaves entries from both sides present - [ ] A normal edit on one machine produces no conflict copy - [ ] sync_state.json never contains anything derived from a password ### Watch out - File sync services resolve simultaneous edits by picking one file and, at best, leaving the other beside it with a renamed copy nobody looks at. The whole point of this phase is to notice before that happens. Hash before you write, every time, with no fast path that skips the check. ## Not in this build - Secure sharing with other people, which needs a server and a key exchange. - Account recovery of any kind. Forgetting the master password is final. - Mobile autofill through the platform providers on iOS and Android. - Breach monitoring, travel mode and the audits that come with a paid account. - Any cryptography written by you, which is the point rather than a gap. ## After v1, if you want it - A read-only mobile workflow using one of the existing KDBX clients on your phone - Automatic backup verification that opens the newest dated copy and counts entries - Tagging and per-tag clipboard timeouts, so a banking credential clears faster
# Copy to .env and fill in. Never commit .env; this file documents it. # Required. The .kdbx file. Put it inside the folder your sync tool watches. VAULT_PATH=~/Sync/vault.kdbx # Optional · secret. Optional second factor for the vault. If you use one, it must never travel in the same sync folder as the vault. VAULT_KEYFILE=~/.config/vault/vault.key # Required. The secret-free search index. Safe to delete; it rebuilds on unlock. INDEX_PATH=~/.cache/vault/index.json # Required. How long a copied secret stays on the clipboard. Chosen by you. CLIPBOARD_CLEAR_SECONDS=20 # Required. Where dated vault copies are written. Deliberately not inside the sync folder. BACKUP_DIR=/Volumes/Backup/vault
# 1Password · product brief ## Problem A basic encrypted vault is buildable, but browser autofill, mobile apps, passkeys, sharing, recovery, audits, and trust make this a dangerous thing to replace casually. ## Product outcome A vault you trust because nothing about its security is your own invention, with a workflow fast enough that you stop reusing passwords, browser autofill and passkeys through the official extension, and a restore you have actually performed. ## Target user A builder who needs a maintainable product foundation, not a one-off demo. ## Required capabilities - strong encryption library - secure key derivation - browser extension if autofill matters - mobile/desktop storage - backup plan ## Explicit non-goals for v1 - Secure sharing with other people, which needs a server and a key exchange. - Account recovery of any kind. Forgetting the master password is final. - Mobile autofill through the platform providers on iOS and Android. - Breach monitoring, travel mode and the audits that come with a paid account. - Any cryptography written by you, which is the point rather than a gap. - browser/mobile autofill - passkey support - secure sharing - recovery - audits - watchtower alerts - family/team management ## Success criteria - The verification script confirms KDBX 4 with Argon2id - No secret appears in the index, proved by the test rather than by inspection - A restore onto a clean machine has been performed and dated in the README - A simulated concurrent edit produces a conflict copy rather than a silent loss - The clipboard timeout is confirmed to clear only the value it wrote
# Build brief · 1Password The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me a local encrypted password vault with generation, search, TOTP and sync you control to replace 1Password. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Rule zero Do not write encryption. Do not invent a vault format, do not implement a key derivation function, and do not roll your own file format with AES bolted on. Use KDBX 4 through KeePassXC, which is audited, cross-platform and already read by clients on every operating system. Your job is the workflow around a format that already exists. If a phase below tempts you into writing a cipher, you have misread it. ### Stack (fixed, do not substitute) - Vault format: KDBX 4 with the Argon2id key derivation function · memory-hard derivation is what makes a stolen vault file expensive rather than free to crack - Vault engine: KeePassXC and keepassxc-cli · audited, actively maintained, and it means no line of cryptography is yours - Your layer: Python 3.12 with a small command line and a Textual terminal interface · the part worth building is search and workflow, and that is where a scripting language pays - Sync: Any file-sync drive you already have, holding the encrypted .kdbx · the file is already ciphertext, so the sync provider never sees a secret - Autofill: KeePassXC-Browser, the official extension · browser integration is a native messaging protocol with a security model you should not reimplement ### Data model (create this before Phase 1) - The vault itself is a single .kdbx file. It is the only durable state and it is encrypted at rest. - Groups and entries live inside the vault: title, username, password, url, notes, TOTP seed, tags. - index.json (local, unencrypted, derived) · title, username, url and tags only, never a secret, rebuilt on unlock to make search instant - sync_state.json · last known file hash, last sync time, and the path of any conflict copy - Never write a decrypted secret to disk. The index exists precisely because it holds nothing worth stealing. ### Phase 1 · A vault with the right parameters Build: Create the vault with keepassxc-cli. Set the key derivation function to Argon2id and raise the memory and iteration parameters above the defaults deliberately. Write a script that reports the vault format and KDF so the setting is verifiable, not folklore. Decide now whether you also use a key file, and record that decision in the README. Trap: A vault created by an old tool may still be KDBX 3.1 with AES-KDF, which is much cheaper to attack, and nothing in the interface shouts about it. Check the format explicitly rather than trusting the default of whatever created the file. Done when: A command you can re-run reports the vault as KDBX 4 with Argon2id and prints the memory and iteration parameters. Opening the vault with a wrong password fails without revealing how many entries it contains or how large it is. Do not build yet: No search, no TOTP, no sync. One correctly configured file. ### Phase 2 · Entries and instant search Build: Add, edit, list and remove entries through keepassxc-cli, never by parsing the file. On unlock, build index.json containing titles, usernames, urls and tags only. Search that index. Fetch the password from the vault only at the moment of copying, and clear the clipboard after a timeout. Trap: Putting the password in the index to make search feel snappier turns a plaintext file into the weakest link in a chain whose whole point was strength. If you catch yourself doing it for performance, measure first: the index without secrets is already fast. Done when: Searching a vault of a thousand entries returns matches without a perceptible pause. Grepping index.json for any known password finds nothing. A copied password is gone from the clipboard after the configured timeout, verified by pasting after it expires. Do not build yet: No TOTP and no sync yet. ### Phase 3 · Generation with honest entropy Build: A generator for random character passwords and for word passphrases, using the operating system random source. Report entropy in bits, computed from the alphabet size and length, or from the word list size and word count. Show the number in the interface next to what it generated. Trap: Entropy comes from the process that generated the secret, not from the string itself. A passphrase drawn from a 7776 word list at four words is about 51.7 bits whatever the words turn out to be, and a strength meter that scores the resulting string instead will confidently disagree. Compute from the generator's own parameters. Done when: A four word passphrase from a 7776 word list reports approximately 51.7 bits, and five words reports approximately 64.6 bits. A sixteen character password from a 94 character alphabet reports approximately 104.9 bits. Generating ten thousand passphrases produces no duplicate and uses the operating system random source, not the language default random module. Do not build yet: No TOTP and no sync yet. ### Phase 4 · TOTP Build: Store TOTP seeds in the entry's dedicated field and generate codes with keepassxc-cli. Display the remaining seconds in the period. Handle the seed formats real services hand out: bare base32, and otpauth:// URLs with period and digit parameters. Trap: Not every issuer uses thirty second periods and six digits, and the parameters ride in the otpauth URL rather than in the seed. Parse the URL and honour period, digits and algorithm, or you will produce confidently wrong codes for a minority of services and never work out why. Done when: A code generated for a known test seed matches a second independent implementation for the same timestamp. A seed supplied as an otpauth URL with a sixty second period produces a code that changes every sixty seconds, not every thirty. The countdown reaches zero exactly when the code changes. Do not build yet: No sync and no browser integration yet. ### Phase 5 · Sync with conflict detection Build: Keep the .kdbx in a synced folder. Record the file hash and modification time in sync_state.json before and after every local change. Before writing, compare the current hash to the last one you recorded. If it moved, someone else wrote, so keep a conflict copy and merge with KeePassXC's own merge rather than choosing a winner. Trap: File sync services resolve simultaneous edits by picking one file and, at best, leaving the other beside it with a renamed copy nobody looks at. The whole point of this phase is to notice before that happens. Hash before you write, every time, with no fast path that skips the check. Done when: Editing the vault on machine A and then, before it syncs, editing on machine B, produces a detected conflict and a preserved conflict copy rather than a silent overwrite. Running the merge afterwards leaves entries from both machines present. A normal single-machine edit produces no conflict copy. Do not build yet: No browser autofill or passkeys yet; those are the product phase. ### Phase 6 · Browser autofill, passkeys and recovery Build: Connect KeePassXC-Browser through native messaging, turn on passkey support in the extension settings, and write and then actually perform a recovery drill: restore the vault from backup onto a machine that has never seen it and unlock it. Trap: Passkeys in KeePassXC are handled by the browser extension, not by the command line, so a passkey created in the browser is invisible to a script-only workflow. Decide which credentials live as passkeys before you spread them across two interfaces you have to remember. Done when: The browser extension connects and fills a login on a real site. A passkey created through the extension survives a browser restart and authenticates again. The recovery drill has been performed on a second machine, and the date it was performed is written in the README. Do not build yet: Nothing after this. This is the last phase. ### Out of scope (and why) - Any cryptography of your own. This is the one rule that is not negotiable, and the reason the vault format is someone else's. - Secure sharing with other people. 1Password's sharing is a key exchange with a server, an account model and a revocation story, and a link with a password in it is not the same thing. - Account recovery. 1Password can help a family member back in; a file encrypted with a password you forgot is gone, and your README must say so plainly. - Mobile autofill. The system autofill providers on iOS and Android are platform integrations with their own review, and reading a KDBX file on a phone is the easy part of that problem. - Breach monitoring and travel mode, both of which are services rather than features. ### README must contain - That the vault is KDBX 4 with Argon2id and how to verify that yourself - That there is no recovery: forgetting the master password loses the vault - Where the vault file lives, where backups go, and the date of the last successful restore drill - That the local index contains no secrets and can be deleted safely - Which clients can open the vault, including KeePassXC on desktop and the mobile clients that read KDBX
# Architecture · 1Password ## Stack | Part | Choice | Why | | --- | --- | --- | | Vault format | KDBX 4 with the Argon2id key derivation function | memory-hard derivation is what makes a stolen vault file expensive rather than free to crack | | Vault engine | KeePassXC and keepassxc-cli | audited, actively maintained, and it means no line of cryptography is yours | | Your layer | Python 3.12 with a small command line and a Textual terminal interface | the part worth building is search and workflow, and that is where a scripting language pays | | Sync | Any file-sync drive you already have, holding the encrypted .kdbx | the file is already ciphertext, so the sync provider never sees a secret | | Autofill | KeePassXC-Browser, the official extension | browser integration is a native messaging protocol with a security model you should not reimplement | ## Modules Each module has one owner concern and a documented way to replace it. | Module | Owns | How to replace it | | --- | --- | --- | | Vault engine | Encryption, key derivation, the file format and merging. | Not yours to swap and not yours to modify. Upgrading KeePassXC is the only change you make here. | | Index | Fast search over non-secret fields. | Replace JSON with SQLite full text search if a very large vault gets slow. The invariant that survives any swap is that no secret enters the index. | | Generator | Random secret creation and entropy reporting. | The word list is data, so a different list is a file change plus an updated expected-bits test. | | Sync | Hash tracking, conflict detection and invoking merge. | Any file transport works because the file is already ciphertext. Swapping the drive changes nothing but a path. | | Browser bridge | Autofill and passkeys. | Provided entirely by KeePassXC-Browser. If you find yourself writing native messaging code, stop. | ## Configuration Every runtime setting is an environment variable documented in `.env.example`, validated at startup, with a safe local default wherever one exists. - `VAULT_PATH` · required · The .kdbx file. Put it inside the folder your sync tool watches. - `VAULT_KEYFILE` · optional, secret · Optional second factor for the vault. If you use one, it must never travel in the same sync folder as the vault. - `INDEX_PATH` · required · The secret-free search index. Safe to delete; it rebuilds on unlock. - `CLIPBOARD_CLEAR_SECONDS` · required · How long a copied secret stays on the clipboard. Chosen by you. - `BACKUP_DIR` · required · Where dated vault copies are written. Deliberately not inside the sync folder. ## Production baseline - Security: least privilege, input validation at every boundary, secret redaction in logs, rate limits on abuse-prone paths, no invented security primitives. - Data: explicit schema and migrations, transactional writes where integrity matters, backup and restore procedures that have been exercised. - Integrations: adapters around third-party providers, idempotent webhook or job processing, bounded retries, timeouts. - Observability: structured logs with request or operation ids, an error-tracking hook, and health and readiness checks where a server exists. - Quality: unit tests for domain rules, integration tests at module boundaries, one end-to-end test of the critical path. ## Decision records For each dependency in the stack table, keep a short note: why it was chosen, its failure mode, and how it is replaced. Do not add infrastructure until a requirement in `PRODUCT.md` justifies it.
# Agent instructions · 1Password product build - Read `PRODUCT.md` and `ARCHITECTURE.md` before changing code. The stack is fixed: KDBX 4 with the Argon2id key derivation function, KeePassXC and keepassxc-cli, Python 3.12 with a small command line and a Textual terminal interface, Any file-sync drive you already have, holding the encrypted .kdbx, KeePassXC-Browser, the official extension. - Implement milestone by milestone from `MILESTONES.md`; keep each change reviewable and leave the application runnable at every commit. - Treat authentication, payments, encryption, imports, webhooks and destructive actions as high-risk boundaries when present. - Never invent cryptography or silently weaken a requirement to make a check pass. - Put every external service behind an interface with a deterministic fake for tests. - Add migrations and rollback or recovery notes for every persistent data change. - Log useful operational context without credentials, tokens, passwords or personal data. - Update documentation and run every check before completing a milestone. ## Known traps - A vault created by an old tool may still be KDBX 3.1 with AES-KDF, which is much cheaper to attack, and nothing in the interface shouts about it. Check the format explicitly rather than trusting the default of whatever created the file. - Putting the password in the index to make search feel snappier turns a plaintext file into the weakest link in a chain whose whole point was strength. If you catch yourself doing it for performance, measure first: the index without secrets is already fast. - Entropy comes from the process that generated the secret, not from the string itself. A passphrase drawn from a 7776 word list at four words is about 51.7 bits whatever the words turn out to be, and a strength meter that scores the resulting string instead will confidently disagree. Compute from the generator's own parameters. - Not every issuer uses thirty second periods and six digits, and the parameters ride in the otpauth URL rather than in the seed. Parse the URL and honour period, digits and algorithm, or you will produce confidently wrong codes for a minority of services and never work out why. - File sync services resolve simultaneous edits by picking one file and, at best, leaving the other beside it with a renamed copy nobody looks at. The whole point of this phase is to notice before that happens. Hash before you write, every time, with no fast path that skips the check. - Passkeys in KeePassXC are handled by the browser extension, not by the command line, so a passkey created in the browser is invisible to a script-only workflow. Decide which credentials live as passkeys before you spread them across two interfaces you have to remember.
# Delivery milestones · 1Password Estimated effort: **multi-day** for the indie phases; the production-only milestones add the trust and operability layer. ## M1 · A vault with the right parameters A KDBX 4 vault exists, uses Argon2id, and you have proved it does rather than assumed it. ### Steps 1. Install KeePassXC and confirm the command line is present The command line tool ships with the desktop application on macOS and Windows and is often a separate package on Linux. Confirm it before building anything on top of it. ```sh keepassxc-cli --version ``` 2. Create the vault with explicit KDF settings Create the database, then set the key derivation function and parameters rather than accepting whatever the version default is. Higher memory costs you a second on unlock and costs an attacker far more. Files: `vault.kdbx` ```sh keepassxc-cli db-create vault.kdbx --set-password keepassxc-cli db-info vault.kdbx ``` 3. Write the verification script A short script that runs db-info and asserts the format is KDBX 4 and the KDF is Argon2id, exiting non-zero otherwise. Run it in your test suite so a future migration cannot quietly downgrade you. Files: `scripts/verify_vault.py` ### Done when - [ ] The verification script reports KDBX 4 and Argon2id and exits zero - [ ] The same script exits non-zero when pointed at a deliberately created KDBX 3.1 vault - [ ] A wrong password is rejected without disclosing entry count or structure - [ ] The chosen memory parameter makes unlock take a noticeable but tolerable moment ### Watch out - A vault created by an old tool may still be KDBX 3.1 with AES-KDF, which is much cheaper to attack, and nothing in the interface shouts about it. Check the format explicitly rather than trusting the default of whatever created the file. ## M2 · Entries and instant search Finding and copying a credential is faster than opening a browser, and the search index never holds a secret. ### Steps 1. Wrap the command line rather than the file One module that shells out for show, add, edit and rm, passing the password on stdin and never as an argument, because arguments are visible in the process list. Files: `vault.py` 2. Build the secret-free index on unlock Enumerate entries and write index.json with title, username, url and tags. Add a test that asserts no field of a seeded entry's password appears anywhere in the file. Files: `index.py`, `tests/test_index.py` 3. Copy with a clipboard timeout Copy the secret, then clear the clipboard after a set number of seconds, but only if the clipboard still holds what you put there. Clearing unconditionally destroys whatever the user copied in between. Files: `clip.py` ### Done when - [ ] Search across a thousand seeded entries returns results without a visible pause - [ ] A known password from a seeded entry cannot be found anywhere in index.json - [ ] The clipboard is cleared after the timeout and only if it still holds the secret - [ ] Passwords are never passed as command line arguments, verified by inspecting the process list during a copy ### Watch out - Putting the password in the index to make search feel snappier turns a plaintext file into the weakest link in a chain whose whole point was strength. If you catch yourself doing it for performance, measure first: the index without secrets is already fast. ## M3 · Generation with honest entropy Every generated secret comes with a number saying how strong it is, and the number is right. ### Steps 1. Use the cryptographic random source Python's secrets module, not random. The random module is seeded predictably and is documented as unsuitable for security purposes. Files: `generate.py` 2. Ship a word list and compute passphrase entropy Bundle a diceware style list and record its exact length. Entropy is words multiplied by the base two logarithm of the list length. Files: `wordlist.txt`, `generate.py` 3. Show the number where the secret appears Print bits next to every generated secret, and refuse to generate below a floor you choose, explaining why rather than silently upgrading. Files: `cli.py` ### Done when - [ ] Four words from a 7776 word list reports about 51.7 bits - [ ] Sixteen characters from a 94 character alphabet reports about 104.9 bits - [ ] Ten thousand generated passphrases contain no duplicate - [ ] The code imports secrets and never random for anything that becomes a credential ### Watch out - Entropy comes from the process that generated the secret, not from the string itself. A passphrase drawn from a 7776 word list at four words is about 51.7 bits whatever the words turn out to be, and a strength meter that scores the resulting string instead will confidently disagree. Compute from the generator's own parameters. ## M4 · TOTP Six digit codes match what your phone shows, including across a clock that is slightly wrong. ### Steps 1. Store seeds in the TOTP field, not in notes KDBX has a dedicated attribute for this and KeePassXC and its browser extension both read it. Putting the seed in notes works only in your own tool. Files: `vault.py` 2. Parse otpauth URLs properly Extract secret, period, digits and algorithm. Default to thirty seconds and six digits only when the URL does not say otherwise. Files: `totp.py` 3. Verify against a known vector Use a published test seed and timestamp, assert the expected code, and keep that as a test so a refactor cannot break code generation silently. Files: `tests/test_totp.py` ### Done when - [ ] A known test seed and timestamp produce the documented expected code - [ ] An otpauth URL with a sixty second period changes the code every sixty seconds - [ ] The countdown hits zero at the moment the code rolls over - [ ] A seed stored in the TOTP field is also readable by KeePassXC itself ### Watch out - Not every issuer uses thirty second periods and six digits, and the parameters ride in the otpauth URL rather than in the seed. Parse the URL and honour period, digits and algorithm, or you will produce confidently wrong codes for a minority of services and never work out why. ## M5 · Sync with conflict detection Two machines can edit the vault and you will be told when that happened, instead of losing one side quietly. ### Steps 1. Record hash and modification time around every write Hash the whole file. Store it with a timestamp in sync_state.json immediately after a successful write. Files: `sync.py` 2. Detect divergence before writing Re-hash the file on disk and compare to the recorded hash. A mismatch means a remote write landed and you must not overwrite it. Files: `sync.py` 3. Merge with the vault engine, never by hand keepassxc-cli merge understands entry history and timestamps. Your own merge would not. ```sh keepassxc-cli merge vault.kdbx vault-conflict.kdbx ``` ### Done when - [ ] A simulated concurrent edit produces a conflict copy and a warning, not a silent overwrite - [ ] Merging the conflict copy leaves entries from both sides present - [ ] A normal edit on one machine produces no conflict copy - [ ] sync_state.json never contains anything derived from a password ### Watch out - File sync services resolve simultaneous edits by picking one file and, at best, leaving the other beside it with a renamed copy nobody looks at. The whole point of this phase is to notice before that happens. Hash before you write, every time, with no fast path that skips the check. ## M6 · Browser autofill, passkeys and recovery (production only) The vault is usable in a browser, can hold passkeys, and you have proved you can get back in after losing a device. ### Steps 1. Connect the browser extension Install KeePassXC-Browser, enable browser integration in KeePassXC settings for your specific browser, and connect the database. Native messaging is what carries the connection. 2. Turn on passkeys and create one Enable the passkeys option in the extension settings, then register a passkey on a site that supports them and confirm it authenticates after a restart. 3. Perform the recovery drill and date it Copy the backup and the key file, if you use one, to a machine that has never had the vault. Unlock it there. Write the date in the README. A backup you have not restored is a rumour. Files: `README.md` ### Done when - [ ] The extension fills a login on a real site - [ ] A passkey created in the browser authenticates after a browser restart - [ ] The vault was restored and unlocked on a second machine and the date is recorded - [ ] Losing the local index does not lose data, because it rebuilds on unlock ### Watch out - Passkeys in KeePassXC are handled by the browser extension, not by the command line, so a passkey created in the browser is invisible to a script-only workflow. Decide which credentials live as passkeys before you spread them across two interfaces you have to remember.
# Operations · 1Password ## Backup A dated copy of the .kdbx to a destination outside the sync folder, on a schedule, keeping several generations. The file is encrypted, so the backup destination does not need to be trusted, but it does need to be separate. ## Restore Copy a dated vault and the key file, if any, to a machine with KeePassXC, and unlock. Do this on purpose at least once a year and write the date in the README, because an untested backup is not a backup. Do a restore drill before the first real user, and write the date here when it passes. ## Monitoring There is no service to watch. What you monitor is the backup: assert that a new dated copy appeared, that its size is plausible, and that the verification script still reports KDBX 4 with Argon2id. ## Incident checklist If the master password is lost the vault is gone and no amount of engineering changes that. If the file is corrupted, restore the newest dated backup and merge any newer conflict copy into it. If a device is stolen, change the master password and rotate the credentials that device had unlocked recently. 1. Contain the issue without destroying evidence or user data. 2. Record the timeline and affected scope. 3. Rotate exposed secrets and revoke compromised sessions or credentials. 4. Restore from a verified backup when needed. 5. Document the root cause, the remediation and the regression test. ## Release gate - [ ] The verification script confirms KDBX 4 with Argon2id - [ ] No secret appears in the index, proved by the test rather than by inspection - [ ] A restore onto a clean machine has been performed and dated in the README - [ ] A simulated concurrent edit produces a conflict copy rather than a silent loss - [ ] The clipboard timeout is confirmed to clear only the value it wrote ## Launch constraint Do not market omitted 1Password capabilities as implemented. The non-goals in `PRODUCT.md` remain user-visible limitations until they are deliberately delivered.
# Copy to .env and fill in. Never commit .env; this file documents it. # Required. The .kdbx file. Put it inside the folder your sync tool watches. VAULT_PATH=~/Sync/vault.kdbx # Optional · secret. Optional second factor for the vault. If you use one, it must never travel in the same sync folder as the vault. VAULT_KEYFILE=~/.config/vault/vault.key # Required. The secret-free search index. Safe to delete; it rebuilds on unlock. INDEX_PATH=~/.cache/vault/index.json # Required. How long a copied secret stays on the clipboard. Chosen by you. CLIPBOARD_CLEAR_SECONDS=20 # Required. Where dated vault copies are written. Deliberately not inside the sync folder. BACKUP_DIR=/Volumes/Backup/vault
$ choose a build depth, inspect the files, then open the complete pack in your agent
They pay because password managers are one place where reliability and audited security beat saving a few dollars.
xbrowser/mobile autofill
xpasskey support
xsecure sharing
xrecovery
xaudits
xwatchtower alerts
xfamily/team management
Don't feel like building it? These folks already made it free.
no votes, no pay-to-list · just what's real
1Password pricing
| plan | monthly | annual (per mo) | what you get |
|---|---|---|---|
| individual | $3.99/user | $2.99/user | 1 person; unlimited passwords and devices; 1 GB document storage |
| families | $5.99/workspace | $4.49/workspace | Up to 5 family members; unlimited passwords and devices; 1 GB document storage/user |
free tierno free tier; 14-day trial
billingmonthly + annual; advertised annual rate is a first-year promotion
hidden costsThe discounted annual amount is introductory and auto-renews at the then-current regular price; taxes may be added and family seats beyond the included 5 can increase the bill
verified 2026-08-14 · source ↗
Vibecode 1Password
Kinda. The core of 1Password is buildable in a weekend with the prompt on this page, but there are real gaps: browser/mobile autofill, passkey support. Read the honest list above before committing.
How much does 1Password cost?
1Password costs about $2.99/month (Individual, checked 2026-07-30), which is $35.88 per year.
What do I lose by replacing 1Password?
Honestly: browser/mobile autofill; passkey support; secure sharing; recovery; audits; watchtower alerts; family/team management. If any of those are load-bearing for you, keep paying.
Is there an open-source alternative to 1Password?
Yes: Vaultwarden (Passwords, passkeys, notes, sharing, and reports on your server; polished recovery support becomes your problem.) Bitwarden Free (Unlimited passwords, passkeys, secure notes, and one-person sharing; built-in TOTP and full reports cost extra.) The prompt is for when you want it exactly your way.