Joey_NostrComments
Joey (NostrComments)
I build NostrComments — a browser extension that adds a censorship-resistant comment section to every website, powered by Nostr. Free and open source, always. Monero: 87aDTPD9HQx2QenKsS7MvHDdqsziFPD7UB37X6G5XVXc2ZPhAs8DdEKUPYJijVcRjj1gU5KvxLCTfWUKWqrd1D5o8uw5EpM
Public Key
npub1ewxm82gprxwkh9qznauyey6vwx62xetpsux3prnmddkyevasatgswmds9e Profile Code
nprofile1qqsvhrdn4yq3n8ttjspf77zvjdx8rd9rv4scwrgs3eakkmzvkwcw45gpz3mhxue69uhhyetvv9ujuerpd46hxtnfduq3vamnwvaz7tmjv4kxz7fwwpexjmtpdshxuet5wadqnv
Show more details
Published at
2026-08-27T04:33:13Z Event JSON
{
"id": "3e7d8b0b25c1f2c4c5e417e5fe4c177eb07562a20a82c4cb887cbc52148c6b82" ,
"pubkey": "cb8db3a901199d6b94029f784c934c71b4a36561870d108e7b6b6c4cb3b0ead1" ,
"created_at": 1787805193 ,
"kind": 0 ,
"tags": [
[
"client",
"Primal Web"
]
],
"content": "{\"name\":\"Joey_NostrComments\",\"about\":\"I build NostrComments — a browser extension that adds a censorship-resistant comment section to every website, powered by Nostr. Free and open source, always.\\n\\nMonero: 87aDTPD9HQx2QenKsS7MvHDdqsziFPD7UB37X6G5XVXc2ZPhAs8DdEKUPYJijVcRjj1gU5KvxLCTfWUKWqrd1D5o8uw5EpM\",\"lud16\":\"[email protected] \",\"nip05\":\"[email protected] \",\"picture\":\"https://blossom.primal.net/c2a465b66963db04624f6fff7643dbb4b1d3d814a8b1fbca6de1c7259c75288f.webp\",\"display_name\":\"Joey (NostrComments)\",\"website\":\"https://briskness-byte.github.io\",\"banner\":\"https://image.nostr.build/75fdc7db728ee37cc523acb0c319ec8e057bf65448edf61173e728721d554bb9.jpg\",\"displayName\":\"Joey (NostrComments)\"}" ,
"sig": "d4aeca50c76ef37b4d37d1900d1a97b0467ad5b5c93956febd2a99a166cf0eb5c71921a06e39dfa31371c188f9f57fa093b357a42dac7fec0e70dcec9dcd29c4"
}
Last Notes npub1ewxm82gprxwkh9qznauyey6vwx62xetpsux3prnmddkyevasatgswmds9e Joey_NostrComments 🦬 @nprofile…9s94 🦬 npub1ewxm82gprxwkh9qznauyey6vwx62xetpsux3prnmddkyevasatgswmds9e Joey_NostrComments 🦬 npub1ewxm82gprxwkh9qznauyey6vwx62xetpsux3prnmddkyevasatgswmds9e Joey_NostrComments I was about to post a measurement. I re-ran it first, which I do, and it fell apart in my hands. The claim: across six news domains, the notes on Nostr that link to news articles are essentially one account. 59 of the 63 notes on the five most-linked pages came from a single bot relaying links. Four distinct authors in total. I had built a whole argument on that — I decided not to add URL search to my client because there was nothing worth surfacing. Redone sixteen days later: 3,050 distinct article pages, 17 distinct authors on the five most-linked, largest single author 16%. The most linked pages are ordinary articles on unrelated subjects, each mentioned by different people. Both runs used the same three search relays that answer a NIP-50 filter at all, of seven tried. The difference between them is one function call. The corpus was there the whole time. My script stripped every query string before counting a page, so thousands of distinct URLs collapsed into a handful of buckets. One account that posts a lot of links with tracking parameters then owned whatever was left. The bot was not the corpus. It was the largest thing still standing after my measurement threw the corpus away. The part I find hardest to be relaxed about: my own client already has a URL normaliser that gets this right. It strips tracking parameters and keeps the query string that identifies a page, because that is what the product needs to file a comment under the correct address. The measurement did not use it. So I measured something my own software would never see, and then made a decision about my own software on the result. The decision happens to survive — I still would not build that search, for reasons about relay reliability and about not sending the page you are reading to an index you did not choose. But it survived by luck, not by reasoning, and those are not the same thing. If you measure your own product: measure with the code your product runs. Anything else and you are describing a system nobody uses. npub1ewxm82gprxwkh9qznauyey6vwx62xetpsux3prnmddkyevasatgswmds9e Joey_NostrComments I'm actually building NostrComments: https://briskness-byte.github.io/ I hope you like it and feedback is always welcome npub1ewxm82gprxwkh9qznauyey6vwx62xetpsux3prnmddkyevasatgswmds9e Joey_NostrComments Hello. I have been reading here a while and posting the occasional finding; this is the introduction I skipped. I build a comment layer for web pages, on Nostr. A thread keyed to a page's address, held on relays instead of by the site — so when a publisher closes their comment section, or deletes the correction somebody left, the reply is still there for whoever arrives next. It started with scam sites. An operator removes warnings from their own page within minutes. That is the case that would not let go of me: a reply has to live somewhere the party being discussed does not control. What I actually have: a browser extension for Chrome and Firefox, MIT, no server, no accounts — an identity is a keypair made in your browser. And almost no users. I would rather say that than imply otherwise. The layer exists before the audience does. What I have been posting is mostly measurements, including the ones that went against me. That my own extension left a private key readable in the page's DOM, and why a closed shadow root is not the fix. That I dropped a read path for a reason which turned out to be nonsense, and it took five releases to notice. That NIP-09 deletion has an author check most clients skip. There is more of that coming. I am here for the people who build the plumbing. If you work on relays, clients or NIPs, I would like to know you. #introductions npub1ewxm82gprxwkh9qznauyey6vwx62xetpsux3prnmddkyevasatgswmds9e Joey_NostrComments LMAO 🤣 🤣 🤣 SPOT ON! 💯 npub1ewxm82gprxwkh9qznauyey6vwx62xetpsux3prnmddkyevasatgswmds9e Joey_NostrComments It's the PayPal miners' fee! It's like paying protection money to the mafia! npub1ewxm82gprxwkh9qznauyey6vwx62xetpsux3prnmddkyevasatgswmds9e Joey_NostrComments Hi, just a heads-up; saw your post! npub1ewxm82gprxwkh9qznauyey6vwx62xetpsux3prnmddkyevasatgswmds9e Joey_NostrComments Lets discuss articles freely with the NostrComments browser extension: https://briskness-byte.github.io/ https://edition.cnn.com/ npub1ewxm82gprxwkh9qznauyey6vwx62xetpsux3prnmddkyevasatgswmds9e Joey_NostrComments Shilajit can contain heavy metals like lead, which are not healthy. npub1ewxm82gprxwkh9qznauyey6vwx62xetpsux3prnmddkyevasatgswmds9e Joey_NostrComments Thank You! (Posting from the NostrComments browser extension) npub1ewxm82gprxwkh9qznauyey6vwx62xetpsux3prnmddkyevasatgswmds9e Joey_NostrComments The premise assumes you can tell when something is rejected. Often you can't. I found a comment on a news page that had simply gone: no deletion event anywhere, and a vote on it still there naming both the event id and its author. Not refused at publish time — accepted, then dropped later. Nothing in the protocol distinguishes that from a comment that never existed. The larger problem underneath it: of the NIP-22 web comments I could find across the usual relays, about one in five sat on exactly one relay. Small sample, but consistent. At that replication a single operator's spring clean is indistinguishable from censorship, and neither one is visible to anybody. A relay for banned comments only collects what somebody was told about. Knowing that something is gone at all seems like the harder half. npub1ewxm82gprxwkh9qznauyey6vwx62xetpsux3prnmddkyevasatgswmds9e Joey_NostrComments This works when the comment section has an owner. The case I keep hitting does not. I build a comment layer keyed to URLs rather than to people, so the thread under a news article belongs to nobody. There is no inbox to clean, and nobody whose deletion would mean anything to the next reader who arrives. Moderation there has to be per-reader — mutes, word filters, hide-on-downvote — which means every reader pays the cost of the same spammer separately, and a first-time visitor always lands on the unfiltered version. Is there a shape for delegated moderation when the thing being moderated has no owner, or is per-reader simply what an ownerless thread costs? npub1ewxm82gprxwkh9qznauyey6vwx62xetpsux3prnmddkyevasatgswmds9e Joey_NostrComments The Truth is.. There is not Truth 😓 Society is based upon lies. npub1ewxm82gprxwkh9qznauyey6vwx62xetpsux3prnmddkyevasatgswmds9e Joey_NostrComments "The reply button lies about where the response will land" is a better way to put it than I managed. One thing I have not solved, and I suspect nobody has: from the events alone, a reader cannot tell which audience actually saw a note. Tags say who was addressed, not who it reached. So a client can be honest about where a reply is going and still leave you guessing about where the thing you are replying to has been. Do you know of anyone treating that as tractable, or is it just the shape of an open relay network? npub1ewxm82gprxwkh9qznauyey6vwx62xetpsux3prnmddkyevasatgswmds9e Joey_NostrComments Exactly that. What the principle doesn't tell you is that reading generously immediately costs you two more decisions. The first is that you have to make the difference visible. A kind 1 that mentions a page and a kind 1111 written about it are not the same object, and rendering them identically is its own kind of lie. Mine marks them as notes rather than comments. The second is that replying stops being one code path. NIP-22 says a comment must not reply to a kind 1, so answering a note has to switch protocols and publish a NIP-10 reply instead. That lands in the author's notifications and in the feeds of people who follow you; a comment does neither. Worth telling the user before they write it rather than after. npub1ewxm82gprxwkh9qznauyey6vwx62xetpsux3prnmddkyevasatgswmds9e Joey_NostrComments I moved my comment events from kind 1 to NIP-22 kind 1111 and stopped reading kind 1 entirely. The move was right. The reasoning was wrong, and it took five releases before I noticed what it had cost. I justified dropping the read path with "there is no corpus worth protecting" — which was true of my own history, and completely beside the point. {kinds:[1], "#r":[url]} does not only return comments made with my extension. An r-tagged note is how any Nostr client links a note to a URL. In one release I made everything anyone had ever said about a page, from any client, invisible. It surfaced as something else first. Reply notifications listen on kind 1 as well as 1111, because somebody can mention you in an ordinary note. So the badge would light up for a mention the thread had no way to display. I hit that myself using my own extension and filed it in my head as a cosmetic glitch. Kind 1 notes are read again now, marked as notes rather than comments. Writing is still 1111 only. And NIP-22 is explicit that a comment must not reply to a kind 1, so replying to one publishes a NIP-10 reply instead. If you are planning the same migration: separate the two decisions. What you write is about being a good citizen of the protocol. What you read is about what your users can see, and dropping a read path is not free just because you are not writing that kind any more. npub1ewxm82gprxwkh9qznauyey6vwx62xetpsux3prnmddkyevasatgswmds9e Joey_NostrComments If your extension puts a private key in the DOM, a script on the page can read it. I found this in my own, so this is not a hypothetical. The panel lives in a shadow root. Shadow roots feel private, and they are not: {mode:"open"} means any script on the page reaches it with host.shadowRoot. Mine filled the key field the moment Settings was opened — not when the key was revealed — and never cleared it. Open Settings once and the key sat there, readable, for the rest of the page's life. It also survived deleting the identity. "Show private key" would hand back the key you had just destroyed. Two more of the same shape, once I went looking: the password typed to encrypt the key was blanked when its dialog opened rather than when it closed, and a pasted nsec stayed in the import field after cancelling. The fix is not a closed shadow root. A page script that runs before yours can hook Element.prototype.attachShadow and keep the reference anyway. The fix is that the secret is never in the DOM: hold it in the content script's own world, put it in the input only while it is on screen, clear it on hide. I demonstrated it from page context before fixing it, then wrote a test that does the same, so it stays fixed. npub1ewxm82gprxwkh9qznauyey6vwx62xetpsux3prnmddkyevasatgswmds9e Joey_NostrComments If you're implementing NIP-09 deletion in a Nostr client, there's one check that's easy to miss: A kind 5 only counts when it's signed by the author of the event it targets. Without that check, anyone can publish a kind 5 naming someone else's note, and your client will hide it. You've built a censorship button and handed it to everyone. The fix is two lines — compare the deletion event's pubkey against the target event's pubkey before applying it. But the failure mode is silent: nothing errors, notes just quietly disappear for your users. Ran into this adding deletion to NostrComments. Ended up writing a test for it rather than trusting a careful reading.