镜像站点 · 本页由第三方 GitHub 只读镜像提供,非 GitHub 官方站点,不接受任何登录或凭据输入。前往 github.com
Skip to content

fix(security): close workflow, Canvas, upload, and broker gaps - #2360

Merged
jmecom merged 4 commits into
mainfrom
jm/security-audit-fixes
Nov 4, 2026
Merged

jmecom merged 4 commits into
mainfrom
jm/security-audit-fixes

Conversation

@jmecom

@jmecom jmecom commented Nov 4, 2026

Copy link
Copy Markdown
Contributor

Close four security findings at their existing authorization and data-validation owners:

  • AST-D117-C01 — workflow authority: Claude, Codex, Pi, and Goose reject workflow-generated mentions before creating or steering sessions. Rendered output does not authenticate which text came from the owner’s template versus trigger data. This intentionally stops automatic activation from existing workflow mentions, including static templates; the owner or a directly attested agent must send a separate request. Workflow display and ordinary direct requests remain supported.
  • AST-D135-C01 — development broker: reject effective non-loopback Vite host settings, including CLI overrides, before loading the signing identity. Unpinned fixtures retain their normal hosting options.
  • AST-D130-C01 — attachment integrity: native successful upload descriptors must match the digest and size of the final uploaded bytes, including converted media. Preserve non-success HTTP responses and federated authorization behavior.
  • AST-D120-C01 — Canvas query binding: require kind 40100 and exactly one canonical matching channel tag before selecting a Canvas head. Invalid newer results cannot suppress a valid returned head. This complements fix(canvas): keep todo edits from approving unrelated instructions #2350’s approval-preservation fix; it does not establish freshness against replay of an older valid Canvas at the same coordinates.

Validation

  • Each finding has a regression that failed before its repair and passed afterward.
  • Shared workflow authorization and all four runtime suites: 809 passed, one existing skip. Claude/Codex fixtures verify rejected workflow mentions start no process or session; a direct owner request still runs.
  • Native relay owner tests: 95 passed, one existing ignored performance probe. Actual ffmpeg conversion, federated uploads, successful descriptors, and HTTP-error preservation exercised over loopback with synthetic identities.
  • Canvas owner/project tests: 35 passed. Seven genuine signed wrong-kind/channel/tag-shape cases rejected.
  • Broker checks use installed Vite configuration resolution and server construction; unsafe settings are rejected before identity access, and a real loopback endpoint succeeds with a synthetic identity.
  • Combined pre-push gate at 43c880f: TypeScript, 9,244 JavaScript tests (16 existing skips), design checks, and workspace Clippy with warnings denied passed. Independent review of each repair found no blockers.

Production: +62/−129 lines; tests: +330/−451 lines. Removed tests covered deleted workflow delegation/profile-fetch machinery; direct authorization, cancellation, and workflow attribution coverage remain.

Deferred acceptance

Draft pending hosted CI and human acceptance. Packaged GUI/IPC journeys and Windows/Linux runtime acceptance have not been exercised. No browser journeys were added or removed. The workflow repair closes automatic promotion of workflow output into a request; it does not claim immunity to malicious history during a later owner-authorized turn.

jmecom and others added 4 commits November 3, 2026 16:09
Rendered workflow messages cannot distinguish owner-authored instructions from trigger substitutions. Deny their automatic promotion to requests across Claude, Codex, Pi and Goose while preserving direct owner and NIP-OA-attested requests and workflow attribution.

Co-authored-by: Codex <noreply@openai.com>
Signed-off-by: Jordan Mecom <jm@squareup.com>
Validate the signed kind and unique canonical channel tag before selecting a Canvas head. Reject invalid coordinates in history as well, preventing unrelated owner-signed content from becoming Solo instructions.

Co-authored-by: Codex <noreply@openai.com>
Signed-off-by: Jordan Mecom <jm@squareup.com>
Reject successful native upload descriptors whose digest or size differs from the file the host hashed, signed and uploaded. Preserve relay failure responses and verify ordinary, converted and federated upload paths.

Co-authored-by: Codex <noreply@openai.com>
Signed-off-by: Jordan Mecom <jm@squareup.com>
Reject exposed Vite host overrides before loading the development signing identity. Preserve unpinned fixture hosting and packaged builds.

Co-authored-by: Codex <noreply@openai.com>
Signed-off-by: Jordan Mecom <jm@squareup.com>
@jmecom
jmecom marked this pull request as ready for review November 4, 2026 00:31
@jmecom
jmecom requested a review from a team as a code owner November 4, 2026 00:31

@wpfleger96 wpfleger96 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Reviewed at 43c880f. All four fixes close the stated holes without breaking the paths I traced: workflow-tagged events are rejected at the single fromOwner gate used by all four runtimes, before any session start or steer; the descriptor check binds to the hash and size of the final (post-conversion) bytes and matches the relay's BlobDescriptor shape, which never rewrites stored bytes; every Canvas writer in the app, CLI and harness emits exactly one h tag, so the stricter filter drops nothing legitimate; and the vite.config/Justfile dev path never sets a non-loopback host. LGTM.

@jmecom
jmecom merged commit 063fccd into main Nov 4, 2026
41 checks passed
@jmecom
jmecom deleted the jm/security-audit-fixes branch November 4, 2026 00:54
tlongwell-block added a commit that referenced this pull request Nov 4, 2026
* origin/main: (26 commits)
  fix(identity): read FI backup status through the native forwarder (#2344)
  fix(relay): show the relay's reason when it refuses a write (#2031)
  perf(relay): look up archived identities in a per-snapshot set (#2359)
  fix(messages): keep link previews clear of message hover actions (#2351)
  fix(security): close workflow, Canvas, upload, and broker gaps (#2360)
  fix: prevent false leave failures after membership updates (#2310)
  feat(bestie): remove your Bestie, and let remote agents be Bestie after a warning (#2041)
  feat(messages): open the sender's profile from the byline name (#2253)
  feat(native-mcp): one-command launcher and MCP registration for agent driving (#2272)
  perf(composer): stop per-keystroke chip re-renders and reparses in long rich drafts (#2251)
  fix(agents): prevent history from forging prompt requests (#2223)
  Enable automatic key backup in preview releases (#2325)
  feat(read-state): n-blob NIP-RS sync with a 30-day floor, frontier only (#1286)
  feat(huddles): provide read-only huddleStatus for optional consumers (#2327)
  feat(search): show verified names in people search (#2322)
  Fix voice-note sending and restore waveform previews (#2283)
  Preserve Solo queued messages across restarts (#1704)
  feat(plugins): resolve attachment renderers by order, like panels and links (#2284)
  fix(channels): keep muted DM names from becoming bold (#2184)
  fix: command-click opening desktop message links in the browser (#2288)
  ...

Co-authored-by: Codex <noreply@openai.com>
Co-authored-by: Tyler Longwell <tlongwell@squareup.com>
Signed-off-by: Tyler Longwell <tlongwell@squareup.com>

# Conflicts:
#	src/bundled/huddles/index.tsx
#	src/features/huddle/service.test.ts
kalvinnchau added a commit that referenced this pull request Nov 4, 2026
Restores workflow-posted agent mentions by reverting only the workflow
authorization portion of #2360. Verified relay workflow messages can
again start or steer agents when the workflow belongs to the receiving
agent's owner or a cryptographically attested sibling and explicitly
targets that agent.

Restores the prior shared authorization helpers, Claude/Codex/Pi/Goose
wiring, associated regression tests, and workflow documentation. The
Canvas query-binding, upload-integrity, and development-broker fixes
from #2360 remain unchanged. Later workflow presentation documentation
changes are preserved.

This intentionally restores the previous trust behavior: workflow
provenance and explicit targets do not authenticate the boundary between
owner-authored template text and trigger-controlled substitutions. The
template/substitution security issue identified in #2360 is therefore
reopened; this PR is a compatibility revert, not a fix for that issue.

Validation: diff/whitespace and scope inspection completed. Local tests
and pre-push checks were skipped at the requester's explicit direction;
hosted CI is the validation gate. Restored tests cover workflow
authority, explicit targets, sibling ownership, and cancellation.
Packaged application behavior and human acceptance remain unverified.
Draft pending CI and review.


Verified 1:1 restoration at `f51ae07ca`: every added and deleted line
across all 13 changed files matches the exact inverse of #2360’s
workflow portion. No new implementation changes were introduced; later
unrelated changes and the other security fixes are preserved.


Following discussion with the team, we will temporarily preserve the
existing workflow-mention behavior to avoid introducing a regression. We
will follow up with a broader discussion of workflow authorization and
the long-term approach.

Signed-off-by: Kalvin Chau <kalvin@block.xyz>
Co-authored-by: hobbes <195dacc673a4fc6706b1ee8e9d1b0b75ad42f8e89e945e5d3df4aaa00bbec135@buzz.block.builderlab.xyz>
joahg added a commit that referenced this pull request Nov 4, 2026
## What

`ctx.agents.harness` gives an agent type that a plugin brings the Buzz
side of each turn, built the way the bundled harnesses build it. The
harness still owns its process, sessions and saved state.

- `conversation(delivery, scope)` admits a mention, plugin wake or
attention wake and freezes where it goes: the key (the thread, or the
channel; a DM or Solo chat is always one), the root, whether replies go
at the top level, and a `where` label for titles. It returns `undefined`
when the harness must ignore the delivery: any other trigger, a mention
that isn't the owner's own or attested by them (workflow mentions never
are, per #2360), no channel, or a wake with no conversation to go to.
- `systemPrompt(delivery, { scope, cwd, instructions, tools? })` is
Buzz's system prompt: base prompt, session model, instructions, teams,
`core` memory and repositories folder. `tools: false` adds a section for
sessions without Buzz tools, whose final message the harness posts as
the reply.
- `turnPrompt(delivery, conversation, { cursor? })` returns `{ text,
cursor, restore }`: what the session has not been shown, the event,
plugins' notes and the tools' end-of-turn note. The cursor is opaque:
the plugin saves it with its session and hands it back next turn (none
means a new session). In a channel-scoped conversation it keeps a mark
per thread, like claude-code's `contextRoot`, so a turn in one thread
never hides another thread's earlier messages. A resumed turn gets the
resume note. `restore()` puts the notes back for a turn that never
started, and asking again takes them again.
- `standalonePrompt(agent, text)` gives a watch's or timer's turn its
routing context.
- `tools(agent, conversation?, { read? })` answers the Buzz tools over
MCP, for `ctx.host.serveMcp`.
- `reply(agent, conversation, text, { notifyOwner? })` posts as the
agent in the thread, or at the top level.

The author package also exports the types a harness needs:
`AgentHandle`, `Delivery`, `StandaloneTurn`, `TurnResult`, `McpHandler`,
`HostMcpServer` and the kit's types. `docs/plugin-architecture.md`
documents the kit.

## Why

An installed plugin can already register an agent type and start its CLI
with `host.spawn` and `host.serveMcp`. But the Buzz side of a turn only
existed inside app internals the author package doesn't expose, so every
harness had to be bundled. The first user is an Amp harness that ships
as a plugin in squareup/agents `buzz-plugins` (AGNTOPS-716), replacing
the bundled approach in #1539. Its agents can also run their Amp threads
in orbs.

## Authority

The kit grants nothing `ctx.agents` and `ctx.host` don't already give
the plugin:
- It acts through the `AgentHandle` the plugin was delivered.
- It reads memory through the owner's relay, as `agents.relay` already
allows.
- It reads files for uploads only through the `read` the plugin passes,
which comes from the plugin's own `host.files` grant. Without `read`,
uploading a local file fails.

Bundled harnesses are unchanged.

## Validation (at 58ed3ee, on main 063fccd)

- `src/features/agent-harness/kit.test.ts` has 17 tests covering:
  - owner gating, including refused workflow mentions and other triggers
  - conversation keys for thread, channel scope, DM and Solo
  - wakes: landing, and dropping one with nowhere to go
- turn prompts: unseen messages, notes, restore, asking again, resume,
per-thread marks in a channel conversation (thread A then thread B), an
unreadable cursor
  - the system prompt, with and without tools
  - reply tags in a thread and in Solo
- the tools handler: `tools/list`, `initialize` instructions, a message
with no `jsonrpc` field, and local-file reads going only through the
plugin's `read`
- `src/plugins/author.test.mjs` generates the author package and
typechecks a consumer that registers an agent type using the kit.
- `pnpm check` passes (Biome, tsc, design typecheck and design checks).
- Full `vitest run`: 11 failures across 9 files, all timeouts while the
machine was loaded. All but one pass when rerun alone. The exception,
`browser-host/media-preparation.integration.test.mjs` (tiled HEIC),
fails on main on this machine too.

Deferred: an in-app run of a plugin harness built on the kit. That will
be the Amp plugin, which needs this change in a desktop build.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Signed-off-by: Joah Gerstenberg <joah@squareup.com>
Co-authored-by: Hertz <c6741012416ef5c49a865db79682113385c5fb998e8c890234fb442b59e22748@buzz.block.builderlab.xyz>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
loganj added a commit that referenced this pull request Nov 4, 2026
The kit test "refuses a mention from anyone else, and triggers that are
not its" fails on main. Its workflow fixture was a message signed by the
owner with a `buzz:workflow` tag. #2364 wrote it against #2360, whose
`fromOwner` refused any event with that tag. #2399 merged first and
restored the older `fromOwner`, which admits any event the owner signs,
so the fixture became an owner message and the kit admitted it.

The kit itself still calls `fromOwner` without a workflow check, so a
real relay workflow mention is refused. Make the fixture that real
mention: signed by the community's workflow key, owned by the owner and
aimed at this agent, with the workflow key set on the session. The
bundled harnesses admit this event; the kit must not. Wiring the
bundled workflow check into the kit makes the test fail.

Before the refusal, assert that the bundled check admits the fixture,
so the negative case cannot pass for the wrong reason again. Say at the
call that the missing workflow check is deliberate, and fix the workflow
docs, which named only Claude Code and Codex.

Signed-off-by: Logan Johnson <loganj@squareup.com>
Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
loganj added a commit that referenced this pull request Nov 4, 2026
The kit test "refuses a mention from anyone else, and triggers that are
not its" fails on main. Its workflow fixture was a message signed by the
owner with a `buzz:workflow` tag. #2364 wrote it against #2360, whose
`fromOwner` refused any event with that tag. #2399 merged first and
restored the older `fromOwner`, which admits any event the owner signs,
so the fixture became an owner message and the kit admitted it.

The kit itself still calls `fromOwner` without a workflow check, so a
real relay workflow mention is refused. Make the fixture that real
mention: signed by the community's workflow key, owned by the owner and
aimed at this agent, with the workflow key set on the session. The
bundled harnesses admit this event; the kit must not. Wiring the
bundled workflow check into the kit makes the test fail.

Before the refusal, assert that the bundled check admits the fixture,
so the negative case cannot pass for the wrong reason again. Also assert that an owner mention carrying only a
`buzz:workflow` marker is still admitted: the marker neither grants
authority nor revokes the owner's own. Say at the
call that the missing workflow check is deliberate, and fix the workflow
docs, which named only Claude Code and Codex.

Signed-off-by: Logan Johnson <loganj@squareup.com>
Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
loganj added a commit that referenced this pull request Nov 4, 2026
…#2444)

🤖

## Summary
- `main` fails the JavaScript unit tests at
`src/features/agent-harness/kit.test.ts` ("refuses a mention from anyone
else, and triggers that are not its"). This PR makes it pass again
without changing what agents accept.
- The cause is two PRs that merged in an order neither one expected.
#2364 added the harness kit, which plugin agent types use to decide
which messages start a turn. Its test was written against the rule from
#2360, which refused every message that carried a workflow tag. #2399
merged before it and restored the older rule, which accepts any message
the agent's owner signs. The test's "workflow" message was signed by the
owner, so it was accepted.
- The kit's behavior has not changed. It still accepts only messages
from the owner or an agent the owner attests. A real relay workflow
message is signed by the community's workflow key, so the kit still
refuses it. Only the test fixture was out of date.

## Details
- The fixture is now a real relay workflow mention: signed by the
community's workflow key, owned by the agent's owner, and aimed at this
agent. The bundled harnesses (Claude Code, Codex, Pi, Goose) would run
this message.
- Before checking the refusal, the test first checks that the bundled
workflow check accepts the fixture. Without that step the negative test
could pass for the wrong reason again. Giving the kit the bundled
workflow check makes the test fail.
- The test also checks that an owner mention with only a `buzz:workflow`
marker is still accepted: the marker neither grants authority nor
removes the owner's own. This covers the same fix as
#2445, so this PR includes it.
- `docs/workflows.md` listed only Claude Code and Codex as accepting
workflow mentions. It now lists Pi and Goose too, and says that plugin
agent types do not.

## Deliberate divergence
A workflow that mentions an agent of a plugin-provided type is ignored,
while the same workflow wakes a Claude Code or Codex agent. This is
intentional. #2399 restored workflow mentions for the bundled harnesses
only to keep existing behavior working, and it says the
template/substitution issue from #2360 is still open. Plugin agent types
have never accepted workflow mentions, so nothing regresses. Accepting
them would give these agent types a known open issue. If the team wants
plugin agent types to accept workflow mentions after the follow-up
discussion on workflow authorization, that change belongs in its own PR.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Signed-off-by: Logan Johnson <loganj@squareup.com>
Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
jmecom added a commit that referenced this pull request Nov 6, 2026
Reapply the workflow authorization portion of #2360 that #2399 reverted. Preserve later Codex protection wiring and align the newer plugin harness tests with the restored shared gate.

Co-authored-by: jm-astra <06644cf6fec857393d5978909f511d88830f1a147ecdbec24e7b6f6e17da1534@buzz.block.builderlab.xyz>
Signed-off-by: Jordan Mecom <jm@squareup.com>
jmecom added a commit that referenced this pull request Nov 6, 2026
Restrict workflow-triggered requests to local agents in bb-block only.
Other communities, including bb-public, keep the checked workflow
delegation from #2399. This replaces the earlier version of this PR,
which reapplied #2360 in every community.

## Behavior

A new frontend build flag, `BUZZ_WORKFLOW_RESTRICTED_COMMUNITIES`, lists
the community origins where workflow-marked messages cannot authorize a
local agent request. The desktop preview workflow sets it to
`https://buzz.block.builderlab.xyz`, which follows the existing
`BUZZ_EXTERNAL_MCP_COMMUNITIES` and `BUZZ_OWNER_MCP_COMMUNITIES` flags.
When the flag is empty, which is the default for OSS and development
builds, behavior matches main.

In a listed community, `fromOwner` rejects any event carrying a
`buzz:workflow` tag before the owner, attestation or delegation checks
run. Claude, Codex, Goose, Pi and plugin harnesses all use this gate, so
owner-created static templates and scheduled workflows no longer start
or steer these agents there. Workflow messages stay visible, and direct
owner requests and attested-sibling requests are unaffected. While any
community is listed, an agent whose community origin is unknown is also
restricted. That case already fails today, because delegation needs the
community's workflow authority.

The community comes from the agent's relay snapshot `origin`, which is
the normalized community origin and not the `origin:viewer` storage
partition. `AgentReads` now exposes it as `origin`.

This change does not prevent workflow output from influencing a later
authorized turn.

## Validation

- Unit tests cover the following: workflow mentions are denied in the
listed community and still delegated in an unlisted one; owner-signed
workflow-marked messages are denied there while direct and attested
requests run; an unknown community is restricted while the list is
non-empty, and the empty list falls back to main's behavior.
- A Claude runtime test checks the call-site wiring end to end. An
owner's trusted workflow mention runs when another community is listed
and is refused when the agent's own community is listed.
- `tsc --noEmit`, Biome, and the Claude, Codex, Goose, Pi,
agent-harness, agents and relay suites passed: 5,844 passed, 22 skipped.
The pre-commit and pre-push hooks passed without bypass.
- A production `vite build` with the flag set compiles the bb-block
origin into the gate.
- Native app execution, packaged preview builds and human acceptance
remain unverified.

For acceptance on a preview build, trigger an owner-created workflow in
bb-block that mentions a local agent and confirm it appears without
starting a turn. Then repeat it in a bb-public community and confirm the
agent runs. A direct owner request should run in both.

Signed-off-by: Jordan Mecom <jm@squareup.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.

3 participants