26 KiB
Zapstore App Stacks — Privacy Review
A systematic privacy and security review of apps in the Zapstore catalog. Each app is cloned from its source repository, scanned for tracking SDKs, permissions, and privacy practices, then assigned a verdict. Approved apps are organized into curated stacks for publication to the Zapstore relay.
Project Structure
.
├── README.md # This file
├── .gitignore
├── plans/ # Planning documents and methodology
│ ├── app-review-methodology.md
│ ├── proposed-categories.md
│ └── review-todo.md
├── scripts/ # Python scripts for review and publishing
│ ├── review_apps.py # Main review script (SSH-based)
│ ├── generate_stacks.py # Map apps to categories, generate stack events
│ ├── publish_stack.py # Sign and publish stacks to relay
│ └── ...
├── data/ # Review results and app data (JSON)
│ ├── review_results_final.json # All 280 apps with verdicts
│ ├── apps_data.json # Zapstore catalog data
│ └── ...
├── reports/ # Human-readable reports (Markdown)
│ ├── review_report_by_category.md # Full category-by-category report
│ ├── final_report.md # Top 2 apps per category
│ └── ...
└── stacks/ # Generated Nostr stack events (kind 30267)
├── all_stacks.json # All 20 stacks in one file
└── ...
Review Results
| Verdict | Count | Meaning |
|---|---|---|
| ✅ APPROVED | 175 | No tracking SDKs, minimal/justified permissions, open source |
| ⚠️ FLAGGED | 36 | Contains tracking SDKs or excessive permissions |
| ⏭️ SKIPPED | 41 | Could not clone repository (no mirror, no GitHub URL) |
| ❓ UNCLEAR | 28 | Needs human investigation (app not in catalog, auth required) |
20 Curated App Stacks
These stacks are ready to publish to the Zapstore relay as kind-30267 events.
1. Bitcoin & Lightning Wallets
Self-custodial Bitcoin and Lightning wallet apps.
- ZEUS, Nunchuk, Cake Wallet, Alby Go, Blitz Wallet, BlueWallet, Electrum, Oubli, MercaSats, Lightning Reaction
2. Nostr Clients
Nostr-native social and communication clients.
- Amethyst, Amber, Wisp, Dark Wisp, White Noise, Nospeak, Nostrord, Shosho, TravelTelly, Nmail, Divine, YakiHonne, Nests, Nostria, Zapstore Alpha, PearCal, PearCircle, Ditto, Flotilla
3. Secure Messaging
End-to-end encrypted messaging and email apps.
- SimpleX, Conversations, Delta Chat, aTalk, Element, Element X, Thunderbird, FairEmail, Tuta, Quiet, SpamBlocker, Deku SMS
4. VPN & Privacy Tools
VPN clients, firewalls, and network privacy tools.
- Orbot, Mullvad VPN, Tailscale, AmneziaVPN, Proton VPN, Rethink, PCAPdroid, WG Tunnel, ByeDPI, NeoStumbler, SD Maid, InviZible Pro, Private DNS Quick Setting
5. Password Managers & Auth
Password managers, 2FA authenticators, and identity tools.
- Bitwarden, KeePassDX, Aegis, Authnkey, Ente Auth, Keep, AliasVault, PassVault, LibreFind
6. Maps & Navigation
Offline maps, navigation, and location tools.
- Organic Maps, OsmAnd~, StreetComplete, CoMaps, OwnTracks, OSMTracker
7. Media Players & Streaming
Video and music players, streaming clients.
- VLC, NewPipe, PipePipe, Auxio, Musify, Metrolist, FreeTube, Zaptrax, Zappix, mpvEx, KurobaEx
8. Productivity & Notes
Note-taking, task management, and productivity tools.
- Flux, Notesnook, Quillpad, Saber, DAVx⁵, Super Productivity, Trilium Notes, SilentNotes, Manent, Meiso, Florid, Plektos, Urn, Screen Time, Grit, Numo, timeto.me, Converter NOW
9. File Management & Cloud Sync
Cloud storage, file sync, and file management tools.
- Nextcloud, Syncthing-Fork, Seafile, File transfer, primitive ftpd, Paperless Mobile, GitSync, wormhole, SFTP Documents Provider, OSS Document Scanner
10. Browsers
Privacy-focused web browsers.
- DuckDuckGo, Cromite
11. Social Media
Federated and alternative social media clients.
- Mastodon, Nekogram, Infinity+, NewsBlur, Claw, Polymarket Viewer
12. Finance & Budgeting
Personal finance, expense tracking, and budgeting.
- Flow, Pennywise AI Tracker, Dev Stocks Widget, Seeker, PearGuard, Mostro, Shopt
13. Health & Fitness
Health tracking, diet, and fitness apps.
- Daily Dozen, Screen Time, Astronia
14. Education & Reference
Learning, dictionary, and reference tools.
- freeCodeCamp, freeDictionary, Ciyue, Keyman, NeverTooManyBooks, Calibre Web Companion, Repertoire, ListenBrainz, CPU Info, microMathematics Plus, Mental Math
15. Photography & Image Tools
Camera, photo editing, scanning, and gallery apps.
- Image Toolbox, FairScan, PhotoPrism, PicGuard, Gallery apps, YTDLnis, ElCaju, Espy, PDF Wallet
16. Utilities & Tools
System utilities, converters, and general-purpose tools.
- Binary Eye, CPU Info, Converter NOW, Catima, BT Remote, Lawnicons, Peristyle, BinEd, MMRL, Canta, Amarok, ServerBox, wX, Mako, Rush, HeliBoard, DeskClock, Redomi, Scrobble, Feeder, SMS2Email, Inure, Unciv
17. Games
Open source games across genres.
- Shattered Pixel Dungeon, Wesnoth, Feudal Tactics, Burger Party, ChipDefense, Roboyard, Breakout 71, Damas Clash, Unciv
18. Communication (Non-Nostr)
Voice/video calls, remote desktop, and messaging.
- Telegram, RustDesk, Sideband, Meshtastic
19. Development Tools
Code editors, Git clients, and developer tools.
- Acode, GitSync, BinEd, freeCodeCamp, GitHub Store, Kai 9000
20. Calendar & Scheduling
Calendar apps, scheduling, and time management.
- Calendar by Form*, PearCal, timeto.me, Sidestep
Review Methodology
Each app is reviewed using a structured process:
- Clone from local Gitea mirror (or GitHub fallback)
- Scan AndroidManifest.xml for requested permissions
- Check build.gradle* for actual dependency declarations (distinguishing
implementationvsplayImplementationvscompileOnly) - Verify API calls — search for actual SDK method invocations (not just dependency names)
- Check manifest metadata that disables analytics
- Check build flavors (Google Play vs F-Droid)
- Determine verdict: ✅ APPROVED or ⚠️ FLAGGED
See plans/app-review-methodology.md for full details.
Publishing
Stack events (kind 30267) are published to wss://relay.zapstore.dev using the publish_all.py script, which signs via qrexec (Qubes OS nostr_signer).
Zapstore Display Requirements
The Zapstore Android app (source) filters which stacks appear on the home screen. A kind 30267 stack event must satisfy all three of these conditions to appear with apps:
Quick Reference: The Three Rules
-
htag = community pubkey — The app filters by#hmatchingacfeaea6e51420e8068fac446ca9d17d7a9ef6a5d20d93894e50fee3d4902a84. Set to""to hide a stack from the app. -
content= empty string — The app treats any non-emptycontentas encrypted/private data. Ifcontentis non-empty, the app skips theatags and shows "no apps". Always usecontent: ""for public stacks. -
atags reference real kind 32267 events — Eachatag must be32267:<real-developer-pubkey>:<package-id>pointing to an actual kind 32267 event on the relay. Apps without kind 32267 events won't appear in the stack.
Complete Example
{
"kind": 30267,
"content": "",
"tags": [
["d", "privacy-approved-health-fitness"],
["t", "app-stack"],
["name", "Privacy Approved — Health & Fitness"],
["description", "Health tracking, diet, and fitness apps..."],
["f", "android-arm64-v8a"],
["h", "acfeaea6e51420e8068fac446ca9d17d7a9ef6a5d20d93894e50fee3d4902a84"],
["a", "32267:78ce6faa72264387284e647ba6938995735ec8c7d5c5a65737e55130f026307d:org.nutritionfacts.dailydozen", "wss://relay.zapstore.dev"],
["a", "32267:78ce6faa72264387284e647ba6938995735ec8c7d5c5a65737e55130f026307d:com.antoniegil.astronia", "wss://relay.zapstore.dev"]
]
}
Required Tags
| Tag | Value | Purpose |
|---|---|---|
h |
acfeaea6e51420e8068fac446ca9d17d7a9ef6a5d20d93894e50fee3d4902a84 |
Zapstore community pubkey — the app filters by #h matching this value. Set to empty string to hide the stack from the zapstore app. |
f |
android-arm64-v8a |
Platform filter — the app filters by #f matching the device platform |
d |
e.g. privacy-approved-nostr-clients |
Unique identifier for the stack |
name |
e.g. Privacy Approved — Nostr Clients |
Display name |
description |
Human-readable description | Shown in stack details |
a |
32267:<pubkey>:<identifier> |
References to kind 32267 app events — must point to real events on the relay |
The h Tag (Community Pubkey)
The Zapstore app's app_stacks_screen.dart queries stacks with:
Map<String, Set<String>> get _tags => {
'#f': {platform},
'#h': {kZapstoreCommunityPubkey},
};
Where kZapstoreCommunityPubkey is defined in app_constants.dart as:
acfeaea6e51420e8068fac446ca9d17d7a9ef6a5d20d93894e50fee3d4902a84
(npub: npub14nl2afh9zsswsp5043zxe2w304afaa496gxe8z2w2rlw84ys92zqlnjx5u)
If the h tag is empty or doesn't match this value, the stack will NOT appear in the Zapstore app.
The content Field (Must Be Empty)
The Zapstore app checks stack.content.isNotEmpty to determine if a stack is encrypted/private. If the content is non-empty, the app attempts to decrypt it and parse private app IDs from it. If decryption fails, it shows "no apps" or "could not be decrypted" instead of reading the a tags.
The content field must be an empty string "" for public stacks. The stack name and description are read from the name and description tags, not from the content field.
The a Tags (App References)
The a tags in stacks reference kind 32267 (Software Application) events. The Zapstore app resolves these references to display app cards within each stack. The a tag format is:
32267:<app-developer-pubkey>:<android-package-identifier>
The pubkey must be the actual app developer's pubkey who published the kind 32267 event — not the stack publisher's pubkey. If the referenced kind 32267 event doesn't exist on the relay, the app won't show that app in the stack.
A lookup file of app identifiers to real developer pubkeys is maintained at app_pubkeys.json, fetched from wss://relay.zapstore.dev.
Stack Comments (Kind 1111)
The Zapstore app displays comments under each stack. Comments are kind 1111 events (not kind 1 text notes) with an A tag referencing the stack's addressable ID.
The zapstore app queries comments in comments_section.dart with:
query<Comment>(
tags: {
'#A': {stack.id}, // stack.id = "30267:<pubkey>:<d-tag>"
},
source: LocalAndRemoteSource(stream: true, relays: 'AppCatalog'),
)
Comment Event Structure
{
"kind": 1111,
"content": "LLM Privacy Review: Both apps have been scanned...",
"tags": [
["A", "30267:<stack-pubkey>:<stack-d-tag>", "wss://relay.zapstore.dev"],
["K", "30267"]
],
"created_at": 1785840000,
"pubkey": "<commenter-pubkey>"
}
| Tag | Value | Purpose |
|---|---|---|
A |
30267:<pubkey>:<d-tag> |
Addressable reference to the stack event (NIP-22 root) |
K |
30267 |
Root kind (the kind of the referenced event) |
Important: Kind 1 is Rejected
relay.zapstore.dev does not accept kind 1 (text note) events. Comments must use kind 1111. The relay's allowed kinds are: [0 5 11 1111 1984 9735 10222 3063 30063 30509 30267 30078 32267].
Use Case: LLM Review Comments
Comments can be used to attach LLM privacy review summaries to stacks. The comment content is free text (supports markdown via the app's note parser). This allows posting review results like:
"LLM Privacy Review: Both apps in this stack have been scanned for tracking SDKs, excessive permissions, and privacy concerns. No issues found — both are approved at privacy level 1."
Relay Acceptance Rules
| Relay | Kind 30267 (Stacks) | Kind 30078 (App Definitions) | Kind 32267 (App Listings) | Kind 1111 (Comments) |
|---|---|---|---|---|
wss://relay.zapstore.dev |
✅ Accepts (requires a tags use kind 32267) |
❌ Rejects d: app-* (only allows zapstore-device-*) |
✅ Accepts | ✅ Accepts |
wss://nos.lol |
✅ Accepts | ✅ Accepts | ✅ Accepts | ✅ Accepts |
wss://relay.primal.net |
✅ Accepts | ✅ Accepts | ✅ Accepts | ✅ Accepts |
wss://laantungir.net/relay |
✅ Accepts | ✅ Accepts | ✅ Accepts | ✅ Accepts |
Nostr Kinds Reference
The following Nostr event kinds are relevant to this project:
| Kind | Name | Usage | Status |
|---|---|---|---|
| 32267 | Software Application | The app listing event published by developers. Identified by d tag (Android package name) and publisher pubkey. Referenced in stacks via a tags as 32267:<pubkey>:<identifier>. |
Core data source |
| 30267 | App curation set | Curated collections of apps (stacks). We publish privacy-approved stacks as kind 30267 events with a tags referencing each approved app. |
Used for output |
| 30078 | App-specific data | Generic parameterized replaceable event. Already used by Zapstore for device state, bookmarks, etc. Candidate for storing per-app review results from LLM agents. | Planned for reviews |
| 3063 | Software Asset | APK metadata including hash, size, version code, platform. Used to track current version info for reviewed apps. | Used for version tracking |
| 30063 | Release artifact set | Group of artifacts for a software release. Links to kind 3063 assets and kind 32267 app events. | Reference |
| 1111 | Comment | Purplebase comment kind (NIP-22). Used by the Zapstore app for stack and app comments. References the root event via A tag. Not kind 1 (text note) — relay.zapstore.dev rejects kind 1. |
Used for stack comments |
| 1986 | Relay reviews | Existing review kind, but scoped to Nostr relays rather than software apps. | Not applicable |
See references/registry-of-kinds/schema.yaml for the full kinds registry and references/nips/51.md for list/set definitions.
How Kinds Interact on Zapstore
The diagram below shows how the different Nostr event kinds relate to each other in the zapstore ecosystem — from app discovery to installation to review.
ZAPSTORE APP (Android)
═════════════════════
│
┌───────────────┼───────────────┐
│ │ │
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│ DISCOVER │ │ INSTALL │ │ UPDATE │
│ (browse) │ │ (download│ │ (notify) │
└────┬─────┘ └────┬─────┘ └────┬─────┘
│ │ │
│ │ │
┌─────▼──────────────▼──────────────▼─────┐
│ NOSTR RELAY │
│ (relay.zapstore.dev) │
│ │
│ ┌────────────────────────────────────┐ │
│ │ KIND 30267 (App Stack/Curation) │ │
│ │ ───────────────────────────────── │ │
│ │ • d: privacy-approved-nostr-clients│ │
│ │ • h: <community pubkey> │ │
│ │ • f: android-arm64-v8a │ │
│ │ • a: 32267:<pubkey>:<app-id> ─────┼──┼──┐
│ │ • content: "" (must be empty!) │ │ │
│ └────────────────────────────────────┘ │ │
│ │ │
│ ┌────────────────────────────────────┐ │ │
│ │ KIND 32267 (Software Application)│◄─┼──┘
│ │ ───────────────────────────────── │ │
│ │ • d: com.vitorpamplona.amethyst │ │
│ │ • name: Amethyst │ │
│ │ • f: android-arm64-v8a │ │
│ │ • Published by APP DEVELOPER │ │
│ └───────────────┬────────────────────┘ │
│ │ │
│ │ (linked via) │
│ │ │
│ ┌───────────────▼────────────────────┐ │
│ │ KIND 30063 (Release Artifact Set)│ │
│ │ ───────────────────────────────── │ │
│ │ • a: 32267:<pubkey>:<app-id> │ │
│ │ • e: <kind 3063 event id> ────────┼──┼──┐
│ │ • Groups assets for a release │ │ │
│ └────────────────────────────────────┘ │ │
│ │ │
│ ┌────────────────────────────────────┐ │ │
│ │ KIND 3063 (Software Asset/APK) │◄─┼──┘
│ │ ───────────────────────────────── │ │
│ │ • i: com.vitorpamplona.amethyst │ │
│ │ • version: 1.13.1 │ │
│ │ • versionCode: 11301 │ │
│ │ • x: <SHA256 hash of APK> │ │
│ │ • url: https://cdn.zapstore.dev/..│ │
│ │ • size: 84849702 │ │
│ │ • f: android-arm64-v8a │ │
│ │ • THIS IS THE INSTALLABLE BINARY │ │
│ └────────────────────────────────────┘ │
│ │
│ ┌────────────────────────────────────┐ │
│ │ KIND 1111 (Comment/Review) │ │
│ │ ───────────────────────────────── │ │
│ │ • A: 30267:<pubkey>:<stack-d-tag> │ │
│ │ • K: 30267 │ │
│ │ • content: "LLM Privacy Review..."│ │
│ │ • Appears under stack in app │ │
│ └────────────────────────────────────┘ │
│ │
│ ┌────────────────────────────────────┐ │
│ │ KIND 30078 (App-Specific Data) │ │
│ │ ───────────────────────────────── │ │
│ │ • t: app-definition │ │
│ │ • d: app-<identifier> │ │
│ │ • Our app catalog metadata │ │
│ │ • (NOT used by zapstore app — │ │
│ │ │ only our frontend uses these) │ │
│ └────────────────────────────────────┘ │
│ │
└──────────────────────────────────────────┘
OUR PRIVACY REVIEW FLOW
═══════════════════════
┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐
│ GITEA │ │ AI AGENT│ │ KIND │ │ KIND │
│ MIRROR │────▶│ SCANS │────▶│ 3063 │────▶│ 1111 │
│ │ │ SOURCE │ │ (APK │ │ COMMENT│
│ Source │ │ CODE │ │ HASH) │ │ ON │
│ code │ │ │ │ │ │ STACK │
└─────────┘ └─────────┘ └─────────┘ └─────────┘
│
│ If approved,
│ app is included
│ in kind 30267
│ stack
▼
┌───────────┐
│ KIND 30267│
│ (Stack) │
│ a: 32267 │
└───────────┘
KEY INSIGHT: An app must have BOTH kind 32267 AND kind 3063
events on the relay to be installable on zapstore. Without
kind 3063, there is no APK to download, no version to display,
and nothing for AI agents to verify.
Kind 30078 Event Data Model
We use kind 30078 (App-specific data) events for two purposes: app definitions and app reviews. The frontend queries the Nostr relay directly for these events on page load, rather than reading a static file.
App Definition Events
Each app in the catalog is defined by a kind 30078 event with #t: app-definition. These are published when an app is added via the PHP backend.
{
"kind": 30078,
"content": "{\"name\":\"Amethyst\",\"identifier\":\"com.vitorpamplona.amethyst\",\"repository\":\"https://github.com/vitorpamplona/amethyst\",\"description\":\"...\"}",
"tags": [
["d", "app-com.vitorpamplona.amethyst"],
["t", "app-definition"],
["t", "nostr-clients"], // ← category tag
["name", "Amethyst"],
["repository", "https://github.com/vitorpamplona/amethyst"],
["gitea", "com.vitorpamplona.amethyst"],
["url", "https://cdn.zapstore.dev/..."],
["f", "android-arm64-v8a"],
["published_at", "1729302793"]
]
}
App Review Events
Each model's review of an app is stored as a kind 30078 event with #t: app-review. The review is pinned to a specific version via SHA256 hash.
{
"kind": 30078,
"content": "{\"verdict\":\"APPROVED\",\"level\":1,\"summary\":\"...\",\"findings\":{...}}",
"tags": [
// Unique ID: model + app identifier + version
["d", "review-deepseek/deepseek-v4-flash-com.vitorpamplona.amethyst-v1.2.3"],
// Reference to the app definition event
["a", "30078:<pubkey>:app-com.vitorpamplona.amethyst", "wss://relay.zapstore.dev"],
// The SPECIFIC asset/APK that was reviewed (kind 3063 event)
["e", "<kind_3063_event_id>", "wss://relay.zapstore.dev"],
// Version info for querying
["version", "1.2.3"],
["version_code", "1234"],
// SHA256 hash of the reviewed APK — cryptographic pin
["x", "a1b2c3d4e5f6..."],
// Review metadata
["model", "deepseek/deepseek-v4-flash"],
["model_name", "DeepSeek Flash 4"],
["verdict", "APPROVED"],
["level", "1"],
["p", "<agent_pubkey>"],
["t", "app-review"]
]
}
Tag Reference
| Tag | Event Type | Purpose |
|---|---|---|
d |
Both | Unique identifier (app-{identifier} or review-{model}-{identifier}-v{version}) |
t |
Both | app-definition or app-review — also carries category for app definitions |
name |
App definition | Human-readable app name |
repository |
App definition | Source code URL |
gitea |
App definition | Gitea mirror identifier |
url |
App definition | APK download URL |
f |
App definition | Platform (e.g. android-arm64-v8a) |
a → kind 30078 |
App review | Reference to the app definition event |
e → kind 3063 |
App review | The specific APK asset that was reviewed |
x |
App review | SHA256 hash — cryptographic pin of the reviewed binary |
version / version_code |
App review | Version info |
model |
App review | LLM model string (e.g. deepseek/deepseek-v4-flash) |
verdict |
App review | Review verdict |
level |
App review | Privacy level (1-4) |
p |
App review | Agent pubkey who performed the review |
Page Load Flow
Page opens
→ Queries relay for kind 30078, #t: app-definition
→ Groups apps by #t category tags → builds categories
→ Queries relay for kind 30078, #t: app-review
→ Matches reviews to apps via a tags
→ Renders categories + apps + per-model review status
Version Pinning Flow
App v1.2.3 defined (kind 30078, #t: app-definition)
└── APK asset (kind 3063, SHA256: abc...)
└── Review (kind 30078, #t: app-review, pinned via x tag)
App v1.2.4 published (kind 3063, SHA256: def...)
└── NEW review needed — old review's SHA256 doesn't match
If a bad actor modifies the app in v1.2.4, the review for v1.2.3 (with its SHA256 abc...) does not carry over. A new review must be performed on the new binary.