* fix(security): fail closed on biometric init error H-03: setting isAuthenticated: true on initialization failure was a security bypass — any crash during biometric setup granted full access. Fail closed instead; user sees auth prompt on next open. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(security): use Crypto.randomUUID for connection IDs H-04: Math.random() is not cryptographically random. Connection IDs are used as SecureStore key suffixes; switch to expo-crypto randomUUID for a secure source. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(deps): pin expo-crypto to ~15.0.9 15.0.10 does not exist on npm; ~15.0.9 is the latest stable in the 15.x series compatible with Expo SDK 54. * feat: add OpenCode Connect coming-soon waitlist card Adds a discoverable 'OpenCode Connect — Coming Soon' card to the add-connection quick-connect screen. Users can enter their email and tap 'Join Waitlist' to send a pre-filled mailto. No backend required. * fix(cua): detect actual screen dimensions and fix JSON parsing - Get real screen size via `wm size` instead of hardcoding 1080x2400; emulator is 1080x1920 so y-coordinates were systematically off - Extract first JSON object via regex when model returns multiple objects - Use AZURE_OPENAI_MODEL env var for deployment name (defaults gpt-5.4) - Add AZURE_DEV_AI_* path for Azure AI Foundry endpoints Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(security): SHA-pin upload-google-play and sanitize notification bodies M-02: Pin r0adkll/upload-google-play to commit SHA e738b9d (v1.1.5) to prevent supply-chain hijack via tag mutation. M-03: Sanitize all push notification bodies — strip control chars, truncate to 200 chars. Prevents server-supplied strings (error messages, file paths from permission patterns, session titles) from leaking unbounded text into the OS notification drawer. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(privacy): add telemetry consent gate for Sentry crash reporting Sentry was always-on, violating F-Droid anti-feature policy and user trust norms. Now gated behind explicit opt-in: - First-launch consent modal (TelemetryConsentModal) shows once on fresh install; user can Allow or Decline. - Consent state persisted in expo-secure-store (survives restarts). - Settings > Privacy section: crash reporting toggle + privacy policy link. - initSentry() called only after consent granted — not on app start. Closes #3 (partial) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(config): add real icons and complete iOS/Android app.json config - Add 1024×1024 app icon, 432×432 adaptive icon foreground, 200×200 splash - iOS: push notification entitlement (aps-environment: production), speech/ microphone/camera/photo usage descriptions for future features, disable ITSAppUsesNonExemptEncryption - Android: adaptive icon with dark background (#0F172A), versionCode: 1 - expo-notifications plugin wired in app.json Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(dist): add iOS CI workflow, README rewrite, CONTRIBUTING, and LICENSE - publish-app-store.yml: EAS Build + TestFlight submission; runs on tag/release/ workflow_dispatch; bumps ios.buildNumber from github.run_number - README: full rewrite — features, install badges, connection guide, contributing - CONTRIBUTING.md: contribution guide for OSS contributors - LICENSE: MIT Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * docs(dist): add store listings, strategy, privacy policy, F-Droid/IzzyOnDroid templates - distribution/strategy.md: monetization strategy (free client + opencode Cloud) - distribution/play-listing.md: Google Play store copy (name, description, tags) - distribution/app-store-listing.md: App Store listing copy - distribution/privacy-policy.{md,html}: GDPR-compliant privacy policy - distribution/PLAY_CONSOLE_SETUP.md: Play Console setup runbook - distribution/ios-enrollment-runbook.md: Apple Developer Program enrollment steps - distribution/SIGNING-KEY-FINGERPRINTS.md: keystore fingerprint for reproducible builds - distribution/fdroid-submission/: F-Droid metadata template - distribution/izzyondroid-submission/: IzzyOnDroid submission template - distribution/whatsnew/: Play Store release notes (en-US) - distribution/whatsnew-ios/: TestFlight release notes - distribution/play-graphics/: Play Store screenshot placeholders - distribution/app-store-graphics/: App Store screenshot placeholders Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(telemetry): handle SecureStore failure + Android back button - add .catch() on loadTelemetryConsent() so SecureStore rejection shows the consent modal instead of blocking startup forever - add onRequestClose={onDecline} to Modal so Android back button records the decline rather than silently dismissing - fix catch block in telemetry.ts to not clobber _resolved when SecureStore read fails mid-session Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(ci): run gradlew clean to prevent stale modules.json duplicate Sentry Gradle plugin writes modules.json to src/main/assets; cached build intermediates contain an old copy → mergeReleaseAssets fails with 'Duplicate resources'. Running clean before assembleRelease clears the intermediate state. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(ci): remove android build output cache causing duplicate modules.json Caching android/app/build/intermediates and android/app/.cxx causes two issues: 1. Stale modules.json in intermediates → Duplicate resources error 2. .cxx CMake artifacts reference absolute paths → ninja clean fails Keeping only Gradle distribution cache (~/.gradle) which is safe. Expo prebuild regenerates android sources fresh each run anyway. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
4.5 KiB
Reproducible Build Notes — ai.opencode.mobile
F-Droid's modern AllowedAPKSigningKeys path requires that F-Droid's build
server can compile the same APK and arrive at a binary that matches the
pre-signed APK we supply via GitHub releases. Any non-determinism in the build
will break this verification.
Issues found (2026-05-24)
1. Kotlin error log files tracked in git — MEDIUM
Files committed:
android/.kotlin/errors/errors-1779181311003.log
android/.kotlin/errors/errors-1779181311094.log
Problem: These log files contain absolute host paths:
While analysing /home/azureuser/workspace/opencode-mobile/node_modules/...
When F-Droid builds from source on their server, these log files will not exist (or will contain different paths). Since they are tracked in git and checked out during the build, they could cause differing build outputs if the Kotlin compiler reads or embeds them. More practically, they make the source tree non-portable — a smell that will draw reviewer attention.
Recommended fix: Add .kotlin/ to android/.gitignore:
# android/.gitignore (add this line)
.kotlin/
Then remove the tracked files:
git rm -r --cached android/.kotlin/
git commit -m "chore: untrack kotlin error log files from android/.kotlin/"
This is a trivial fix. Do it before filing the F-Droid MR.
2. android/ directory tracked in git — LOW (expected but notable)
expo prebuild regenerates android/ from app.json and package.json.
F-Droid's build metadata uses npx expo prebuild as a prebuild: step,
which means F-Droid rebuilds android/ from scratch on their server.
The tracked android/app/build.gradle and other generated files must match
what expo prebuild produces. If the Expo SDK version drifts between what is
committed and what npm installs, the build will fail.
Mitigation already in place: package-lock.json is committed, which pins
all npm dependency versions. The F-Droid metadata Builds: step uses
npm install --legacy-peer-deps which respects package-lock.json.
Residual risk: If expo prebuild is non-deterministic (e.g., writes the
current date/time into generated files), subsequent runs will produce different
outputs. This is unlikely but should be verified by running prebuild twice and
comparing outputs:
npx expo prebuild --platform android --non-interactive --clean
git diff android/
3. Hermes bytecode embedding — LOW
The React Native Hermes engine compiles the JavaScript bundle to Hermes bytecode
at build time. The bytecode format is versioned but should be deterministic for
the same JS source + Hermes version. The Hermes version is pinned via
react-native in package-lock.json, so this is low risk.
4. PNG crunching — LOW
build.gradle has crunchPngs true for release builds. PNG crunching via aapt2
is generally deterministic but can vary across aapt2 versions. F-Droid's build
environment may use a different Android build tools version.
Mitigation: Pin buildToolsVersion in android/build.gradle explicitly
rather than relying on the Expo-supplied default. Check via:
grep buildToolsVersion android/build.gradle android/app/build.gradle
5. No hardcoded timestamps found — PASS
Grepped android/ for System.currentTimeMillis, new Date(), buildTime,
BUILD_DATE, UUID.randomUUID() — no results. This is the most common
reproducibility killer and is clean here.
6. No absolute host paths in build files — PASS
Grepped android/ *.gradle and *.properties for /home/, /Users/,
C:\ — no results in build config files.
Priority action items before F-Droid MR
| Priority | Item | Effort |
|---|---|---|
| HIGH | Add .kotlin/ to android/.gitignore and untrack log files |
5 min |
| MEDIUM | Run expo prebuild twice, compare output with git diff |
15 min |
| MEDIUM | Pin buildToolsVersion explicitly in android/build.gradle |
5 min |
| LOW | Verify Hermes bytecode is deterministic (compare two builds) | 30 min |
| LOW | Test full reproducible build using F-Droid's Docker build env | Hours |
How to test reproducible builds
F-Droid provides a reproducible build test tool:
# Install fdroidserver
pip install fdroidserver
# Test reproducibility against a released APK
fdroid signatures path/to/app-release.apk
# Full build test
fdroid build ai.opencode.mobile:<versionCode> --verbose
See https://f-droid.org/en/docs/Reproducible_Builds/ for the full guide.