Skip to content

feat(identity): support a local debug restore helper - #1517

Merged
dr0uleau merged 3 commits into
mainfrom
drouleau/secure-dev-identity
Oct 27, 2026
Merged

dr0uleau merged 3 commits into
mainfrom
drouleau/secure-dev-identity

Conversation

@dr0uleau

@dr0uleau dr0uleau commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

Why

Local macOS debug builds can need a new Keychain approval after each rebuild. A fixed local helper can restore the saved human identity across builds.

What

Add an opt-in BUZZ_DEV_CREDENTIAL_HELPER hook for human restore on macOS with Rust debug assertions. Release builds ignore it.

How

Send the key through a fresh Unix socket into zeroizing memory. Bound the response and execution time. Keep helper failures distinct. The existing identity owner still validates the nsec. Normal reads and other credential operations keep their current behavior. Helper setup and signing keys stay outside this repository. See the helper contract.

Risk

The opt-in helper is trusted local code. It must verify the recipient audit token and live signature before it sends a key. The certificate check trusts access to the signing key. Another program with that access and the app identifier can satisfy the same requirement. This PR supplies the app hook, not helper provisioning.

Testing

A native probe linked to this crate restores the expected identity through the installed helper on restart, after a rebuild, and after a move. Ad hoc callers, pipe recipients, forged parents, and sockets created before exec receive no key. Hardened runtime blocks dylib injection. A release probe ignores the hook. Normal debug reads complete with the hook absent and Keychain dialogs disabled. The installed helper stays unchanged.

Attended macOS checks pass on 60517378b. The helper transport stays unchanged after the rebase onto 9f60c3d1b. The hook runs before the normal service migration in read_human(). Focused checks cover this entry point on cd6bb5505. The full native app is not rebuilt after this rebase. The expected identity and community return without a Keychain prompt after restart and a native rebuild. The rebuilt window has a changed title. The signed binary hash changes and the fixed helper hash stays unchanged. The independent security review and clean-commit agent review find no blockers.

buzz-review-completed

Generated with Codex

@dr0uleau
dr0uleau marked this pull request as ready for review October 21, 2026 23:53
@dr0uleau
dr0uleau requested a review from a team as a code owner October 21, 2026 23:53

@wesbillman wesbillman left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🤖 Star Lord’s automated source review via Wes’s account, integrating independent Rocket (systems/lifecycle), Mantis (correctness/security/concurrency), and Groot (product/error/retry) reviews.

No concrete correctness or security defects requiring changes found in the four-file PR diff. Source assessment: 9/10 for minimalness, elegance and correctness.

Reviewed head: 595ef41
Base: 5c314d1
Merge base: 44f38de

Checked macOS/debug gating, helper exit and timeout handling, bounded zeroizing output, retained-socket rejection, native key validation and create-only persistence, restore/retry/sign-out callers, CLI signing, tests, and the public description. No attached images or publication-surface disclosure findings.

Optional documentation improvement: docs/identity.md could mention that a debug build of the plugin CLI also calls read_human through sign_release_saved (crates/plugin-manager/src/lib.rs:508). If it inherits BUZZ_DEV_CREDENTIAL_HELPER, a helper restricted to the app’s signature can reject CLI signing; unset the variable for that CLI invocation. This is optional advice, not a required repair.

Validation limits: source-only; no tests, builds, app/helper execution or credential access performed. The hosted head-check snapshot showed CI required and all executed checks successful; Windows and flake lanes were skipped. The added macOS integration test uses synthetic helpers, and hosted success does not establish real helper audit-token/signature enforcement, hardened-runtime setup, attended authorization, rebuild/restart behavior, or keyboard/focus acceptance. Those remain outside this review’s verification; the PR’s reported attended checks were not independently reproduced. No browser journeys were added or removed. This COMMENT is not approval or merge authorization.

@dr0uleau
dr0uleau requested a review from a team October 22, 2026 02:05
dr0uleau and others added 2 commits October 27, 2026 13:23
@dr0uleau
dr0uleau force-pushed the drouleau/secure-dev-identity branch from 595ef41 to cd6bb55 Compare October 27, 2026 20:26

@wesbillman wesbillman left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🤖 Star Lord’s automated source review via Wes’s account, integrating Rocket, Mantis and Groot. One migration integration issue needs repair; see the inline comment.

Head cd6bb550569c344776abaf31070f38a764aed6ac; base f0d5f229e836912ed5c7e01a05311dc930d2bffe. Source-only: no tests, builds, helper/app execution or credential access; native authorization and restore behavior were not independently exercised. This is a comment review, not approval.

Comment thread crates/credential-store/src/lib.rs Resolved
Co-authored-by: Codex <[email protected]>
Signed-off-by: Daniel Rouleau <[email protected]>
@dr0uleau
dr0uleau requested a review from a team October 27, 2026 20:48
@dr0uleau
dr0uleau merged commit 04327c2 into main Oct 27, 2026
36 checks passed
@dr0uleau
dr0uleau deleted the drouleau/secure-dev-identity branch October 27, 2026 21:03
rileycrane added a commit that referenced this pull request Oct 28, 2026
…ted-setup

* origin/main: (24 commits)
  feat(agents): share agents to the community catalog and export snapshots (#1649)
  feat(navigation): dialogs never become Back/Forward destinations (#1696)
  fix(builderlab): sign in when finishing a hosted agent's setup (#1768)
  fix(messages): link #name prose to open channels the directory has named (#1706)
  feat(agents): keep the Mac awake while agents work (#1718)
  perf(panels): skip layout for covered workspace tabs (#1542)
  feat(composer): offer model and effort in a DM with an agent without a mention (#1621)
  test(workflows): wait for dialog initial focus before pasting YAML in WorkflowEditor test (#1705)
  fix(builderlab): show all captured activity without a conversation toggle (#1764)
  feat(agents): add a UI for Attention watches (#1663)
  Keep thread reply branches one level deep (#1751)
  fix(messages): keep table scrollbars inside webkit message rows (#1677)
  feat(agents): sync agent config through config channels (#1226)
  fix(agents): offer a new hosted agent in Solo chats right away (#1759)
  test(usage): make UsagePage read-window test DST-safe (#1668)
  fix(panels): scope split motion selectors to layout owners (#1681)
  feat(agents): simplify imports and clarify community scope (#1660)
  fix(pi): stop posting replies the agent did not send (#1758)
  feat(identity): support a local debug restore helper (#1517)
  test(perf): cover rich thread open close and reopen (#1683)
  ...

Signed-off-by: Riley Crane <[email protected]>
rileycrane added a commit that referenced this pull request Oct 28, 2026
…into rileycrane/bestie-hourly-wake

* origin/rileycrane/bestie-hosted-setup: (25 commits)
  Give Bestie's test fixture the native keepAwake that main now requires
  feat(agents): share agents to the community catalog and export snapshots (#1649)
  feat(navigation): dialogs never become Back/Forward destinations (#1696)
  fix(builderlab): sign in when finishing a hosted agent's setup (#1768)
  fix(messages): link #name prose to open channels the directory has named (#1706)
  feat(agents): keep the Mac awake while agents work (#1718)
  perf(panels): skip layout for covered workspace tabs (#1542)
  feat(composer): offer model and effort in a DM with an agent without a mention (#1621)
  test(workflows): wait for dialog initial focus before pasting YAML in WorkflowEditor test (#1705)
  fix(builderlab): show all captured activity without a conversation toggle (#1764)
  feat(agents): add a UI for Attention watches (#1663)
  Keep thread reply branches one level deep (#1751)
  fix(messages): keep table scrollbars inside webkit message rows (#1677)
  feat(agents): sync agent config through config channels (#1226)
  fix(agents): offer a new hosted agent in Solo chats right away (#1759)
  test(usage): make UsagePage read-window test DST-safe (#1668)
  fix(panels): scope split motion selectors to layout owners (#1681)
  feat(agents): simplify imports and clarify community scope (#1660)
  fix(pi): stop posting replies the agent did not send (#1758)
  feat(identity): support a local debug restore helper (#1517)
  ...

Signed-off-by: Riley Crane <[email protected]>
baxen added a commit that referenced this pull request Nov 7, 2026
…#3002)

## Why

`just desktop me` asks for Keychain access about six times on every
restart. A debug `Buzz` binary is only ad-hoc signed, so Keychain ties
each approval to that exact build: every rebuild, and every worktree,
asks again, once per saved item.

The login keychain shows the build-up: `jev-key.debug` has 46 per-build
entries, `identity.debug` 35, `agents` 30. A self-signed certificate
alone doesn't fix it. A rebuilt binary signed with the same certificate
is still refused, because code with no Apple team ID gets a per-build
partition ID.

## What

`just desktop me` now does the Keychain work through a small helper
that's installed once and kept. Keychain approvals belong to the helper,
so they last.

- **First `me` on a Mac:**
- Installs `crates/dev-credential-helper` at
`~/.buzz-foundation/dev-credential-helper/`.
- Creates a self-signed **Buzz Dev Signing** certificate in the login
keychain.
  - Records the code requirement the helper checks callers against.
- You click **Always Allow** once per dialog: one for `codesign`, then
one per saved item.
- **Every later launch, rebuild or worktree:** no dialogs.
- **Not affected:** plain `just desktop`, agents, CI and release builds,
plus Linux and Windows.
- **No Apple account or Xcode needed.**

## How

- **Helper (`crates/dev-credential-helper`)**
- One request form: `[--authorize] --item <read|add|replace|delete>
<service> <account>`, replacing the human, `--agent-store` and
`--session-store` modes from #1517 / #2174 / #2541.
- It serves only an exact list of items: the release human key (read
only, for `me`), the debug human key (create-only), paired tools,
agents, debug federated sessions, the Jev and Claude debug keys, and
external-app pairings.
  - It checks the caller before touching the Keychain:
    - stdout must be a Unix socket;
- the peer's audit token must be its parent process, with the same
effective uid;
- the peer's live code signature must meet the recorded requirement,
`identifier "xyz.block.buzz.desktop.dev" and certificate leaf = H"…"`.
- Normal requests run with Keychain dialogs off. Only an item not yet
approved, under `--authorize`, is retried once with a dialog allowed.
- `--version` reports its protocol. `me` reinstalls the helper only when
the checkout needs a newer protocol.
- **App (`crates/credential-store`)**
- In macOS debug builds with `BUZZ_DEV_CREDENTIAL_HELPER` set, every
`read` / `add` / `replace` / `delete`, and the session functions, go
through `dev_item`.
- As before, a helper failure is returned and never retried against the
Keychain.
- `jev.rs`, `host_mcp/pairing.rs` and the macOS human `add` in
`identity.rs` used to call `SecKeychain` directly. They now use
`credential-store`, with the same semantics and messages.
  - The agent store's macOS `add` goes through the helper too.
- **Launcher (`scripts/desktop-dev.mjs`,
`scripts/dev-credential-helper.mjs`, `scripts/dev-sign-run.sh`)**
- Only `me` on macOS turns this on. `BUZZ_DEV_CREDENTIAL_HELPER*` from
`.env.local` or the shell is dropped, as `BUZZ_DEV_RELEASE_KEY` already
is.
- Tauri 2.12 `dev` starts the app with `cargo run`, verified with a
logging `--runner` (`run --no-default-features --color always --`). So
`CARGO_TARGET_<triple>_RUNNER=scripts/dev-sign-run.sh` signs the binary
just before it starts. That takes about 1 s on the 420 MB debug binary.
  - If setup fails, it warns and launches as before.
- **Docs**
- `docs/identity.md`: the "Local restore helper" section is rewritten as
"Local credential helper on macOS", including the trust trade-off.
- `docs/contributing.md`, `docs/buzz-help.md` and `AGENTS.md` (agents
never run `me`) are updated.

## Risk

- **Trust:** after approval, any program running as you that can sign
code with the local certificate can read these items without a dialog,
including your release key under `me`. The certificate's key lets
`codesign` use it without asking. The docs say this, and say not to use
`me` on a shared account. Before this change, the per-rebuild prompt was
the only gate.
- **Existing helpers:** a private helper written to the old human /
agent / session modes stops working. The repository now ships the
helper.
- **Approval timeout:** under `me`, every helper call uses the 2-minute
approval timeout, since any item may still need its one approval. A hung
helper therefore takes 2 minutes to fail.
- **Old Buzz imports:** bringing over old Buzz's items (legacy import)
still reads the Keychain directly. It runs once.

## Testing

- `cargo test -p buzz-dev-credential-helper`: parsing and the item list,
including what it refuses.
- `cargo test -p buzz-credential-store`: `tests/dev_helper.rs` covers
`--item` routing, the `me` release read through the helper, no fallback
on a denied release read, add/replace/delete with Occupied, sessions,
limits, timeouts and a retained socket. `closed_helper.rs` updated.
- `cargo test -p buzz-agent-controller`: `dev_credentials.rs` updated.
- `cargo test -p Buzz --lib`: 1060 pass.
`window_state::tests::onboarding_snapshot_is_kept_until_next_launch_restore`
segfaults on this branch even when run alone; its file is not in this
diff, and I have not checked it on main.
- `node --test tests/integration/dev-commands.test.mjs`: 56 pass. The
helper environment and runner are set only for `me` on macOS; values
from `.env.local` or the shell are ignored; setup failure falls back.
- Real helper against the real login keychain, with throwaway session
items only and no dialogs shown:
- a caller signed with the dev cert: absent → 10, add → 0, add again →
13, read → value, replace → 0, delete → 0, read → 10;
- an ad-hoc caller, a caller with the wrong identifier, a non-listed
service, and stdout that isn't a socket all → 11;
  - about 17 ms per call with a 420 MB caller.

Not yet done: a human run of `just desktop me` (first launch approves;
restart, rebuild and a second worktree show no dialogs).

---------

Signed-off-by: Bradley Axen <[email protected]>
Signed-off-by: Tyler Longwell <[email protected]>
Co-authored-by: mbot <3754f8729004d95654c46dbab3129e4ab9ef05cc2534e2a3fbfc155983bd637b@buzz.block.builderlab.xyz>
Co-authored-by: Tyler Longwell <[email protected]>
Co-authored-by: Quinn <96f056ad5f2305c8ddf637dc65d048aa4c12d7daeb8867690e34fca46b0ef64c@buzz.block.builderlab.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.

3 participants