Files
opencode-mobile/docs/threat-model.md
Dennis V b8fb390f5c feat(release): production signing in build.yml + F-Droid metadata filled
- build.yml: use production keystore (KEYSTORE_BASE64) on tag pushes,
  fall back to debug key for PRs/branch builds — build.gradle already
  reads RELEASE_STORE_FILE env var so no Gradle changes needed
- distribution/fdroid-submission/metadata.yml: filled
  AllowedAPKSigningKeys with actual SHA-256 fingerprint, commit tag
  updated to v0.3.1, version bumped to 0.3.1
- app.json: bump version 0.2.3 → 0.3.1, versionCode 1 → 2
- Add eas.json + EAS README for iOS App Store builds
- Add fastlane/metadata/android for Play Store / F-Droid graphics
- Add distribution docs: applestore, fdroid, market, playstore,
  security, threat-model, opencode-site-deploy

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 01:26:35 +00:00

7.8 KiB

Threat model — opencode-mobile

Date: 2026-05-24 Version: 0.2.3


Assets we protect

Asset Sensitivity Storage
opencode server URL Medium — reveals network topology expo-secure-store (opencode_connections)
HTTP Basic auth password for opencode server High — grants full server access expo-secure-store (opencode_password_<id>)
User prompts sent to the AI model High — may contain proprietary code or PII In-flight only (not persisted on device)
AI responses / generated code High — proprietary output In-flight only (not persisted on device)
Sentry DSN Low — public by design; quota-abuse risk Bundle (EXPO_PUBLIC_) + SecureStore (consent state)
Biometric/PIN auth settings Low expo-secure-store (opencode_auth_settings)
Telemetry consent decision Low expo-secure-store (opencode_telemetry_consent)
Session IDs Low — server-assigned, ephemeral In-memory only
Notification preferences Low expo-secure-store (opencode_settings)

Attackers and attack scenarios

1. Network observer (passive MitM)

Capability: Can observe network traffic between phone and opencode server.

Attack scenarios:

  • Intercept HTTP traffic on local LAN (e.g., corporate Wi-Fi, coffee shop).
  • Read user prompts and AI responses in cleartext if server is accessed via http://.

Defenses:

  • HTTPS: When user provides an https:// URL, standard TLS verification via expo/fetch applies. No rejectUnauthorized overrides.
  • Documentation: App UI explicitly recommends using https:// when TLS is configured; the quick-connect form defaults to http:// only for LAN/Tailscale where the user has a trusted network path.
  • Tailscale: Most users access local servers over Tailscale (encrypted mesh VPN), making cleartext HTTP safe in practice.

Residual risk: Users who expose their opencode server on a public IP via HTTP (not HTTPS) without Tailscale have no transport-layer protection. This is documented as user responsibility.


Capability: Any installed app or web page can fire opencode:// deep links at the app.

Attack scenarios:

  • Inject a malicious server URL via deep link to phish the user into connecting to an attacker-controlled opencode server.
  • Cause the app to navigate to an attacker-controlled session ID.

Defenses:

  • Current deep link handling uses expo-router. The only parameterized routes are /session/[id] and /connection/[id]. Neither auto-connects to an external URL via deep link; server URLs are only stored via the explicit "Add Connection" UI.
  • No deep link handler was found that auto-creates a connection from URL parameters.
  • Notifications navigation (router.push('/session/${data.sessionId}')) uses server-assigned session IDs only, not user-supplied URLs.

Residual risk: Low. No auto-connect-from-deep-link path exists. If this is added in future, validate scheme+host against stored connections before navigating.


3. Lost or stolen device

Capability: Physical access to a locked device.

Attack scenarios:

  • Read server URL and password from device storage.
  • View AI session content / conversation history from lock screen notifications.
  • Bypass app authentication.

Defenses:

  • All sensitive data in expo-secure-store (iOS Keychain with kSecAttrAccessibleWhenUnlockedThisDeviceOnly; Android Keystore-backed EncryptedSharedPreferences). Data is inaccessible when the device is locked.
  • Optional biometric app lock (requireBiometric setting). Default off for UX; users with sensitive servers should enable it.
  • Notification lock-screen preview: session titles may appear in "Task completed" notifications. No prompt content or AI response content is included in notification bodies.
  • Known gap (M-03): permission.asked notifications can show tool file-path glob patterns on lock screen. Fix pending.

Residual risk: Medium. Without biometric lock enabled, a device that auto-unlocks (e.g., trusted location) exposes app contents. Users should be prompted to enable biometric lock during onboarding when a server is added.


4. Compromised opencode server

Capability: Attacker controls the opencode server the app connects to (e.g., via supply-chain, server compromise, or DNS hijacking to an HTTPS server).

Attack scenarios:

  • Server returns malicious tool permission requests designed to trick user into approving harmful file operations.
  • Server returns crafted SSE events to cause client-side errors and exfiltrate stack traces.

Defenses:

  • App renders server data (session titles, tool permission descriptions) as text only — no eval(), no dangerouslySetInnerHTML, no JavaScript injection surface.
  • Sentry reports are scrubbed of URLs and query params before upload.
  • Tool permission requests require explicit in-app user approval. The permission.asked UI shows the user what patterns the tool wants to access.

Residual risk: Medium. The app trusts the connected server by design. Users are responsible for connecting only to servers they control. This is documented in the UX.


5. Sentry DSN abuse

Capability: Attacker extracts the DSN from the APK/IPA (trivially readable via strings).

Attack scenarios:

  • Flood the Sentry project with fake events, exhausting quota and masking real errors.
  • Enumerate org/project metadata via the public DSN.

Defenses:

  • The Sentry DSN is EXPO_PUBLIC_ and therefore intentionally public (this is Sentry's documented model).
  • sendDefaultPii: false ensures device identifiers are not sent.
  • Sentry ingest rate-limiting can be configured per-project (deferred enhancement).

Residual risk: Low. Quota exhaustion is a nuisance, not a security breach. No user data flows from DSN exposure.


6. CI/CD supply chain attack

Capability: Attacker compromises a third-party GitHub Action used in the build pipeline.

Attack scenarios:

  • Malicious action exfiltrates PLAY_STORE_SERVICE_ACCOUNT_JSON or keystore secrets.
  • Tampered action injects malicious code into the APK.

Defenses:

  • Secrets are stored as GitHub Secrets, not in code.
  • pull_request trigger (not pull_request_target) prevents fork PRs from accessing secrets.
  • KEYSTORE_PASSWORD, KEY_ALIAS, KEY_PASSWORD are separate from KEYSTORE_BASE64, minimizing damage from any single secret leak.

Residual risk: Medium. Third-party actions are pinned to tags, not commit SHAs (see H audit finding M-02). SHA pinning is the recommended fix.


Out of scope

  • Security of the user's opencode server itself. The server is user-managed; its authentication, file access controls, and model provider secrets are outside this app's threat model.
  • Model provider security. API keys for Anthropic, OpenAI, etc. are held by the opencode server, not the mobile app. The app never sees model provider credentials.
  • Tailscale or VPN security. Users connecting via Tailscale rely on Tailscale's security model for transport protection.
  • Device operating system integrity. Rooted/jailbroken devices can bypass expo-secure-store. This is a platform-level threat outside app scope.

Security architecture summary

[Phone] ──(HTTPS or HTTP-via-Tailscale)──> [opencode server (user-owned)]
   │                                               │
   │  expo-secure-store (Keychain/Keystore)        │── AI model provider (user's keys)
   │  • server URL + auth password                 │
   │  • biometric settings                         │
   │  • telemetry consent                          │
   │                                               │
   │  opt-in only                                  │
   └──(HTTPS)──> [Sentry ingest] (scrubbed, no PII, no prompts)