A secrets manager for developers on macOS, iPhone, and iPad: .env files, API keys, SSH and GPG keys in an encrypted vault, with a CLI to use them in your projects.
English Β· EspaΓ±ol
Development secrets tend to end up scattered across unencrypted .env files, notes, and ~/.ssh folders on several machines. OpenVault keeps them in a single encrypted vault protected by a master password (and Touch ID), along with the passphrases you need to use each key.
It has two parts that share the same core (OpenVaultCore):
- OpenVault.app β a native SwiftUI app to create, view, and organize secrets by type and project.
ovaultβ a CLI you add to any repository to inject the project's secrets as environment variables, without the.envever existing on disk.
Both read the same file directly, so the CLI works even when the app is closed, and the app instantly reflects whatever the CLI writes.
- Encrypted vault β AES-256-GCM with a key derived via PBKDF2-SHA256 (600,000 iterations). Nothing is stored in plain text.
- Touch ID / Face ID β unlock with the key stored in a biometry-protected Keychain item.
- iPhone and iPad β the same app on iOS 26+, with Face ID and an auto-lock when the device locks or the app stays in the background.
- iCloud sync β optional (Settings β iCloud): the vault file moves to the app's iCloud Drive container, still encrypted with your master password, so every device sees the same items. The CLI follows the app's setting.
- Item types β
.envfiles, individual secrets, passwords (username, password, URL), SSH keys, GPG keys, arbitrary files (.p12,.p8, JSONβ¦ encrypted byte for byte and exportable back to disk), and others. - Projects and folders β group items by project (optionally shown as sections in the list) and by folder; the CLI loads what a
.ovaultrcfile in the repo selects. ovault runβ run any command with the project's secrets as environment variables.- SSH keys β generate ed25519 keys, view the public key and fingerprint, and export to
~/.ssh. - GPG keys β view the fingerprint and identity without importing the key into your keyring.
- Drag and drop β drop a
.env,id_*, or.ascfile onto the window and its type is detected. - Hidden values β revealed with the eye button or by holding β₯.
- Secure clipboard β copied values are marked as concealed for clipboard managers and cleared automatically.
- Auto-lock β after inactivity, when the Mac sleeps, and when the screen locks.
- Automatic updates β checks GitHub Releases and installs signed updates in place (Settings β Updates, or OpenVault β Check for Updatesβ¦).
- Languages β available in English and Spanish (Latin America).
| Layer | Technology |
|---|---|
| App | SwiftUI (macOS 26+) |
| CLI | Swift + swift-argument-parser |
| Cryptography | CryptoKit (AES-GCM), CommonCrypto (PBKDF2) |
| Biometrics | LocalAuthentication + Keychain |
| Xcode project | XcodeGen |
| CI | GitHub Actions (macOS 26) |
- macOS 26 or later
- Xcode 26 or later (Swift 6.2+)
- XcodeGen β
brew install xcodegen - Optional: gpg (to view GPG fingerprints);
ssh-keygenships with macOS
git clone https://github.com/Im-Fran/openvault.git
cd openvaultmake openOn first launch it asks you to create a master password. It cannot be recovered if you forget it.
Touch ID requires the app to be signed. Change
DEVELOPMENT_TEAMinApp/project.ymlto your Team ID if you build with a different account; without signing, the Touch ID button doesn't appear.
The app ships with ovault: in Settings β CLI, click Installβ¦. It links /usr/local/bin/ovault to the copy inside the app (asking for an administrator password if needed), so it updates with the app. Open the app from /Applications first; a link into the disk image would break when it's ejected.
Without the app, from the repository:
make install-cli # installs ovault into ~/.local/binMake sure ~/.local/bin is on your PATH, or use make install-cli PREFIX=/usr/local.
cd my-project
ovault init # creates .ovaultrc with the project name (safe to commit)
ovault import .env # imports a .env into the project
echo "sk_live_..." | ovault set STRIPE_KEY # without VALUE it reads stdin (stays out of shell history)
ovault run -- npm run dev # runs with the secrets as environment variables
ovault load my-project -- npm run dev # same, naming the project; includes passwords and files
eval "$(ovault load my-project)" # loads the variables into the current shell
ovault get STRIPE_KEY # prints a value
ovault get deploy_key --passphrase # prints an item's passphrase
ovault get cert.p12 > cert.p12 # a file item is written raw (binary)
ovault export --format json # combined environment (env | json)
ovault list # project items, without values (-a for all).ovaultrc says what the repository loads (plain text, supports # comments; it only names things and never contains secrets). Every line adds to the selection, and each key can repeat:
project-name=my-project # every item of the project
secret-name=STRIPE_KEY # an item by name, from any project
folder-name=CI # every item in the folder
secret-name=AWS_.* # values are whole-name regexesβ¦
secret-name=WORKER_{1..12}_TOKEN # β¦and {a..b} is a numeric range ({01..12} keeps the zero padding)With it, ovault load -- npm run dev, eval "$(ovault load)", run, export, get and list don't need a name. Commands that write (set, import) use the first project-name. It is looked up in the current directory and upwards; a legacy .openvault (JSON) is still read. ovault load warns about lines that match nothing.
eval "$(ovault hook zsh)" # in ~/.zshrc (or `ovault hook bash` in ~/.bashrc)When you enter a directory with a .ovaultrc (or one of its subdirectories), the hook loads the project's variables; when you leave, it unsets them and deletes the temporary files. It never prompts for the master password: it only loads if OPENVAULT_PASSWORD is set; with the vault locked it warns once, and running eval "$(ovault load)" is enough to unlock and load. Note: a third-party repository with a .ovaultrc that names your projects, secrets or folders would receive that environment when you enter it; the hook warns every time it loads.
Every command accepts --project <name> so it doesn't depend on .ovaultrc. If an individual secret and a variable from a .env share the same name, the individual secret wins.
ovault load also exposes passwords (NAME and NAME_USERNAME; a name like "Postgres prod" becomes POSTGRES_PROD) and files: it writes them to a private temporary directory (0700, files 0600) and exports their path (AuthKey_AB12.p8 β AUTHKEY_AB12_P8). With -- command it deletes them when the command exits. SSH/GPG keys and "other" items are not loaded. Details in ovault load --help.
| Variable | Description |
|---|---|
OPENVAULT_PASSWORD |
Master password without a prompt (useful in CI). Removed from the environment before ovault run. |
OPENVAULT_FILE |
Alternative path to the vault file (app and CLI). Turns iCloud sync off. |
In OpenVault β Settings (β,):
| Option | Default | Description |
|---|---|---|
| Lock After Inactivity | 5 minutes | 1, 5, 15, 60 minutes, or never |
| Clear Clipboard After | 30 seconds | 15, 30, 60, or 90 seconds |
| Unlock with Touch ID | On | Requires a signed app and available Touch ID |
| Sync with iCloud | Off | Keeps the vault in iCloud Drive. If another device already put one there, OpenVault asks for that vault's master password and merges both |
| Change Master Password | β | Re-encrypts the whole vault with a new salt |
Shortcuts: βN new item, βL lock, β« delete selected item.
make test # core tests (swift test)
make cli # builds ovault in release
make project # generates App/OpenVault.xcodeproj
make app # builds OpenVault.app into build/
make clean # removes build artifacts and the generated projectSigning, the DMG, and notarization are handled by fastlane (make dmg for a local test DMG). Releases are published by pushing a v* tag; see .github/RELEASING.md.
Layout:
Sources/OpenVaultCore/ encryption, vault format, .env parser, .ovaultrc
Sources/ovault/ CLI
Tests/ core tests
App/ SwiftUI app (project.yml + Sources/)
assets/ branding: icon, logos, palette (see assets/README.md)
graphify turns the repo (code, docs, and images) into a knowledge graph: it extracts symbols and relationships, detects communities, and tags each relationship as EXTRACTED, INFERRED, or AMBIGUOUS. We use it to find our way around the code, and so AI agents can query the graph instead of rereading the whole repo.
The output lives in graphify-out/, which isn't versioned: everyone generates it locally.
| File | What it is |
|---|---|
GRAPH_REPORT.md |
Summary: most connected nodes, communities, surprising connections |
graph.html |
Interactive graph, opens in the browser without a server |
graph.json |
Raw graph data, for queries |
It is generated and queried from Claude Code with the /graphify skill:
/graphify . # rebuilds the full graph
/graphify . --update # re-extracts only the files that changed
/graphify query "how is the vault encrypted?"
/graphify path "VaultStore" "VaultCrypto"
/graphify explain "VaultKey"Run /graphify . the first time; after that, /graphify . --update keeps your graph up to date.
- The vault lives at
~/Library/Application Support/OpenVault/vault.ovault, with0600permissions (folder0700). - The app and the CLI write under an exclusive lock and reread the latest version before modifying, so they don't overwrite each other.
- When locking, the key is discarded from memory.
- With iCloud sync on, the vault lives in the app's iCloud container (
~/Library/Mobile Documents/iCloud~cl~franciscosolis~openvault/Documents/). iCloud only ever stores the encrypted file; when two devices save at once, OpenVault merges their items (the most recent edit wins).
Found a vulnerability? Don't open a public issue: read the security policy and report it privately.
- Fork the repo
- Create a branch from
dev:git checkout -b feat/my-change - Commit using Conventional Commits:
git commit -m "feat: add my change" - Make sure
make testandmake apppass, and open a PR againstdev
OpenVault is free software under the GNU General Public License v3.0 β see LICENSE.