Security Architecture

How we secure remote Claude Code

Remote vibe coding means routing commands to Claude Code on your machine. That power demands an architecture where security is the foundation, not an afterthought.

Three pillars of trust

🔐

Zero-Trust Relay

The cloud relay is treated as an untrusted intermediary. It routes encrypted payloads it cannot decrypt. A fully compromised relay learns nothing about your code.

🛡

Least Privilege

Tools are only auto-approved if your existing Claude Code settings allow them. Everything else requires explicit human approval from your phone.

🔒

End-to-End Encryption

Your prompts, code, and tool calls are encrypted on your device and only decrypted on the other. Private keys never leave the device they were created on — the relay sees only opaque bytes.

End-to-end security architecture

Complete system diagram showing encryption boundaries, trust zones, and data flow between all components. For the full system architecture and tech stack, see the architecture overview.

SECURITY_ARCHITECTURE.md
## BeachViber Security Architecture ## Encryption boundaries, trust zones, and data flow ### Trust Zones TRUSTED UNTRUSTED TRUSTED Phone Cloud Relay Desktop ┌─────────────────────┐ ┌─────────────────────────┐ ┌──────────────────────┐ │ │ │ │ Mobile App (PWA) │ │ Encrypted Relay │ │ BV Agent │ │ │ │ Encrypt plaintext │ │ Route opaque blobs │ │ Decrypt to plaintext with AES-256-GCM │ │ CANNOT read content │ │ with AES-256-GCM │ │ CANNOT modify content │ │ Keys stay on device │ │ CANNOT forge messages │ │ Keys stay on device │ │ │ │ └──────────┬──────────┘ └────────────┬────────────┘ └───────────┬──────────┘ │ │ │ AES-GCM encrypt(msg, key)AES-GCM decrypt(ct, key, iv) ───────────────────────────►───────────────────────────► │ │ │ ### Encryption Protocol Algorithm: X25519 key exchange + AES-256-GCM AEAD Desktop: Node.js crypto module (native, zero-dependency) Webapp: Web Crypto API (SubtleCrypto, non-extractable keys) Key size: 256-bit (Curve25519 public keys) IV: 96-bit random per message (CSPRNG) Auth: GCM 128-bit authentication tag (tamper = rejection) PFS: Per-pairing keypairs (compromise one ≠ compromise all) ### Tool Approval Flow Claude Code BeachViber Agent Relay Phone ───────────── ─────────────── ───── ───── Tool-use hook fires ──────────────────────────► Allowed by settings? YES: auto-approve ◄─ ─ ─ ─ ─{approve}─ ─ ─ ─ NO: ask phone encrypt(request) ──────────────────►────────────────► User reviews tool + args encrypt(response) ◄──────────────────◄──────────────── {approve} or {deny} ◄────────────────────────── TIMEOUT: ◄─ ─ ─ ─ ─{DENY}─ ─ ─ ─ ─ ### Pairing Security 1. Desktop generates X25519 keypair 2. Public key embedded in QR code 3. Phone scans QR, generates own keypair 4. Phone sends its public key to desktop via relay 5. 8-digit verification code displayed on both devices 6. User confirms codes match (prevents MITM on relay) 7. Shared secret established ✔

Hardened authentication layer

Multiple layers of authentication protect user accounts and device registration.

🔑 Password Security

  • Industry-standard adaptive hashing with per-user salts
  • Minimum password strength validation via zod schemas
  • Password changes invalidate all existing sessions
  • Account enumeration prevention (identical responses for existing/new accounts)

🎫 JWT Token Management

  • Algorithm pinned to prevent downgrade attacks
  • Short-lived access tokens with refresh rotation
  • Atomic refresh token rotation
  • Refresh token reuse detection triggers session revocation

🔗 OAuth Security

  • OAuth with CSRF state parameter validation
  • Timing-safe state comparison prevents bypass
  • No automatic account linking by email (prevents hijack)
  • OAuth tokens never stored or logged on relay

📡 Device Tokens

  • Unique device tokens for each BeachViber agent
  • Tokens scoped to specific user account
  • Registration requires authenticated API call
  • Tokens validated on every WebSocket connection

End-to-end encryption in depth

🔐 Cryptographic Primitives

Desktop encryption uses the Node.js built-in crypto module. The webapp uses the Web Crypto API (SubtleCrypto) with non-extractable private keys that JavaScript cannot access.

crypto-primitives.md
## Cryptographic Stack Key Exchange X25519 (Curve25519 Diffie-Hellman) ├── 256-bit keys, ECDH-based shared secret └── One keypair per pairing (per-project isolation) Symmetric Cipher AES-256-GCM (AEAD) ├── 256-bit derived key, 96-bit IV ├── Authenticated encryption (tamper = rejection) └── Random IV per message (CSPRNG) Message Auth GCM Authentication Tag ├── 128-bit authentication tag appended to ciphertext ├── Built into AES-GCM construction └── Any modification → decryption fails IV Generation CSPRNG — 96-bit random IV per message ├── CSPRNG from OS entropy source └── No counter mode (no state to desync) ## What the Relay Sees Plaintext prompts Opaque encrypted blob Code content Message type envelope Tool arguments Connection metadata File paths Timestamp Session transcripts Message size

Your existing tool approvals, on your phone

BeachViber reads the permissions you've already configured in Claude Code. Tools you've allowed stay allowed — everything else gets sent to your phone for approval. We never bypass Claude Code's permission model with --dangerously-skip-permissions. See the message protocol for how approval requests flow between components.

Least Privilege

The approval hook reads your existing Claude Code permission settings. Tools you've already allowed in Claude Code are auto-approved — no redundant phone prompts.

Allowed by your Claude Code settings

Requires Phone Approval

Any tool not already permitted by your Claude Code settings is sent to your phone for explicit approval. You see the tool name and arguments before deciding:

Bash Write Edit NotebookEdit MultiEdit + any unconfigured tool

🛠 Approval Flow Implementation

  • Hook reads your existing Claude Code permission settings first
  • Tools matching your existing allow-list are auto-approved — no phone round-trip needed
  • Local-only IPC with no network exposure
  • IPC channel created with restrictive permissions (owner-only access)
  • Short approval timeout — unanswered requests are denied
  • IPC unreachable = tool denied (never silently approved)
  • Hook installed on agent start, removed on agent shutdown
  • Claude Code runs with your existing desktop permissions — --dangerously-skip-permissions is never used

Relay and infrastructure hardening

🛡

CORS Policy

CORS restricted to a single configured origin. No wildcard origins. Prevents cross-origin attacks from malicious sites.

📄

Content Security Policy

Strict CSP headers limit script sources, connection endpoints, and frame embedding. X-Frame-Options: DENY prevents clickjacking.

🕒

Automatic Expiry

Pairing codes, verification codes, and temporary records auto-expire. No stale sensitive data persists in the database.

🔎

Input Validation

All API inputs validated with zod schemas at the boundary. Strict type checking prevents injection and malformed data from reaching handlers.

🔒

TLS Enforcement

TLS certificate verification enabled on all outbound connections. No certificate pinning bypass. HTTPS enforced for all API and WebSocket traffic.

📝

No Debug Logging

All debug logging removed from production. No secrets, tokens, keys, or sensitive data written to logs. Console output sanitized.

Local file system protection

filesystem-security.md
## BeachViber Agent Key Storage OS Keychain (macOS Keychain / Windows DPAPI / Linux Secret Service) └── X25519 secret key OS-protected, never on filesystem Local config directory Owner-only access ├── Config file Owner read/write only Contains: public key, peer public key, device token Secret keys: NEVER stored here (OS keychain only) └── Session data Owner-only access ## Local IPC Security Access: Owner-only — restricted to the agent process Scope: Local machine only — no network exposure Cleanup: Removed automatically on agent shutdown and process exit No other users can connect to the approval channel No network exposure (local IPC only) Race conditions mitigated with atomic creation ## Hook Lifecycle 1. Agent starts Tool-use hook installed 2. Agent running Hook intercepts every tool call 3. Agent stops Hook removed automatically 4. Unexpected exit Next startup detects stale hook and cleans up

Runtime protections

Active defenses that protect sessions beyond encryption and authentication.

🔄 Replay Protection

  • Every encrypted message includes a unique nonce
  • Nonce deduplication with a short-lived tracking window
  • Strict timestamp validation rejects stale messages
  • Minimal clock skew tolerance for network latency

⚡ Rate Limiting

  • Server-side rate limiting prevents message flooding
  • Per-connection enforcement with configurable thresholds
  • Atomic counters prevent race conditions
  • Connections exceeding the limit are throttled

🔒 Webapp Key Isolation

  • Private keys created as non-extractable CryptoKey objects
  • Web Crypto API enforces: JavaScript cannot read key material
  • Even full XSS cannot extract the private key
  • Private keys are never persisted — only derived AES keys are stored

💻 Multi-Desktop Isolation

  • Each paired desktop gets its own X25519 key exchange
  • Separate AES-256-GCM key derived per device
  • Compromising one device's key cannot decrypt another's traffic
  • Re-pairing a device automatically revokes old tokens

Why we made these choices

?

Why platform-native crypto?

The desktop uses Node.js built-in crypto (OpenSSL-backed, zero dependencies). The webapp uses the Web Crypto API with non-extractable CryptoKey objects — private keys never touch JavaScript, so even full XSS cannot extract key material. No third-party crypto libraries to audit or keep updated.

?

Why local IPC for tool approval?

Local IPC provides OS-enforced access control, has no network attack surface, and is cleaned up automatically. It's the most secure mechanism available for same-machine communication between the agent and Claude Code hook.

?

Why per-pairing keypairs?

Compromising one pairing's keypair doesn't compromise others. Each pairing gets its own X25519 keypair with secret keys stored in the OS keychain. This limits the blast radius of any single key compromise.

?

Why no --dangerously-skip-permissions?

Claude Code's CLI has a flag that bypasses all tool permission checks. BeachViber never uses it. Instead, we layer on top of Claude Code's existing permission system — the same one you already trust on your desktop. Your machine, your permissions, no shortcuts.