From 0ae69a277469368e386365e0d479d0c1779f4e3b Mon Sep 17 00:00:00 2001 From: thePR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com> Date: Thu, 11 Jun 2026 22:37:57 -0400 Subject: [PATCH] Board: show only real VoxVera flyers, not foreign kind-30078 events MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit kind 30078 is a shared "application data" kind, so other apps (e.g. artstr.studio) publish under it too — and relays may ignore our #t filter, or an unrelated note may carry a "voxvera" hashtag. The board only checked kind + t-tag, never the payload, so foreign events could appear. parseFlyers now also requires VoxVera's own markers: a `voxvera:` d-tag and a `type: "voxvera_flyer"` content payload (both real flyers and tombstones have these), filtering foreign events out client-side regardless of relay behavior. Test: a foreign kind-30078 event carrying a voxvera t-tag is excluded while a real flyer shows. Full suite 384 passing. Co-Authored-By: Claude Opus 4.8 --- e2e/flyer.spec.js | 21 +++++++++++++++++++++ site/board.js | 11 ++++++++++- 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/e2e/flyer.spec.js b/e2e/flyer.spec.js index b181705..80e98c0 100644 --- a/e2e/flyer.spec.js +++ b/e2e/flyer.spec.js @@ -1089,6 +1089,27 @@ test.describe("VoxVera static client", () => { await expect(page.locator(".content h1")).toHaveCount(0); }); + test("board shows only VoxVera flyers, not foreign kind-30078 app data", async ({ page }) => { + const OTHER = "b".repeat(64); + await stubNip07(page); // viewer = 1*64, no follows → show-all + await stubRelays(page, [ + flyerEvent("real", OTHER, "Real VoxVera Flyer"), + // A foreign app's kind-30078 event that still carries a "voxvera" tag + // (hashtag collision, or a relay ignoring the #t filter). It has neither a + // voxvera: d-tag nor a voxvera_flyer payload, so it must be excluded. + { + id: "f".repeat(64), kind: 30078, pubkey: OTHER, created_at: 5000, + tags: [["d", "artstr:gallery"], ["t", "voxvera"], ["t", "flyer"]], + content: JSON.stringify({ type: "artstr_note", title: "Foreign App Note", url: "https://artstr.studio/x" }) + } + ]); + await page.goto("/board.html"); + await page.locator("#board-connect").click(); + await expect(page.locator("#board-content")).toBeVisible(); + await expect(page.locator("#board-rows")).toContainText("Real VoxVera Flyer"); + await expect(page.locator("#board-rows")).not.toContainText("Foreign App Note"); + }); + test("blocking an author hides their flyers, persists, and clears", async ({ page }) => { const OTHER = "b".repeat(64); await stubNip07(page); // viewer = 1*64 diff --git a/site/board.js b/site/board.js index c45f452..9b8f88b 100644 --- a/site/board.js +++ b/site/board.js @@ -190,7 +190,16 @@ if (!e || e.kind !== EVENT_KIND) return; const tags = e.tags || []; if (!tags.some((x) => x[0] === "t" && x[1] === "voxvera")) return; - const key = e.pubkey + ":" + dtagOf(tags); + // kind 30078 is a shared "application data" kind, and relays may ignore the + // #t filter (or another app may carry a "voxvera" hashtag), so foreign + // events can arrive. Require VoxVera's own markers: a voxvera: d-tag and a + // voxvera_flyer content payload (real flyers and tombstones both have it). + const d = dtagOf(tags); + if (!/^voxvera:/.test(d)) return; + let payload; + try { payload = JSON.parse(e.content); } catch (_) { return; } + if (!payload || payload.type !== "voxvera_flyer") return; + const key = e.pubkey + ":" + d; const prev = latest.get(key); if (!prev || (e.created_at || 0) > (prev.created_at || 0)) latest.set(key, e); });