The client
This page is the website and the app, not the essay. On 7 October 2026 the document at https://armada.buzz/ carried <meta name="build" content="2026-10-07 14:28:02Z">. The checks below are from that response and from the scripts it loaded. The GitLab mirror of the client (soapbox-pub/armada) reported last activity on 29 September 2026, so a line cited from the mirror is labeled that way. Where the live script and the mirror agree, both are named.
Two scripts the app did not ask for
The HTML starts with /_npanel/errors.js and later loads /_npanel/script.js. Both are outside the Vite bundle. A self-hosted build from the repository does not include them. The official site does.
script.js is the Plausible tracker. At the bottom it runs:
init({"domain":"armada.buzz","endpoint":"/_npanel/api/event","bindToWindow":false,"logging":false});
Pageview capture is on by default in that file. The reported URL is location.href unless a caller passes a different one. The host caller does not. An invite with a secret in the path, a DM route with a public key, and a query string all go to /_npanel/api/event on the first load and again when the history API moves.
The app has a second, careful tracker. src/lib/plausibleUrl.ts in the mirror collapses DM peers, communities, invites, and profiles to templates, and it drops the query and the hash, because "query/hash may carry invite secrets or login tokens." That helper runs only if the build set PLAUSIBLE_DOMAIN. In the live platform script that value is whatever window.ENV says, and the HTML never sets window.ENV. The careful tracker stays off. The host tracker stays on.
The privacy policy is a branch on the same flag. Empty flag, and the 7 October policy chunk renders this:
This build of Armada does not collect analytics. No tracking cookies, no telemetry, and no third-party analytics are active in the client.
In-app privacy policy, last updated 11 July 2026, string in PrivacyPolicyPage of the 7 October build.
errors.js describes itself in the header comment: it catches what the page throws, and it sends a Sentry-shaped envelope "with the trail of clicks, navigations, requests and console output that led up to it." The call at the bottom of the file is {"endpoint":"/_npanel/api/report","release":"3b4c7f9c48d1cae4d4a97dcacec7ec7f8dd66c456626f9777b1259c23ee34259"}. The trail includes the click target, the navigation path plus search plus hash, and the fetch method and URL, truncated. Reports go out when an error is thrown, not on every click. Before send, the script replaces nsec and ncryptsec strings and a list of secret-like query parameters. A path that contains an invite token is not in that list.
Where the secret key sits
The login store in the live index bundle splits setItem three ways. The web path calls localStorage.setItem. Desktop tries an encrypted envelope (enc: "safeStorage") and, if encryption returns nothing, writes the plaintext and logs localStorage (plaintext). Native Android and the iOS project use the Capacitor secure-storage plugin.
The terms, in the same build, last updated 28 August 2026:
If you sign in with a secret key, Armada stores it on your device so you stay signed in; it is never sent to the developers or to any server.
The web push worker, in the mirror's src/sw/worker.ts, keeps "the viewer's pubkey, (nsec logins only) the decrypt key, and the per-channel Concord stream keys" so a closed tab can decrypt a notification. That copy lives in Cache Storage on the same origin.
There is no shipped iPhone app. The comparison essay says the web app works on an iPhone, and the self-host guide collects an email for the day an iOS build ships. Every iPhone user of the official client is on the localStorage path, with the npanel scripts in the page.
The policy recommends a browser extension or a hardware signer, and it says that with those the app never sees the key. The signup wizard generates a key and calls the nsec login. The recommended path and the default path are different paths.
Defaults in the bundle
From /assets/platform-BwrAzseL.js and /assets/useAppContext-DTYTe5I0.js on 7 October, used when the matching environment value is missing:
| Setting | Fallback the bundle ships |
|---|---|
| App relays | wss://relay.ditto.pub, wss://relay.dreamith.to |
| Also in the fleet list | wss://jskitty.com/nostr, wss://asia.vectorapp.io/nostr, plus the two above |
| Write-only broadcast | wss://relay.primal.net (profiles and personal lists; the README says community and DM traffic does not go here) |
| Relay-list discovery | wss://purplepag.es, wss://user.kindpag.es, wss://relay.nos.social |
| Media upload | https://blossom.ditto.pub/, https://blossom.dreamith.to/, https://blossom.primal.net/ |
| Concord voice brokers | https://armada.buzz |
| Link previews | https://ditto.pub/api/link-preview/{url} |
| Discord bridge | https://bridge.armada.buzz |
| Web push gateway | Pubkey 4c812266…e174c7 on wss://relay.ditto.pub and wss://relay.dreamith.to. AGENTS.md names this service as nostr-push2 under Alex Gleason's npub. |
| KLIPY gif key | Empty. Sponsored gif results stay off. |
| Sandbox for embeds | iframe.diy |
You can change relays and media servers in settings. The fresh account does not start empty. Ross's guide says the same thing in prose, then later says a fresh client arrives with no baked-in servers. The script is the one that runs.
The response headers also send an Onion-Location for armadahvhvaj57ojv7mavkqaecdajzejansfiksp5mdscczyh5g7stid.onion. The essays link the clearnet URL. The onion address is a response header. The site opens on clearnet.
Which chats are encrypted
| Mode | What the code does |
|---|---|
| Concord communities | Control, chat, invites, and rekey traffic are gift-wrapped Nostr events. Attachments go through encryptFileForUpload or encryptImageBlob before they hit a Blossom server. Voice derives per-sender keys and will not join the room if the end-to-end worker fails to start. This is the mode the essays describe, and here the essays match the client. |
| NIP-29 servers, including Buzz | The relay is the server. It owns membership, moderation, and the stored events. The voice function is commented, in the mirror, "no media E2EE (relay-trusted SFU)." Fain's Buzz post says a workspace is one relay and everything lives on it: chat, forums, and git. The operator of that relay can read the room. Armada is a client for it, not a seal around it. |
| Direct messages | The composer offers Automatic, Private (NIP-17), and Legacy (NIP-04). The legacy label in the bundle says older encryption "leaks who's talking and when." Automatic uses NIP-17 when the other person can receive it, and it asks before dropping to NIP-04. The privacy policy still describes DMs as NIP-04 or NIP-44 with visible sender and recipient. That paragraph is behind the product, and it is the paragraph a careful reader finds. |
| Discord bridge | The hosted app sets the portal, so the import UI is present. The wizard's own copy: the bridge keeps an admin role, you can revoke it later, and "bridged channels leave end-to-end encryption" because messages are mirrored to Discord in plaintext. The consent checkbox is real. The homepage sentence "every community is end-to-end encrypted" does not grow a matching checkbox. |
| Link previews and embeds | YouTube and Spotify are fetched from those companies' oEmbed endpoints. Everything else goes to ditto.pub with the raw URL in the path. Pasting a link inside an encrypted channel still tells Soapbox's preview server which URL you pasted, from your IP, at the time you pasted it. The content-security policy also allows frames from YouTube, Spotify, Twitter, Instagram, Streamable, and *.iframe.diy. |
| Gif search | With the KLIPY key empty, search uses the keyless provider named in the README. The search term leaves the device. |
What a relay still learns
Gift wrap hides the author of a single stored event behind a one-time key. It does not hide the TCP connection. The privacy policy admits relay operators may log your IP. Ross tells you to keep the default that demands authentication before a client may fetch community messages, "which keeps a passerby from hoovering up sealed envelopes they cannot open anyway." The client that authenticates presents the account key. A relay run the way the guide says to run one learns which key is reading, and from where, even when it cannot open the envelope.
Profiles are public Nostr events on purpose. The default broadcast list sends them to Primal's relay as well as Soapbox's. "No database of your documents" can be true and "no record of your name and key" can be false. Both are the product.
Discover is ranked
The in-app Discover copy calls a Concord community "an encrypted community that runs without a server." The git history on the mirror includes patches from M. K. Fain to rank Discover "by owner: team pack first, then trusted set." The public list you see first is ordered by the people who ship the client.
Source of truth, two places
The README says the canonical repository is a Nostr git remote, and that GitLab is a read-only mirror. The mirror's project timestamp lagged the live build by about a week when this page was written. AGENTS.md is titled as guidance for agents working on the client. On Hacker News, 28 September 2026, answering a comment that called the project vibe-coded, Alex Gleason wrote: "Look at the underlying Concord Protocol. There is definitely engineering. This part by hand." He pointed at the protocol.
On 1 October 2026 he posted that he had prompted a model to "port Armada to SolidJS," that the port took about six hours and looked identical to him, and that he was leaving it unshipped. The build this page checked, three days after that note, is still the React bundle named above.