Vault is an open-source iPhone, iPad and Mac app for the secrets you can't afford to lose: seed phrases, private keys and private notes, and your two-factor codes too. It's fully offline, with no servers, no accounts, no sync, no analytics and no network requests. And it's built for the moment someone forces you to unlock it.
- Anyone who holds the keys to their own crypto, or other secrets they can't afford to lose, and wants them on their own device, backed up where they choose, rather than synced to someone else's servers. Vault keeps the keys, never the funds: it signs nothing and makes no network requests.
- Anyone who might be made to unlock their iPhone or Mac, at a border, in a robbery or at home. Face ID and the passcode don't stop that, so Vault has an App Lock Password, a duress password, hidden items and killphrases.
MANIFESTO.mdsets out the threat model.
- Recovery phrases (crypto wallet seed words), always encrypted and locked. They're checked against the BIP39 (all 10 languages), SLIP-39, Electrum and Monero word lists and checksums, masked until tapped, and can't be copied.
- Private keys, protected as recovery phrases are: WIF, extended private keys (xprv, zprv and others), raw hex, Nostr nsec, and OpenPGP, OpenSSH and PEM private keys, or any other text. Vault says what each key is and checks its checksum, and never derives anything from it.
- App Lock, off by default: Face ID, Touch ID or the passcode, locking straight away or after 1, 5 or 15 minutes, with a cover in the app switcher.
- An App Lock Password that encrypts the whole vault on the device with AES-256-GCM. It's asked for after Face ID or the passcode, and wrong ones wait longer each time, as iOS does.
- A duress password that opens a separate vault, with its own backups, instead of the real one.
- Hidden items that only show while the search is their passphrase.
- Killphrases that quietly delete an item from the vault as soon as they're searched for.
- If you turn it on, 10 wrong App Lock Passwords in a row erase every vault.
- Hide While Recording, on by default, covers Vault while the screen is recorded, mirrored or shared, and Apple's keyboard learns nothing typed into Vault.
- Encrypted PDF backups to print, or to save anywhere, restored from the file or by scanning their QR codes. In plain text, a backup shows only that it's a Vault backup, when it was made, and a hint if you write one.
- Automatic encrypted backups to a folder you choose, such as one in iCloud Drive, whenever your items change.
- Moving the vault to another iPhone, iPad or Mac with QR codes, with no file saved.
- Every export is the whole vault, encrypted with its backup password. There's no unencrypted export.
- No servers, no accounts, no sync, no analytics and no network requests.
- Your iPhone's own backups, to iCloud or a computer, include Vault's data, as they do other apps'. Set an App Lock Password to encrypt the vault, on the iPhone and in those backups.
- Notes in plain text or Markdown, with tags and instant search. Images in notes are never loaded.
- Lock any item behind Face ID, Touch ID or the passcode, and encrypt a note with a password of its own.
- Time-based (TOTP) and counter-based (HOTP) codes, added by scanning a QR code or typing in the secret.
- Tap a code to copy it, or to open its details. Copies are cleared after 1 minute by default, and stay on the device unless you allow Universal Clipboard.
- AutoFill for one-time codes, QuickType suggestions for the codes you choose, and widgets on the Home Screen, the Lock Screen and in StandBy.
- Show Next Code near the end of a code's countdown, and Show in Spotlight, which is off by default and works only while App Lock is off.
Every promise, the code that keeps it, the test that pins it, and its limits are in docs/security-model.md.
- Platform native: it should look like Apple made this app.
- Modern: we should use modern features and push for fast deprecations.
- Open source: no binary code or obfuscated stuff in the app. A few build tools are prebuilt binaries, pinned by checksum.
- Robust: test-driven development, modular PRs/commits.
- Duress-resistant: see
MANIFESTO.mdfor the security principles that govern what Vault will and will not do.
- Tested: high level of test coverage, mockolo for mocking, Swift Testing, snapshot tests and UI tests
- Safe: Swift 6 concurrency
- Modern: iOS 27 and macOS 26, SwiftUI, Structured Concurrency
- Availability: iPhone & iPad Support
- Modular: Swift Package w/ multiple targets
- Resilient: everything should be versioned, we never need to break old clients, old backups should always be able to be restored
docs/security-model.md lists every security and privacy promise Vault makes, the code that keeps it and the test that pins it, and the limits Vault accepts.
MANIFESTO.md has the principles behind them, and docs/on-device-encryption.md the design of the encrypted vault.
To report a security problem, see SECURITY.md. Please don't open a public issue.
Development takes place in /Vault, so take a look in there.
As soon as we are able, we will be dropping the xcodeproj project wrapper and going all-in on the Swift Package Manager.
Vault.xcworkspacewhat you should open/VaultSwift Package that defines targets used by the app, build settings, tooling./VaultAppminimal wrapper that packages this into the iOS app (VaultApp) and the Mac app (VaultMacApp).
There is no hosted CI. Changes are validated on the developer's Mac, and the result is posted to the commit on GitHub as the Validate (local) status check. main requires that check, so a PR can't be merged until its latest commit has passed.
- Install Bun, then run
bun installat the root of the repo, once per clone. It installs local-check, the tool that does the validating, and enables its pre-push hook. - Commit your changes, then run
make validatefrom/Vault.
The checks are in local-check.config.ts. local-check checks out the exact commit into a separate worktree, so uncommitted changes and your usual DerivedData can't affect the result. With Xcode 27.1, it then runs:
make lint;- the Fastlane config check, which is skipped, and noted on the check, if the Ruby version in
.ruby-versionisn't installed; - a build and full run of the
iOSAllTeststest plan on a throwaway iPhone 18 Pro Max / iOS 27.0 simulator, created for the run and deleted afterwards; - a build and run of the UI tests, the
VaultAppUITestsscheme, on the same simulator; - a build of the iOS app,
VaultApp, for the simulator, then a check that it and its extensions have only their own entitlements: the keychain access group the app shares with its AutoFill extension is theirs alone, and not the widget extension's; - a build and run of the
macOS_SupportedTeststest plan, the shared modules' tests, on the Mac itself, so that work on the Mac app can't break iOS, or the other way round (seedocs/mac-app.md); - a build of the Mac app,
VaultMacApp, with its tests running inside it, then a check that it's signed with only the entitlements the design lists, and its UI tests, theVaultMacAppUITestsscheme.
The Mac UI tests need the Mac's screen unlocked: on a locked Mac, every one of them fails to activate the app. To validate while it's locked, run VAULT_SKIP_MAC_UI_TESTS=1 make validate, which skips "Build Mac UI tests" and "Mac UI tests". The run's output and its stored result name them as skipped, but the check on GitHub may not: its description is cut off at 140 characters, before the skipped checks. Everything else still runs. Validate again without it once the Mac is unlocked.
If the commit is already on GitHub, the result is posted straight away. Otherwise it's stored, and the pre-push hook posts it when you push, so you can validate before or after pushing. Every new commit needs validating again. Logs are kept in .git/local-check/logs/.
The check is self-attested: it records that the commit passed on the machine that posted it, rather than on independent CI.
Issues are tracked in Trackslash, in the VAULT project owned by badbundle. Along with the commit history, it's the source of truth for the project's issues and progress: what's open, what's in progress and what's done. A bug, a follow-up or a planned change goes there, not in a TODO comment or a file in the repo. The repo has no GitHub Issues.
The project is public: anyone can read it, and anyone signed in to Trackslash can open an issue. Agents can read and update it through Trackslash's MCP server. Issues are referred to by their ref, such as VAULT-60.