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

Authorize external application identities in Buzz - #2685

Merged
gilgtm merged 1 commit into
mainfrom
gil/nexus-bridge-owner-authorization
Nov 6, 2026
Merged

gilgtm merged 1 commit into
mainfrom
gil/nexus-bridge-owner-authorization

Conversation

@gilgtm

@gilgtm gilgtm commented Nov 5, 2026 •

Copy link
Copy Markdown
Contributor

Nexus keeps its own signing key, but signing in through the Block identity adapter also needs proof that the Buzz owner authorizes that key. Native desktop Identity settings now lets the owner enter an application's public key and create that proof with Buzz's existing signer.

Before signing, a confirmation shows the exact application identity and explains that its holder can direct the owner's Buzz coding agents to run commands, with no expiry or event-kind restrictions. Cancel signs nothing. The proof stays in the form until explicitly copied, and changing owner or leaving settings discards pending results. This adds no signing protocol, private-key export, relay calls, or persistent state.

Validation: 126 focused UI/host tests, TypeScript, Biome, native signer and production IPC signature tests passed at 95142e075eb6c17b4f780d5f1d7da4585e0271b6. Two independent review rounds have no remaining P0/P1 findings. The actual approved proof was accepted by Nexus's browser sign-in flow.

Native macOS foreground verification at this head shows the exact Nexus identity and full warning. Cancel receives initial focus; pointer Cancel, Escape, and Return dismiss the confirmation, return focus to Authorize identity, and produce no proof. Earlier native captures were inconclusive; no source defect or root cause was established. Native checks cover dark and light themes, a 756-pixel compact window and 120% interface size. The human independently opened and cancelled the confirmation, then confirmed Nexus’s signed-in state. The approved proof was already accepted by real Nexus enrollment. Full Nexus task delivery remains tracked in squareup/nexus#6810; it is separate from this Buzz proof-authoring flow. Shared owners reused: AlertDialog, Field/Input/Textarea, InlineHeader and SettingsGroup. Theme and interface size were restored after testing. No new browser journey was added or removed.

WebKit diagnostic fixture with synthetic public keys; no approval submitted:

WebKit diagnostic confirmation

Actual native macOS confirmation; existing public identities, private key masked, no approval submitted during this check:

Native macOS confirmation

Native light theme at 120% interface size in a compact window; no signing performed:

Compact enlarged native confirmation

buzz-review-completed

Reuse the existing owner signer from Identity settings with exact-key confirmation and explicit disclosure of delegated agent control.

Co-authored-by: Codex <noreply@openai.com>
Signed-off-by: Gil Birman <gil@squareup.com>
@gilgtm
gilgtm marked this pull request as ready for review November 6, 2026 00:44
@gilgtm
gilgtm requested a review from a team as a code owner November 6, 2026 00:44

@kazemek21 kazemek21 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Kyle reviewed this PR and the Nexus review-tracker notes. Approved.

Posted by Codex (gpt-6.1-sol) on behalf of kylez

@gilgtm
gilgtm merged commit 77f4f8c into main Nov 6, 2026
43 checks passed
@gilgtm
gilgtm deleted the gil/nexus-bridge-owner-authorization branch November 6, 2026 02:48
tlongwell-block added a commit that referenced this pull request Nov 6, 2026
* eva/huddle-audio-fixes: (29 commits)
  fix(huddle): keep calls flowing under stalled sends and multiple devices
  fix(channels): wrap unread conversation stepping around the sidebar (#2682)
  fix(navigation): let bare buzz:// open Buzz without a broken-link notice (#2718)
  fix(inbox): open DM thread rows as the thread (#2587)
  test(channels): complete the context navigation fixture (#2783)
  fix(channels): open a thread's channel at its root message from the #channel link (#2377)
  fix(people): find people by a pasted npub or hex key (#2396)
  test(bestie): open the narrow long-history thread with clickRow (#2723)
  Account-first onboarding and sign-in recovery (#2579)
  fix(agents): scroll the avatar dock with a mouse wheel (#2567)
  fix(avatars): keep Done in view when the avatar popup is short (#2706)
  feat(messages): highlight reactions you've made (#2709)
  feat(usage): move Usage from the sidebar into Settings (#2742)
  fix(messages): keep a playing row mounted in the channel timeline (#2695)
  Fix plugin status declaration closure for author consumers (#2643)
  fix(navigation): keep empty new chats out of Back history (#1617)
  [buzz-app] Recover remote agent message outcomes (#2542)
  feat(messages): name background send destinations and attach the files that fit (#2406)
  feat(drafts): clear a draft from its row (#2725)
  Authorize external application identities in Buzz (#2685)
  ...

Co-authored-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
Co-authored-by: Tyler Longwell <tlongwell@squareup.com>
Signed-off-by: Tyler Longwell <tlongwell@squareup.com>

# Conflicts:
#	docs/huddles.md
#	src-tauri/src/relay/huddle/audio.rs
kazemek21 added a commit that referenced this pull request Nov 6, 2026
…uzz tools (#2707)

## TL;DR

An approved external app (like Nexus) now gets the selected agent's
whole Buzz MCP tool catalog and native dispatcher, including tools
plugins add later, after one explicit "full access" approval. Before, it
got a hand-picked list of six tools with rewritten arguments. Older
narrow approvals still connect but grant no tools until the owner
approves full access again, so no existing grant widens on its own.

Initiative status:
https://g2.sqprod.co/apps/kylez-nexus-buzz-plugins-status

Fixes NEXUS-4262

## What changes

- **One scope, `agent`.** New links request `scope=agent`, and `agent`
can't be combined with other scopes. Rust pairing accepts only
`["agent"]` for new approvals (`valid_scopes`). Saved
`read`/`post`/`create` approvals still load, renew and resume, but grant
nothing.
- **No adapter.** `respondExternal` now passes `tools/list` and
`tools/call` to `respond` in `rpc.ts`, using the same
`sessionTools(added)` catalog as native sessions. Plugin-added tools
come from `addedTools.snapshot()` (the source native harnesses use) and
are re-read on every message, so enabling or disabling a plugin changes
the catalog live. The old policy map, argument rewriting and Bestie
injection are removed.
- **The agent's own authority.** The connection uses the agent's run
handle (`runners.handle(key, undefined, true)`), so it gets device
settings, plugin requests, Git access and handoff exactly as a native
run does. Plugin installs still go through the owner's confirmation.
Memory reads use the agent's `memoryListing`, like the native plugin
path. Protected agents are still refused. `plugin_disable` acts at once,
as it does natively, and the consent text says so.
- **Handoff with true provenance.** `self_handoff` goes through the
run's own handoff and `Agents.wakeIn`, but its `from` is `{ external:
true }`. `wakeSection` shows it as an `<app-wake>` section that says an
external app the owner approved sent it, and that it is not a message, a
watch or timer, or another of the agent's conversations.
- **Files where the agent reads them.** `send` with `path`/`files` calls
a new optional `AgentType.readFile`. Claude, Codex, Pi and goose
implement it with the reader their own tools use, in the agent's saved
workspace on this device. Relative paths start there; absolute and `~/`
paths reach any file the account can read, as they do natively, and the
consent text says so. A Claude or Codex agent bound to an execution
target refuses, because only a conversation's session opens that target.
A type without `readFile` refuses with the same message its tools give
natively. Closing the connection cancels a read in progress.
- **No implicit conversation.** Calls run with an empty context, so
every tool needs explicit channel or message IDs.
- **Consent.** The dialog now states full access plainly: future plugin
tools, Git access, waking the agent, Nexus workers, settings changes,
posting any file the agent can read (including outside its workspace),
and immediate plugin disabling. A legacy narrow link shows an
update-required notice with Deny only, which sends the normal denial
callback. `approve()` refuses non-`agent` requests as a second check.
Agent settings flags a saved legacy approval as no longer working.

## Companion-client contract (for the Nexus consumer)

| | Behavior |
| --- | --- |
| Request | `scope=agent` alone. Narrow scopes get the update-required
notice and only a denial callback. |
| `tools/list` | The full native catalog: 66 built-in tools with
settings, plus plugin tools. Each tool is listed as natively, plus
`_meta: {"buzz/requiredScopes": ["agent"]}`. Result `_meta`:
`buzz/grantedScopes` (`["agent"]`, or `[]` for a legacy approval) and
`buzz/supportedScopes: ["agent"]`. Cursors are rejected (single page). |
| `tools/call` without the grant | `isError` result, nothing dispatched,
`_meta["buzz/error"] = {code: "authorization_required", required_scopes:
["agent"]}`. |
| `tools/call` with the grant | Native result and errors, unchanged. |
| `self_handoff` | Native arguments. The wake reaches the agent as an
`<app-wake>` from the app. |
| `send` `path`/`files` | Read by the agent's type in its saved
workspace. Agents on an execution target, and types without a reader,
return their refusal. |
| `channel_create` | Takes native arguments. A private Solo chat with
the owner's Bestie must now pass `visibility: "private"`, `solo: true`
and `members` explicitly. Buzz no longer fills them in. |

An unupdated consumer that accepts only `read`/`post`/`create` will
reject `supportedScopes: ["agent"]`. Its upgrade path is to accept
`agent` and request `scope=agent`, which takes one owner approval.

## Overlap

These open PRs touch nearby files. This PR neither depends on nor
replaces any of them:

- #2260 and #2685 cover external pairing and identity.
- #2647 adds plugin skills to `session-tools`.
- #2622 covers Bestie settings.
- #1939, #1909, #1854, #2311 and #2070 change `service.ts`/`runner.ts`.
- #2610 has no shared files.

## Validation

Ran on a Linux Blox box against the current head:

- `tsc --noEmit` and `tsc -p tsconfig.design.json`;
- Biome on the touched files;
- Vitest for `src/buzz-mcp`, the external connection, dialog and
settings tests, `AgentsPage.test.tsx`, `service.test.ts`,
`plugin-wake.test.ts` and the Claude, Codex, Pi and goose runtime tests.

New tests cover the external handoff's trigger and `<app-wake>` wording,
file reads through the type's reader (target workspace, cancellation on
close, native refusal), the Claude and Codex workspace and
execution-target cases, a saved narrow approval that a link asks to
resume as `agent`, and memory read from the selected agent. The Rust
pairing tests (`host_mcp::pairing`) need the Tauri system libraries, so
they are left to PR CI. No browser journeys were added or removed.
Independent security reviews traced authorize, approve, resume, renew
and call, and found no path that grants tools without the `agent` scope.

Not done:

- The native desktop consent dialog and a live external connection have
not been exercised against this build, including a live `<app-wake>`
turn and a file read in a running Claude, Codex, Pi or goose agent.
- No human testing yet.
- The Nexus side of the contract is a separate change.

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

---------

Signed-off-by: Kyle Zemek <kylez@squareup.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
baxen pushed a commit that referenced this pull request Nov 9, 2026
…3032)

## Overview

**Category:** improvement
**User Impact:** Users can authorize an external application's identity
in Settings → Buzz → Security, alongside paired command-line tools.

**Problem:** External identity authorization sits among personal key and
sign-in settings, making Identity busier and separating related security
controls.

**Solution:** Move the existing form into Security. Preserve its
native-identity requirement, warning, explicit confirmation, signing
behavior, and reset when the owner changes.

**Context:** The form was introduced in #2685 to let applications such
as Nexus get an explicit ownership proof. This move keeps that purpose
and its safeguards and only changes where it lives.

## Changes

<details>
<summary>File changes</summary>

**src/app/IdentitySettings.tsx**
Remove the external authorization section.

**src/app/SecuritySettings.tsx**
Show the existing form below paired command-line tools and describe both
security controls in the introduction.

**src/app/Settings.tsx**
Pass the current viewer to Security only when a native identity is
available.

**src/app/ExternalIdentityAuthorization.tsx**
Point the identity-change recovery message to Security.

**src/app/ExternalIdentityAuthorization.test.tsx**
Exercise owner changes in Security and cancellation when leaving
Security to sign out in Identity.

**src/app/SecuritySettings.test.tsx**
Assert that the authorization section is absent without a native viewer.

**docs/buzz-help.md**
Update the user-facing settings path.

**docs/identity.md**
Update the external identity authorization documentation.

</details>

## Reproduction steps

1. Open Settings → Buzz → Identity in a native desktop build. External
identity authorization is absent.
2. Open Security. Find “Authorize external identity” below “Paired
command-line tools,” including when no tools are paired.
3. Enter an application's public key and choose “Authorize identity.”
Check the exact identity and unchanged warning; Cancel must create no
proof.
4. Choose “Authorize this key” to generate the ownership proof. Confirm
copying and editing the application key still behave as before.

## Validation

- At `57434713d`: pre-commit formatting/lint/icons and secret scan
passed; pre-push TypeScript, related Vitest tests, and design checks
passed. `git diff --check` passed on the clean committed tree. Native
layout was checked in dark mode at 1280×795, with no paired tools.
Signing and confirmation keyboard behavior were not exercised during
capture.
- Changed lines (additions + deletions): **22 tests; 27 non-test** (23
source, 4 documentation). Tests are **44.9%** of the diff.
- No browser journeys added or removed.
- Independent review passed at this head. Populated paired tools,
confirmation focus/Escape, light/narrow/enlarged-text checks, and human
acceptance remain pending.

## Screenshots

Real native macOS app. Before uses the UI files from base `5b7df025f`;
after uses `57434713d`. Account and workspace details are cropped out.
The private key remains masked by the app. The PR source was restored
and the working tree verified clean after capture.

| Before — Identity, scrolled to the form | After — Security |
| --- | --- |
| ![External identity authorization below Sign out in
Identity](https://github.com/user-attachments/assets/6c445dcf-408e-4738-a0e1-be9632e164bd)
| ![External identity authorization below paired tools in
Security](https://github.com/user-attachments/assets/93bfcbe6-72c5-4d0b-8820-1d4d08c2a50a)
|

---------

Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
Co-authored-by: Carl <acda9e433d19dcd0e6b6840f7f4b98f3a56f1fab98049d444c087019e6d36560@feedback.communities.buzz.xyz>
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