Skip to content

fix(acp): gate relay-signed workflow messages on their attributed author - #6129

Merged
TheSentinel454 merged 3 commits into
mainfrom
fizz/workflow-wake-author-gate
Aug 17, 2026
Merged

fix(acp): gate relay-signed workflow messages on their attributed author#6129
TheSentinel454 merged 3 commits into
mainfrom
fizz/workflow-wake-author-gate

Conversation

@TheSentinel454

@TheSentinel454 TheSentinel454 commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

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.

Fizz and others added 2 commits August 17, 2026 10:06
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>
@wesbillman

Copy link
Copy Markdown
Collaborator

The overall fix and explicit buzz:workflow-owner attribution look right for the live wake bug. One security-boundary hardening is needed before this is merge-ready: workflow_attributed_author currently accepts any tag whose key is buzz:workflow and uses find_map for the owner, so ambiguous or malformed provenance can still qualify.

Please make the delegated-author path require all of the following, failing closed otherwise:

  • kind 9 with a valid event signature;
  • event.pubkey equal to a syntactically valid NIP-11 self key;
  • exactly one tag equal to ["buzz:workflow", "true"];
  • exactly one tag equal to ["buzz:workflow-owner", "<valid pubkey>"];
  • no duplicate marker/owner tags and no extra fields.

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. p tags should continue to have no role in authority.

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 nostr::PublicKey::from_hex. This removes ambiguous signed-event semantics without expanding the PR's scope.

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>
@TheSentinel454

Copy link
Copy Markdown
Contributor Author

Addressed in dd2f29f — thanks, agreed on all points.

workflow_attributed_author now requires all of the following, failing closed otherwise:

  • kind 9 with a locally verified event signature (event.verify() — the gate no longer relies on the relay having verified upstream);
  • event.pubkey equal to a syntactically valid NIP-11 self key (parsed via nostr::PublicKey::from_hex, compared as parsed keys rather than strings);
  • exactly one tag exactly equal to ["buzz:workflow", "true"] — the whole slice is compared, so duplicates, wrong values, missing values, and extra fields all disqualify;
  • exactly one tag exactly equal to ["buzz:workflow-owner", <pubkey>], destructured to a singleton two-element slice, with the owner parsed via nostr::PublicKey::from_hex — duplicates (even with identical values), extra fields, and invalid pubkeys all disqualify.

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. p tags continue to play no role in authority.

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 self (empty, non-hex, hex-length-but-not-hex).

cargo test -p buzz-acp: 792 passed, 0 failed. Clippy + fmt clean.

Agreed on the scope note — offline pending-work discovery and workflow retry idempotency stay out of this PR.

@TheSentinel454
TheSentinel454 marked this pull request as ready for review August 17, 2026 15:16
@TheSentinel454
TheSentinel454 requested a review from a team as a code owner August 17, 2026 15:16
@TheSentinel454
TheSentinel454 merged commit 54f1121 into main Aug 17, 2026
51 of 54 checks passed
@TheSentinel454
TheSentinel454 deleted the fizz/workflow-wake-author-gate branch August 17, 2026 17:29
wpfleger96 pushed a commit that referenced this pull request Aug 17, 2026
…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
morgmart added a commit that referenced this pull request Aug 17, 2026
…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>
wpfleger96 pushed a commit that referenced this pull request Aug 17, 2026
…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>
wpfleger96 pushed a commit that referenced this pull request Aug 18, 2026
…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
bhargavms pushed a commit to EWA-Services/buzz that referenced this pull request Aug 18, 2026
…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>
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.

2 participants