Security
Authentication
- Owner bootstrap via
bunny configure(first run only). - Email + password with Argon2id hashing.
- Optional TOTP MFA (RFC 6238, 6 digits / 30 s) — compatible with Google Authenticator, Microsoft Authenticator, GitHub Mobile, and other standard TOTP apps.
- Session tokens stored as SHA-256 hashes in SQLite.
- Web:
HttpOnlycookiebunny_session. - Mobile:
session_tokenin login JSON, sent asAuthorization: Bearer …(Keychain / Keystore).
URLs are never credentials. /s/:sessionId requires a valid session.
MFA login flow
POST /api/v1/auth/loginwith email + password.- If MFA is enabled: response includes
mfa_required: trueandmfa_challenge_token(plus cookiebunny_mfa_challengefor the web UI). No full session is created yet. POST /api/v1/auth/mfa/verifywith TOTP or recovery code →bunny_sessioncookie andsession_tokenin JSON.
Rate limiting: 5 failed attempts per challenge, then 15-minute lock.
Enabling MFA
From the web UI: Security (/security) while signed in. Scan the QR code or enter the manual secret once. Save the recovery codes immediately — they are shown only once.
Sensitive actions (setup, disable, regenerate recovery codes) require recent authentication: password re-entry or a session where the password was verified within the last 5 minutes.
Recovery codes
Ten one-time codes (format bunny-XXXX-XXXX-XXXX-XXXX, ~80 bits entropy). Stored hashed in SQLite. Use instead of TOTP if you lose your phone.
TOTP secret storage
TOTP secrets are encrypted at rest (AES-256-GCM). Encryption key:
- Prefer
BUNNY_MFA_ENCRYPTION_KEY(32 bytes, hex or base64) from Docker secrets, systemd, K8s, etc. - Fallback:
{data_dir}/mfa.key(auto-generated, mode0600).
Threat model: if an attacker reads both the database and the encryption key, TOTP secrets can be recovered. This is acceptable for self-hosted deployments but means server compromise can bypass MFA — rotate MFA and invalidate sessions after incident response.
MFA audit events
Logged to audit_logs: auth.login.mfa_required, auth.login.success, auth.mfa.challenge_created, auth.mfa.challenge_locked, auth.mfa.failed, auth.mfa.enabled, auth.mfa.disabled, auth.mfa.recovery_code_used, auth.mfa.recovery_regenerated, auth.mfa.setup_started.
Secrets
Three scopes: system, project, session.
- PTY children receive allowlisted env only — never full
process.env. - Injected secrets use env names
BUNNY_SECRET_<NAME>(uppercase, sanitized). - Database stores secret references (
secret_refstable), not values. - Values live in an encrypted file:
~/.config/bunny/secrets.enc(Argon2id + AES-256-GCM).
CLI
bunny secrets init # create vault (interactive passphrase)
bunny secrets unlock # unlock in-memory for this shell
export BUNNY_SECRETS_PASSPHRASE=... # auto-unlock on `bunny start`
bunny secrets set API_KEY --scope system --value 'sk-...'
bunny secrets set DB_URL --scope session --session-id <uuid> --value 'postgres://...'
bunny secrets list
bunny secrets get API_KEY
bunny secrets remove API_KEY
bunny secrets status
Unlock the vault before starting terminals that need injected secrets, or set BUNNY_SECRETS_PASSPHRASE when running bunny start.
Mobile credentials
- SSH passwords and private keys are stored as AES-256-GCM envelopes in secure storage (master key in Keychain/Keystore).
- Supports password auth and PEM private keys (optional passphrase for encrypted keys).
Redaction
All timeline, audit, and streamed events pass through bunny_core::redaction::Redactor:
- Known secret values (from unlocked vault)
- Bearer tokens, JWT, AWS keys, PEM headers
- URL query strings stripped by default
Network collection
Default: metadata only (method, redacted URL, status, timing).
Headers and bodies require explicit opt-in with redaction.
Browser / CDP / VNC
- Chromium CDP binds
127.0.0.1only. - VNC and noVNC proxied through authenticated API.
- CDP sidecar runs locally, outputs redacted JSON lines.
- Watch read-only (v1): noVNC UI lock (
bunny_lock) hides settings and forces client-sideview_only; this does not block RFB input at the WebSocket proxy. See novnc-readonly-server-enforcement.
Discord
- Bridge authenticates with
Authorization: Bearer(hashed token in agent config). - Shell/agent commands require a Discord account linked to a Bunny user (home page OAuth) with Editor+ on the session.
- Tool approvals and risky shell commands require DiscordApprove (Admin+).
- All actions are written to
discord_audit_log. - Watch read-only links: see novnc-readonly-server-enforcement.
Relay mode
Outbound WSS from agent. Relay must not receive raw secrets. Auth remains on the bunny instance.