Editorial introduction
Confirms v2 encrypted relay traffic, consent-gated telemetry and protected secret storage. Flags QR credential lifetime and legacy v1 uncertainty.
Reading scope: About 35 security-related desktop/shared files; mobile app and private backend not reviewed.
Download the complete original English text · SHA-256 ff9601031e278d614399c6dfd51589787adcd52360a893aab600e8bcab075cb7
Complete original response
Security assessment: Orca at commit 7a06b65
Bottom line: I found no sign that this is malicious or that it quietly sends your data somewhere. The mobile-relay encryption, telemetry and local secret storage are reasonably well built. The real risk comes from what the tool is for: it gives AI agents, and optionally a paired phone, broad control of your machine as your user account. If you turn on mobile pairing, the QR code effectively hands someone remote control of your computer.
How I reviewed it: I read about 35 security-relevant files on GitHub at this commit. I had no local clone, so each file went through a fetch tool that summarizes the page with a smaller model. I didn’t run grep across the whole repo or read every line myself, so treat this as a targeted review, not an audit.
Verified from the code
Phone ↔ desktop encryption (v2 protocol). The relay server can’t read your traffic.
- Key exchange: the desktop has an X25519 keypair (via the
tweetnacllibrary,src/main/runtime/e2ee-keypair.ts). Its public key is in the pairing QR code, and the phone checks it during the handshake, so the relay can’t swap in its own key. - Session keys: derived with HKDF-SHA256 from the shared secret, a random 32-byte value from each side, and a hash of the handshake. Each direction gets its own key (
src/shared/mobile-e2ee-v2-node-key-schedule.ts). - Messages: encrypted and authenticated with
nacl.secretbox. Each message carries a sequence number, and the desktop only accepts the next expected one, so replayed or reordered messages are rejected (mobile-e2ee-v2-framing.ts,mobile-e2ee-v2-desktop-session.ts). - Device login: the phone’s device token is sent inside the encrypted channel, after the handshake (
mobile-e2ee-auth-validation.ts).
Relay design. Per the cloud/ README, both devices connect out to the relay, which forwards frames between them. With the encryption above, it only sees ciphertext plus metadata such as timing, sizes and IDs.
Local network listener. The WebSocket server is plain ws:// with no TLS, relying on the encryption layer above. It starts on loopback (127.0.0.1) and only opens to all network interfaces when you generate a LAN/QR pairing offer (runtime-rpc-network-exposure.ts). On Windows, a firewall rule is created only through an explicit “repair” action with a UAC prompt. It applies to Private networks but doesn’t restrict remote addresses.
Telemetry. It goes to PostHog (us.i.posthog.com) and is consent-gated: nothing is sent until you choose (src/main/telemetry/consent.ts).
- It sends app version, OS, a persistent install ID and a session ID, with event fields checked against a schema. GeoIP lookup is turned off in the SDK.
DO_NOT_TRACK=1orORCA_TELEMETRY_DISABLED=1disables it, and so does any CI environment.- Source and dev builds send nothing because there’s no write key.
Secrets at rest. Settings secrets (proxy URL, OpenCode cookie and similar) are encrypted with Electron’s safeStorage, which uses the OS keychain. If encryption isn’t available, they aren’t written in plaintext (protected-secret-persistence.ts). The E2EE private key itself is stored as plain base64 JSON. Its file is written atomically and locked to your user (0600 on macOS/Linux, ACLs on Windows; src/shared/secure-file.ts).
Weaknesses and caveats
- The QR code is a long-lived credential. It contains the device token, the desktop public key, the address to connect to, and the relay details (
runtime-rpc-mobile-pairing.ts). Insrc/shared/pairing.tsI found no expiry and no one-time-use check. Anyone who photographs it can pair. - A paired phone has wide powers. Allowed actions include
terminal.send,terminal.create,agent.launch,files.read,git.push,github.mergePRandssh.connect. That is effectively shell access as your user. - The old v1 protocol is still accepted by default. The
requireV2flag defaults to false (rpc/e2ee-channel.ts). v1 has no handshake binding and no visible replay protection. I couldn’t confirm whether production builds turnrequireV2on. - Minor: the device token is checked with
===, not a constant-time comparison. This happens inside the encrypted channel, so the impact is low. - Inherent to the tool: it runs coding agents with your full user permissions and offers “Computer Use” for desktop apps. Treat agent output and any repo you point it at as untrusted.
What I couldn’t verify
- Relay auth and back end: the API and auth services are in a private repo (
stablyai/orca-cloud), so server-side behavior and what it logs can’t be checked. - Binaries vs. source: whether the released DMG, EXE, AppImage, Homebrew and AUR builds match this source, and how the auto-updater checks signatures. I didn’t read
updater.tsin depth. - Mobile app: I didn’t review
mobile/. - Linux keychain: whether a weak
safeStoragebackend (such asbasic_text) is rejected. - Electron hardening: renderer settings (sandbox, contextIsolation, CSP) and the Design Mode and embedded-browser code.
Recommendations
- If you don’t need the phone app, don’t pair. The listener then stays on loopback.
- If you do pair, treat the QR code like a password and revoke devices you no longer use. Prefer “This computer only” or relay mode over LAN exposure on untrusted networks.
- Set
DO_NOT_TRACK=1or decline the telemetry prompt if you want zero analytics. - Install from the official release or Homebrew cask and check the code signature.
- Run agents only on repos you trust.