Repository navigation
Releases: cameri/nostream
Release list
nostream@3.11.0
Minor Changes
-
#1030
9b585dbThanks @Ferryx349! - feat(peering): authenticate peer relays with NIP-42 using the relay keypairAdds the peering identity layer: a configured
peering.peerstrust list keyed
by relay pubkey (hex ornpub), outbound NIP-42 AUTH signed with the relay's own
keypair, and inbound recognition of a verified peer pubkey on the connection
state. Peer status is tied to the NIP-42 session, so it expires or clears with
the socket; a valid AUTH from an unconfigured pubkey stays an ordinary client.peering.peers[].relayUrlis the outbound address only — identity is always the
verified pubkey, never the URL. Relay key storage and the AUTH exchange are
documented indocs/PEERING.md, and the new fields are validated with the rest
of the settings document.
Patch Changes
-
#1024
eb304b8Thanks @chappie-daemon! - fix(deps): bump knex to 3.2.9 for the search_path SQL injection (AIKIDO-2026-10469)knex 0.0.1–3.2.8 interpolates values passed to
searchPathfor the Postgres
dialect into generated SQL without validating or escaping them, so a value an
attacker can influence becomes an injected identifier. 3.2.9 escapes the double
quotes (#6411); this repository declaresknex2.4.2 on both the default branch
andrewrite/main.nostream never sets
searchPath—grep -rn "search_path" src/finds nothing —
so the reachable path is not exercised today, and the bump is taken to remove a
vulnerable dependency from the image, not to close a reachable hole.knex 3.0.0 drops Node < 16, which the repo already requires (>=24.14.1), and 3.2.0
tightensRawBinding. The one compile error that surfaced,
src/scripts/benchmark-queries.tspassingbindings as readonly unknown[]to
db.raw(), is fixed by dropping the assertion:toSQL().bindingsis already the
accepted type. The emitted JavaScript is byte-identical with and without the
assertion.
nostream@3.10.2
Patch Changes
-
#986
3696f3eThanks @Ferryx349! - fix(admin): Greptile follow-ups for moderation limits and activity statsModeration limit PATCH and ban/unban actions could interleave read–update–save across workers and drop updates. Those paths now run under an async settings write lock that yields while waiting and reclaims stale locks without deleting a fresh holder’s file. Activity stats windows filter on
first_seen(when the relay first saw the event), notevent_created_at, with partial indexes on live rows for those queries. IPv6 addresses on the rate-limit bypass list are normalized the same way as connection IPs so bypass rules cannot be skipped by formatting, including when removing entries stored in another spelling. PATCH rejects kind ranges withstartgreater thanend. PATCH and moderation actions return the normalized limits read back from settings after save.
nostream@3.9.0
Minor Changes
-
#999
469f1b1Thanks @Ferryx349! - feat(peering): advertise peering terms and scopes in NIP-11The relay information document now carries a
peeringsection built from the new
peeringsettings block. It advertises whether peering is offered, the feeds
available askindsscopes with an absolutesince/untilrange or a rolling
windowSeconds, terms in the existing NIP-11 fee-object shape (amount,unit,
period) where an emptytermslist means settlement-free, a peercontactor
WebSocketendpoint, andmin_pow_difficulty— the NIP-13 work a peering request
must carry,0meaning none.Disabled peering publishes only
{ "enabled": false }, so half-configured terms are
never read as an accepted peer: this is the discovery surface other relays read
before connecting, not a peer-acceptance path. Settings validation rejects invalid
scopes, terms, time ranges, endpoints and proof-of-work bounds with a clear path, and
allpeering.*fields are documented inCONFIGURATION.mdwith an example document.Closes #932.
-
#926
2819e30Thanks @Priyanshubhartistm! - feat(payments): let clients see their subscription tier
Patch Changes
-
#972
a46ae06Thanks @chappie-daemon! - fix(compose): keep dynamic container addresses off the static onesdocker-compose.ymlpinsnostream,nostream-dbandnostream-cacheto10.10.10.2–.4, but dynamic allocation drew from the bottom of the same/24. The observability stack has no dependency that orders it afternostream, sootel-collectorcould be given.2andnostreamthen failed to start withAddress already in use— most reliably after a host reboot, when the daemon starts containers in arbitrary order.Dynamic allocation is now confined to
10.10.10.128/26. The subnet and gateway are unchanged, so10.10.10.1stays the trusted proxy, and the overlay pins (.5,.252,.253) stay outside the pool. -
#1005
b185f35Thanks @chappie-daemon! - ci(deploy): retry the website deploy on a transient Cloudflare failureRun 40103130697 failed at
Deploy the preview workerwith Cloudflare API error
code: 10013after a build that had already succeeded, and the next push deployed
the same commit successfully — a transient upstream failure, not a defect in the
site or the workflow.Both website deploys now run through
.github/scripts/deploy-with-retry.sh, which
wraps the deploy step (not the build) in three attempts with a 10 s then 30 s
backoff, a per-attempt timeout, and a step-leveltimeout-minutes. It retries only
code: 10013, an HTTP 5xx, a connection reset and an expired attempt timeout; an
authentication failure, a missing environment or a wrangler validation error still
fails on the first attempt. Every retry emits a::warning::naming the attempt and
the reason, so the flake rate stays visible instead of disappearing behind a
passing job. -
#1000
dff00baThanks @chappie-daemon! - Add canonical first-party production relay setup guide at/docs/setupand replace outdated third-party guides inREADME.md. -
#1003
80b0a9eThanks @chappie-daemon! - test(redis): prove the relay keeps publishing after the cache loses its scriptsNothing is shipped in this change — it is coverage for #1002. The relay issues
EVALSHA, and Lua scripts live in the cache's memory, so a Redis restart leaves every script call answeringNOSCRIPT. The adapter's retry for that was added in #920 and had only a stubbed unit test:evalShawas mocked, which cannot catch a retry that stops working through the workers that actually call it.The new integration scenario publishes, runs
SCRIPT FLUSHagainst the same Redis, then publishes twice more.SCRIPT FLUSHis the whole of a restart as far as the relay can tell, and it is deterministic — no container to restart and no reconnect to wait for. The scenario configures a message rate limit on purpose, because both limiters return early on an empty list and a scenario without one would pass whether or not the fallback existed.
nostream@3.10.1
Patch Changes
-
#1011
fd7679bThanks @chappie-daemon! - fix(rate-limit): send the backoff the strategy actually means, name an outage as an error, and bucket messages by typeThree defects on one wire path, from the spike in #1004.
The EWMA backoff was a half-life, sent as a delay.
RateLimit.periodis the decay half-life underewma, so the time until a limit lifts isperiod · log2(R_new / rate), notperiod. Withrate: 6, period: 60000, a client four times over the limit was told to wait 60 s and needed 120 s. The Lua script now returns its score instead of a yes/no, and the limiter returnsperiod · log2(score / rate). It was only accidentally safe before: the hash carried a TTL of exactlyperiod, so a quiet client's state expired anyway — an unstated coupling that would break the moment anyone lengthened the TTL to keep state warm.A limiter outage was announced as
rate-limited. A failing backend made every connected client back off for a fault on this side. It now answerserror: rate limiter unavailableand still rejects the event; fail-closed behaviour is unchanged.The message bucket ignored the message type. One bucket per client meant a
REQcould be refused because itsEVENTs had filled it — the wrong message throttled, and the wrong one told to back off. The bucket key now carries the type, read from a bounded prefix of the frame because the limiter runs before validation.IRateLimiter.hitreturnsIRateLimiterResult(limited, plusretryAfterMswhen the strategy can estimate one) so each strategy answers in its own terms; a caller with no estimate keeps sending the period. -
#1012
d8095deThanks @chappie-daemon! - fix(startup): print the banner as text, and letLOG_FORMAT=jsonopt out of itdocker compose logs nostreamshowed the logo as a single log record with the newlines escaped. The banner and its centred facts now go to stdout as text.A pipeline that requires every line to parse sets
LOG_FORMAT=json: the logo is then replaced by onerelay startingrecord carrying the same facts as fields (version,supportedNips,payments,eventStore,clientWorkers, …). Any other value keeps the banner, which is what a person reading the logs wants.
nostream@3.10.0
Minor Changes
- #922
fcb33deThanks @Priyanshubhartistm! - feat(payments): add per-plan event rate limits and retention
Patch Changes
-
#1009
ad8c5c4Thanks @chappie-daemon! - build: pin the node base image in every file that names oneDockerfilepinnednode:24.22.1-alpineby digest, butDockerfile.railwayapp,Dockerfile.test,docker-compose.ymlandtest/integration/docker-compose.ymlstill saidnode:24-alpine. The relay image, the CI image, the Railway image and both migration containers could each start from a different base, and a floating tag moves under a release — so the same commit built twice could run different node code without a change saying so.All five now carry the same tag and digest, and a unit test holds them there: it fails if any reference loses its digest, and it fails if two different references appear.
-
#1010
fa54cb0Thanks @chappie-daemon! - fix(settings): state that rate-limit periods are milliseconds and reject a period that is not oneEvery shipped limit counts in milliseconds —
1000,60000,900000,3600000— but nothing said so.RateLimit.periodis a barenumber, so an operator writingperiod: 60for a minute got a 60 ms window — and becauserateis hits per window, that is a limit a thousand times looser than intended, honoured without complaint, and caught by nothing.The unit is now stated on
RateLimitand on everyrateLimitsblock inresources/default-settings.yaml, andvalidateRateLimitPeriodsrejects what cannot be a count of milliseconds: a fraction, a string, a non-number, or anything below one millisecond. A whole number is not enough to tell 60 from 60000, which is why the unit is documented where it is set.
nostream-website@1.3.4
Patch Changes
- #1000
dff00baThanks @chappie-daemon! - Add canonical first-party production relay setup guide at/docs/setupand replace outdated third-party guides inREADME.md.
nostream@3.8.0
Minor Changes
-
#941
42ed39eThanks @Ferryx349! - feat(admin): moderation tools for pubkey, kind, and IP limits (closes #634)Adds admin API and console view to read and update event pubkey/kind lists, rate-limit bypass lists, quick ban/unban actions, and activity stats for top publishers, client IPs, and rate-limit hits.
Patch Changes
-
#968
98bd234Thanks @chappie-daemon! - chore(deps): build and test on Node 24.22.1, which patches the Brotli decoderCVE-2026-111497 is an out-of-bounds write in every Brotli decoder up to 1.2.0,
reachable by anything that decodes abrstream. Node bundles its own decoder,
so the fix is the Node release: 24.22.1 on the 24 line..nvmrcand both
Dockerfile stages move from floatingnode:24-alpineto a pinned
node:24.22.1-alpine. -
#945
062ee00Thanks @chappie-daemon! - Namespace EWMA rate-limiter keys so they cannot collide with sliding-window keysThe two strategies store different Redis types under the same key name: a sliding
window keeps a sorted set, EWMA keeps a hash. A cache volume carried over from 2.x,
or an operator switchinglimits.rateLimiter.strategy, therefore left EWMA reading a
sorted set as a hash — aWRONGTYPEerror on every connection and message until the
old key expired. EWMA now writes under anewma:prefix, which also keeps both
strategies' windows intact if the strategy is switched back. -
#965
ee65257Thanks @chappie-daemon! - fix(ci): publish legal image tags and keeplatestoff prereleasesA changesets relay release is tagged
nostream@3.7.0, and@is not legal in a
Docker tag, so thetype=ref,event=tagrule handed buildx a tag it refused: the
first live run of the Release workflow failed withinvalid tag ...: invalid reference formatand published no image for the release (#964).The tags are now computed by a shell step, so every tag is a value this
repository chose and can check: the bare version,vX.Y.Zas well beside it for
a legacy tag, andlatestonly when the release is not a prerelease. Each tag is
validated against Docker's grammar before the build, so an illegal one fails by
name at that step instead of at buildx.docs/CI-MIGRATION.md,docs/DEPLOYMENT.mdanddocs/DEPLOY-RUNBOOK.mdno
longer promisenostream@X.Y.Zas a pinnable image tag, and the runbook now
names the receiver change that subscribing toreleaseevents depends on. -
#969
8ea15fcThanks @Ferryx349! - feat(website): add the relay peering launch film and home-page section -
#967
9c736c8Thanks @chappie-daemon! - ci(release): allow the image build to be dispatched for an existing tagA published release is immutable, so when the image build fails while publishing
it, the release cannot be re-published to retry: GitHub refuses the round trip
withimmutable cannot be changed when release is immutable. That left #964's
release with no image and no way to build one.release.ymlnow acceptsworkflow_dispatchwith ataginput, and refuses a
tag that is not a relay release, since the event carries no tag of its own. With
no release flags present the prerelease is read from the version, so a dispatch
ofnostream@3.8.0-rc.1still leaveslatestwhere it is.
nostream@3.7.0
Minor Changes
-
#895
a52cbfdThanks @Ferryx349! - Add admin Operations area with mirror and worker heartbeats, NIP-05 verification ops, storage counts, and dashboard view. -
#920
abd4a1bThanks @Ferryx349! - Add admin trust and automation: Redis-backed audit log API and UI, signed HTTP webhooks for probe/mirror/SSL events, OpenTelemetry probe and mirror metrics, and redacted diagnostics export. -
#849
7e504e1Thanks @Priyanshubhartistm! - feat(payments): add the subscription data model and plan settings -
#909
7b4f627Thanks @Priyanshubhartistm! - feat(payments): add the subscription lifecycle, renewal invoices and plan-based write access -
#924
e417437Thanks @Priyanshubhartistm! - chore: sunset the StaticMirroringWorkerThe static mirroring worker is removed, together with the
mirroring.staticsettings and the mirror health table and/admin/ops/mirrorsroute of the admin console. Amirroring.staticthat is still configured is ignored and logs a warning at start-up. To sync events with another relay, use NIP-77 (Negentropy), for example withstrfry sync.
Patch Changes
-
#943
17157d5Thanks @chappie-daemon! - fix(payments): parse the ISO 8601time/expiryreturned by LNbits >= 1.0 so paid admissions are confirmed and expired invoices are retired -
#944
82124b0Thanks @chappie-daemon! - fix(ci): stop building and pushing container images outside a releaseRemove the container publish job from checks.yml so pushes to main no longer publish
images to GHCR. Container image builds and pushes are now restricted exclusively to
release.yml on published releases. Update deployment documentation and reference compose
stacks to target the released image tag (:latest) instead of :main.
nostream-website@1.3.3
Patch Changes
-
#965
ee65257Thanks @chappie-daemon! - fix(ci): publish legal image tags and keeplatestoff prereleasesA changesets relay release is tagged
nostream@3.7.0, and@is not legal in a
Docker tag, so thetype=ref,event=tagrule handed buildx a tag it refused: the
first live run of the Release workflow failed withinvalid tag ...: invalid reference formatand published no image for the release (#964).The tags are now computed by a shell step, so every tag is a value this
repository chose and can check: the bare version,vX.Y.Zas well beside it for
a legacy tag, andlatestonly when the release is not a prerelease. Each tag is
validated against Docker's grammar before the build, so an illegal one fails by
name at that step instead of at buildx.docs/CI-MIGRATION.md,docs/DEPLOYMENT.mdanddocs/DEPLOY-RUNBOOK.mdno
longer promisenostream@X.Y.Zas a pinnable image tag, and the runbook now
names the receiver change that subscribing toreleaseevents depends on. -
#969
8ea15fcThanks @Ferryx349! - feat(website): add the relay peering launch film and home-page section -
#967
9c736c8Thanks @chappie-daemon! - ci(release): allow the image build to be dispatched for an existing tagA published release is immutable, so when the image build fails while publishing
it, the release cannot be re-published to retry: GitHub refuses the round trip
withimmutable cannot be changed when release is immutable. That left #964's
release with no image and no way to build one.release.ymlnow acceptsworkflow_dispatchwith ataginput, and refuses a
tag that is not a relay release, since the event carries no tag of its own. With
no release flags present the prerelease is read from the version, so a dispatch
ofnostream@3.8.0-rc.1still leaveslatestwhere it is.
nostream-website@1.3.2
Patch Changes
-
#894
02b70cfThanks @Ferryx349! - Refresh the operator console marketing film with an updated notifications screenshot and re-shipadmin.mp4/admin-poster.webpfor the home page. -
#944
82124b0Thanks @chappie-daemon! - fix(ci): stop building and pushing container images outside a releaseRemove the container publish job from checks.yml so pushes to main no longer publish
images to GHCR. Container image builds and pushes are now restricted exclusively to
release.yml on published releases. Update deployment documentation and reference compose
stacks to target the released image tag (:latest) instead of :main. -
#916
c40e570Thanks @Ferryx349! - Add Abhay Pandey Nostr profile link on the community page.