Skip to content

Instantly share code, notes, and snippets.

@omid3098
Created February 28, 2026 00:46
Show Gist options
  • Select an option

  • Save omid3098/912d5f5c6a6edd71bed575a33a8b8a6c to your computer and use it in GitHub Desktop.

Select an option

Save omid3098/912d5f5c6a6edd71bed575a33a8b8a6c to your computer and use it in GitHub Desktop.
Security Audit Report: zemzeme-android
# Security Audit Report: zemzeme-android
**Repository:** https://github.com/whisperbit-labs/zemzeme-android
**Version Audited:** v1.0.2 (release/v1.0.2)
**Audit Date:** 2026-02-28
**Auditor:** Independent Security Review
---
## خلاصه اجرایی (فارسی)
**هشدار: از نصب و استفاده از این اپلیکیشن تا انجام ممیزی امنیتی مستقل خودداری کنید.**
زمزمه (zemzeme) یک فورک از پروژه متن‌باز [bitchat-android](https://github.com/permissionlesstech/bitchat-android) است که با تغییراتی به عنوان «پیام‌رسان امن» برای کاربران ایرانی عرضه شده است. بررسی امنیتی این ریپازیتوری **نشانه‌های هشداردهنده متعددی** را آشکار کرده است:
- **فایل‌های APK منتشرشده از طریق CI/CD ساخته نشده‌اند** — به صورت دستی آپلود شده‌اند و هیچ تضمینی وجود ندارد که با سورس‌کد مطابقت دارند
- **APKها فاقد امضای دیجیتال (unsigned) هستند** — امکان تزریق کد مخرب وجود دارد
- **تمام اکانت‌های مشارکت‌کنندگان در یک بازه زمانی بسیار کوتاه ساخته شده‌اند** با نام‌های تصادفی و بدون هیچ فعالیت عمومی دیگری
- **تابع تأیید رمز عبور کانال همیشه `true` برمی‌گرداند** — کانال‌های «محافظت‌شده» هیچ محافظتی ندارند
- **آپدیت‌ها بدون تأیید امضای رمزنگاری دانلود و نصب می‌شوند**
- **باینری‌های از پیش کامپایل شده (golib.aar و libarti_android.so)** قابل تأیید نیستند
---
## Executive Summary
**RISK RATING: HIGH — Do not use for sensitive communications until independently audited.**
Zemzeme is a fork of the open-source [bitchat-android](https://github.com/permissionlesstech/bitchat-android) (5,128 stars, 690 forks) project by permissionlesstech. It has been modified and rebranded as a "secure messenger" targeting Iranian users. While the underlying bitchat codebase has legitimate cryptographic foundations, the zemzeme fork introduces several **critical trust and security concerns** that undermine its claims of security.
### Key Risk Summary
| Severity | Count | Summary |
|----------|-------|---------|
| CRITICAL | 4 | Password bypass, unsigned updates, no cert pinning, cleartext traffic |
| HIGH | 4 | Unsigned relay list, icon hiding, unverifiable binaries, auto-download |
| MEDIUM | 4 | Location leakage, no reproducible builds, self-update permission, attribution |
| Trust Issues | 7 | Fake-looking accounts, manual APK uploads, no team transparency |
---
## 1. Red Flags: Organizational Trust Issues
### 1.1 Suspicious Timeline
| Entity | Creation Date | Notes |
|--------|--------------|-------|
| Organization `whisperbit-labs` | Feb 24, 2026 | Created 2 days before repo went public |
| Repository `zemzeme-android` | Feb 26, 2026 | Published same day as release |
| Release v1.0.2 | Feb 22-26, 2026 | Created before repo was public, published on publication day |
| Contributor `romankovalets` | Jan 8, 2026 | 0 public repos, 0 followers, no bio |
| Contributor `mimi25lueguvq` | Jan 8, 2026 | 0 public repos, 0 followers, random username |
| Contributor `eric71ressei5` | Jan 8, 2026 | 0 public repos, 26 followers (suspicious), random username |
| Contributor `cory46diuraa7` | Jan 12, 2026 | 0 public repos, 0 followers, random username |
**Analysis:** All four contributor accounts were created within a 4-day window (Jan 8-12, 2026). None have any public repositories, bios, or typical GitHub activity. The usernames `mimi25lueguvq`, `cory46diuraa7`, and `eric71ressei5` appear randomly generated. This pattern is consistent with sockpuppet accounts created for a single purpose.
### 1.2 Organization Has Only One Repository
The `whisperbit-labs` organization:
- Has **no description, no email, no blog, no location**
- Contains only the `zemzeme-android` repository
- Was created just 2 days before the repo went public
- Is **not verified** on GitHub
### 1.3 No Team Transparency
The website [zemzeme.app](https://zemzeme.app):
- **No privacy policy**
- **No terms of service**
- **No team page or "About Us"**
- **No contact information** beyond GitHub
- **No mention of Iran or Iranian users** despite being promoted to this audience
- No security audit certifications or third-party reviews
### 1.4 Over 3,300 Downloads Already
Despite being published only 2 days ago, the ARM64 APK has **3,306 downloads**. Total across all architectures: **~5,000 downloads**. This is unusually high for a brand-new, unverified project from an unknown organization.
---
## 2. CRITICAL Vulnerabilities
### 2.1 Channel Password Verification Bypass
**File:** `app/src/main/java/com/roman/zemzeme/ui/ChannelManager.kt` (lines 131-134)
```kotlin
private fun verifyChannelPassword(channel: String, password: String): Boolean {
// TODO: REMOVE THIS - FOR TESTING ONLY
return true
}
```
**Impact:** Any password is accepted for "password-protected" channels. Users who believe their channels are protected are communicating in channels that anyone can join with any password. The `deriveChannelKey()` function exists but is never called because `verifyChannelPassword()` always returns `true` before key derivation occurs.
**Severity:** CRITICAL — This means channel encryption keys are never derived or stored. Messages in "protected" channels cannot be encrypted because the encryption key was never generated.
### 2.2 Unsigned APK Updates Without Verification
**File:** `app/src/main/java/com/roman/zemzeme/update/UpdateManager.kt`
The update mechanism:
1. Fetches release metadata from `https://api.github.com/repos/whisperbit-labs/zemzeme-android/releases/latest`
2. Downloads the APK file directly
3. **Installs it without any signature verification, checksum validation, or integrity check**
```kotlin
// Line 216: Auto-download starts immediately after finding an update
currentUpdateInfo = githubUpdate
_updateState.value = UpdateState.Available(githubUpdate)
downloadUpdate(githubUpdate) // <-- Starts downloading automatically
```
Additionally, `installViaPackageInstaller()` (line 447) attempts to install with `USER_ACTION_NOT_REQUIRED`, meaning on Android 12+, the update could install silently without user confirmation.
**Attack Vectors:**
- DNS hijacking to redirect GitHub API requests
- MITM attack (no certificate pinning)
- Compromised GitHub account pushing malicious APK
- Since APKs in the release are already manually uploaded (not built by CI), the attacker already has this capability
**Severity:** CRITICAL — Arbitrary code execution through malicious update.
### 2.3 No Certificate Pinning
**File:** `app/src/main/java/com/roman/zemzeme/net/OkHttpProvider.kt`
The centralized HTTP client has **no certificate pinning** for any endpoint:
- GitHub API (update checks)
- OpenStreetMap Nominatim (geocoding)
- GitHub raw content (relay list)
- Nostr relay WebSocket connections
This means any MITM attacker with a trusted CA certificate (common in corporate/government networks, especially in Iran where state-issued CAs exist) can intercept all HTTPS traffic.
**Severity:** CRITICAL — In the specific threat model of Iranian users, government-controlled CAs make this especially dangerous.
### 2.4 Cleartext Traffic Enabled
**File:** `app/src/main/AndroidManifest.xml`
```xml
android:usesCleartextTraffic="true"
```
This allows **all HTTP (non-encrypted) traffic** from the app. While this may have been set for Nostr relay compatibility, it is a blanket permission that affects all network communication. The correct approach is to use a `network_security_config.xml` with domain-specific rules.
**Severity:** CRITICAL — Combined with lack of cert pinning, this makes all traffic interceptable.
---
## 3. HIGH-Risk Findings
### 3.1 Relay List Fetched Without Signature Verification
**File:** `app/src/main/java/com/roman/zemzeme/nostr/RelayDirectory.kt`
The app downloads its Nostr relay list from:
```
https://raw.githubusercontent.com/permissionlesstech/georelays/refs/heads/main/nostr_relays.csv
```
This CSV file determines which Nostr relay servers the app connects to. It is downloaded every 24 hours **without any signature verification**. The app computes a SHA256 hash but only uses it for logging, not validation.
**Attack Vector:** If this GitHub repository is compromised, or if a MITM attack intercepts the download (remember: no certificate pinning), an attacker could redirect all Nostr traffic through malicious relays, enabling:
- Message interception
- Metadata collection (who talks to whom, when, from where)
- Message suppression or modification
Additionally, the CI/CD workflow `fetch-georelays.yml` automatically commits updated relay lists directly to the main branch without review.
### 3.2 100 Launcher Icon Aliases (App Hiding)
**File:** `app/src/main/AndroidManifest.xml`
The manifest contains **100 activity aliases** (Alias_1 through Alias_100) that allow the app to change its launcher icon to appear as a different app (calculator, notes, weather, etc.). This is a technique commonly associated with:
- Stalkerware / spyware apps
- Apps designed to evade detection by authorities or family members
While this could be argued as a privacy feature in restrictive environments, it is also a hallmark of malicious applications. Legitimate security-focused apps typically do not include this feature.
### 3.3 Unverifiable Precompiled Binaries
The repository includes two precompiled binary blobs:
| Binary | Size | Purpose |
|--------|------|---------|
| `golib.aar` | 21 MB | Go-based libp2p networking library |
| `libarti_android.so` | varies by arch | Tor networking (Arti, Rust-based) |
**There is no way to verify that these binaries match any published source code.** The `golib.aar` could contain additional code beyond what the Go source would produce. The `libarti_android.so` files could contain modifications to the Tor implementation.
For a security-critical application targeting at-risk users, all binaries should be reproducibly built from source with documented build instructions.
### 3.4 Auto-Download Without User Consent
**File:** `app/src/main/java/com/roman/zemzeme/update/UpdateManager.kt` (line 216)
When an update is found, the app **immediately starts downloading the APK** without asking the user. Combined with the unsigned APK issue (2.2), this means a malicious update could be pre-staged on the device without the user's knowledge, waiting only for installation approval (which on Android 12+ can also be bypassed via `USER_ACTION_NOT_REQUIRED`).
---
## 4. MEDIUM-Risk Findings
### 4.1 Location Leakage via Nostr
**Files:** `app/src/main/java/com/roman/zemzeme/nostr/NostrIdentity.kt`, `NostrClient.kt`
The app sends geohash data (approximate location, ~5-10km precision) in plaintext to Nostr relay servers. While this is intentional for location-based messaging, it means:
- Nostr relay operators can see approximate user locations
- If relays are compromised (see 3.1), an attacker gains location intelligence
- For Iranian users specifically, this could be used for physical location tracking
### 4.2 No Reproducible Builds
There is no mechanism to verify that the published APKs were built from the published source code:
- No checksums published with releases
- No signed build attestations
- No reproducible build documentation
- The release workflow has never successfully run (see Section 5)
- APKs are manually uploaded by repository owners
### 4.3 Self-Update Permission (REQUEST_INSTALL_PACKAGES)
**File:** `app/src/main/AndroidManifest.xml`
```xml
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
```
This permission allows the app to install other APK files. Combined with the unsigned update mechanism, this creates a supply-chain attack vector.
### 4.4 Incomplete Attribution of Changes
The app is clearly a fork of bitchat v1.7.0 but the extent of modifications is not documented. Key additions include:
- Nostr transport layer
- Geohashing system
- Icon switching (100 aliases)
- Tor support
- Custom relay directory
Without a clear changelog from bitchat, it's difficult to identify all modifications, some of which could affect security properties.
---
## 5. Release & Build Pipeline Analysis
### 5.1 CI/CD Workflows
The repository has three workflows:
| Workflow | File | Status | Purpose |
|----------|------|--------|---------|
| Android CI | `android-build.yml` | 3 runs, 0 successful | Build and test |
| Release | `release.yml` | **Never executed** | Build and publish releases |
| Fetch GeoRelays | `fetch-georelays.yml` | Unknown | Auto-update relay list |
### 5.2 Release Workflow Has Never Run
The release workflow (`release.yml`) would produce APKs named:
- `bitchat-android-arm64.apk`
- `bitchat-android-x86_64.apk`
- `bitchat-android-universal.apk`
However, the actual release contains APKs named:
- `app-arm64-v8a-release.apk`
- `app-armeabi-v7a-release.apk`
- `app-universal-release.apk`
- `app-x86-release.apk`
- `app-x86_64-release.apk`
**The naming mismatch proves that the release APKs were NOT produced by the CI/CD pipeline.** They were built elsewhere (on a developer's machine or unknown build environment) and manually uploaded.
### 5.3 APK Signing Is Commented Out
**File:** `.github/workflows/release.yml` (lines 65-73)
```yaml
# Optional: Sign APKs (uncomment and configure secrets when ready)
# - name: Sign APKs
# uses: r0adkll/sign-android-release@v1
# with:
# releaseDirectory: app/build/outputs/apk/release
# signingKeyBase64: ${{ secrets.SIGNING_KEY }}
# alias: ${{ secrets.ALIAS }}
# keyStorePassword: ${{ secrets.KEY_STORE_PASSWORD }}
# keyPassword: ${{ secrets.KEY_PASSWORD }}
```
Even if the workflow were to run, APK signing is disabled. The signing configuration in `build.gradle.kts` uses `local.properties` which is not available in CI environments.
### 5.4 Implications
**Users have no way to verify that the APK they download corresponds to the source code in this repository.** The APK could contain additional code, modified encryption, data exfiltration, or backdoors that are not present in the published source.
This is the single most critical trust issue for this project.
---
## 6. Contributor Analysis
### 6.1 Account Patterns
| Username | Created | Public Repos | Followers | Following | Contributions |
|----------|---------|-------------|-----------|-----------|---------------|
| mimi25lueguvq | Jan 8, 2026 | 0 | 0 | 0 | 42 |
| romankovalets | Jan 8, 2026 | 0 | 0 | 0 | 26 |
| eric71ressei5 | Jan 8, 2026 | 0 | 26 | 0 | 18 |
| cory46diuraa7 | Jan 12, 2026 | 0 | 0 | 0 | 24 |
| actions-user | N/A | N/A | N/A | N/A | 2 |
### 6.2 Analysis
- **All human contributors created their GitHub accounts within a 4-day window** (Jan 8-12, 2026)
- **None have any other public activity** on GitHub — no repositories, no gists, no public contributions to other projects
- **Three of four usernames appear randomly generated** (mimi25lueguvq, cory46diuraa7, eric71ressei5)
- `eric71ressei5` has 26 followers despite 0 public repos — suspicious for a new account
- The top contributor `mimi25lueguvq` (42 commits) has no presence in the commit history visible through the API, suggesting work was done before the repository was made public
This pattern is consistent with:
- A single entity operating multiple sockpuppet accounts
- An organization creating developer personas for operational security
- A coordinated effort to create an appearance of multiple independent developers
---
## 7. Code-Level Security Analysis
### 7.1 What the Code Actually Does Well
Despite the trust issues, the underlying codebase (inherited from bitchat) has some solid security properties:
- **Noise Protocol (Noise_NK)** with Curve25519 for peer-to-peer encryption
- **EncryptedSharedPreferences** with AES256_GCM for secure key storage
- **Ed25519** signing for peer identity verification
- **No analytics, telemetry, or tracking** code detected
- **No device identifier collection** (IMEI, Android ID, etc.)
- **No contact/SMS/call log access**
- **No Firebase/Crashlytics** integration
- Proper backup exclusion for sensitive SharedPreferences
### 7.2 What the Code Does Poorly
- `verifyChannelPassword()` always returns `true`
- No certificate pinning on any HTTP client
- No APK signature verification for updates
- Cleartext traffic allowed globally
- Relay list downloaded without integrity verification
- WebView with JavaScript enabled for geohash picker (minor)
### 7.3 Permissions Analysis
| Permission | Justified | Risk |
|------------|-----------|------|
| INTERNET | Yes (P2P, Nostr) | Normal |
| BLUETOOTH_* | Yes (BLE mesh) | Normal |
| ACCESS_FINE_LOCATION | Partially (needed for BLE, but also geohashing) | Medium — location data sent to relays |
| ACCESS_BACKGROUND_LOCATION | Questionable | High — persistent location access |
| RECORD_AUDIO | Yes (voice notes) | Low |
| CAMERA | Yes (QR scanning) | Low |
| REQUEST_INSTALL_PACKAGES | Concerning (self-update) | High — combined with unsigned APK updates |
| FOREGROUND_SERVICE | Yes (BLE mesh) | Normal |
| RECEIVE_BOOT_COMPLETED | Yes (auto-start mesh) | Low |
### 7.4 No Backdoors Detected in Source Code
After thorough analysis, **no explicit backdoors were found in the source code**. Specifically:
- No `Runtime.getRuntime().exec()` calls
- No `ProcessBuilder` usage
- No dynamic class loading beyond legitimate library initialization
- No remote code execution through Nostr or P2P
- No data exfiltration endpoints
**However:** The source code may not match the distributed APKs (see Section 5.4). The absence of backdoors in source does not guarantee their absence in the distributed binaries.
---
## 8. Comparison with Original bitchat-android
### 8.1 Original Project
| Attribute | bitchat-android | zemzeme-android |
|-----------|----------------|-----------------|
| Organization | permissionlesstech | whisperbit-labs |
| Stars | 5,128 | 50 |
| Forks | 690 | 27 |
| Created | Jul 8, 2025 | Feb 26, 2026 |
| License | MIT | MIT |
| Contributors | Community | 4 sockpuppet-like accounts |
### 8.2 Key Modifications in zemzeme
1. **Added:** Nostr transport layer (entire `nostr/` package)
2. **Added:** Geohashing for location-based messaging
3. **Added:** 100 icon switching aliases
4. **Added:** Tor/Arti optional support
5. **Added:** Relay directory with auto-update from GitHub
6. **Added:** Self-update mechanism via GitHub Releases
7. **Changed:** Package name to `com.roman.zemzeme`
8. **Changed:** Branding, fonts, theming
9. **Broken:** Channel password verification (returns `true`)
---
## 9. Network Architecture Risk Assessment
### 9.1 Traffic Flow
```
User Device
├── BLE Mesh (local, no internet) ──> Nearby peers
├── libp2p P2P (internet) ──> IPFS bootstrap nodes ──> Other peers
│ Bootstrap: bootstrap.libp2p.io (legitimate IPFS nodes)
├── Nostr (internet) ──> Relay servers (from RelayDirectory)
│ Default: damus.io, primal.net, offchain.pub, nostr21.com
│ Updated from: raw.githubusercontent.com (unsigned CSV)
├── GitHub API ──> Update checks + APK download (unsigned)
└── (Optional) Tor ──> All above through SOCKS5 proxy
```
### 9.2 Risk Points
1. **Nostr relays see:** geohash (location), pubkeys, message metadata, timestamps
2. **GitHub sees:** IP addresses during update checks (identifiable as zemzeme users via User-Agent)
3. **MITM attacker can:** intercept all traffic, inject malicious relay list, push malicious APK
4. **Tor mitigates:** IP address exposure, but NOT content or metadata if relays are compromised
---
## 10. Recommendations
### For Users
1. **Do NOT use this application for sensitive communications** until:
- The APKs are built reproducibly from source via CI/CD
- APK signing is implemented and enforced
- An independent security audit is conducted by a reputable firm
- The development team's identity is verified
2. **If you must use a mesh messenger**, consider the original [bitchat](https://github.com/permissionlesstech/bitchat-android) which has a larger community and more transparent development
3. **Do NOT trust the "password-protected" channel feature** — it provides zero protection in v1.0.2
4. **Be aware** that your approximate location is shared with Nostr relay operators when using online messaging
5. **Do NOT install updates through the app's built-in updater** — always verify APK sources independently
### For the Development Team (if legitimate)
1. **Implement CI/CD-based release builds** with signed APKs
2. **Fix `verifyChannelPassword()`** immediately
3. **Add certificate pinning** for all network endpoints
4. **Implement APK signature verification** in UpdateManager
5. **Replace `usesCleartextTraffic="true"`** with network security config
6. **Sign the relay list** with a known public key
7. **Provide reproducible build instructions**
8. **Publish a privacy policy and team information**
9. **Remove or explain** the 100 icon switching aliases
10. **Provide build instructions** for golib.aar and libarti_android.so
---
## 11. Technical Appendix
### Files Examined
| File | Lines | Key Findings |
|------|-------|-------------|
| `app/src/main/AndroidManifest.xml` | 2,242 | cleartext traffic, 100 aliases, permissions |
| `app/build.gradle.kts` | ~120 | conditional signing, ProGuard enabled |
| `.github/workflows/release.yml` | 108 | signing commented out, never ran |
| `.github/workflows/android-build.yml` | 105 | 0 successful runs |
| `.github/workflows/fetch-georelays.yml` | 40 | auto-commits to main, no review |
| `UpdateManager.kt` | 519 | unsigned APK download & install |
| `ChannelManager.kt` | ~200 | password bypass at line 131 |
| `RelayDirectory.kt` | 302 | unsigned relay list fetch |
| `OkHttpProvider.kt` | ~50 | no cert pinning, Tor proxy support |
| `NostrClient.kt` | ~400 | NIP-17 DM, geohash channels |
| `NostrIdentity.kt` | ~200 | per-geohash derived identities |
| `ArtiTorManager.kt` | ~200 | optional Tor, SOCKS5 proxy |
| `EncryptionService.kt` | ~150 | AES256_GCM, proper key management |
| `P2PConfig.kt` | ~50 | standard IPFS bootstrap nodes |
### Open Pull Requests (as of audit date)
| PR | Title | Author | Status |
|----|-------|--------|--------|
| #66 | Security fix: session key compromise and network interception | Iman | Open |
| #65 | Fix: channel password verification bypass | sssabet | Open |
Both PRs acknowledge the vulnerabilities identified in this audit, but neither has been merged as of the audit date.
### Release Asset Analysis
| Asset | Size | Downloads | SHA256 | Source |
|-------|------|-----------|--------|--------|
| app-arm64-v8a-release.apk | 65.8 MB | 3,306 | Unknown | Manual upload |
| app-armeabi-v7a-release.apk | 15.9 MB | 584 | Unknown | Manual upload |
| app-universal-release.apk | 97.9 MB | 672 | Unknown | Manual upload |
| app-x86-release.apk | 21.0 MB | 183 | Unknown | Manual upload |
| app-x86_64-release.apk | 21.0 MB | 241 | Unknown | Manual upload |
No checksums or signatures were published with the release.
---
## 12. Conclusion
Zemzeme presents itself as a secure messenger for at-risk users in Iran, but this security audit reveals that **the trust foundation is fundamentally broken**:
1. **You cannot verify the APK matches the source code** — releases are manually built and uploaded
2. **The development team is anonymous** with sockpuppet-like GitHub accounts
3. **Critical security features are broken** (password verification bypass)
4. **The update mechanism can be weaponized** to push malicious code
5. **No certificate pinning** in an environment where state-controlled CAs are a known threat
The source code itself (inherited from bitchat) has reasonable cryptographic foundations, but **the distribution and trust chain is compromised**. For Iranian users facing government surveillance, the lack of certificate pinning is especially dangerous given Iran's known use of state-issued TLS certificates for interception.
**Bottom line: The code may be technically sound in places, but users cannot trust that the APK they install contains only the code they can read on GitHub.**
---
*This report was generated through automated analysis of the source code, GitHub API, and public infrastructure. It does not include dynamic analysis (running the APK in a sandbox), binary reverse engineering, or network traffic analysis of the compiled application. A complete security audit would require these additional steps.*
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment