Harden shared agent instruction review - #4220
Conversation
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f9536a5673
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
Requesting changes on Wes's behalf after independent security, regression, and integration reviews of d56523fcbab119e78cc2a208c0b78cae55dfe4b3.
Two blockers remain:
- The catalog accepts unverified relay events as executable definitions and attributes them to attacker-selected pubkeys. A compromised relay can forge a member's shared agent and prompt a user to import arbitrary instructions.
- The validator rejects legitimate VS16/ZWJ emoji sequences, breaking edits to existing local agents and hiding valid shared definitions from the catalog.
The literal <pre> review surface, Rust ingress validation, snapshot/team-snapshot coverage, pre-retention validation, and TypeScript/Rust range parity otherwise look sound. Existing CI at this exact head is green; reviewers did not duplicate CI-equivalent suites locally.
wesbillman
left a comment
There was a problem hiding this comment.
Requesting changes on Wes's behalf after coordinated review of fc53800ef0d849a127d850598f35b9d2d867c099 with Princess Donut and Mongo.
The two prior blockers are fixed: catalog events are now verified on fresh wire-shaped objects before they can claim coordinates or influence paging, and the TS/Rust validators permit contextual VS16/ZWJ emoji while rejecting detached formatting. The literal review surface and catalog add path preserve exact prompt bytes. Mongo independently found no remaining issue in those paths.
One blocker remains: the Rust safety invariant is incomplete for other executable, relay-synced definition text.
- Definition-less
ManagedAgentRecord.system_promptis executable, butcreate_managed_agentandupdate_managed_agentaccept it withoutvalidate_agent_definition_text; those records are persisted and queued for publication. Inbound definition-less records do get validated, leaving an asymmetric local bypass. - Team instructions are executable, but team create/update and inbound kind
30176apply them without the validator. Team snapshot validation validates member definitions but not the executableteam.instructions(or the team name), then import persists those fields.
As a result, invisible/bidi text such as Review\u{200B}code can still be persisted, executed, and published through local managed-agent/team paths even though equivalent persona and inbound managed-agent definitions are rejected. Please centralize validation for executable name/instruction fields across create, update, import, sync, and publication boundaries, with direct regressions for definition-less agents and teams—or explicitly narrow the promised security contract and demonstrate why these executable fields are outside it.
Existing CI is green at this exact head. Focused local test attempts in my clean review worktree were environment-blocked (missing JS dependencies; missing Tauri sidecar), so I did not count them as validation. Mongo separately ran the focused TS suites: 44/44 passed.
|
Scope clarification on my changes-requested review, on Wes's behalf: I agree the team-instructions work can be split into a follow-up. It is executable shared text and the gap is real, but this PR's concrete review surface is agent/persona definitions; extending the invariant to teams broadens the product/UI contract enough to justify separate work. Please track that follow-up before merge. I do not think the definition-less managed-agent gap is separable. This PR explicitly added inbound validation for definition-less kind So the narrowed blocker is: validate definition-less managed-agent name/prompt on local create/update (and at publication as defense in depth), with direct regressions. Team validation may follow separately with an issue/PR. |
3858539 to
e6aa358
Compare
Signed-off-by: Alex Rosenzweig <arosenzweig@squareup.com>
Signed-off-by: Alex Rosenzweig <arosenzweig@squareup.com>
Signed-off-by: Alex Rosenzweig <arosenzweig@squareup.com>
Signed-off-by: Alex Rosenzweig <arosenzweig@squareup.com>
Signed-off-by: Alex Rosenzweig <arosenzweig@squareup.com>
Signed-off-by: Alex Rosenzweig <arosenzweig@squareup.com>
Signed-off-by: Alex Rosenzweig <arosenzweig@squareup.com>
Signed-off-by: Alex Rosenzweig <arosenzweig@squareup.com>
wesbillman
left a comment
There was a problem hiding this comment.
Reviewing e6aa35849204fc33dd9bab846e2987b637e32e7d on Wes's behalf.
The remaining in-scope blocker from my prior changes-requested review is fixed:
- local definition-less agent create validates the trimmed name and executable prompt before persistence;
- update applies the prospective prompt/name, validates the complete record, and returns before save on failure;
- kind
30177inbound sync validates before retention; - publication validates again as defense in depth;
- linked records continue to treat their record-level prompt as inert and persona-authoritative.
I also re-traced catalog signature verification, coordinate selection, cursor derivation, literal prompt rendering, persona create/update/import, and agent snapshot decode/import at this head. I found no new actionable defect in the PR's narrowed agent/persona scope. Existing required CI is green for this exact head; I did not duplicate the broad suites locally.
Two merge-state notes remain:
- GitHub currently reports this branch as conflicting with
main(baseRefOidis96ae141763e5459beb68a847e0082e931af72f4cwhile currentorigin/mainis538e5e113fc33571f939c87b925567fd4e277109), so the rebased result still needs review/CI. - The agreed follow-up for executable team names/instructions is not present in this PR, and I found no matching open or closed issue in
block/buzzusing searches for team-instruction Unicode/zero-width/bidi validation. Please track that before merge as previously agreed. Team create/update, inbound kind30176, publication, and team snapshot validation remain outside this patch.
I am leaving this as a comment rather than approval: Wes requested review, not approval.
e6aa358 to
e19bf1e
Compare
Approved by will and feedback addressed
Upstream changes merged: - Refine channel settings and profile panels (block#5574) - Harden shared agent instruction review (block#4220) - feat(deletion): add durable whole-community deletion (block#4425) - Various agent, relay, and UI improvements Conflict resolutions: same as feat/i18n-desktop-ru merge — i18n t() calls preserved with upstream structural changes adopted. Locale sync: en.json + ru.json brought up to date with feat/i18n-desktop-ru (25 new channel.* keys added). Signed-off-by: Sergey Taranenko <s.taranenko@vdi-service.ru>
…-projection * origin/main: Harden shared agent instruction review (#4220) chore(release): release Buzz Desktop version 0.5.11 (#5714) feat(acp): report standard adapter usage (#4950) fix(mobile): settle hydrated threads on latest reply (#4702) perf(desktop): persist channel snapshot hash (#5684) fix(agent): raise output limit and allow 3 recoveries (#5475) fix(desktop): defer foreground resume work (#5696) Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com> # Conflicts: # desktop/src-tauri/src/commands/personas/update.rs # desktop/src-tauri/src/managed_agents/mod.rs
…n-surface * origin/main: Harden shared agent instruction review (#4220) Signed-off-by: Hayt <41ea58f1e64c243627e8acde7c89be667052ee6e17d8f021c1195be4324ebf04@buzz.block.builderlab.xyz>
…graphy-staging * origin/main: Harden shared agent instruction review (#4220) chore(release): release Buzz Desktop version 0.5.11 (#5714) feat(acp): report standard adapter usage (#4950) fix(mobile): settle hydrated threads on latest reply (#4702) perf(desktop): persist channel snapshot hash (#5684) fix(agent): raise output limit and allow 3 recoveries (#5475) fix(desktop): defer foreground resume work (#5696) perf(desktop): coalesce thread-activity localStorage writes (#5693) Batch observer-store publications per relay envelope (#5680) feat(buzz-acp): idle re-sleep for woken lazy pools (#5682) fix(desktop): preserve agent mention separator after send (#5623) fix(link-previews): proxy sent preview media (#5627) feat(deletion): add durable whole-community deletion (#4425) fix(desktop): preserve live channel timelines (#5662) Refine channel settings and profile panels (#5574) fix(deps): bump webbrowser to 1.2.4 for RUSTSEC-2026-0257 (#5659) feat(acp): deliver channel description in prompt [Context] (#4552) fix(desktop): launch Databricks OAuth from passive model discovery (#5607) Signed-off-by: morgmart <98432065+morgmart@users.noreply.github.com>
* origin/main: Harden shared agent instruction review (#4220) Signed-off-by: tlongwell-block <tlongwell@block.xyz>
…ent-config * origin/main: (31 commits) Harden shared agent instruction review (#4220) chore(release): release Buzz Desktop version 0.5.11 (#5714) feat(acp): report standard adapter usage (#4950) fix(mobile): settle hydrated threads on latest reply (#4702) perf(desktop): persist channel snapshot hash (#5684) fix(agent): raise output limit and allow 3 recoveries (#5475) fix(desktop): defer foreground resume work (#5696) perf(desktop): coalesce thread-activity localStorage writes (#5693) Batch observer-store publications per relay envelope (#5680) feat(buzz-acp): idle re-sleep for woken lazy pools (#5682) fix(desktop): preserve agent mention separator after send (#5623) fix(link-previews): proxy sent preview media (#5627) feat(deletion): add durable whole-community deletion (#4425) fix(desktop): preserve live channel timelines (#5662) Refine channel settings and profile panels (#5574) fix(deps): bump webbrowser to 1.2.4 for RUSTSEC-2026-0257 (#5659) feat(acp): deliver channel description in prompt [Context] (#4552) fix(desktop): launch Databricks OAuth from passive model discovery (#5607) chore(release): release Buzz Desktop version 0.5.10 (#5613) fix(desktop): remove 0.5.9+ perf regressions, speed up get_channels (#5599) ... Co-authored-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz> Signed-off-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz> # Conflicts: # desktop/src-tauri/src/commands/personas/inbound.rs # desktop/src-tauri/src/commands/personas/inbound/inbound_tests.rs
Resolved conflicts in crates/buzz-acp/src/acp.rs and config.rs (additive struct fields/tests from both sides, kept), and mobile/lib/features/channels/thread_detail_page.dart (took upstream's tail-follow scroll rework from block#4702, kept our shouldShowNestedThreadSummary gating from feat/named-conversation-threads). Notable upstream change: block#4220 hardens shared agent instruction review (literal-text rendering, Unicode control rejection, signature verification before trusting catalog content).
…ties-manifest * origin/main: Harden shared agent instruction review (#4220) chore(release): release Buzz Desktop version 0.5.11 (#5714) feat(acp): report standard adapter usage (#4950) fix(mobile): settle hydrated threads on latest reply (#4702) perf(desktop): persist channel snapshot hash (#5684) fix(agent): raise output limit and allow 3 recoveries (#5475) fix(desktop): defer foreground resume work (#5696) perf(desktop): coalesce thread-activity localStorage writes (#5693) Batch observer-store publications per relay envelope (#5680) feat(buzz-acp): idle re-sleep for woken lazy pools (#5682) Signed-off-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz> Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
* origin/main: Harden shared agent instruction review (#4220) chore(release): release Buzz Desktop version 0.5.11 (#5714) feat(acp): report standard adapter usage (#4950) fix(mobile): settle hydrated threads on latest reply (#4702) perf(desktop): persist channel snapshot hash (#5684) fix(agent): raise output limit and allow 3 recoveries (#5475) fix(desktop): defer foreground resume work (#5696) perf(desktop): coalesce thread-activity localStorage writes (#5693) Batch observer-store publications per relay envelope (#5680) feat(buzz-acp): idle re-sleep for woken lazy pools (#5682) fix(desktop): preserve agent mention separator after send (#5623) fix(link-previews): proxy sent preview media (#5627) feat(deletion): add durable whole-community deletion (#4425) fix(desktop): preserve live channel timelines (#5662) Signed-off-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz> Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
## Summary - render shared-agent instructions as literal text so Markdown cannot conceal spoiler contents, link destinations, or image sources - reject non-reviewable Unicode controls at every agent-definition boundary while preserving legitimate rendered emoji sequences - verify shared catalog event IDs and signatures before trusting authorship, coordinates, pagination, or executable content - preserve the exact system-prompt bytes between review and execution instead of silently stripping or normalizing content ## Security rationale Shared system prompts are executable configuration. Previously, catalog prompts were projected through the chat Markdown renderer, which could hide text, replace link destinations with benign labels, and turn image syntax into remote loads. Zero-width and bidirectional controls could also make reviewed text differ from what the agent executes. This change establishes a review invariant: the prompt a user sees is the prompt the agent executes. Definitions that cannot be reviewed faithfully are rejected rather than rewritten. Catalog events must also pass Nostr ID/signature verification before they can claim a publisher, coordinate, or cursor. ## What changed - catalog instructions render as exact literal text rather than rich Markdown - catalog relay events are verified on a fresh wire-shaped object before paging, coordinate selection, attribution, or projection - forged content, pubkeys, signatures, and invalid newer heads are ignored and cannot shadow a valid signed definition - TypeScript catalog parsing rejects unsafe remote definitions before they reach the UI - shared Rust validation covers persona create/update/import, inbound relay sync, definition-less managed-agent sync, and catalog publication paths - definition-less managed agents now fail closed on local create, local update, and publication before persistence or relay retention - linked managed agents validate their local name while treating the persona definition as authoritative; their inert record-level prompt is not executed or published - names reject layout controls; prompts retain ordinary newlines and tabs - legitimate emoji composition is supported, including contextual VS16, ZWJ, skin-tone, family, flag, and keycap sequences - detached selectors/joiners, bidirectional controls, tag characters, zero-width concealment, and other default-ignorables remain rejected - names are bounded to 128 characters and prompts to 64 KiB - contributor guidance documents the byte-for-byte review requirement for future sharing paths Validation reports the offending code point and never silently removes it. ## E2E recording [buzz-shared-agent-security-e2e.webm](https://github.com/user-attachments/assets/44d6b75f-0877-490f-bda4-a716fae3f700) The recording demonstrates: - a safe definition remains visible - a prompt containing zero-width `U+200B` is rejected - a name containing bidi override `U+202E` is rejected - the prompt is preserved exactly - spoiler, link, and image syntax remains literal and does not render or load ## Verification Passed locally: - `just test`: all 10 unit and Docker-backed integration stages - desktop frontend unit suite: 4,295 tests - persona catalog relay unit suite: 32 tests, including forged-event and cursor-shadowing cases - focused Rust definition-validation coverage: 3 local create/update tests and 6 publication-filtered tests - complete desktop Tauri library suite after rebase: 2,263 passed, 14 ignored, 0 failed - desktop Tauri clippy with warnings denied and Rust formatting - complete agent Playwright spec: 34 tests - the exact formerly failing `inbox-edit` immediate-attachment smoke test after rebase: 1 test - focused shared-agent publish, literal-review, hidden-control, signature, and cross-member import Playwright coverage - desktop E2E production build and TypeScript typecheck - changed-file formatting/lint and file-size ratchet - pre-commit secret scan and DCO signoff The branch was rebased onto current `main`, which includes the upstream attachment-button label fix. Fresh post-rebase GitHub CI is green for every required and selected check: Desktop Core, all four Desktop Smoke E2E shards, both Desktop E2E Integration shards and their aggregate, Desktop E2E Relay, Desktop Build (macOS), Windows Rust, Rust Lint, DCO, security scanners, and Desktop Release Candidate. The previously failing `Desktop Smoke E2E (3)` shard now passes. The repository-wide desktop check also reports existing CSS formatting/`!important` findings in `components.css` and `terminal.css`; neither file is changed by this PR. GitHub's Desktop Core lint and format stage passes on the rebased branch. --------- Signed-off-by: Alex Rosenzweig <arosenzweig@squareup.com> Co-authored-by: Alex Rosenzweig <64241648+shellz-n-stuff@users.noreply.github.com>
…read-context * origin/main: (38 commits) Add mobile community invites (#5641) Harden shared agent instruction review (#4220) chore(release): release Buzz Desktop version 0.5.11 (#5714) feat(acp): report standard adapter usage (#4950) fix(mobile): settle hydrated threads on latest reply (#4702) perf(desktop): persist channel snapshot hash (#5684) fix(agent): raise output limit and allow 3 recoveries (#5475) fix(desktop): defer foreground resume work (#5696) perf(desktop): coalesce thread-activity localStorage writes (#5693) Batch observer-store publications per relay envelope (#5680) feat(buzz-acp): idle re-sleep for woken lazy pools (#5682) fix(desktop): preserve agent mention separator after send (#5623) fix(link-previews): proxy sent preview media (#5627) feat(deletion): add durable whole-community deletion (#4425) fix(desktop): preserve live channel timelines (#5662) Refine channel settings and profile panels (#5574) fix(deps): bump webbrowser to 1.2.4 for RUSTSEC-2026-0257 (#5659) feat(acp): deliver channel description in prompt [Context] (#4552) fix(desktop): launch Databricks OAuth from passive model discovery (#5607) chore(release): release Buzz Desktop version 0.5.10 (#5613) ... Signed-off-by: Lazy Joe <dbd8c9941ba6dafebcef0abc015b65e75d52e7452f2ce483c9c3fd4d180f2504@buzz.block.builderlab.xyz>
…icit-settling * origin/main: fix(desktop): more compact "compact" link previews (#5629) Fix mobile composer input regressions (#5594) Add mobile community invites (#5641) Harden shared agent instruction review (#4220) chore(release): release Buzz Desktop version 0.5.11 (#5714) feat(acp): report standard adapter usage (#4950) fix(mobile): settle hydrated threads on latest reply (#4702) perf(desktop): persist channel snapshot hash (#5684) fix(agent): raise output limit and allow 3 recoveries (#5475) fix(desktop): defer foreground resume work (#5696) Co-authored-by: Taylor Ho <taylorkmho@gmail.com> Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
* fix(buzz-agent): harden Databricks OAuth token cache and callback (block#5534) Hardens the Databricks PKCE OAuth code in `crates/buzz-agent/src/auth.rs`. Two fixes. ## Token cache is owner-only across its whole lifecycle, and race-safe The PKCE cache holds both the access and refresh tokens, but `save()` wrote it with a bare `fs::write` + `fs::rename`. Under a `022` umask the file landed world-readable, and the fixed `*.json.tmp` temp name races across concurrent savers sharing `$HOME` — one writer's `rename` can fail on another's half-written temp. **On write**, `write_private_cache()` creates a temp file with owner-only permissions from the moment it exists — mode `0o600` on Unix via `OpenOptions::mode` — writes and fsyncs it, then renames over the destination. The rename swaps the inode wholesale, so a pre-existing cache file with loose permissions is *replaced* by the new private inode rather than inheriting its mode. `unique_suffix()` (getrandom, timestamp fallback) gives each write a distinct temp name, and a drop guard removes the temp on any failure path. **On load**, owner-only is enforced as a cache lifecycle invariant, not just a write-path property. A world-readable cache left by an older buzz-agent was previously read straight into memory and returned on the fresh cache-hit path without ever invoking `save()`, so a token file with no advertised expiry could stay exposed indefinitely. `read_cache()` now funnels every load — initial and cross-process re-reads — through `read_private_cache()`, which on Unix opens with `O_NOFOLLOW` (kernel-level symlink refusal, no stat/open TOCTOU), requires a regular file, and `fchmod`s the pinned handle to `0o600` when any group/other bit is set. A cache that cannot be secured is treated as absent, so callers fail closed to a fresh flow rather than trusting an exposed file. ## OAuth callback no longer reflects untrusted input The localhost callback embedded the untrusted `error` query param straight into the HTML response — an XSS sink on the redirect page — and routed that same raw value into the error string that reaches the logs. `callback_outcome()` is now a pure function returning `(result, static_page)`: the browser always sees a fixed literal page that embeds no request parameter, and failure detail travels only through the result channel. `sanitize_callback_detail()` strips control characters (CR/LF log-line injection) and caps length before that detail enters the error string bound for the logs. ## Deferred: Windows owner-only ACLs Windows owner-only protection is out of scope for this change. The goose-parity route (`CreateFileW` with an owner-only SDDL `D:P(A;;FA;;;OW)`) requires `unsafe` FFI, which this crate's `#![forbid(unsafe_code)]` prohibits; reconciling that conflict is a separate decision. Both platform seams — `create_private_temp_file` (write) and `read_private_cache` (load) — have a `#[cfg(not(unix))]` branch that relies on the default per-user ACLs and is the drop-in point if Windows protection is added later. No new dependency and no `unsafe` are introduced here. --------- Signed-off-by: Will Pfleger <pfleger.will@gmail.com> Co-authored-by: Hayt <41ea58f1e64c243627e8acde7c89be667052ee6e17d8f021c1195be4324ebf04@buzz.block.builderlab.xyz> (cherry picked from commit 5e4d0fe) * fix(desktop): launch Databricks OAuth from passive model discovery (block#5607) When a user's agent runtime is `buzz-agent` with no cached Databricks OAuth token, the desktop app's passive model-discovery surfaces were forbidden from launching interactive auth. Discovery failed silently, so the model dropdown showed only built-in fallback models behind a vague "Could not load live models for `databricks_v2`" note (reported internally by Nick and Jose). ## What changed Both discovery surfaces — the passive draft-form discovery and the explicit saved-model picker — now launch the browser OAuth flow, matching goose's behavior. The only behavioral difference between them is cooldown handling: - **Passive draft discovery** fires on every form-state change, so a failed, cancelled, or timed-out sign-in records a per-host cooldown (5 min) that suppresses re-popping the browser on the next keystroke. While the cooldown is active it returns the "sign-in required" guidance instead of relaunching. - **The explicit model picker** is a deliberate user action, so it always launches and clears any stale cooldown first. Safety rails: - A 150s hard timeout (`AUTH_FLOW_TIMEOUT`) bounds the whole interactive flow so an abandoned SSO tab fails discovery cleanly rather than wedging the dropdown. Success clears the cooldown; failure and timeout both record it. - `AuthCooldown` recovers from a poisoned lock rather than wedging every future sign-in on one panic. The frontend maps the terminal Databricks sign-in states to typed, actionable copy in `formatModelDiscoveryErrorStatus`: "sign-in required" is a muted note pointing at the picker and `buzz-agent auth databricks`; a failed or timed-out sign-in is a warning pointing at the explicit retry. Other Databricks failures fall through to the existing generic notice. ## Scope Changes are confined to Databricks discovery and its frontend status formatter — no `agent_models.rs` call sites are touched. The interactive-auth helper takes an injected timeout so the timeout/cooldown policy is unit-testable without a live browser. ## Deferred Cooldown keys use the raw trimmed `DATABRICKS_HOST`, while the catalog and OAuth cache normalize trailing slashes (`crates/buzz-agent/src/catalog.rs:96`, `crates/buzz-agent/src/llm.rs:2046`). So `https://workspace/` and `https://workspace` share credentials but get separate cooldown entries — an equivalent-spelling change to the host field mid-cooldown can re-pop passive OAuth once within the 5-minute window. Self-limiting (one extra browser launch, never auth corruption). Follow-up: a `trim_end_matches('/')` on the cooldown key plus an equivalent-host test, picked up with the coordinator migration if [block#5545](block#5545) ever merges. Signed-off-by: Will Pfleger <pfleger.will@gmail.com> Co-authored-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz> (cherry picked from commit 1ff98fa) * fix(relay): stop panicking the ingest worker on reactions to project events (block#5294) A NIP-25 reaction whose target is a project root or project comment (kind 1621 issue, 1618 PR, or a kind-1 comment on one) carries no h tag, so channel_id is None on the reaction write path. The conformance-trace emission asserted a channel was always present: channel: channel_label(channel_id.expect("reaction path has channel")), so the worker panicked at ingest.rs:2824. The row was inserted before the panic, so the client saw a failed request for a persisted event and retried, and the duplicate branch carried the same expect, head-of-line blocking a durable publish queue forever. Mirror the message write's three-way split at the same seam: (Some, true) -> WriteInsert, (Some, false) -> WriteDuplicate, (None, _) -> WriteInsertGlobal. The conformance vocabulary already models channel-less writes; only the reaction path was missing it. Closes block#4936 Signed-off-by: Taksh <takshkothari09@gmail.com> Signed-off-by: Ravneet Arora <rarora@squareup.com> (cherry picked from commit 16b7ae7) * Harden shared agent instruction review (block#4220) ## Summary - render shared-agent instructions as literal text so Markdown cannot conceal spoiler contents, link destinations, or image sources - reject non-reviewable Unicode controls at every agent-definition boundary while preserving legitimate rendered emoji sequences - verify shared catalog event IDs and signatures before trusting authorship, coordinates, pagination, or executable content - preserve the exact system-prompt bytes between review and execution instead of silently stripping or normalizing content ## Security rationale Shared system prompts are executable configuration. Previously, catalog prompts were projected through the chat Markdown renderer, which could hide text, replace link destinations with benign labels, and turn image syntax into remote loads. Zero-width and bidirectional controls could also make reviewed text differ from what the agent executes. This change establishes a review invariant: the prompt a user sees is the prompt the agent executes. Definitions that cannot be reviewed faithfully are rejected rather than rewritten. Catalog events must also pass Nostr ID/signature verification before they can claim a publisher, coordinate, or cursor. ## What changed - catalog instructions render as exact literal text rather than rich Markdown - catalog relay events are verified on a fresh wire-shaped object before paging, coordinate selection, attribution, or projection - forged content, pubkeys, signatures, and invalid newer heads are ignored and cannot shadow a valid signed definition - TypeScript catalog parsing rejects unsafe remote definitions before they reach the UI - shared Rust validation covers persona create/update/import, inbound relay sync, definition-less managed-agent sync, and catalog publication paths - definition-less managed agents now fail closed on local create, local update, and publication before persistence or relay retention - linked managed agents validate their local name while treating the persona definition as authoritative; their inert record-level prompt is not executed or published - names reject layout controls; prompts retain ordinary newlines and tabs - legitimate emoji composition is supported, including contextual VS16, ZWJ, skin-tone, family, flag, and keycap sequences - detached selectors/joiners, bidirectional controls, tag characters, zero-width concealment, and other default-ignorables remain rejected - names are bounded to 128 characters and prompts to 64 KiB - contributor guidance documents the byte-for-byte review requirement for future sharing paths Validation reports the offending code point and never silently removes it. ## E2E recording [buzz-shared-agent-security-e2e.webm](https://github.com/user-attachments/assets/44d6b75f-0877-490f-bda4-a716fae3f700) The recording demonstrates: - a safe definition remains visible - a prompt containing zero-width `U+200B` is rejected - a name containing bidi override `U+202E` is rejected - the prompt is preserved exactly - spoiler, link, and image syntax remains literal and does not render or load ## Verification Passed locally: - `just test`: all 10 unit and Docker-backed integration stages - desktop frontend unit suite: 4,295 tests - persona catalog relay unit suite: 32 tests, including forged-event and cursor-shadowing cases - focused Rust definition-validation coverage: 3 local create/update tests and 6 publication-filtered tests - complete desktop Tauri library suite after rebase: 2,263 passed, 14 ignored, 0 failed - desktop Tauri clippy with warnings denied and Rust formatting - complete agent Playwright spec: 34 tests - the exact formerly failing `inbox-edit` immediate-attachment smoke test after rebase: 1 test - focused shared-agent publish, literal-review, hidden-control, signature, and cross-member import Playwright coverage - desktop E2E production build and TypeScript typecheck - changed-file formatting/lint and file-size ratchet - pre-commit secret scan and DCO signoff The branch was rebased onto current `main`, which includes the upstream attachment-button label fix. Fresh post-rebase GitHub CI is green for every required and selected check: Desktop Core, all four Desktop Smoke E2E shards, both Desktop E2E Integration shards and their aggregate, Desktop E2E Relay, Desktop Build (macOS), Windows Rust, Rust Lint, DCO, security scanners, and Desktop Release Candidate. The previously failing `Desktop Smoke E2E (3)` shard now passes. The repository-wide desktop check also reports existing CSS formatting/`!important` findings in `components.css` and `terminal.css`; neither file is changed by this PR. GitHub's Desktop Core lint and format stage passes on the rebased branch. --------- Signed-off-by: Alex Rosenzweig <arosenzweig@squareup.com> (cherry picked from commit a96af89) * fix: reject non-reviewable Unicode formatting * fix: close upstream security review gaps * fix: authorize inbound agent sync events --------- Signed-off-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Taksh <takshkothari09@gmail.com> Signed-off-by: Ravneet Arora <rarora@squareup.com> Signed-off-by: Alex Rosenzweig <arosenzweig@squareup.com> Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Co-authored-by: Hayt <41ea58f1e64c243627e8acde7c89be667052ee6e17d8f021c1195be4324ebf04@buzz.block.builderlab.xyz> Co-authored-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz> Co-authored-by: Taksh Kothari <takshkothari09@gmail.com> Co-authored-by: Alex Rosenzweig <64241648+shellz-n-stuff@users.noreply.github.com>
Co-authored-by: Taylor Ho <taylorkmho@gmail.com> Signed-off-by: Taylor Ho <taylorkmho@gmail.com> * origin/main: Polish glass Huddle tray behavior (#5590) test: add deterministic desktop release smoke (#5699) fix(channels): return complete member rosters (#5765) feat(desktop): add Inbox message delete action (#5779) fix(desktop): enforce agent mention authorization at send boundaries (#5681) fix(desktop): route compact preview geometry fixture through media proxy (#5799) Make workflow run history authoritative in Desktop (#5780) fix(desktop): more compact "compact" link previews (#5629) Fix mobile composer input regressions (#5594) Add mobile community invites (#5641) Harden shared agent instruction review (#4220) chore(release): release Buzz Desktop version 0.5.11 (#5714) feat(acp): report standard adapter usage (#4950) fix(mobile): settle hydrated threads on latest reply (#4702) perf(desktop): persist channel snapshot hash (#5684) Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
Summary
Security rationale
Shared system prompts are executable configuration. Previously, catalog prompts were projected through the chat Markdown renderer, which could hide text, replace link destinations with benign labels, and turn image syntax into remote loads. Zero-width and bidirectional controls could also make reviewed text differ from what the agent executes.
This change establishes a review invariant: the prompt a user sees is the prompt the agent executes. Definitions that cannot be reviewed faithfully are rejected rather than rewritten. Catalog events must also pass Nostr ID/signature verification before they can claim a publisher, coordinate, or cursor.
What changed
Validation reports the offending code point and never silently removes it.
E2E recording
buzz-shared-agent-security-e2e.webm
The recording demonstrates:
U+200Bis rejectedU+202Eis rejectedVerification
Passed locally:
just test: all 10 unit and Docker-backed integration stagesinbox-editimmediate-attachment smoke test after rebase: 1 testThe branch was rebased onto current
main, which includes the upstream attachment-button label fix. Fresh post-rebase GitHub CI is green for every required and selected check: Desktop Core, all four Desktop Smoke E2E shards, both Desktop E2E Integration shards and their aggregate, Desktop E2E Relay, Desktop Build (macOS), Windows Rust, Rust Lint, DCO, security scanners, and Desktop Release Candidate. The previously failingDesktop Smoke E2E (3)shard now passes.The repository-wide desktop check also reports existing CSS formatting/
!importantfindings incomponents.cssandterminal.css; neither file is changed by this PR. GitHub's Desktop Core lint and format stage passes on the rebased branch.