Your secrets, on your keychain.
An encrypted vault that lives on a USB drive — passwords, TOTP codes, SSH keys, and secure notes, protected by PBKDF2-HMAC-SHA256 and AES-256-GCM. Nothing installed on your computer.
Fob turns any USB stick into a cryptographic security key. Plug it in, unlock with a passphrase, and your credentials are available as a password manager, TOTP generator, and SSH agent. Unplug and everything locks.
- Passwords — store, generate, and auto-copy credentials
- TOTP — built-in two-factor code generation with live countdown
- SSH keys — import existing keys from the CLI or browser vault; unlocked keys are exposed via a local SSH agent socket, compatible with any SSH client (spawned automatically by the CLI on unlock — passphrase-protected keys must have that passphrase stripped first, e.g.
ssh-keygen -p -N "") - Secure notes — encrypted free-text entries
- Plausible deniability — decoy vault slot with realistic fake data; duress slot that destroys the vault silently
- Browser vault — a zero-dependency HTML file that runs entirely offline, using the exact same encrypted vault format as the CLI — either can create, open, or update a vault the other made
There is one vault format, used identically by the CLI and the browser vault — PBKDF2 (not a memory-hard KDF like Argon2id) is used specifically because WebCrypto has no native Argon2id primitive, and keeping one interoperable format was judged more valuable than a stronger KDF the browser couldn't run.
| Component | Algorithm |
|---|---|
| Key derivation | PBKDF2-HMAC-SHA256 — 310,000 iterations |
| Encryption | AES-256-GCM |
| Key separation | HKDF-SHA256, per vault slot |
| Post-quantum | Planned — ML-KEM-1024 hybrid wrapping, not yet implemented |
| TOTP | RFC 6238 — HMAC-SHA1/SHA256/SHA512 |
| Threat | Mitigation |
|---|---|
| USB stolen | PBKDF2 at 310k iterations raises the brute-force cost; weaker than a memory-hard KDF against GPU/ASIC attackers — choose a long passphrase |
| Coercion | Decoy vault slot opens with its own independent, realistic-looking data |
| Extreme coercion | Duress passphrase returns the same error as a wrong passphrase and wipes the local copy (logical wipe only — see Known limitations) |
| Quantum adversary | Not yet mitigated — ML-KEM-1024 hybrid wrapping is planned |
| Clipboard exfil | Auto-clears 30 seconds after any copy |
| Memory dumps | Sensitive buffers zeroized and mlocked where possible |
All cryptographic operations live in fob-core, which has no filesystem or network access. The CLI and browser vault cannot leak key material because they never handle raw secrets — passphrases are passed directly to the crypto layer and zeroized immediately after use.
- RSA SSH keys are stored but not signed. The
rsacrate used by our SSH library carries RUSTSEC-2023-0071 (a timing side-channel), with no fixed version available upstream. RSA key entries can still be stored and viewed in the vault, but the SSH agent won't load them for signing. Use Ed25519 (the default for new keys) instead. - Imported SSH keys must not be passphrase-protected — the vault passphrase is already the protection layer; strip a key's own passphrase before importing it (
ssh-keygen -p -N ""). - The duress wipe overwrites the vault file's logical content, not necessarily its physical storage. On a copy-on-write filesystem (e.g. macOS APFS) or an SSD doing wear-leveling internally, an in-place overwrite can leave the pre-wipe bytes recoverable from other physical blocks via forensic tools, filesystem snapshots, or drive firmware — the OS-level guarantee "this file's contents are now random bytes" does not extend to "the old bytes are physically gone." If your threat model includes forensic recovery of the underlying storage medium, don't rely on the duress wipe alone; treat it as closing off the easy/logical recovery path, not a physical-media guarantee.
- The browser vault can only wipe the actual
vault.fobfile if it was opened via the native file picker in a Chromium browser (Chrome, Edge, Opera — anything supporting the File System Access API). Opening the vault by drag-and-drop, via a plain<input type=file>fallback, or via the automatic same-directory load on afile://page gives the page no writable handle to the original file at all — browsers don't allow arbitrary local file writes without one. In those cases (and always in Firefox/Safari, which don't implement the File System Access API), a duress passphrase still clears the browser's own IndexedDB cache, but thevault.fobfile on the USB drive itself is left completely untouched. If you rely on the duress feature, use the CLI (fob), which always wipes the real file, or confirm you opened the browser vault through its file picker (not drag-and-drop) in a supported browser. - The vault file is trivially identifiable as a Fob vault, even without the passphrase. The first 4 bytes of
vault.fobare the literal ASCII magicFOB2— anyone with the file (afile/xxd/headaway) can immediately confirm "this is a Fob password vault," before ever touching a passphrase. The decoy/duress design protects which passphrase unlocks which content once someone is already trying to open the vault; it does not hide that the file is a vault at all — the filename (vault.fob) gives that away too. If your threat model requires the file itself to be unidentifiable (e.g. deniability that you even use a password manager), rename it to something innocuous and be aware the magic bytes still identify it to anyone who inspects the content directly, not just the name. - Actively-edited secret fields (the master passphrase, a password, a note body, an SSH private key) aren't guaranteed zeroized in every intermediate state while you're typing. The TUI zeroizes each field's final value when its form is dropped, but Rust's
Stringcan reallocate internally as you type (e.g. viapush/insert) — each old backing buffer is freed by the allocator without being zeroed first, so fragments of an in-progress passphrase can in principle linger in freed-but-unoverwritten heap memory until something else reuses that allocation. This only matters against an adversary who can already read the process's memory (a debugger, a core dump, a swapped-out page) — a much higher bar than a passing observer — but it means the README's "sensitive buffers zeroized" claim is exact for data at rest and for a field's value once you stop editing it, not for every transient buffer touched while you were actively typing it.
curl -fsSL https://raw.githubusercontent.com/Arcel-Org/Fob/main/install/install.sh | shInstalls fob and fob-agent to ~/.fob/bin and adds that to your PATH.
Every downloaded release is verified against its published SHA256 checksum
before anything is installed, and against its cosign
signature too if cosign is on your PATH.
Useful flags (pass them after | sh -s --, e.g. ... | sh -s -- --no-path):
| Flag | Effect |
|---|---|
--version=vX.Y.Z |
install a specific release instead of latest |
--no-path |
don't modify your shell rc file |
--local=/path/to/fob |
install from a local build instead of downloading |
--uninstall |
remove Fob's installed binaries |
--help |
show all flags |
No release built yet, or want to build it yourself? See below.
Requires Rust 1.88+.
git clone https://github.com/Arcel-Org/Fob.git
cd Fob
cargo build --release -p fob-cli -p fob-agentThe binaries land at target/release/fob and target/release/fob-agent — both are required (the CLI spawns the agent as a sibling process for SSH agent support).
cargo test --workspace # crypto/vault + TUI rendering tests
cargo clippy --workspace --all-targets -- -D warnings
cargo fmt --all -- --check
python3 web/tests/browser_interaction_test.py # headless-chromium checks for web/index.htmlThe browser-vault check drives a real headless Chromium instance (requires
chromium and Python's websockets package) through the actual UI — search
filtering, the TOTP countdown, auto-lock on inactivity, and entry-list
rendering/scrolling with many entries.
Automated tests cover the logic and rendering paths; they are not a
substitute for a real person using the app. See USABILITY_TESTING.md for a
first-time-user test script covering both interfaces.
fob/
├── crates/
│ ├── fob-core/ # cryptography and vault format — no I/O, pure logic
│ ├── fob-cli/ # TUI — USB provisioning and vault browsing/editing
│ └── fob-agent/ # SSH agent daemon
├── install/
│ └── install.sh # one-line installer
└── web/
├── index.html # zero-dependency browser vault
└── tests/ # headless-chromium interaction checks for index.html
Issues and pull requests welcome. For security vulnerabilities, please open a private advisory rather than a public issue.
Licensed under either of MIT or Apache 2.0 at your option.