Skip to content

Releases: cameri/nostream

nostream@3.11.0

Choose a tag to compare

@cameri cameri released this 02 Nov 11:42
Immutable release. Only release title and notes can be modified.
f154598

Minor Changes

  • #1030 9b585db Thanks @Ferryx349! - feat(peering): authenticate peer relays with NIP-42 using the relay keypair

    Adds the peering identity layer: a configured peering.peers trust list keyed
    by relay pubkey (hex or npub), 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[].relayUrl is the outbound address only — identity is always the
    verified pubkey, never the URL. Relay key storage and the AUTH exchange are
    documented in docs/PEERING.md, and the new fields are validated with the rest
    of the settings document.

Patch Changes

  • #1024 eb304b8 Thanks @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 searchPath for 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 declares knex 2.4.2 on both the default branch
    and rewrite/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
    tightens RawBinding. The one compile error that surfaced,
    src/scripts/benchmark-queries.ts passing bindings as readonly unknown[] to
    db.raw(), is fixed by dropping the assertion: toSQL().bindings is already the
    accepted type. The emitted JavaScript is byte-identical with and without the
    assertion.

nostream@3.10.2

Choose a tag to compare

@cameri cameri released this 01 Nov 18:30
Immutable release. Only release title and notes can be modified.
00bf76a

Patch Changes

  • #986 3696f3e Thanks @Ferryx349! - fix(admin): Greptile follow-ups for moderation limits and activity stats

    Moderation 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), not event_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 with start greater than end. PATCH and moderation actions return the normalized limits read back from settings after save.

nostream@3.9.0

Choose a tag to compare

@cameri cameri released this 31 Oct 18:59
Immutable release. Only release title and notes can be modified.
54c0187

Minor Changes

  • #999 469f1b1 Thanks @Ferryx349! - feat(peering): advertise peering terms and scopes in NIP-11

    The relay information document now carries a peering section built from the new
    peering settings block. It advertises whether peering is offered, the feeds
    available as kinds scopes with an absolute since/until range or a rolling
    windowSeconds, terms in the existing NIP-11 fee-object shape (amount, unit,
    period) where an empty terms list means settlement-free, a peer contact or
    WebSocket endpoint, and min_pow_difficulty — the NIP-13 work a peering request
    must carry, 0 meaning 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
    all peering.* fields are documented in CONFIGURATION.md with an example document.

    Closes #932.

  • #926 2819e30 Thanks @Priyanshubhartistm! - feat(payments): let clients see their subscription tier

Patch Changes

  • #972 a46ae06 Thanks @chappie-daemon! - fix(compose): keep dynamic container addresses off the static ones

    docker-compose.yml pins nostream, nostream-db and nostream-cache to 10.10.10.2–.4, but dynamic allocation drew from the bottom of the same /24. The observability stack has no dependency that orders it after nostream, so otel-collector could be given .2 and nostream then failed to start with Address 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, so 10.10.10.1 stays the trusted proxy, and the overlay pins (.5, .252, .253) stay outside the pool.

  • #1005 b185f35 Thanks @chappie-daemon! - ci(deploy): retry the website deploy on a transient Cloudflare failure

    Run 40103130697 failed at Deploy the preview worker with Cloudflare API error
    code: 10013 after 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-level timeout-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 dff00ba Thanks @chappie-daemon! - Add canonical first-party production relay setup guide at /docs/setup and replace outdated third-party guides in README.md.

  • #1003 80b0a9e Thanks @chappie-daemon! - test(redis): prove the relay keeps publishing after the cache loses its scripts

    Nothing 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 answering NOSCRIPT. The adapter's retry for that was added in #920 and had only a stubbed unit test: evalSha was mocked, which cannot catch a retry that stops working through the workers that actually call it.

    The new integration scenario publishes, runs SCRIPT FLUSH against the same Redis, then publishes twice more. SCRIPT FLUSH is 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

Choose a tag to compare

@cameri cameri released this 31 Oct 23:02
Immutable release. Only release title and notes can be modified.
bc22677

Patch Changes

  • #1011 fd7679b Thanks @chappie-daemon! - fix(rate-limit): send the backoff the strategy actually means, name an outage as an error, and bucket messages by type

    Three defects on one wire path, from the spike in #1004.

    The EWMA backoff was a half-life, sent as a delay. RateLimit.period is the decay half-life under ewma, so the time until a limit lifts is period · log2(R_new / rate), not period. With rate: 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 returns period · log2(score / rate). It was only accidentally safe before: the hash carried a TTL of exactly period, 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 answers error: rate limiter unavailable and still rejects the event; fail-closed behaviour is unchanged.

    The message bucket ignored the message type. One bucket per client meant a REQ could be refused because its EVENTs 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.hit returns IRateLimiterResult (limited, plus retryAfterMs when the strategy can estimate one) so each strategy answers in its own terms; a caller with no estimate keeps sending the period.

  • #1012 d8095de Thanks @chappie-daemon! - fix(startup): print the banner as text, and let LOG_FORMAT=json opt out of it

    docker compose logs nostream showed 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 one relay starting record 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

Choose a tag to compare

@cameri cameri released this 31 Oct 20:38
Immutable release. Only release title and notes can be modified.
2d62766

Minor Changes

Patch Changes

  • #1009 ad8c5c4 Thanks @chappie-daemon! - build: pin the node base image in every file that names one

    Dockerfile pinned node:24.22.1-alpine by digest, but Dockerfile.railwayapp, Dockerfile.test, docker-compose.yml and test/integration/docker-compose.yml still said node: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 fa54cb0 Thanks @chappie-daemon! - fix(settings): state that rate-limit periods are milliseconds and reject a period that is not one

    Every shipped limit counts in milliseconds — 1000, 60000, 900000, 3600000 — but nothing said so. RateLimit.period is a bare number, so an operator writing period: 60 for a minute got a 60 ms window — and because rate is 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 RateLimit and on every rateLimits block in resources/default-settings.yaml, and validateRateLimitPeriods rejects 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

Choose a tag to compare

@cameri cameri released this 31 Oct 18:59
Immutable release. Only release title and notes can be modified.
54c0187

Patch Changes

  • #1000 dff00ba Thanks @chappie-daemon! - Add canonical first-party production relay setup guide at /docs/setup and replace outdated third-party guides in README.md.

nostream@3.8.0

Choose a tag to compare

@cameri cameri released this 24 Oct 15:42
Immutable release. Only release title and notes can be modified.
75ccb0e

Minor Changes

  • #941 42ed39e Thanks @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 98bd234 Thanks @chappie-daemon! - chore(deps): build and test on Node 24.22.1, which patches the Brotli decoder

    CVE-2026-111497 is an out-of-bounds write in every Brotli decoder up to 1.2.0,
    reachable by anything that decodes a br stream. Node bundles its own decoder,
    so the fix is the Node release: 24.22.1 on the 24 line. .nvmrc and both
    Dockerfile stages move from floating node:24-alpine to a pinned
    node:24.22.1-alpine.

  • #945 062ee00 Thanks @chappie-daemon! - Namespace EWMA rate-limiter keys so they cannot collide with sliding-window keys

    The 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 switching limits.rateLimiter.strategy, therefore left EWMA reading a
    sorted set as a hash — a WRONGTYPE error on every connection and message until the
    old key expired. EWMA now writes under an ewma: prefix, which also keeps both
    strategies' windows intact if the strategy is switched back.

  • #965 ee65257 Thanks @chappie-daemon! - fix(ci): publish legal image tags and keep latest off prereleases

    A changesets relay release is tagged nostream@3.7.0, and @ is not legal in a
    Docker tag, so the type=ref,event=tag rule handed buildx a tag it refused: the
    first live run of the Release workflow failed with invalid tag ...: invalid reference format and 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.Z as well beside it for
    a legacy tag, and latest only 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.md and docs/DEPLOY-RUNBOOK.md no
    longer promise nostream@X.Y.Z as a pinnable image tag, and the runbook now
    names the receiver change that subscribing to release events depends on.

  • #969 8ea15fc Thanks @Ferryx349! - feat(website): add the relay peering launch film and home-page section

  • #967 9c736c8 Thanks @chappie-daemon! - ci(release): allow the image build to be dispatched for an existing tag

    A 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
    with immutable cannot be changed when release is immutable. That left #964's
    release with no image and no way to build one.

    release.yml now accepts workflow_dispatch with a tag input, 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
    of nostream@3.8.0-rc.1 still leaves latest where it is.

nostream@3.7.0

Choose a tag to compare

@cameri cameri released this 24 Oct 12:46
Immutable release. Only release title and notes can be modified.
689e6fa

Minor Changes

  • #895 a52cbfd Thanks @Ferryx349! - Add admin Operations area with mirror and worker heartbeats, NIP-05 verification ops, storage counts, and dashboard view.

  • #920 abd4a1b Thanks @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 7e504e1 Thanks @Priyanshubhartistm! - feat(payments): add the subscription data model and plan settings

  • #909 7b4f627 Thanks @Priyanshubhartistm! - feat(payments): add the subscription lifecycle, renewal invoices and plan-based write access

  • #924 e417437 Thanks @Priyanshubhartistm! - chore: sunset the StaticMirroringWorker

    The static mirroring worker is removed, together with the mirroring.static settings and the mirror health table and /admin/ops/mirrors route of the admin console. A mirroring.static that is still configured is ignored and logs a warning at start-up. To sync events with another relay, use NIP-77 (Negentropy), for example with strfry sync.

Patch Changes

  • #943 17157d5 Thanks @chappie-daemon! - fix(payments): parse the ISO 8601 time/expiry returned by LNbits >= 1.0 so paid admissions are confirmed and expired invoices are retired

  • #944 82124b0 Thanks @chappie-daemon! - fix(ci): stop building and pushing container images outside a release

    Remove 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

Choose a tag to compare

@cameri cameri released this 24 Oct 15:42
Immutable release. Only release title and notes can be modified.
75ccb0e

Patch Changes

  • #965 ee65257 Thanks @chappie-daemon! - fix(ci): publish legal image tags and keep latest off prereleases

    A changesets relay release is tagged nostream@3.7.0, and @ is not legal in a
    Docker tag, so the type=ref,event=tag rule handed buildx a tag it refused: the
    first live run of the Release workflow failed with invalid tag ...: invalid reference format and 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.Z as well beside it for
    a legacy tag, and latest only 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.md and docs/DEPLOY-RUNBOOK.md no
    longer promise nostream@X.Y.Z as a pinnable image tag, and the runbook now
    names the receiver change that subscribing to release events depends on.

  • #969 8ea15fc Thanks @Ferryx349! - feat(website): add the relay peering launch film and home-page section

  • #967 9c736c8 Thanks @chappie-daemon! - ci(release): allow the image build to be dispatched for an existing tag

    A 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
    with immutable cannot be changed when release is immutable. That left #964's
    release with no image and no way to build one.

    release.yml now accepts workflow_dispatch with a tag input, 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
    of nostream@3.8.0-rc.1 still leaves latest where it is.

nostream-website@1.3.2

Choose a tag to compare

@cameri cameri released this 24 Oct 12:46
Immutable release. Only release title and notes can be modified.
689e6fa

Patch Changes

  • #894 02b70cf Thanks @Ferryx349! - Refresh the operator console marketing film with an updated notifications screenshot and re-ship admin.mp4 / admin-poster.webp for the home page.

  • #944 82124b0 Thanks @chappie-daemon! - fix(ci): stop building and pushing container images outside a release

    Remove 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 c40e570 Thanks @Ferryx349! - Add Abhay Pandey Nostr profile link on the community page.