Security: Found a vulnerability? Please read our Security Policy and report it privately to security@ethos-protocol.app — do not open a public issue. A
security.txt(RFC 9116) is published at/.well-known/security.txtand will also be served fromhttps://ethos-protocol.app/.well-known/security.txtonce the domain is configured.
Ethos-Protocol mobile apps (iOS + Android) provide a native interface for managing vaults, checking in, and receiving expiry reminders. Both apps share the same REST API contract and feature set.
mobile/
├── shared/
│ └── api-contract.md # Shared API spec (iOS + Android)
├── ios/EthosProtocol/
│ └── Sources/
│ ├── App/ # Entry point, app lifecycle
│ ├── Models/ # Vault, AuthToken, etc.
│ ├── Services/
│ │ ├── APIClient.swift # Ktor-style async HTTP client
│ │ ├── PasskeyService.swift # ASAuthorization / WebAuthn
│ │ ├── KeychainService.swift# Secure token storage
│ │ ├── NotificationService.swift # APNs + local reminders
│ │ └── OfflineSupport.swift # NetworkMonitor + disk cache
│ ├── ViewModels/ # AuthStore, VaultStore (ObservableObject)
│ └── Views/ # SwiftUI screens
└── android/app/src/main/java/com/ethosprotocol/
├── api/
│ ├── ApiClient.kt # Ktor HTTP client
│ └── Infrastructure.kt # NetworkMonitor, OfflineCache, TokenProvider
├── models/ # Kotlinx.serialization data classes
├── services/
│ ├── PasskeyService.kt # CredentialManager / WebAuthn
│ ├── PushService.kt # Firebase Messaging
│ └── NotificationHelper.kt# Local notification display
├── ui/
│ ├── ViewModels.kt # AuthViewModel, VaultViewModel (Hilt)
│ ├── MainActivity.kt # NavHost entry point
│ ├── screens/Screens.kt # Compose screens
│ └── theme/Theme.kt # Material3 dynamic color
└── di/AppModule.kt # Hilt DI bindings
- iOS:
ASAuthorizationPlatformPublicKeyCredentialProvider(iOS 16+) - Android:
CredentialManagerAPI (Android 9+, API 28+) - Flow:
getChallenge()→ device biometric prompt →verifyPasskey()→ JWT stored in Keychain/SharedPreferences - Relying party:
ethos-protocol.app(requires.well-known/assetlinks.json+ Apple App Site Association)
- iOS: APNs via
UNUserNotificationCenter. Device token registered to backend on first launch.- Local reminders scheduled 24h before vault expiry via
UNTimeIntervalNotificationTrigger - Actionable notification category
CHECK_INwith inline "Check In" action
- Local reminders scheduled 24h before vault expiry via
- Android: Firebase Cloud Messaging (FCM). Token refreshed via
onNewToken.- Notification channel
ttl_reminders(IMPORTANCE_HIGH) - Deep-link intent to
MainActivitywithvault_idextra
- Notification channel
NetworkMonitorchecks live connectivity before every requestOfflineCachestores last successful GET responses keyed by URL (SHA-256 filename)- On network unavailable: cached data served transparently; mutations show "offline" error
- iOS:
CryptoKit.SHA256for cache keys; Android:MessageDigest("SHA-256")
- iOS:
@StateObject/ObservableObjectstores (AuthStore,VaultStore) injected via SwiftUI environment - Android: Hilt-injected
ViewModels withStateFlow+collectAsStateWithLifecycle
- Install XcodeGen (
brew install xcodegen) — the.xcodeprojis generated, not checked in - From
ios/EthosProtocol, runmkdir -p Xcode && xcodegen generate --project Xcodeto produceXcode/EthosProtocol.xcodeproj(anEthosProtocolapp target +TTLWidgetwidget extension, perproject.yml) — theXcode/directory must exist beforexcodegen generateruns, or the copy step fails - Open
ios/EthosProtocol/Xcode/EthosProtocol.xcodeprojin Xcode 15+ - Set your Apple Developer Team in signing settings for both the
EthosProtocolandTTLWidgettargets (project.ymlleavesDEVELOPMENT_TEAMblank on purpose — bundle IDscom.ethosprotocol/com.ethosprotocol.TTLWidgetare already set) API_BASE_URLis already set inEthosProtocol/Info.plistandTTLWidget/Info.plist; edit both (they're separate bundles, read independently at runtime) if you need to point at a different environment- Both
Info.plists must also carry a non-emptyTLS_PUBLIC_KEY_PINSarray (Base64-encoded SPKI SHA-256 hashes — seeSources/Services/CertificatePinning.swiftfor the rotation strategy) before shipping a Release build.ios-ci.yml'sbuild-and-testjob runs.github/scripts/check_tls_pinning.py --configuration Releaseagainst both files and fails the build if either is missing or empty; Debug builds are exempt (PinningDelegateintentionally treats an empty pin set as "pinning disabled" for local dev) - Configure Apple App Site Association at
https://ethos-protocol.app/.well-known/apple-app-site-association, listing this app's App ID under bothapplinks(Universal Links) andwebcredentials(platform passkeys) - In the Apple Developer portal, enable Push Notifications, Associated Domains, iCloud (Key-Value storage), and Keychain Sharing capabilities for the
com.ethosprotocolApp ID, and Keychain Sharing forcom.ethosprotocol.TTLWidget— matchingEthosProtocol/EthosProtocol.entitlements/TTLWidget/TTLWidget.entitlements. Set up an APNs key in App Store Connect for push. - Re-run
mkdir -p Xcode && xcodegen generate --project Xcodeany timeproject.ymlchanges; the generatedXcode/directory is disposable and shouldn't be committed
- Open
androidin Android Studio Hedgehog+ - Add
google-services.jsonfrom Firebase Console - Configure
assetlinks.jsonathttps://ethos-protocol.app/.well-known/assetlinks.json - Set
API_BASE_URLinbuild.gradle.ktsbuildConfigField - Configure the certificate pins for release builds by setting
ETHOS_CERT_PINS(environment variable) orethos.certPins(in~/.gradle/gradle.properties, never committed) to a comma-separated list of Base64 SHA-256 SPKI digests — the current certificate's pin plus a backup for the next one. When unset, release builds fall back to the placeholder pins inCertificatePinnerand CI'sVerify release certificate pins are not placeholdersstep reports them (#173) — shipping placeholder pins would make every request fail for every user. That step fails the build outright once the pins are configured but wrong, or once release signing is configured (i.e. the artifact is actually shippable). Debug builds are not gated, since an empty pin set disables pinning for local/dev hosts. Compute a pin with:openssl s_client -connect api.ethos-protocol.app:443 2>/dev/null \ | openssl x509 -pubkey -noout | openssl pkey -pubin -outform der \ | openssl dgst -sha256 -binary | openssl enc -base64
cd ios/EthosProtocol
swift testCovers: model decoding, Keychain round-trip, offline cache, Base64URL encoding.
Tests run against the SPM package (Package.swift) directly and don't require the
XcodeGen-generated project; CI runs this the same way, via xcodebuild test against
an iOS Simulator destination (swift test alone defaults to macOS, which can't build
the app's iOS-only framework imports).
cd android
./gradlew test # Unit tests (JVM)
./gradlew connectedAndroidTest # Instrumented tests (device/emulator)Covers: ViewModel state transitions, model logic, Compose UI smoke tests.