root/private/: vault-touchid-gate-0.2.0 metadata and description
Touch ID gate for rbw/bw (Bitwarden CLI) — remote client for SSH hosts
| author | Tobias Hochgürtel |
| author_email | Tobias Hochgürtel <tobias.hochguertel@googlemail.com> |
| classifiers |
|
| description_content_type | text/markdown |
| keywords | bitwarden,rbw,touchid,ssh,vault,security |
| license_expression | MIT |
| metadata_version | 2.4 |
| requires_dist |
|
| requires_python | >=3.11 |
Because this project isn't in the mirror_whitelist,
no releases from root/pypi are included.
| File | Tox results | History |
|---|---|---|
vault_touchid_gate-0.2.0-py3-none-any.whl
|
|
|
vault_touchid_gate-0.2.0.tar.gz
|
|
vault-touchid-gate
Gate rbw and bw (Bitwarden CLI) behind macOS Touch ID — locally and over SSH. No sudo, no env-var juggling, auto-unlock from Keychain.
Why
The official bw CLI requires you to manually lock/unlock and pass session keys via environment variables. rbw solves this with a background agent (like ssh-agent), but still requires typing your master password to unlock.
This project adds a Touch ID gate on top of rbw/bw so that:
- Every vault access triggers a Touch ID prompt — you approve per-action, biometrics only.
- No master password typing — it's stored in the macOS Keychain (encrypted, Touch ID-gated) and auto-fed to
rbw unlockviaexpect. - No sudo, no root, no sudoers changes — Touch ID is triggered via Apple's
LocalAuthenticationframework directly. - No env-var juggling — the
rbw-agentholds keys in memory; the wrapper just gates access to it. - Works over SSH — a daemon on your Mac + SSH reverse tunnel lets you use
vaulton remote servers. Touch ID fires on your Mac, the result tunnels back. Works through jump hosts.
Architecture
Local (on the Mac)
vault rbw get "GitHub"
│
├─→ touchid-gate (Swift / LocalAuthentication)
│ 🔑 Touch ID requested — waiting for fingerprint...
│ ✓ Touch ID approved
│
├─→ (if vault locked) retrieve master password from Keychain
│ → expect feeds it to `rbw unlock`
│
└─→ exec rbw get "GitHub"
Remote (over SSH, including jump hosts)
[Remote server] [MacBook]
vault rbw get "GitHub"
│
├─→ vault-agent-client
│ connects to localhost:8423
│ │
│ SSH reverse tunnel ────────→ vault-agent (daemon, 127.0.0.1:8423)
│ │
│ touchid-gate (Touch ID prompt on Mac)
│ │ approved
│ rbw get "GitHub"
│ │
│ ←──── JSON response ─────────────┘
│
└─→ prints stdout/stderr, exits with code
The SSH reverse tunnel chains through jump hosts automatically:
MacBook ──SSH──→ srv-fedora ──SSH──→ vps9
│ │ │
vault-agent tunnel tunnel
(127.0.0.1:8423) (localhost:8423) (localhost:8423)
→ Mac:8423 → srv-fedora:8423 → Mac:8423
| Component | Description |
|---|---|
touchid-gate.swift |
Swift program using LocalAuthentication to trigger Touch ID. Compiled to a fast binary. Biometrics only (no password fallback). |
vault |
Bash wrapper. Detects local vs remote mode. Local: Touch ID gate + Keychain auto-unlock. Remote: connects to vault-agent via tunnel. |
vault-setup |
One-time setup. Stores Bitwarden master password in macOS Keychain. |
vault-agent |
Python daemon (Mac). Listens on 127.0.0.1:8423. For each request: Touch ID → run rbw/bw → return result as JSON. |
vault-agent-client |
Python client (remote). Connects to localhost:8423 via SSH tunnel, sends request, prints result. |
Requirements
Mac (local mode)
- macOS with Touch ID (Apple Silicon or Touch ID-enabled Intel Mac)
- Xcode Command Line Tools (
swiftc) rbw(brew install rbw)expect(pre-installed on macOS orbrew install expect)pinentry-tty(installed withrbwvia Homebrew)- Python 3 (pre-installed on macOS)
bw(optional, only forvault bw ...)
Remote servers (remote mode)
- Python 3
- SSH access from the Mac (with reverse tunnel configured)
Installation
On the Mac
git clone <repo-url>
cd vault-touchid-gate
./install.sh
This compiles touchid-gate.swift and installs all scripts to ~/.local/bin/.
On remote servers
# Clone the repo on the remote server
git clone <repo-url>
cd vault-touchid-gate
./install.sh --remote
This installs only vault and vault-agent-client (no Swift, no touchid-gate needed).
Configuration
1. Configure rbw (on the Mac)
rbw config set base_url "https://your-bitwarden-server.com"
rbw config set email "you@example.com"
rbw config set pinentry "pinentry-tty"
rbw config set lock_timeout 300 # auto-lock after 5 min (default: 3600)
2. Register the device (official Bitwarden server only)
rbw register
Self-hosted servers may not require this.
3. Store your master password in Keychain
vault-setup
4. Start the vault-agent daemon (on the Mac)
# Foreground (for testing):
vault-agent
# Background:
vault-agent &
# Or auto-start on login via launchd:
mkdir -p ~/Library/LaunchAgents
# Edit launchd/com.hochguertel.vault-agent.plist to use your home path
cp launchd/com.hochguertel.vault-agent.plist ~/Library/LaunchAgents/
launchctl load ~/Library/LaunchAgents/com.hochguertel.vault-agent.plist
5. Test locally
vault rbw list
Remote SSH Access
SSH config setup
Add RemoteForward to your ~/.ssh/config on the Mac:
# Direct connection to a server
Host srv-fedora
HostName srv-fedora.example.com
User youruser
RemoteForward 8423 127.0.0.1:8423
ExitOnForwardFailure yes
# Jump host: Mac → srv-fedora → vps9
Host vps9
HostName vps9.internal
User youruser
ProxyJump srv-fedora
RemoteForward 8423 127.0.0.1:8423
ExitOnForwardFailure yes
With ProxyJump, the reverse tunnel chains automatically through the jump host. ExitOnForwardFailure yes ensures the SSH connection fails if the tunnel can't be set up (rather than silently continuing without vault access).
Manual chaining (without ProxyJump)
If you SSH manually through hops (Mac → srv-fedora → vps9), add RemoteForward at each hop:
# On Mac:
ssh -R 8423:127.0.0.1:8423 srv-fedora
# On srv-fedora:
ssh -R 8423:127.0.0.1:8423 vps9
This chains the tunnel: vps9:8423 → srv-fedora:8423 → Mac:8423.
Remote installation (Python package)
The remote components (vault + vault-agent-client) are published to the
private devpi registry as the vault-touchid-gate package. Install them on a
remote host with:
uv tool install --extra-index-url https://pypi.registry.hochguertel.work/root/private/+simple/ vault-touchid-gate
No clone, no build, no mise.toml on the host — just Python 3.12+. The
package depends on ruamel.yaml (resolved from PyPI; devpi serves only the
package itself, so always install with --extra-index-url as above). The
vault entry point is remote-mode only: if it detects touchid-gate (a
Mac), it refuses to run so it can never shadow the local-mode bash wrapper
from install.sh.
Update an existing install:
uv tool upgrade --extra-index-url https://pypi.registry.hochguertel.work/root/private/+simple/ vault-touchid-gate
Test from a remote server
# On the remote server (after SSHing in with the tunnel):
vault rbw list
Touch ID fires on your Mac. You approve. The result appears on the remote.
How it works
vault-agentdaemon runs on your Mac, listening on127.0.0.1:8423- You SSH to a remote server with
RemoteForward 8423 127.0.0.1:8423 - On the remote,
localhost:8423is tunneled back to your Mac's8423 vaulton the remote detects it's not on a Mac (notouchid-gate) → usesvault-agent-clientvault-agent-clientconnects tolocalhost:8423→ tunnel →vault-agenton Macvault-agenttriggers Touch ID on your Mac- You approve →
vault-agentrunsrbw/bw→ returns result as JSON vault-agent-clientprints the result on the remote
Environment variables (remote)
| Variable | Default | Description |
|---|---|---|
VAULT_AGENT_HOST |
127.0.0.1 |
Host to connect to (use with non-standard tunnel setups) |
VAULT_AGENT_PORT |
8423 |
Port to connect to |
VAULT_AGENT_TIMEOUT |
120 |
Timeout in seconds (waiting for Touch ID) |
Usage
# List all entries (local or remote — same command)
vault rbw list
# Get a password
vault rbw get "GitHub"
# Get all fields (username, password, notes, custom fields)
vault rbw get --full "GitHub"
# Get a TOTP code
vault rbw code "GitHub"
# Sync from server
vault rbw sync
# Add an entry (password first line, notes after — no editor, works
# identically locally and from remote hosts, Touch ID gated):
printf '%s\n' 'hunter2' 'note line' | vault rbw add "My Entry" user --uri https://example.com
# Use the official bw CLI (also gated behind Touch ID)
vault bw sync
# Manually lock the vault (on the Mac)
rbw lock
# Start/stop the daemon (on the Mac)
vault-agent & # start
pkill -f vault-agent # stop
Security Model
What this does
- Per-action Touch ID approval. Every
vaultcall triggers a fresh Touch ID prompt. No time window, no standing access. - Biometrics only. The Touch ID gate has no password fallback (
localizedFallbackTitle = ""). - No sudo. Touch ID is triggered via
LocalAuthentication, not viasudo/PAM. No host-admin privileges are involved. - Auto-lock. The
rbw-agentpurges keys from memory afterlock_timeoutseconds (default 300 = 5 min). - Localhost-only daemon.
vault-agentbinds to127.0.0.1— not accessible from the network. Remote access is only via SSH tunnel. - No secrets on remote servers. The remote
vault-agent-clientsends requests but never stores passwords, tokens, or keys. All secrets stay on the Mac.
What this does NOT do
- Per-item granularity. Touch ID approval grants access to the entire vault for that one command.
- Audit logging. There's no log of which entries were read, when, or by whom.
- Protect the Keychain entry. The master password is stored in the macOS Keychain. If your Mac login password is compromised, an attacker could access the Keychain.
- Encrypt the tunnel protocol. The JSON protocol is plaintext over the SSH tunnel. This is fine because SSH itself is encrypted, but if you expose port 8423 directly (without SSH), the traffic would be unencrypted.
Removing the master password from Keychain
security delete-generic-password -s "rbw-master-password" -a "$(id -un)"
Files
vault-touchid-gate/
├── bin/
│ ├── touchid-gate.swift # Swift source — LocalAuthentication Touch ID gate
│ ├── vault # Bash wrapper — detects local/remote, gates access
│ ├── vault-setup # One-time setup — stores master password in Keychain
│ ├── vault-agent # Python daemon (Mac) — Touch ID + rbw over TCP
│ └── vault-agent-client # Python client (remote) — connects to agent via tunnel
├── launchd/
│ └── com.hochguertel.vault-agent.plist # macOS auto-start for the daemon
├── docs/
│ └── ARCHITECTURE.md # Design decisions and trust model
├── install.sh # Installation script (auto-detects Mac vs remote)
├── LICENSE # MIT
└── README.md
License
MIT