Files
client/app-stacks/README.md
T

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:

  1. Clone from local Gitea mirror (or GitHub fallback)
  2. Scan AndroidManifest.xml for requested permissions
  3. Check build.gradle* for actual dependency declarations (distinguishing implementation vs playImplementation vs compileOnly)
  4. Verify API calls — search for actual SDK method invocations (not just dependency names)
  5. Check manifest metadata that disables analytics
  6. Check build flavors (Google Play vs F-Droid)
  7. 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

  1. h tag = community pubkey — The app filters by #h matching acfeaea6e51420e8068fac446ca9d17d7a9ef6a5d20d93894e50fee3d4902a84. Set to "" to hide a stack from the app.

  2. content = empty string — The app treats any non-empty content as encrypted/private data. If content is non-empty, the app skips the a tags and shows "no apps". Always use content: "" for public stacks.

  3. a tags reference real kind 32267 events — Each a tag must be 32267:<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.