feat(stickers): add Sonar sticker packs (supersedes #2968) - #4026
Draft
mattkanwisher wants to merge 4 commits into
Draft
feat(stickers): add Sonar sticker packs (supersedes #2968)#4026mattkanwisher wants to merge 4 commits into
mattkanwisher wants to merge 4 commits into
Conversation
Rebases vincenzopalazzo:feat/sonar-stickers onto current main (182 commits newer). Conflict resolutions: - ingest.rs / relay_admin.rs: union of main's project + team-catalog validation with the sticker pack/list/reference validators. - buzz-db event.rs: main's NIP-09 created_at deletion predicate now runs inside the PR's advisory-lock transaction. - Migration renumbered 0025 -> 0027 (0025_relay_invites and 0026_replica_heartbeat landed on main); migration test expects 27. - Desktop: adopted main's MessageComposer.types.ts and ComposerDockToolbar, dropping the PR's resurrected MessageComposerProps.ts; settings sections stay extracted in lib/settingsSections.ts with main's 'voice' section added. - Mobile message_content.dart: main's KeyedSubtree + trailing-gallery body composed with the sticker preview branch. main removed check-file-sizes.mjs's per-file override map, so the four files the sticker work grew past the ceiling were split rather than exempted: - desktop/src-tauri: NIP-IA builders + their tests -> identity_archive_events.rs - shared/api/tauri.ts: NIP-44 + NIP-AB pairing calls -> api/pairing.ts - MessageComposer.tsx: edit payload, emoji insertion, autocomplete key dispatch, and the sticker send path -> four sibling modules Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Matthew Campbell <hyper@hyperworks.nu> Co-authored-by: Vincenzo Palazzo <vincenzopalazzodev@gmail.com>
Reviewed all 25 findings against current code. Most had already been fixed by the author in later commits; the rest are fixed here, each with tests. Security: - desktop sticker import performed no SSRF check on relay hints from a Sonar link, so `ws://127.0.0.1:<port>` (or a hostname resolving to a private address) reached a WebSocket handshake from the desktop process. New commands/sticker_relay.rs validates the hint (wss:// only outside debug, no credentials), resolves it, rejects when ANY answer is a private/reserved address via buzz-core's existing is_private_ip, and then connects the vetted SocketAddr directly so DNS rebinding cannot slip past the check. Adds a message-size cap and an event ceiling for hostile relays. - externally fetched sticker assets bypassed the metadata guard that uploads apply, so a pack could republish EXIF/XMP/comment data through the relay cache. validate_sticker_content now runs validate_image_metadata_free before the geometry parse. - animation budgets used only the logical canvas, so a 1x1 GIF/WebP/APNG could declare a 65535x65535 frame and pass the pixel guard. Frame rectangles are now parsed and bounded for all three formats. Correctness: - buzz-core network.rs blocked the whole IPv6 2001::/23 IETF protocol-assignment block, which is mostly globally reachable — it rejected 2001:1::1 (PCP anycast) and broke main's Teredo test. Narrowed to the registered non-reachable sub-blocks and tightened 3fff::/20 from a /16. Same over-broad-range class as the 192.0.1.0/24 finding. - the sticker fetch-lock map evicted entries inline after the fetch, which leaks the entry whenever the request future is cancelled. Eviction moved to an RAII Drop handle. - the admin approval queue derived from a single 500-row query, so packs past the cap were undiscoverable. Replaced with a paged until-cursor walk. - APNG was enabled for sticker uploads by widening the shared upload MIME allowlist, which also widened ordinary attachments. Rescoped to a sticker-only superset. - the sticker send path restored the pre-send draft unconditionally, clobbering anything typed while the send was in flight. Restores only when the composer is still empty. Pre-existing and unrelated: buzz-relay's mesh_demo round-trip test fails identically on upstream/main (verified in a clean worktree). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Matthew Campbell <hyper@hyperworks.nu> Co-authored-by: Vincenzo Palazzo <vincenzopalazzodev@gmail.com>
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
Every Signal import failure was collapsed into "Could not import that Signal sticker pack. Check the link and try again." via `map_err(|_| ...)`, discarding the cause entirely. That message is actively misleading for the most likely failure. The link is validated against the official signal.art shape *before* any request is made, so by the time the import runs its form is already known good. Meanwhile cdn.signal.org is served under Signal's own private root CA (`O=Signal Messenger, LLC, CN=Signal Messenger`), which no public trust store contains — reqwest is built with `accept_invalid_certs: false` and the system roots, so the handshake fails on a perfectly valid link and the user is told to go re-check the one thing that cannot be wrong. Reproduced directly: curl https://cdn.signal.org/stickers/<id>/manifest.proto -> exit 60 curl -k https://cdn.signal.org/stickers/<id>/manifest.proto -> HTTP 200 Not a local middlebox: signal.org and cloudflare.com both resolve and verify fine from the same host; only cdn/cdn2.signal.org fail. Distinguishes transport, decryption, and other failures, and logs the underlying error (Signal's URLs carry the pack id, never the pack key). Does not disable certificate verification. Note this only fixes diagnosability: Signal import still cannot succeed until the client trusts Signal's root CA. The pinned bitchat-to-sonar crate only exposes `accept_invalid_certs`, so a real fix needs an upstream API to supply a custom root via `add_root_certificate` — disabling verification is not an acceptable substitute. Co-authored-by: Vincenzo Palazzo <vincenzopalazzodev@gmail.com> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Matthew Campbell <hyper@hyperworks.nu>
PR block#2968 shipped with the sticker picker rendering only its empty state, because the E2E mock bridge had no sticker catalog to serve — the PR body listed seeding one as a follow-up. This is that follow-up. Adds kind:30031 pack events, the kind:13536 approved-catalog snapshot, and the viewer's kind:10031 installed list to the mock relay, with asset URLs shaped to pass `isHttpsHashUrl`, plus a Playwright route that fulfils the sticker cache path with real image bytes so the grid renders actual art instead of broken images. The spec asserts `naturalWidth > 0`, so a regression fails the test rather than silently capturing broken pixels. Covers four states: the populated composer picker, a sticker sent and rendered in the timeline, the settings catalog with its pending-approval queue, and the picker before any pack is installed. Co-authored-by: Vincenzo Palazzo <vincenzopalazzodev@gmail.com> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Matthew Campbell <hyper@hyperworks.nu>
mattkanwisher
force-pushed
the
feat/sonar-stickers-rebase
branch
from
July 31, 2026 23:03
13e9b68 to
7862315
Compare
mattkanwisher
marked this pull request as draft
August 2, 2026 03:22
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Resurrects #1920 and #2968 — Sonar sticker packs, rebased onto current main
This PR brings back the sticker-packs work from #1920 (closed unmerged) and rebases it onto today's main (103 commits newer). All merge conflicts are resolved and every local quality gate is green.
Why Buzz must have this
Emoji reactions and custom emoji already proved that lightweight, expressive communication matters in Buzz — but every serious chat product our users compare us to (Slack, Signal, Telegram, Discord) ships stickers as the next tier of expression: larger, authored, pack-based, and fun. Today a Buzz user who wants to react with more personality than an emoji has to upload an image by hand, every time.
Stickers are also a community-culture primitive: teams build shared identity around their own packs. With this PR Buzz gets that — but with a safety model no competitor has:
Admin-approved catalog (kind 13536) — a community's sticker catalog is a materialized, curated list. Clients never fetch arbitrary pack events from the open network.
Content-addressed, approval-gated asset cache — the relay only serves sticker assets whose SHA-256 matches an approved pack revision (/media/sticker/{author}/{identifier}/{shortcode}/{sha256}). It never fetches unapproved assets, caps dimensions/frame counts, and pins DNS resolution.
Signal interop — users can import an official signal.art pack link and republish it as a Sonar pack, so existing sticker culture ports over instead of starting from zero.
Authoring built-in — create/edit packs in Settings, upload WebP covers and images from the desktop app, publish via buzz stickers CLI commands (agent-first, like everything else in Buzz).
Agents can sticker too — stickers ride ordinary channel messages as validated sticker tags, so managed agents and CLI flows can send them through the same pipeline.
What it looks like
Composer — sticker picker entry point (empty state prompts pack install):
Settings → Stickers — install curated packs, author your own, or import a Signal pack:
(Screenshots in the comment below — posted via scripts/post-screenshots.sh.)
Note on the screenshots: the E2E mock bridge has no seeded sticker catalog yet, so the picker renders its honest empty state. A follow-up will seed a mock catalog + pack events in e2eBridge.ts so we can capture the populated grid and add a screen recording of the full send flow. To try it live: buzz stickers --help against any relay running this branch.
What changed since #1920 (rebase notes)
Migration renumbered 0020_sticker_catalog.sql → 0021_sticker_catalog.sql (0020 is now join_policy_acceptances on main); migration tests updated for 21 migrations.
Merged with main's media-auth rework — kept authenticate_media_read / blob_cache_control alongside the new verified-sticker route; kept should_retry_legacy_upload upload fallback in both buzz-cli and the desktop app while exposing do_upload to the sticker commands.
Merged with main's mention-flow rework — sticker tags compose with audienceGeneration/audienceRevision, postSendContent, and the persistent-agent-audience send path; MessageComposerProps extraction kept and extended with main's newer props.
File-size guard compliance — split desktop sticker commands into commands/stickers.rs and the tag validator into sticker_events.rs (keeps media.rs, events.rs, lib.rs, AppShell.tsx under the 1000-line ceiling without overrides); added a small useLiveUpdates aggregator hook in the app shell.
Mobile — receive-only sticker rendering conflicts resolved against main's relay-provider changes.
Security properties (unchanged from #1920)
No fetching of unapproved assets; approval pins exact pack event IDs.
SHA-256 verified, dimension- and frame-capped animated media; zeroized secrets on import.
Pinned bitchat-to-sonar dependency (rev-locked).
Verification (this branch)
cargo clippy --workspace --all-targets -D warnings ✅
just desktop-tauri-check / just desktop-tauri-clippy / just desktop-tauri-test (1469 passed) ✅
Desktop: pnpm check (biome + file-size + px-text guards) ✅, pnpm test (3129 passed) ✅, tsc --noEmit ✅
Mobile: dart format ✅, flutter analyze ✅, flutter test (520 passed, incl. new sticker preview/reference tests) ✅
cargo test -p buzz-db -p buzz-core -p buzz-media -p buzz-sdk -p buzz-cli ✅ (incl. updated migration-sequence tests)
Design doc: docs/brainstorms/2026-07-15-sonar-sticker-packs-design.md.
Supersedes and [ ](#1920