Files
VoxVera/docs/roadmap.md
T
thePR0M3TH3ANandClaude Opus 4.8 73c28f31ec Bulletin board: per-row manage (edit / re-broadcast / delete) + local Block
Add inline management to the board for the connected viewer:

- On your own flyers: Edit (links to /?edit=1#<naddr>, which the main client
  now honors by loading the flyer straight into the editor), Re-broadcast
  (re-sends the already-signed event to all relays — no signing), and Delete
  (replaceable tombstone + NIP-09, signed via the device key or NIP-07; a
  pasted-nsec connection is pointed to the editor where the key is available).
- On flyers from others: Block — a per-device blocklist of author pubkeys
  (voxvera_blocked_pubkeys) that always hides them, with a "Clear blocked"
  control. This is roadmap #1's local blocklist direction.

The board hides tombstoned flyers (newest-version-wins so a delete can't be
undone by an older copy). New non-sortable actions column; a board delete-
confirm modal; 12 new strings × 14 languages. Adds e2e for block (hide/persist/
clear), own-vs-other actions, and board delete (tombstone + NIP-09 published),
plus a ?edit=1 deep link. 234 e2e across all six engines; docs updated.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-10 20:06:07 -04:00

15 KiB
Raw Permalink Blame History

VoxVera Roadmap & Known Issues

This document captures known limitations and the development directions that would address them. None of these are code bugs — they are product- and threat-model-level gaps in the current static Nostr client. They are listed roughly in priority order: the trust model (#1–#2) is the highest-leverage area because almost every other risk flows from it being undefined.

Status legend: Open (not started) · Partial (some mitigation exists) · Planned (committed direction) · In progress (partially shipped).

Shipped so far (2026-06-10)

The trust model (#1–#2) has moved from undefined to implemented:

  • Bulletin board login gate — viewing the board requires a connected Nostr identity (NIP-07 extension, a pasted nsec, or a generated key).
  • Web-of-trust filter — the board defaults to flyers from authors in the viewer's NIP-02 follow graph, seeded from a curator account for fresh keys, with a "Show all" opt-out (so it is no longer an unmoderated firehose).
  • Editor publishing identity — authors can publish anonymously (default), via NIP-07, or with an imported nsec that is optionally PIN-encrypted at rest (PBKDF2 → AES-GCM; plaintext never stored).
  • Flyer management (edit / re-publish / delete) — an author can edit a loaded flyer (re-publish replaces in place via the d tag), re-publish as a keep-alive, or delete it. Delete is belt-and-suspenders: a replaceable tombstone (so the board/viewer hide it even on relays that ignore NIP-09) plus a NIP-09 deletion request. Best-effort, author-only. These actions are available in the editor and inline on the bulletin board (Edit / Re-broadcast / Delete on your own rows).
  • Local blocklist — a per-device "Block" on the board hides flyers from any author you didn't post (separate from the web-of-trust filter), with a clear control. This is the local blocklist direction from #1.

All of the above is localized across the 14 languages and covered by the cross-engine Playwright suite. What remains on #1–#2 is polish (degree-2 trust, blocklist/report, NIP-05 badges, an identified-vs-anonymous display) plus the editor follow-ups noted under the Planned direction section. #3–#5 are still fully open — distribution centralization (#3) is now the highest-leverage untouched item.


1. Bulletin board has no moderation or abuse story — In progress

Direction agreed and Phases 1–2 shipped: the board requires a connected Nostr identity to view, and it now filters to flyers from the viewer's NIP-02 web of trust (with a bootstrap seed for new keys). See Planned direction: identity + web-of-trust on the board below (jointly addresses #1 and #2). Remaining: degree-2 trust, blocklist/report, NIP-05 badges (Phase 3).

Problem. board.html lists every Nostr event tagged t=voxvera / t=flyer from the public default relays, with no filtering, verification, or moderation. Anyone can publish a kind-30078 event with those tags and immediately appear on the board — including spam, hostile titles, or links to harmful content, all under VoxVera's branding.

Current mitigation (Partial on safety, not abuse). Relay content is treated as untrusted: titles and npubs are HTML-escaped, and link URLs are rejected unless http(s) (so javascript:/data: never render). This prevents script injection but does nothing about spam or unwanted-but-valid content.

Why it matters. A static client cannot rate-limit or moderate server-side, so the board is trivially gameable the moment it is used adversarially. This is the single biggest liability for promoting the board publicly.

Possible directions.

  • Client-side curation: an allowlist of npubs (a "follows"/featured set) so the default view shows only vetted authors.
  • A local, user-controlled blocklist / "hide this" with localStorage persistence (per-device, no server needed). ✅ Shipped — the board's per-row Block (voxvera_blocked_pubkeys).
  • A dedicated VoxVera relay (or relay set) that applies its own admission policy, with the public relays as fallback.
  • A "report" affordance that records to a relay/list a curator can act on.
  • Make the unfiltered firehose an explicit opt-in mode, not the default.

Depends on #2 (without identity, allowlists/blocklists are weak).

2. Anonymous keys give no authenticity or accountability — In progress

Phases 1–2 shipped: the board viewer connects a real Nostr identity (NIP-07) and the board is filtered by that viewer's web of trust (with a curator seed for new keys), so spam from unknown authors is hidden by default. The editor now lets authors publish under their own identity too — NIP-07, an imported nsec (optionally PIN-encrypted at rest), or stay anonymous (default) — so a flyer can carry a stable, identifiable author when the creator wants one. Authenticity display on the board (NIP-05 badges, an identified/anonymous distinction) remains ahead.

Problem. The client generates a fresh anonymous key per browser, so the board's "Posted by" npub is effectively random and there is no way to verify a flyer came from who it claims. The trust model is currently empty.

Why it matters. Authenticity underpins the board, "report"/blocklist features, and any notion of a trusted author. Without stable identity, #1 has no foundation to build on.

Possible directions.

  • Optional persistent identity: let a user keep/reuse a key (or import one) and associate it with their flyers, while keeping anonymous-by-default.
  • First-class NIP-07 support for users who already have a Nostr identity.
  • Optional NIP-05 / verified handle display on the board.
  • A clear UI distinction between "anonymous" and "identified" flyers.

The npub column on the board was built deliberately forward-looking so this maps in cleanly.

Planned direction: identity + web-of-trust on the board

Jointly addresses #1 and #2. The goal is to make the board accountable without abandoning anonymous-by-default authoring.

Key reality that shapes the design. You cannot gate writing to public relays — anyone can publish a t=voxvera event directly, never touching this client. So a "login required to post" button is only a soft deterrent; it can't bound what is on the board. The real lever is filtering what the board displays. Login's actual job is to supply the viewer's identity so the board can show them flyers from people they trust.

Principles.

  • Login enables a trust-filtered view; it is not access control. The data is public on relays regardless, so hiding it in the UI is cosmetic. Treat login as "we know who you are, so we can show you a trustworthy board."
  • Keep anonymous creation + printing fully available. The core authoring tool must not require a persistent, linkable identity (activist threat model). Identity is required only to appear on the public board, not to make/print a flyer.
  • Web-of-trust is the actual spam defense, not login. NIP-07 keys are free and unlimited, so "must have a key" alone just makes a spammer generate one. Filtering by the viewer's NIP-02 follow graph (degree 1, optionally follows-of-follows) is what raises the cost.
  • Don't lock out mobile. NIP-07 (window.nostr) needs a desktop extension, but scanned-QR flyers open on phones. "Login" should support NIP-07 plus nsec import (with warnings) and/or NIP-46 remote signer, and/or promoting the existing anonymous key to a kept identity.

Phased plan.

  • Phase 1 — Identity. ✅ Shipped. The board (board.html / board.js) shows a login gate instead of the table until the viewer connects. Three methods: a NIP-07 extension (window.nostr.getPublicKey()), pasting an nsec, or creating a new key (see decision 2 below). On success it reveals the table, surfaces the connected npub with a Disconnect control, and remembers the pubkey in localStorage (voxvera_connected_pubkey) so a return visit reconnects from the pubkey alone — no prompt or re-entry, regardless of method. Anonymous create/print is untouched — connecting is required only to view the board.
  • Phase 2 — Web-of-trust filter. ✅ Shipped. On connect the board fetches the viewer's NIP-02 contact list (kind 3) and defaults to flyers from authors in their follow graph (degree 1) plus themselves, with a "Show all" opt-out. Bootstrap seed: a viewer with no follow list of their own (a fresh key) is seeded from a pinned curator account's follows (FALLBACK_CURATOR_PUBKEY in board.js = npub15jnttpymeytm80hatjqcvhhqhzrhx6gxp8pq0wn93rhnu8s9h9dsha32lx), so a brand- new viewer still gets a curated board instead of the unmoderated firehose. If no trust data can be fetched at all, the board falls back to showing everything (never mysteriously empty). Covered by the cross-engine e2e suite (followed-only view + show-all opt-out; curator-seeded fallback).
  • Phase 3 — Optional / next. Degree-2 trust (follows-of-follows), a local blocklist / "hide this", report-to-list, and NIP-05 verified badges. Also: the bootstrap curator is currently a single hardcoded pubkey — consider making it configurable or a small curated set.

Decisions made (2026-06-10).

  1. Board viewing: login-required. The board is blank (a Connect prompt) until a Nostr identity is connected — framed as a members' space rather than a public discovery surface.
  2. Login methods: NIP-07 + nsec import + key generation. ✅ Shipped. The gate offers three ways in: a NIP-07 browser extension, pasting a private key (nsec), or creating a new key. The nsec is decoded in-page to derive the pubkey and then discarded — only the pubkey is persisted, since the board only reads (never signs). "Create a new key" reuses this device's existing anonymous key if present (so it doesn't orphan an editor identity) or generates one, and reveals the nsec with a "save this — we can't recover it" warning before continuing. This resolves the earlier mobile-lockout trade-off: a phone visitor with no extension can now generate or paste a key to view the board. Covered by the cross-engine e2e suite (nsec import + pubkey-only persistence + reload restore; invalid nsec; key generation + reveal + connect).

Follow-ups this opens.

  • In-page nsec handling. Pasting a secret key into a web page is inherently riskier than a NIP-07 extension that keeps the key isolated; the input is a password field, the value is cleared after use, and nothing is transmitted, but a NIP-46 remote signer would be a safer mobile path to add later.
  • Editor identity (shipped). The editor now signs under anon / NIP-07 / imported nsec. A remembered nsec is PIN-encrypted at rest (PBKDF2 600k → AES-GCM); plaintext is never stored. Known weakness: a short numeric PIN is brute-forceable offline if the encrypted blob leaks — it guards casual snooping, not a targeted attacker. Next: NIP-46 remote signer; an attempt-limit / lockout on repeated wrong PINs; and optionally encrypting the anonymous device key the same way (today it is stored in plaintext, as before).
  • Editor vs board identity are independent. The editor's publishing identity and the board's viewing identity are stored separately and don't yet sync; a future pass could share one connected identity across both pages.

3. Client distribution is centralized (a single chokepoint) — Open

Problem. The content lives on decentralized relays, but the client is one domain (voxvera.org) on a centralized host (Vercel). A censor can block the domain and most users are stuck, which undercuts the censorship-resistance premise even though the backend is robust.

Current mitigation (Partial). The client is fully static and host-agnostic, so it can be served from any static host or mirror — but discovery and trust of mirrors is unsolved, so in practice users depend on the canonical domain.

Possible directions.

  • Document and support official mirrors (IPFS/IPNS, other static hosts) with a verifiable build (subresource integrity / reproducible hash).
  • A one-file/offline build users can save and reopen (file://) — verify WebCrypto/clipboard degrade gracefully off a secure context.
  • Revisit the dropped Tor/onion distribution path as an optional mirror (kept off main per current guardrails, but a documented option).
  • Publish the client itself to a content-addressed network so the front door is not a single DNS name.

4. Relay permanence is overstated — Partial

Partial mitigation shipped: the editor now has a Re-publish (keep-alive) action, so an author can refresh a flyer before relays age it out. The durable fix (a pinned/dedicated relay) is still open.

Problem. The app depends on a few general-purpose relays (relay.damus.io, nos.lol, relay.primal.net) that can prune app-data kinds, rate-limit, or drop events. "Your flyer is still there later" is therefore not guaranteed. The single relay hint in the naddr also reduces fetch robustness for events not on the default set.

Possible directions.

  • Run/pin a dedicated VoxVera relay (or several) so published flyers have a durable home, with the public relays as redundancy.
  • Let authors configure and embed their own preferred relays.
  • Consider a re-broadcast / "keep alive" helper for important flyers.
  • Reconsider how many relay hints to embed in the poster naddr (tradeoff: URL length / QR density vs. fetch robustness — see the QR Target notes in the spec).

5. Content-safety guardrails are minimal — Open

Problem. Beyond HTML-escaping and URL-scheme rejection, there is nothing preventing the tool (and especially the public board) from being used to author or surface illegal/harmful content carrying VoxVera's branding.

Possible directions. Tie this to #1/#2: curation, reporting, and a clear terms/acceptable-use stance for any hosted board. A purely local authoring tool has a smaller surface than a public discovery board, so scope decisions here should follow the board's trust model.


Smaller / lower-priority items

  • Field-fit is layout-measured, not a fixed cap. Robust, but capacity varies with font availability and zoom; the cheat sheet is explicitly approximate. Fine as-is; noted so it is not mistaken for a contract.
  • Favicon — ✅ done. A small inline SVG (site/favicon.svg) is linked from both pages, so browsers no longer request (and 404 on) /favicon.ico.
  • WebKit/Safari coverage in CI needs libavif16 on Linux runners (sudo npx playwright install-deps); headless WebKit is also not a perfect stand-in for real Safari/iOS print + clipboard behavior, so a real-device pass before releases is still worth it.

Reviewed 2026-06-10. The trust model (#1–#2) is now implemented — see "Shipped so far" above. The highest-leverage untouched item is now #3 (the client is a single centralized domain), since it most undercuts the censorship-resistance premise even with the backend on decentralized relays.