MyPass is a iOS + macOS password manager that reads and writes KeePass KDBX vaults. Users pick an existing `.kdbx` file from anywhere in the Files system (iCloud Drive, Dropbox, local storage) and MyPass becomes their viewer/editor for that vault. A bundled AutoFill Credential Provider extension enables password fill-in across every app on the device — not just Safari.
### Goals
- Open, browse, add, edit, and delete entries in an existing KDBX vault
- Unlock with master password once; Face ID / Touch ID on subsequent opens
- AutoFill credentials into any app via `ASCredentialProviderViewController`
- Support full KeePass entry structure: groups/subgroups, custom fields, TOTP, attachments
### Non-goals
- Creating new KDBX vaults from scratch
- Syncing or hosting vaults (the user owns the file)
- Web browser extension
---
## Architecture
### Option chosen: Local Swift Package + App Extension
A local Swift Package (`MyPassCore`) holds all vault logic. Both targets link to it. A shared App Group bridges the two processes.
```
MyPassCore (Swift Package)
├── KDBXDocument — parse and write KDBX 3.1 / 4.0
├── Entry / Group / Attachment — value-type models
├── TOTPGenerator — RFC 6238 TOTP from otp:// custom field
├── VaultSession — owns the live KDBXDatabase; lock/unlock lifecycle
└── KeychainStore — read/write master password in shared Keychain group
- KDBX parsing: KeePassKit (Objective-C, MIT). No upstream SPM support, so vendored locally as a git submodule (`Vendor/KeePassKit`) with a hand-written `Package.swift`. This pulled in the same problem three more times — none of the following had clean drop-in SPM support either, so all are vendored the same way:
- KissXML (`Vendor/KissXML`) — XML parsing KeePassKit depends on
1. User taps a login field in any app → iOS shows QuickType bar with MyPass icon
2. User taps the icon → iOS instantiates `ASCredentialProviderViewController` and calls `prepareCredentialList(for: [ASCredentialServiceIdentifier])`
3. Extension checks Keychain for a valid biometric token:
- **Token valid (vault was recently unlocked):** skip to step 5
- **Token missing/expired:** show mini `UnlockView` → `LAContext` Face ID / Touch ID → password fallback
4. Extension resolves the security-scoped bookmark from shared App Group `UserDefaults`, opens the KDBX file, decrypts using the master password from the shared Keychain group
5.`CredentialListView` displays entries filtered by matching the `serviceIdentifier` URL/domain against each entry's URL field. Search bar allows manual lookup across all entries
6. User taps an entry → `completeRequest(withSelectedCredential: ASPasswordCredential(user:password:))` → field filled → extension dismissed
### URL matching
Entry URL field is matched against the `serviceIdentifier` using host comparison (e.g. `github.com` matches `https://github.com/login` and bundle ID-based identifiers via Associated Domains). Entries with no URL are shown in a separate "All entries" section below suggestions.
### Extension constraints
- Memory limit ~50 MB: KDBX is parsed fresh on each extension invocation; no SwiftData or persistent cache inside the extension
- If KDBX parse fails, show an error alert with an "Open MyPass" deep-link button (`mypass://unlock`)
---
## Security Model
| Concern | Implementation |
|---|---|
| Master password at rest | Keychain item: `kSecAttrAccessibleWhenUnlockedThisDeviceOnly`, shared via Keychain Access Group |
| Biometric unlock | `LAContext.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics)` retrieves master password from Keychain on success |
| File access | Security-scoped bookmark in shared App Group `UserDefaults`; `startAccessingSecurityScopedResource()` / `stopAccessingSecurityScopedResource()` bracket every read/write |
| In-memory passwords | `ProtectedString` XOR-obfuscates values; decoded only on `reveal()` |
| Clipboard | `UIPasteboard` item set with 30-second expiration using `UIPasteboard.setItems(_:options:)` with `UIPasteboardOptionExpirationDate` |
| KDBX crypto | Fully delegated to the KDBX parsing library (AES-256-CBC / ChaCha20, Argon2d / AES-KDF) — no custom crypto |
| Vault lock on background | `VaultSession.lock()` called in `sceneDidEnterBackground` — zeroes master password from memory; biometric re-auth required on next foreground |
1.~~**KDBX library:** Evaluate Swift Package Manager options that support KDBX 3.1 + 4.0 with Argon2. Fallback: KeePassKit (Objective-C via bridging header).~~**Resolved:** no SPM-native option was found; KeePassKit is used, vendored locally (see External dependencies above).
2.**macOS AutoFill:**`ASCredentialProviderExtension` on macOS 13+ has reduced scope vs iOS — verify which apps support third-party fill on macOS and document limitations.
3.**Associated Domains:** For bundle-ID-based AutoFill matching, an associated domains file may be needed for first-party apps. Evaluate at implementation time.