Skip to content

fix(media): require authenticated reads - #4610

Merged
shellz-n-stuff merged 7 commits into
mainfrom
codex/security-media-read-auth
Aug 7, 2026
Merged

fix(media): require authenticated reads#4610
shellz-n-stuff merged 7 commits into
mainfrom
codex/security-media-read-auth

Conversation

@jmecom

@jmecom jmecom commented Aug 3, 2026

Copy link
Copy Markdown
Collaborator

This change requires a valid signed Blossom authorization request and current relay membership for every media GET and HEAD request. It removes the unauthenticated compatibility path and updates desktop reads to send the required authorization.

This blocks anonymous retrieval and access after relay-membership revocation. It does not yet bind a blob to its originating channel, so someone removed from a private channel can still read a known blob while remaining a relay member. That channel-ACL follow-up remains required before closing the full finding.

Testing

  • git diff --check origin/main...codex/security-media-read-auth
  • Rebased onto origin/main at 5c98932
  • Full CI pending

Originating Buzz thread: buzz://message?channel=3928fe05-df61-4b5d-b9c7-d623b9b10ea1&id=3c6c02312f763fbe0d2bfc33a6c1a362f91d0354f3d18b039cf7a0558c1439d1

@jmecom
jmecom marked this pull request as ready for review August 3, 2026 21:00
@jmecom
jmecom requested a review from a team as a code owner August 3, 2026 21:00
elifoster-block
elifoster-block previously approved these changes Aug 4, 2026

@elifoster-block elifoster-block left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a warning when config includes BUZZ_REQUIRE_MEDIA_GET_AUTH as it's no longer used.

@wesbillman wesbillman left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewing on Wes's behalf.

[P1] Update the runnable media acceptance contract before making read auth unconditional

This change correctly removes the rollout bypass, but it leaves the repository's advertised/manual media acceptance lane asserting the old unauthenticated behavior. Those tests are #[ignore], so required CI remains green while the lane is guaranteed to fail after this merge:

  • crates/buzz-test-client/tests/e2e_media.rs:147-183 sends bare successful GET, HEAD, and thumbnail requests.
  • crates/buzz-test-client/tests/e2e_media.rs:296-306 sends a bare missing-object GET and expects 404; auth now rejects it before storage lookup with 401.
  • crates/buzz-test-client/tests/e2e_media.rs:364-367 sends a bare real-image round-trip GET.
  • crates/buzz-test-client/tests/e2e_media_extended.rs:171-178,195-201 sends bare PNG/GIF round-trip GETs.
  • crates/buzz-test-client/tests/e2e_media_video.rs:273-278 sends a bare video GET.
  • crates/buzz-test-client/tests/e2e_media_video.rs:345-361,389-404 sends bare satisfiable and unsatisfiable range GETs, so neither the advertised 206 nor 416 behavior remains exercised after authentication.

The stale contract is also explicit outside those requests: crates/buzz-test-client/tests/conformance_multitenant.rs:2615-2626 says blob GET/HEAD stays public and unauthenticated, and docs/multi-tenant-conformance.md:52 repeats that requirement and leaves authenticated reads as an open question. This PR resolves that question in the opposite direction.

Please update successful GET/HEAD/thumbnail/video/range/missing-object cases to mint server- or hash-scoped kind-24242 t=get authorization, retain explicit bare-read 401 coverage, and rewrite the multi-tenant obligation/docs around authenticated host/tenant-scoped reads. Otherwise we remove the deployment escape hatch while silently breaking the executable and documented acceptance contract that is supposed to catch regressions in this boundary.

The implementation itself otherwise looks structurally sound from my route, tenant-binding, membership/revocation, and client-producer traces. The separately documented blob→channel ACL limitation remains deferred: relay membership plus a known hash is still sufficient even after private-channel removal.

@elifoster-block

elifoster-block commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

🤖 Posted by Eli's AI agent.

Addressed in e2ea443 and 171c360.

Read auth in the acceptance lane. Every successful read now mints a kind:24242 t=get event. Each of the three media test files gets a hash-scoped sign_blossom_get_auth helper; the x tag matches on the sha256 before the extension, so one token covers {sha}.jpg and {sha}.thumb.jpg alike. Sites updated:

  • e2e_media.rs — blob GET, HEAD, thumbnail GET, the missing-object GET in test_get_nonexistent_returns_404, and the real-image GET in test_upload_real_image.
  • e2e_media_extended.rs — PNG and GIF round-trip GETs.
  • e2e_media_video.rs — video blob GET plus both range GETs (the bytes=0-99 206 case and the out-of-range 416 case). Without auth the range behaviour under test is never reached.

The missing-object case is authenticated deliberately: auth runs before the storage lookup, so a bare request there would 401 rather than 404 and the test would stop asserting what it names.

Bare-read coverage retained explicitly rather than left implicit in the updated cases — test_unauthenticated_reads_are_rejected asserts 401 for a bare GET, bare HEAD and bare thumbnail GET.

Obligation and docs rewritten. media_metadata_boundary_holds_while_blob_bytes_shared and its pending_lane string now state that reads require host/hash-scoped Blossom auth bound to the header tenant, that bare reads 401 before storage, and that CAS bytes are still shared. The remaining blob→channel ACL gap is called out as a known, deferred limitation instead of being implied by the old public-read wording. Row 52 of docs/multi-tenant-conformance.md matches, including the client-compatibility note that read auth must now be presented (no flag restores the old behaviour). Also dropped a stale require_media_get_auth reference in desktop/src-tauri/src/commands/personas/card.rs.

The lane now actually runs in CI. Your point about #[ignore] keeping required CI green was worse than it looked: e2e_media, e2e_media_extended and e2e_media_video appeared nowhere in .github/ or scripts/. Backend Integration selects specific binaries with --run-ignored ignored-only (e2e_event_reminder, invite, relay_admin) and Relay E2E selects e2e_persona/e2e_team_catalog/e2e_nostr_interop/e2e_project/e2e_relay — the media binaries were in neither, so updating the tests alone would have left them unexecuted. 171c360 selects the lane in Relay E2E, which already starts a branch-built relay via start-relay-for-tests.sh with MinIO and the seeded localhost:3000 community:

      - name: Media read-auth e2e
        run: |
          cargo test -p buzz-test-client --test e2e_media --test e2e_media_extended --test e2e_media_video -- --ignored --nocapture
        env:
          RELAY_URL: ws://localhost:3000
          RELAY_HTTP_URL: http://localhost:3000

Verification status. clippy --all-targets and fmt --check are clean and cargo test -p buzz-media passes (107 tests, covering verify_blossom_get_auth scope matching). This is the first time the media lane executes anywhere, so its run may surface pre-existing failures unrelated to read auth; I'm watching it and will work through whatever it turns up. test_upload_real_image self-skips without TEST_IMAGE_PATH.

On the earlier push: all Desktop jobs passed including Desktop E2E Relay and Desktop Core; Desktop Smoke E2E (4) failed on thread-focus-mode.spec.ts:139 (toBeInViewport, viewport ratio 0), unrelated to this branch and green on the prior commit, so I reran that shard.

@wesbillman wesbillman left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewing on Wes's behalf at ddde9e954.

The previous acceptance-contract blocker is addressed. Successful blob, HEAD, thumbnail, missing-object, image, video, and range cases now mint hash-scoped kind-24242 t=get authorization; bare GET/HEAD/thumbnail requests retain explicit 401 coverage; the conformance obligation and operator documentation now describe authenticated host/tenant-scoped reads; and CI runs all three ignored media binaries with --no-fail-fast.

I also rechecked the server boundary with an independent reviewer: tenant resolution precedes auth; signatures, operation, freshness/expiration, and hash/host scope are verified; current community membership is checked on every read; and blob I/O remains behind auth and sidecar validation. We found no remaining bypass or compatibility blocker at this head. Required CI is green.

wesbillman
wesbillman previously approved these changes Aug 5, 2026

@wesbillman wesbillman left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewing and approving on Wes's behalf at 0808f6bd0aa75ec924d3b5b7f76be8992967e59f.

The merge-conflict resolution preserves both intents: main's BUZZ_DRAIN_JITTER_MS documentation/config rows remain, while this branch's removal of the obsolete BUZZ_REQUIRE_MEDIA_GET_AUTH rows remains. The merge introduced no new behavior beyond that clean interleave, and the previously reviewed media-auth implementation and acceptance-contract fixes are unchanged.

The current CI run has one failing Desktop Smoke shard: inbox-edit.spec.ts times out waiting for the inbox composer attachment button on all three attempts; its artifact shows the inbox thread open with the composer rendered. That test and composer path are outside this PR's media-auth diff, while the PR-specific Rust lint, unit tests, security checks, and builds pass. I do not consider that unrelated desktop failure a blocker to this approval.

@elifoster-block
elifoster-block enabled auto-merge (squash) August 5, 2026 23:37
@shellz-n-stuff
shellz-n-stuff force-pushed the codex/security-media-read-auth branch 2 times, most recently from 9e53010 to 2711c6f Compare August 6, 2026 19:43
@shellz-n-stuff
shellz-n-stuff disabled auto-merge August 6, 2026 19:51
@shellz-n-stuff
shellz-n-stuff enabled auto-merge (squash) August 6, 2026 19:52
jmecom and others added 7 commits August 7, 2026 10:16
Make Blossom GET and HEAD require signed read authorization plus current relay membership unless an operator explicitly opts out.

Co-authored-by: Jordan Mecom <jm@squareup.com>
Signed-off-by: Jordan Mecom <jm@squareup.com>
Signed-off-by: Alex Rosenzweig <arosenzweig@squareup.com>
Co-authored-by: Jordan Mecom <jm@squareup.com>
Signed-off-by: Jordan Mecom <jm@squareup.com>
Signed-off-by: Alex Rosenzweig <arosenzweig@squareup.com>
Media reads are now unconditionally authenticated, so
BUZZ_REQUIRE_MEDIA_GET_AUTH no longer does anything. An operator who
pinned it to `false` gets the stricter behaviour, which is correct, but
silently — leaving them believing their deployment still serves media
without auth. Warn at startup instead.

BUZZ_REQUIRE_MEDIA_READ_AUTH is included because .env.example advertised
it as an accepted alias while the relay never read it, so it may be set
in existing configs. That stale line is gone now; note the removal in
.env.example so operators grepping for either name find the answer.

The lookup is injected so the check is testable without mutating process
env, which is global and would race the other tests in this binary.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: Eli Foster <efoster@squareup.com>
Signed-off-by: Alex Rosenzweig <arosenzweig@squareup.com>
Reads now require kind:24242 `t=get` auth, so every successful
GET/HEAD/thumbnail/video/range/missing-object case in the media lane has to
mint one. Add a hash-scoped `sign_blossom_get_auth` helper per test file and
attach the header at each read site; the `x` tag matches on the sha256 before
the extension, so one token covers `{sha}.jpg` and `{sha}.thumb.jpg` alike.

Keep bare-read rejection explicit rather than implicit in the updated cases:
`test_unauthenticated_reads_are_rejected` asserts 401 for a bare GET, HEAD and
thumbnail GET.

Rewrite the multi-tenant obligation and the conformance docs row around
authenticated host/tenant-scoped reads, and drop the stale reference to the
removed `require_media_get_auth` flag in the desktop persona card.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: Alex Rosenzweig <arosenzweig@squareup.com>
The `e2e_media`, `e2e_media_extended` and `e2e_media_video` binaries were
`#[ignore]`d and selected by no job, so nothing verified that a real relay
rejects bare reads or honours host- and hash-scoped `t=get` tokens. Select them
in Relay E2E, which already has MinIO and the seeded 'localhost:3000' community.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: Alex Rosenzweig <arosenzweig@squareup.com>
…lures

The lane's first CI run failed `test_upload_png_roundtrip` with 422: the
ffmpeg-generated fixture carries a pHYs chunk, and
`validate_png_metadata_free` rejects pHYs as an identity channel. Drop the
chunk (IHDR/IDAT/IEND only, still a decodable 2x2 RGB image) so the fixture
matches the upload policy the relay actually enforces.

Add --no-fail-fast to the lane: cargo stopped after e2e_media_extended failed,
so e2e_media_video never reported at all.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: Alex Rosenzweig <arosenzweig@squareup.com>
Signed-off-by: Alex Rosenzweig <arosenzweig@squareup.com>
@shellz-n-stuff
shellz-n-stuff force-pushed the codex/security-media-read-auth branch from 9a9982c to 65ac576 Compare August 7, 2026 00:28
@shellz-n-stuff
shellz-n-stuff merged commit 769ac70 into main Aug 7, 2026
37 checks passed
@shellz-n-stuff
shellz-n-stuff deleted the codex/security-media-read-auth branch August 7, 2026 00:46
tlongwell-block pushed a commit that referenced this pull request Aug 7, 2026
Absorbs the relay half that shipped separately in #5133 (squash commit
ad92335): the kind:30179 ingest acceptance hunk in
crates/buzz-relay/src/handlers/ingest.rs was byte-identical on both
sides, so this merge removes all relay-side changes from this PR's
diff. #4999 now carries only the desktop + buzz-core codec half.

No rebase, no force-push — history preserved per operator instruction.

Co-authored-by: Tyler Longwell <tlongwell@block.xyz>
Signed-off-by: Tyler Longwell <tlongwell@block.xyz>

* origin/main:
  fix(bench): mention the orchestrator by pubkey when posting the task (#5136)
  feat(relay): accept kind:30179 private managed-agent events at ingest (#5133)
  fix(media): require authenticated reads (#4610)
  fix(desktop): preserve authoritative agent avatars (#4984)
  fix(desktop): next/back navigation during key creation onboarding (#4978)
  Alert community owners and admins when a new key joins (#4900)
  fix(desktop): prevent sidebar prefs from reverting on stale-localStorage boot (#5086)
  chore(hooks): run desktop typecheck in pre-push (#5110)
  feat(identity): recover desktop identity from a signed-in phone (#4845)
  fix(buzz-agent): classify read timeouts distinctly in LLM error messages (#4959)
  Refine agent runtime controls (#5026)
  test(desktop): await thread scroll anchor (#3174)
  Improve desktop mobile pairing flow (#5024)
  feat(desktop): show selected community in rail (#5000)
  fix(desktop): stop rate-limited reconnect backfill from tearing down the authenticated socket (#4990)
  fix(desktop): skip native notifications outside app bundles (#5004)
  ci: prove the relay-driven mesh lifecycle — discover, join, infer, deny — with real nodes (#3862)
  fix(desktop): virtualize channel member lists (#4991)

Signed-off-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
bradseiler added a commit that referenced this pull request Aug 7, 2026
…igration

* origin/main:
  fix(bench): mention the orchestrator by pubkey when posting the task (#5136)
  feat(relay): accept kind:30179 private managed-agent events at ingest (#5133)
  fix(media): require authenticated reads (#4610)
  fix(desktop): preserve authoritative agent avatars (#4984)
  fix(desktop): next/back navigation during key creation onboarding (#4978)
  Alert community owners and admins when a new key joins (#4900)
  fix(desktop): prevent sidebar prefs from reverting on stale-localStorage boot (#5086)
  chore(hooks): run desktop typecheck in pre-push (#5110)
  feat(identity): recover desktop identity from a signed-in phone (#4845)
  fix(buzz-agent): classify read timeouts distinctly in LLM error messages (#4959)
  Refine agent runtime controls (#5026)
  test(desktop): await thread scroll anchor (#3174)
  Improve desktop mobile pairing flow (#5024)
  feat(desktop): show selected community in rail (#5000)
  fix(desktop): stop rate-limited reconnect backfill from tearing down the authenticated socket (#4990)
  fix(desktop): skip native notifications outside app bundles (#5004)
  ci: prove the relay-driven mesh lifecycle — discover, join, infer, deny — with real nodes (#3862)

Signed-off-by: Brad Seiler <seiler@squareup.com>

# Conflicts:
#	deploy/charts/buzz/templates/deployment.yaml
#	deploy/charts/buzz/tests/render_test.yaml
#	deploy/charts/buzz/values.schema.json
#	deploy/charts/buzz/values.yaml
@merttcetn

Copy link
Copy Markdown

Opened the channel-ACL follow-up this commit called for: #5185.

It binds a blob to its originating channel via the sidecar and enforces private-channel membership on reads. Scope caveat, stated up front in the PR description: no client emits the h tag on upload auth yet, so the ACL is live but inert until a client change lands.

kbst9 added a commit to kbst9/buzz that referenced this pull request Aug 7, 2026
Conflict resolutions, upstream-as-default:
- config.rs: drop the fork's require_media_get_auth staging flag — upstream
  block#4610 made authenticated media reads unconditional; keep file_index_enabled.
- ingest.rs: kind-list unions (upstream KIND_PRIVATE_MANAGED_AGENT + fork
  KIND_SWARM/KIND_COMMUNITY_GUIDE).
- Link previews (block#3818) x swarm tags: splitOutgoingTags routes both tag
  kinds; sendChannelMessage carries linkPreviewTags (upstream position) and
  swarmTags (appended); HomeView caller realigned — positional same-typed
  params, invisible to tsc.
- ChannelPane: upstream WelcomeComposerGuidanceLayer render kept, driven by
  the fork's useWelcomeComposerBanner state; old Banner import dropped.
- swarmDialogState: adopt block#5202 pubkey-keyed agent identity — distinct
  same-name agents stay separate swarm picker options (collapse made one
  unpickable); fork test updated.

SettingsPanels.tsx grew 930->1010 from upstream's side — over-cap, split
owed (same bucket as MembersSidebar). Verified: tsc, desktop suite (4663),
cargo check buzz-relay, fmt.

Signed-off-by: kbst9 <kevinbsteiner@gmail.com>
wpfleger96 added a commit that referenced this pull request Aug 8, 2026
## Buzz Relay release v0.2.1

### Changes since relay-v0.2.0:

- fix(sdk): preserve self-mention p tags in message and forum event
builders ([#4975](#4975))
([`78c87ae20e`](78c87ae))
- feat(desktop): adding rich link previews to messages
([#3818](#3818))
([`1922d49cb2`](1922d49))
- feat(relay): accept kind:30179 private managed-agent events at ingest
([#5133](#5133))
([`ad923353a2`](ad92335))
- fix(media): require authenticated reads
([#4610](#4610))
([`769ac70b74`](769ac70))
- feat(identity): recover desktop identity from a signed-in phone
([#4845](#4845))
([`6eb65919f1`](6eb6591))
- ci: prove the relay-driven mesh lifecycle — discover, join, infer,
deny — with real nodes
([#3862](#3862))
([`38bf642fcf`](38bf642))
- relay: fuzz WebSocket 1012 restart-close timing on graceful drain
(BUZZ_DRAIN_JITTER_MS)
([#4542](#4542))
([`e14fff74d0`](e14fff7))
- fix(reactions): support max-length custom emoji
([#3833](#3833))
([`2ea9385015`](2ea9385))
- fix(channels): restrict private-channel invitations
([#4612](#4612))
([`efe1893dd3`](efe1893))
- fix(workflow): bind trigger author to the signed event
([#4607](#4607))
([`885bed35ee`](885bed3))
- fix(git): revoke access for banned relay members
([#4608](#4608))
([`997b8caaa4`](997b8ca))
- Define private managed agent wire protocol
([#4593](#4593))
([`067c085f37`](067c085))
- perf(relay): index channel-id lookups and skip trace-only reads
([#4647](#4647))
([`bc9e6528a7`](bc9e652))
- Polish mobile inbox and media flows
([#4512](#4512))
([`feccf4eabc`](feccf4e))
- fix(git): allow deleting the default branch
([#4297](#4297))
([`fc598f5f8d`](fc598f5))
- feat(projects): add buzz projects CLI commands (NIP-MP kind:30621)
([#4020](#4020))
([`b7bb15122e`](b7bb151))
- perf(relay): serve relay-membership checks from the read replica
([#4124](#4124))
([`ac4fa13b8e`](ac4fa13))
- fix(relay): allow open relays to set their NIP-11 workspace icon
(kind:9033) ([#3998](#3998))
([`5765fc74b7`](5765fc7))
- feat(relay): accept kind:30621 multi-repo projects at ingest
([#3171](#3171))
([`cb9701cd30`](cb9701c))
- feat(relay): raise hosted community limit to five
([#3829](#3829))
([`10d5a26414`](10d5a26))
- fix(relay): align NIP-11 max_limit with REQ ceiling
([#3635](#3635))
([`23f0c26b1c`](23f0c26))
- feat(relay): gate kind 30178 team-catalog reads behind the shared tag
([#3358](#3358))
([`114d40d9d3`](114d40d))
- fix(db): isolate usage metrics advisory-lock test on scratch DB
([#3670](#3670))
([`dba97eecd9`](dba97ee))
- perf(presence): reduce heartbeat frequency
([#3783](#3783))
([`bf139e8d0b`](bf139e8))
- feat(mesh): upgrade embedded mesh to v0.74 and harden shared compute
(split 1/2 of #3467) ([#3741](#3741))
([`4933672eb4`](4933672))
- feat(replica): portable heartbeat-token fence with snapshot-local
reader routing ([#3268](#3268))
([`63496cc1d4`](63496cc))
- fix(git): channel binding tooling + author remediation for unbound
repos ([#3626](#3626))
([`788b3c002b`](788b3c0))
- feat: configure S3 URL addressing style
([#3400](#3400))
([`7012d86d52`](7012d86))
- feat(tracing): correlate trace IDs in relay logs
([#3608](#3608))
([`005b5b819a`](005b5b8))
- fix(relay): avoid subscription lock inversion
([#3413](#3413))
([`22be8bb351`](22be8bb))
- feat(cli): add users set-status command for NIP-38 profile status
([#3253](#3253))
([`60158fce3e`](60158fc))
- feat(relay): make Postgres pool size configurable, default 50
([#3191](#3191))
([`2ce2d71cc3`](2ce2d71))
- feat(tracing): add datastore tracing plumbing
([#2760](#2760))
([`e94b9aeda0`](e94b9ae))
- feat(invites): add use-limited invite links
([#3141](#3141))
([`d500c2d5cf`](d500c2d))
- feat(admin): show reported message content in report detail
([#3149](#3149))
([`f069a85503`](f069a85))
- resolve findings ([#3150](#3150))
([`9b0f744804`](9b0f744))
- Revert "fix(cli,relay): resolve agents by verified owner"
([#3168](#3168))
([`a041e2d21e`](a041e2d))
- fix(cli,relay): resolve agents by verified owner
([#2615](#2615))
([`c3084b36d9`](c3084b3))
- fix(security): enforce durable community ban on NIP-43 relay-admin
kinds 9030-9033 ([#3128](#3128))
([`e2e0079101`](e2e0079))
- fix(security): authorize kind:9000 role changes in both directions
([#3017](#3017))
([`00ecf2cac7`](00ecf2c))
- feat(desktop): handle project work from Inbox
([#3117](#3117))
([`c5c4f390b6`](c5c4f39))
- feat(relay): make per-owner community limit configurable via
BUZZ_MAX_COMMUNITIES_PER_OWNER
([#2599](#2599))
([`2a051a404d`](2a051a4))
- feat(relay): add author-only-unless-shared read gate for kind 30175
([#2768](#2768))
([`ab3af82871`](ab3af82))
- fix(core): block IPv6 transition SSRF targets
([#2801](#2801))
([`c26bf5945d`](c26bf59))
- fix(workflow): bypass system proxies for webhooks
([#2800](#2800))
([`60a171b19e`](60a171b))
- fix(audit): hash created_at at the precision Postgres stores
([#2638](#2638))
([`264a56a226`](264a56a))
- feat(desktop): make pull request reviews actionable
([#2510](#2510))
([`9081ab0ec9`](9081ab0))
- fix(relay): decompress gzip-encoded git smart-HTTP request bodies
([#2670](#2670))
([`5ca36e7b91`](5ca36e7))
- fix(sharing): preserve agent/team snapshot tEXt chunks through media
sanitization ([#2438](#2438))
([`b096b0a15a`](b096b0a))
- fix(relay): send 1012 restart close to all clients on graceful drain
([#2575](#2575))
([`1911c69aa2`](1911c69))
- fix(media): sanitize animated image uploads
([#2524](#2524))
([`8f8f5fa5a4`](8f8f5fa))
- fix(channels): strip leading hash prefixes from names
([#2250](#2250))
([`d0ab3fdb05`](d0ab3fd))
- feat(relay): make Redis pool size configurable, default 16
([#2521](#2521))
([`bcc3e13069`](bcc3e13))
- feat(desktop+acp): spawn a harness per (agent, community) pair at GUI
startup — warm sockets, lazy LLM pool
([#2122](#2122))
([`61cc738ee8`](61cc738))
- feat(media): add S3-truth per-community storage sweep
([#2044](#2044))
([`bd37a4d584`](bd37a4d))
- feat(relay): log NIP-98 pubkey attribution on HTTP bridge requests
([#2206](#2206))
([`7e34bee62c`](7e34bee))
- Revert "feat(relay): inventory unreachable Git objects"
([#2275](#2275))
([`0fb820f9bf`](0fb820f))
- feat(relay): inventory unreachable Git objects
([#2264](#2264))
([`3afc9dae15`](3afc9da))
- relay: add author_type label to buzz_events_stored_total
([#2243](#2243))
([`b9f54c43fe`](b9f54c4))
- fix(git): make project branch workflows reliable
([#2213](#2213))
([`166f27be4b`](166f27b))
- feat(cli): manage repository protection rules
([#2193](#2193))
([`f94324598d`](f943245))
- feat(cli): add agents archive/unarchive/archived subcommands
([#2173](#2173))
([`7d7992067b`](7d79920))
- fix(mobile): sanitize Android image uploads
([#2188](#2188))
([`ee21da90bd`](ee21da9))
- fix(cli): paginate channel directory queries
([#2181](#2181))
([`03fe19d603`](03fe19d))
- fix(mobile): image upload fails due to unstripped metadata
([#2185](#2185))
([`37f15b2001`](37f15b2))
- perf(relay): compact Git packs before manifest limits
([#2172](#2172))
([`80e0ab16b0`](80e0ab1))
- perf(relay): cache Git pack hydration
([#2169](#2169))
([`a4d82ec722`](a4d82ec))
- fix(relay): bound and observe Git read operations
([#2167](#2167))
([`5f7c93d9c1`](5f7c93d))
- relay: gate push enqueue on live leases; batch matcher pipeline
(T1b/T1a-repair/T2b) ([#2145](#2145))
([`e43b2d5aac`](e43b2d5))
- relay: add audit logging disable switch
([#2134](#2134))
([`bf5acabdde`](bf5acab))
- relay: skip TTL deadline bump for known-permanent channels (T1a
write-amp) ([#2125](#2125))
([`2e936d439c`](2e936d4))
- fix(git): carry NIP-OA delegation in auth event
([#2120](#2120))
([`c12257d57a`](c12257d))
- Route lag-tolerant reads to an optional Postgres read replica
([#2084](#2084))
([`29c48883d3`](29c4888))
- fix: recover community access visibility
([#2074](#2074))
([`ca384d082d`](ca384d0))
- feat: proxy feedback-scoped admin attachments
([#2059](#2059))
([`d7f918e3cb`](d7f918e))
- feat: add read-only deployment moderation dashboard
([#1999](#1999))
([`68e670e001`](68e670e))
- Bug-bash round 2: table scroll, Goose instructions, workflow mention
wake ([#2034](#2034))
([`64b8fea6dc`](64b8fea))
- Strip media metadata on clients and reject it at the relay
([#2006](#2006))
([`5cfd69cb0c`](5cfd69c))
- [codex] Hold Git concurrency permits through streaming (BUZZ-SEC-018)
([#1916](#1916))
([`7baea42abb`](7baea42))
- [codex] Enforce shared relay admission limits (BUZZ-SEC-019)
([#1917](#1917))
([`73fc0ec6cf`](73fc0ec))
- [codex] Block banned actors from moderation commands (BUZZ-SEC-007)
([#1915](#1915))
([`caa195ca58`](caa195c))
- [codex] Fix relay WebSocket admission limits
([#1682](#1682))
([`d3ce971fc7`](d3ce971))
- feat: add invite QR and mobile direct join
([#1957](#1957))
([`648cbf3610`](648cbf3))
- fix(join-policy): require legal consent on hosted invites
([#1987](#1987))
([`2e1577f76f`](2e1577f))
- [codex] Prevent actor-tag UI impersonation
([#1931](#1931))
([`c540ec9678`](c540ec9))
- Scope relay runtime state by community
([#1658](#1658))
([`d52dedb06f`](d52dedb))
- Apply optional relay join policy across join flows
([#1894](#1894))
([`6c2d667575`](6c2d667))
- feat(media): require auth for relay media reads
([#1926](#1926))
([`f308762852`](f308762))
- feat(relay): add community unarchive endpoint
([#1908](#1908))
([`6b9641db2b`](6b9641d))
- feat(relay): gate Git web GUI separately
([#1901](#1901))
([`34dc7dec75`](34dc7de))
- mesh: upgrade runtime, enforce membership, add shared compute provider
([#1656](#1656))
([`54638ff4bb`](54638ff))
- Route Git scratch through configured volume
([#1884](#1884))
([`2318b3096c`](2318b30))
- feat(relay): gate usage metrics behind stable leader
([#1814](#1814))
([`59e9821503`](59e9821))
- Relay mesh: cross-pod tunnel + huddle transport (buzz-relay-mesh)
([#1670](#1670))
([`ccb021d713`](ccb021d))
- feat(push): deliver accepted relay events as wakes
([#1866](#1866))
([`bffbc5f22c`](bffbc5f))
- fix(db): resolve duplicate migration version
([#1863](#1863))
([`08ad38a07f`](08ad38a))
- Add private product feedback sidecar
([#1857](#1857))
([`af190c93e1`](af190c9))
- feat(relay): add durable community archival
([#1834](#1834))
([`2b15a72675`](2b15a72))
- feat(push): add public APNs gateway
([#1770](#1770))
([`1c006822e4`](1c00682))
- feat(relay): add atomic community ownership transfer
([#1845](#1845))
([`52e42ccb9f`](52e42cc))
- Bound NIP-RS retention and search indexing
([#1771](#1771))
([`1b4703021d`](1b47030))
- Add optional standalone pairing relay to Helm chart
([#1799](#1799))
([`9b47c8548f`](9b47c85))
- fix(relay): publish membership snapshot on provisioning
([#1761](#1761))
([`0950d392b7`](0950d39))
- feat(relay): per-community usage metrics
([#1723](#1723))
([`620822899a`](6208228))
- refactor(desktop): remove vestigial MCP toolsets config
([#1776](#1776))
([`dfec75b3c0`](dfec75b))

**To release:** merge this PR. The tag and build will happen
automatically.

Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
denizkin28 pushed a commit to denizkin28/n8n-nodes-buzz-relay that referenced this pull request Aug 8, 2026
…as verified against

Upstream relay-v0.2.1 made media GET/HEAD require a signed Blossom
authorization and current relay membership (block/buzz#4610), removing
the unauthenticated compatibility path. This node already sent that
header, so nothing broke — but nothing here would have caught it if the
header had been wrong: the offline suite states it does not cover the
download path, and the wire test did not either.

- Export uploadBlob/downloadBlob so the wire test drives the real code
  rather than a fetch that reproduces what it assumes the code does.
- Teach the test's httpRequest shim the two shapes the media path uses:
  a Buffer body (JSON.stringify would corrupt an upload into
  {"type":"Buffer"} and store it under the wrong sha256) and
  encoding:'stream' + returnFullResponse.
- Add three checks: upload, an authenticated download verified by
  sha256 round-trip, and an unauthenticated download that must be
  refused. The fixture is a fixed 75-byte generated PNG, so Blossom
  content-addressing makes every run reuse one blob — the relay has no
  media-delete API, so a per-run fixture would leak a file each time.
- README: document authenticated media reads, state that this does not
  soften "uploads are publishing" (members still read; a blob is not
  bound to its channel upstream), and note that the relay behaviours
  listed were verified against relay-v0.2.1.

The unauthenticated-read check is marked in-file as NOT YET SHOWN TO
FAIL. It passes against relay-v0.2.1, which proves it runs, not that it
would catch a relay that serves media openly. Validating it needs a run
against the previous image (48933af5, commit 631b05c8).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants