fix(acp): gate relay-signed workflow messages on their attributed author - #6129
Conversation
Workflow send_message actions are signed by the relay keypair, so the harness's inbound author gate saw the relay pubkey — never the workflow owner — and dropped every scheduled wake-up under the default respond-to=owner-only before the p-tag mention check could run. Scheduled automations fired and posted their messages, but never woke any agent. Recognize relay-signed buzz:workflow events (author == the relay's NIP-11 'self' pubkey, fetched once at startup) and gate them on their attributed author instead: the workflow owner's p tag, which workflow_sink.rs pushes first and whose channel access the relay has already verified. Fail-closed everywhere: no NIP-11 self pubkey, no buzz:workflow tag, a non-relay signer, or no p tag all fall back to the plain author gate. A member forging the buzz:workflow tag on their own event gains nothing — the exemption requires the relay's signature, which the relay verifies on submission. The attributed author goes through the same owner/sibling/ allowlist policy as a direct author, so a workflow owned by a random channel member still cannot wake an owner-only agent. Co-authored-by: Luke Tornquist <tornquist@squareup.com> Signed-off-by: Luke Tornquist <tornquist@squareup.com>
…-owner tag Review feedback: gating on the first p tag made attribution positional — an implicit cross-crate contract that could drift if workflow_sink ever reordered tags, and it conflated mention p tags with ownership. The relay now names the workflow owner (workflow.owner_pubkey — the pubkey that created the workflow, already what the executor passes as author_pubkey) explicitly via a buzz:workflow-owner tag, and the ACP gate reads only that tag. Mention p tags no longer participate in attribution at all: who is @mentioned in the message text has no bearing on whose authority the gate evaluates. A malformed owner value (not 64-hex) is rejected, falling back to the plain author gate. Co-authored-by: Luke Tornquist <tornquist@squareup.com> Signed-off-by: Luke Tornquist <tornquist@squareup.com>
|
The overall fix and explicit Please make the delegated-author path require all of the following, failing closed otherwise:
In particular, please add focused negative tests for duplicate markers, marker value/shape mismatch, duplicate owners, owner tags with extra fields, invalid owner pubkeys, wrong kind, tampered signatures, wrong relay author, and missing/invalid NIP-11 identity. A compact implementation pattern is to collect tags by exact key, require a singleton, then compare the entire marker slice and parse the entire owner value with Scope note, not a blocker for this PR: this fixes online workflow wake. It does not add offline pending-work discovery or cross-event workflow retry idempotency; those should be handled separately rather than broadening this patch. |
…local signature check Review feedback: the delegated-author path accepted any tag whose key was buzz:workflow and used find_map for the owner, so ambiguous or malformed provenance could still qualify. workflow_attributed_author now requires, failing closed otherwise: - kind 9 with a locally verified event signature (never relies on the relay having verified upstream); - event.pubkey equal to a syntactically valid NIP-11 self key (parsed via nostr::PublicKey, not string compare); - exactly one tag exactly equal to ["buzz:workflow", "true"]; - exactly one tag exactly equal to ["buzz:workflow-owner", <pubkey>] whose value parses as a full pubkey; - no duplicate marker/owner tags and no extra fields on either. p tags continue to play no role in authority. Adds negative tests for duplicate markers, marker value/shape mismatch, duplicate owners (same and differing values), owner tags with extra fields, invalid owner pubkeys, wrong kind, tampered signatures, wrong relay author, and missing/invalid NIP-11 identity. Co-authored-by: Luke Tornquist <tornquist@squareup.com> Signed-off-by: Luke Tornquist <tornquist@squareup.com>
|
Addressed in dd2f29f — thanks, agreed on all points.
Implementation follows the pattern you suggested: collect tags by exact key, require a singleton, compare the entire marker slice, and parse the entire owner value. New negative tests cover: duplicate markers; marker value mismatch, missing value, and extra fields; duplicate owners (same and differing values); owner tag with extra fields; invalid owner pubkey; wrong kind (kind 1); tampered content with intact tags/pubkey (signature check); wrong relay author; and missing plus syntactically invalid NIP-11
Agreed on the scope note — offline pending-work discovery and workflow retry idempotency stay out of this PR. |
…p-repair * origin/main: Rename Bumble agent to Pollen (#5864) fix(desktop): resolve agent profiles through one archive-aware selector (#5706) fix(acp): gate relay-signed workflow messages on their attributed author (#6129) fix(acp): replace Goose native system prompt (#5964) feat(workflows): add responsive library card actions (#6008) fix(desktop): enforce shared agent access across devices (#6086) feat(model-capabilities): drive model capabilities and labels from one manifest (#5597) docs: refresh agent development guidance (#6049) feat(mobile): require device authentication for identity export (#5116) fix(desktop): hide the offcanvas-collapsed sidebar so it stops painting over the community rail (#5947) Polish mobile message threads and composer (#5645) chore(release): release Buzz Desktop version 0.5.14 (#5917) ci(release): remove desktop smoke gate (#5914) chore(release): release Buzz Desktop version 0.5.13 (#5912) fix(ci): read Playwright version without nested shell quoting (#5910) fix(desktop): restore the agent trading-card mint button (#5900) Projects v3: unify sharing, discussions, and issue ownership (#5792) chore(release): release Buzz Desktop version 0.5.12 (#5903) fix(mobile): unwrap batched observer telemetry (#5805) perf(desktop): update active turns incrementally (#5897) Signed-off-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz> # Conflicts: # desktop/src-tauri/src/migration.rs
…graphy-staging * origin/main: Remove GitHub security advisory commitment (#6144) Rename Bumble agent to Pollen (#5864) fix(desktop): resolve agent profiles through one archive-aware selector (#5706) fix(acp): gate relay-signed workflow messages on their attributed author (#6129) fix(acp): replace Goose native system prompt (#5964) feat(workflows): add responsive library card actions (#6008) fix(desktop): enforce shared agent access across devices (#6086) feat(model-capabilities): drive model capabilities and labels from one manifest (#5597) Signed-off-by: morgmart <98432065+morgmart@users.noreply.github.com>
…gaps * origin/main: fix(desktop): align preview sidebar row styling (#6163) fix(desktop): repair dropped team membership links at boot and on edit (#5904) fix(cli): keep project replacement timestamps at or after wall clock (#5666) Remove GitHub security advisory commitment (#6144) Rename Bumble agent to Pollen (#5864) fix(desktop): resolve agent profiles through one archive-aware selector (#5706) fix(acp): gate relay-signed workflow messages on their attributed author (#6129) fix(acp): replace Goose native system prompt (#5964) feat(workflows): add responsive library card actions (#6008) fix(desktop): enforce shared agent access across devices (#6086) Signed-off-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
…arer-auth * origin/main: (21 commits) fix(desktop): bind presence retry timers (#6213) ci: make file-size policy a first-class gate (#6187) fix(desktop): eliminate mounted-view CPU burn — compositor-safe shimmer, observer append fast path, poll-tick disk reads (#6198) chore(release): release Buzz Desktop version 0.5.16 (#6191) fix(desktop): restore release agent mentions (#6182) test(desktop): cover exact workflow batch limit (#6168) chore(release): release Buzz Desktop version 0.5.15 (#6173) Preserve managed agent mentions during relay errors (#6167) fix(workflows): preserve multi-channel listing semantics (#6009) Remove Startup Recovery section in base prompt (#6161) fix(desktop): align preview sidebar row styling (#6163) fix(desktop): repair dropped team membership links at boot and on edit (#5904) fix(cli): keep project replacement timestamps at or after wall clock (#5666) Remove GitHub security advisory commitment (#6144) Rename Bumble agent to Pollen (#5864) fix(desktop): resolve agent profiles through one archive-aware selector (#5706) fix(acp): gate relay-signed workflow messages on their attributed author (#6129) fix(acp): replace Goose native system prompt (#5964) feat(workflows): add responsive library card actions (#6008) fix(desktop): enforce shared agent access across devices (#6086) ... Signed-off-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz> # Conflicts: # CHANGELOG.md
…hor (block#6129) ## Problem Scheduled workflow `send_message` actions fire and land in the channel with correct `p` tags for the mentioned agents — but the agents never wake. The wake-up is silently dropped. **Root cause:** workflow messages are signed by the **relay keypair** (`workflow_sink.rs` signs with `state.relay_keypair`), so `event.pubkey` is the relay's pubkey, not the workflow owner. In `buzz-acp`, the inbound author gate (`author_allowed`) runs **before** the `p`-tag mention check. Under the default `respond_to = owner-only`, the relay pubkey is neither the owner nor a sibling, so every workflow wake-up dies at the gate with a debug-level `"inbound author gate — dropping event"`. The relay-side comment even says the mention `p` tags exist *"so mentioned agents are woken (wake is p-tag gated)"* — but wake is also author-gated, and that path was missed. ## Fix Gate relay-signed workflow messages on their **attributed author** — the pubkey that created the workflow — instead of the relay pubkey: - **Relay:** `workflow_sink.rs` now emits an explicit `buzz:workflow-owner` tag carrying `workflow.owner_pubkey` (the workflow creator, which the executor already passes as `author_pubkey` and whose channel access the relay verifies before emitting). Ownership is never inferred from `p`-tag order; mention `p` tags play no role in attribution. - **Harness:** at startup, `buzz-acp` fetches the relay's NIP-11 `self` pubkey (new `RestClient::fetch_relay_self`, public `/info` endpoint). Best-effort: fetch failure just logs a warning and preserves pre-fix behavior. - **Gate:** an event that is (a) authored by the relay `self` key, (b) tagged `buzz:workflow`, and (c) carries a well-formed `buzz:workflow-owner` pubkey is gated on that owner, through the exact same owner/sibling/allowlist policy as a direct author. ## Security notes (all fail closed) - No NIP-11 `self` pubkey → no exemption. - `buzz:workflow` / `buzz:workflow-owner` tags on a non-relay-signed event → ignored (a member cannot forge the exemption; the relay verifies signatures on submission and only the relay holds its key). - Relay-signed event without the tags, or with a malformed owner value (not 64-hex) → plain author gate. - Who is @mentioned in the message has no bearing on whose authority is evaluated. - A workflow owned by a random channel member still cannot wake an owner-only agent — the owner's pubkey must pass the same policy. ## Testing - 7 unit tests (`workflow_attributed_author_tests`) covering attribution, fail-closed paths, p-tag independence, malformed owner values, and the forgery case. - Extended the PG-gated `workflow_send_message_p_tags_mentioned_member` integration test to assert the `buzz:workflow-owner` tag. - `cargo test -p buzz-acp`: 785 passed, 0 failed. `cargo test -p buzz-relay --lib workflow_sink`: 17 passed. Clippy + fmt clean. (9 pre-existing `buzz-relay` failures in unrelated `api::media`/`api::admin` tests fail identically on the base commit without this change.) Found while debugging scheduled automations in a Buzz review-pipeline channel: two cron workflows fired daily @mentions at agents that never responded, while direct human @mentions woke them instantly. --------- Signed-off-by: Luke Tornquist <tornquist@squareup.com> Co-authored-by: Fizz <3a9f8a30fbb462abec1e2977b2280a7ae50c7ff794433790be15bd48bfd52d0b@buzz.block.builderlab.xyz> Signed-off-by: bhargavms <bhargav.m@ewa-services.com>
Problem
Scheduled workflow
send_messageactions fire and land in the channel with correctptags for the mentioned agents — but the agents never wake. The wake-up is silently dropped.Root cause: workflow messages are signed by the relay keypair (
workflow_sink.rssigns withstate.relay_keypair), soevent.pubkeyis the relay's pubkey, not the workflow owner. Inbuzz-acp, the inbound author gate (author_allowed) runs before thep-tag mention check. Under the defaultrespond_to = owner-only, the relay pubkey is neither the owner nor a sibling, so every workflow wake-up dies at the gate with a debug-level"inbound author gate — dropping event".The relay-side comment even says the mention
ptags exist "so mentioned agents are woken (wake is p-tag gated)" — but wake is also author-gated, and that path was missed.Fix
Gate relay-signed workflow messages on their attributed author — the pubkey that created the workflow — instead of the relay pubkey:
workflow_sink.rsnow emits an explicitbuzz:workflow-ownertag carryingworkflow.owner_pubkey(the workflow creator, which the executor already passes asauthor_pubkeyand whose channel access the relay verifies before emitting). Ownership is never inferred fromp-tag order; mentionptags play no role in attribution.buzz-acpfetches the relay's NIP-11selfpubkey (newRestClient::fetch_relay_self, public/infoendpoint). Best-effort: fetch failure just logs a warning and preserves pre-fix behavior.selfkey, (b) taggedbuzz:workflow, and (c) carries a well-formedbuzz:workflow-ownerpubkey is gated on that owner, through the exact same owner/sibling/allowlist policy as a direct author.Security notes (all fail closed)
selfpubkey → no exemption.buzz:workflow/buzz:workflow-ownertags on a non-relay-signed event → ignored (a member cannot forge the exemption; the relay verifies signatures on submission and only the relay holds its key).Testing
workflow_attributed_author_tests) covering attribution, fail-closed paths, p-tag independence, malformed owner values, and the forgery case.workflow_send_message_p_tags_mentioned_memberintegration test to assert thebuzz:workflow-ownertag.cargo test -p buzz-acp: 785 passed, 0 failed.cargo test -p buzz-relay --lib workflow_sink: 17 passed. Clippy + fmt clean. (9 pre-existingbuzz-relayfailures in unrelatedapi::media/api::admintests fail identically on the base commit without this change.)Found while debugging scheduled automations in a Buzz review-pipeline channel: two cron workflows fired daily @mentions at agents that never responded, while direct human @mentions woke them instantly.