block / block/buzz

[Bug] Historical message attachments break after a relay origin migration

Open
#6,233 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
Rust
Stars
32.7k
Forks
4.3k
Avg merge
1d 13h
Merged PRs (30d)
253

Description

## Describe the bug

Historical message attachments become permanently unavailable in Desktop and Mobile after a self-hosted community changes its canonical relay origin, even when the relay database and media store are unchanged and every blob is still present at the new origin.

Buzz stores absolute media URLs in immutable signed message `imeta` tags. Both clients only attach protected-media authorization when the URL's complete origin matches the currently configured relay. After a scheme, host, or port migration, the historical URL is treated as external and fetched without relay authorization.

This is related to #3601, but not the same repair boundary. That issue covers mutable avatar/profile records that can be republished. Signed message events cannot be rewritten or republished without changing their identity and history.

## Reproduction

1. Run a self-hosted relay at an origin such as `http://relay.example:3000`.
2. Upload an image and send a message containing the resulting absolute `imeta` URL.
3. Move the same relay database and media store to `https://relay.example:3000`, then configure Desktop or Mobile to use that new origin.
4. Confirm the identical `/media/.` object is available with authenticated access at the new origin.
5. Open the historical message.

A port migration reproduces the same problem, for example from `https://relay.example:8443` to `https://relay.example:3000`.

## Actual behavior

- Desktop leaves the historical URL unchanged because its origin differs from the cached active relay origin. The request bypasses Desktop's authenticated media proxy.
- Mobile returns no media authorization headers for the historical URL because its authority differs from the configured relay authority, then fetches the original URL.
- A retired plaintext origin fails at the network/TLS boundary.
- A still-reachable protected HTTPS origin returns `401` because the client deliberately omitted media authorization.
- The UI reports the image as unavailable even though its bytes remain intact on the current relay.

## Verified production-scale evidence

A read-only audit of one migrated self-hosted deployment paginated 6,094 reachable signed events and found:

- 61 original attachment references in 55 message events;
- 48 attachments signed against a former plaintext origin;
- one attachment signed against a former secure port;
- 12 attachments signed against the current secure origin.

All 61 originals and all 55 distinct thumbnail paths were fetched through authenticated access at the current origin. Every original matched its signed SHA-256 and declared byte size. Lost blobs: zero.

The failure is therefore historical UI reachability and authorization, not media loss.

## Confirmed code boundary

Desktop's `desktop/src/shared/lib/mediaUrl.ts::rewriteRelayUrl` only proxies a relay media URL when its canonical origin equals the cached active relay origin.

Mobile's `mobile/lib/shared/relay/media_auth.dart::MediaGetAuthService` only returns authorization when the media authority equals the configured relay authority, while `MediaImageProvider` fetches the original URL.

## Expected behavior

Buzz should preserve immutable message history while allowing explicitly trusted former media origins for the same community to resolve through the current authenticated relay.

The client should retain the original signed event unchanged, preserve the strict content-addressed `/media/` path, and use the current relay's authenticated media transport for the network request.

This should work on Desktop, Mobile, and fresh installations that never had the old origin configured.

## Design question for maintainers

The missing upstream contract is how an official client learns the exact trusted former origins for a community. Possibilities include authenticated relay information, owner-signed community metadata, or another explicit migration record.

This should not be inferred merely from similar hostnames, and downstream deployments should not require client-specific hardcoded aliases. A fresh client needs an authoritative source rather than only local URL history.

## Security requirements

1. Trust only exact former origins explicitly authorized for the currently selected relay/community.
2. Never send credentials or media authorization to a former origin.
3. Fetch the strict content-addressed path through the current authenticated origin.
4. Reject malformed authorities, embedded credentials, undeclared ports/hosts/schemes, query or fragment confusion, non-media paths, and invalid media IDs.
5. Preserve unrelated external Blossom URLs unchanged.
6. Do not make protected media public, weaken media authorization, or rewrite immutable signed events.
7. Do not forward authorization across redirects to another authority.

## Suggested acceptance criteria

1. Add a generic, explicit trusted-former-media-origin contract for a community.
2. Add equivalent resolution in Desktop and Mobile.
3. Cover HTTP to HTTPS, port changes, hostname changes, current-origin media, thumbnails, account/community switches, and fresh installations.
4. Add adversarial tests for undeclared and malformed origins.
5. Verify historical attachments render while the underlying signed event remains byte-identical.

## Environment

- Buzz Desktop: official 0.5.14
- Buzz Mobile: iPhone app configured to the current secure relay origin
- Deployment: self-hosted relay with unchanged database and media store after origin migration

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.