Repository navigation
Add trusted plugin publishers and self-developed plugins - #2746
shellz-n-stuff wants to merge 5 commits into
Conversation
Co-authored-by: Codex <[email protected]> Signed-off-by: Jordan Mecom <[email protected]>
Co-authored-by: Codex <[email protected]> Signed-off-by: Jordan Mecom <[email protected]>
Keep verified publisher metadata through update discovery and confirmation. Retire pending updates when the installed release changes. Co-authored-by: Codex <[email protected]> Signed-off-by: Jordan Mecom <[email protected]>
An optional plugin-trust.json beside the profile folders limits external plugins to registered publisher keys and, when allowSelfDeveloped is true, to releases signed with the saved Buzz human identity. Without the file, any valid signature still runs. A valid file also forbids unsigned plugins regardless of BUZZ_REQUIRE_PLUGIN_SIGNATURES; an unusable file blocks every external plugin rather than dropping the restriction. The check runs beside signature verification on install, reload, enable, rollback, load and catalog refresh. buzzodz plugin trust shows the policy and registers or removes publishers and toggles self-developed plugins. Co-authored-by: 1337Coder <adf0418798bd3ad1b52660dcccfe216c759bc09707da2c0c26907ba13229b5ef@buzz.block.builderlab.xyz> Co-Authored-By: Claude Opus 5.5 <[email protected]> Signed-off-by: Alex Rosenzweig <[email protected]>
A BUZZ_PLUGIN_TRUST path whose file does not exist, or a trust file that is a dangling symbolic link, now blocks every external plugin instead of lifting the restriction. Only a missing file at the default location means no policy. buzzodz plugin trust add can still create the explicitly named file. Co-authored-by: 1337Coder <adf0418798bd3ad1b52660dcccfe216c759bc09707da2c0c26907ba13229b5ef@buzz.block.builderlab.xyz> Co-Authored-By: Claude Opus 5.5 <[email protected]> Signed-off-by: Alex Rosenzweig <[email protected]>
| //! it, only listed publishers run, plus the saved Buzz human identity when self-developed | ||
| //! plugins are allowed. An unusable file blocks every external plugin rather than | ||
| //! silently dropping the restriction. | ||
| use crate::{atomic_write, managed::read_file, Result}; |
There was a problem hiding this comment.
Main logic is here
ab867b0 to
7484be9
Compare
jmecom
left a comment
There was a problem hiding this comment.
🤖 Reviewed by jm-astra at jm's request, at b76c93a1.
No blocking correctness or security findings in the trusted-publisher implementation. I reviewed the two trust commits on top of ab867b0, traced the checks through install/reload, enable/rollback, module/content/host-grant reads and catalog reconciliation, and had an independent agent review parsing, persistence and identity caching. The fail-closed policy behavior and unsigned-plugin rejection under an active policy match the documented contract.
Validation: cargo test -p buzzodz-plugins passed all 134 tests at this exact head (90 library, 9 build-configuration, 35 management), including the real CLI trust workflows. git diff --check ab867b0..HEAD passed. Hosted CI required is green.
Integration note: GitHub currently reports conflicts against the rebased #2212 (7484be92). Refresh the stack while preserving its newly opt-in signature default, and rerun affected checks after resolving conflicts. One optional documentation comment below addresses that default. I did not exercise the desktop app or OS keychain in this review; the desktop and human checks already listed in the PR remain pending.
|
|
||
| | File | Effect | | ||
| | --- | --- | | ||
| | Missing at the default location | Any valid signature runs (the default). | |
There was a problem hiding this comment.
🤖 Optional documentation follow-up when refreshing the stack: #2212 now makes signature enforcement opt-in (BUZZ_REQUIRE_PLUGIN_SIGNATURES=1). Please describe the missing-policy case as inheriting that setting, so readers know unsigned plugins are also permitted when enforcement is off. The introduction and PR description should use the same wording. The active trust policy correctly still rejects unsigned plugins regardless of that flag.
7484be9 to
c111df1
Compare
… an update (#3024) ## TL;DR A `buzz://plugin-install` Git link pinned to both a `commit` and a `path`, for a plugin already installed from that same repository and folder, now opens the **Update** review that a plugin's Git `requestInstall` already gets. If the link names another branch or tag than the plugin tracks, the review says so. Before, it hit the Add path's **Already installed** refusal, so there was no reviewed way to update from such a link. Links without both pins, and links to a plugin installed from another source, are still refused as duplicates. ## What changed - When the installed plugin was imported (not bundled), `PluginImport` treats a link with both `commit` and `path` like a bound `requestInstall` request when deciding between update and duplicate (`src/app/PluginImport.tsx`). It reuses the existing same-source check: the installed `updateSource` must be a Git source whose repository matches the fetched preview's source and whose folder matches the candidate's folder, and the candidate must have the installed manifest ID. Native import saves the requested `ref`, and later update checks follow it. So when a link's or plugin request's `ref` differs from the installed Git source's ref, the review adds "Future updates will follow <new> instead of <old>." An empty ref reads as "the repository default". - Confirming a requested update (link or plugin request) re-reads the live catalog and refuses with "The installed plugin changed. Close this review and check again." if the installed revision, enabled state or saved source changed after the review opened, so the review's "stays disabled" line can't go stale. - A requested update of a disabled plugin now says "This plugin will stay disabled.", as the Details **Update** review already does. Native install already keeps the enabled state, and the review offers no enable switch for updates. - Docs: `docs/plugin-architecture.md`, `docs/deep-links.md` and `docs/buzz-help.md` describe the pinned-link update. Protections that still apply: the commit pin (native refuses a ref whose tip differs, and the review re-checks the fetched commit), the folder filter, the access review against the installed manifest, the publisher line and native publisher-continuity refusal, the unsigned-to-signed rollback warning, explicit **Update** confirmation, and preview discard on cancel. Links with only `commit`, only `path`, or neither, and pinned links to a bundled plugin, keep showing **Already installed** / **Open in Buzz**. Not in scope: update discovery, Update-all, managed policy feeds or checkers (#2504, #2621, #637), signature or publisher-trust policy (#2212, #2746), and the compact request dialog (#2625). This PR doesn't touch their hunks. #2983 and #2762 also edit `tests/browser/plugin-import.spec.mjs`; this PR leaves that file unchanged. ## Supported operation On a Buzz desktop build that includes this change, open `buzz://plugin-install?repository=<repo>&ref=<ref>&path=<folder>&commit=<40-or-64-hex>` for a plugin whose installed copy came from a Git import of that same repository and folder. The review shows **Update <name>**, with access changes listed against the installed version, and states any change to the branch or tag later updates follow. Nothing changes until you choose **Update**. Native import still refuses the update if the ref's tip isn't the pinned commit, or if the publisher changed or a signed plugin became unsigned. ## Tests New `src/app/PluginImport.test.tsx` cases, all with fictional `example.com` fixtures: - A pinned link to the installed source reviews as **Update**, installs the pinned folder, and doesn't call enable. This is the regression test for the original failure. - A disabled plugin stays disabled and the review says so. - A pinned link with another ref than the installed plugin tracks (a named ref, or the repository default) reviews as **Update**, says which ref future updates will follow, and installs on **Update**. A same-ref link shows no such line. - A link without both pins (none, path only, commit only), or a pinned link to a bundled plugin, still shows **Already installed** with **Open in Buzz**. - An installed source in another repository or folder, a local folder, or an unknown source is refused with the source-mismatch message and **Open in Buzz**. - When only another plugin ID is installed from that source, the link offers a new install. - A fetched commit that differs from the pin offers no **Update**. - Cancel discards the preview without installing. - If the installed revision, enabled state or source changes after the review opens, **Update** is refused, for both a link and a plugin's `requestInstall`. The request and link harnesses now return a ready catalog from `plugins.snapshot()`, which the new confirm-time check reads. Checked on a Linux box at `37cf73e1`: `biome check` on both files, `pnpm typecheck`, and `vitest run src/app/PluginImport.test.tsx` (100 passed). After the first review fixes, the same three checks passed locally (102 tests), and the bundled-plugin test fails on the earlier predicate. The later enabled-state drift case (two lines plus one test input) passed biome locally; host load refused a local test run, so PR CI covers it. With only the `requested` line reverted, 6 of the 8 `pinned link` tests fail. Against `main`'s `PluginImport.tsx`, the same 6 fail and the other 94 pass. The two that pass either way are the existing pin-retry test and the other-plugin-ID new-install case, which should behave the same before and after. After review feedback, the tracked-ref disclosure was verified on a Blox box at `e913727f`: `biome check`, `pnpm typecheck` and `vitest run src/app/PluginImport.test.tsx` (103 passed). With the review line disabled, the new tracked-ref test fails. The before/after screenshots predate this line and show a same-ref update. ### Before / after These screenshots come from a temporary Chromium journey using the existing `nativeImports` IPC fixture and fictional `example` data; the journey is not committed. It installs `example.linked` from `https://github.com/example/plugins` at `plugins/linked`, then delivers the same pinned link again with a newer candidate. | | Light | Dark | |---|---|---| | Before (`main`) |  |  | | After |  |  | ## Remaining gaps - No native (Tauri) run: the Playwright screenshots use the existing IPC fixture. Native import, publisher continuity and the deep-link handoff were not changed here. - No human has tried it in the app yet, so `buzz-review-completed` is not set. ## Initiative context Status: https://g2.sqprod.co/apps/kylez-buzz-improvements-status (buzz-improvements T027). Fixes NEXUS-4327 🤖 Generated with [Claude Code](https://claude.com/claude-code) Signed-off-by: Kyle Zemek <[email protected]> Co-authored-by: Claude Opus 5.5 <[email protected]>
4850e87 to
9793c15
Compare
## TL;DR The manual Git import form now maps a plugin listing's Ref to Branch or tag and explains that a blank ref scans the repository's default branch. Known Git-size refusals keep their native error text and add focused recovery guidance. ## Related work This remains separate from the importer changes in [#2712](#2712), [#2621](#2621), and [#2746](#2746). ## Testing Component tests cover blank and explicit refs, preserved native refusals, matching size guidance, and unchanged behavior for unrelated errors and install calls. A fictional visible-UI browser case checks the guidance and recovery text, keyboard return path, and narrow-width/enlarged-interface-size layout in light and dark themes. No live plugin install or manual product test was performed. ## Screenshots **Main — Git form, light, 320px**  **Updated — Git form, light, 320px**  **Main — size refusal, dark, 320px at 200% interface size**  **Updated — size refusal, dark, 320px at 200% interface size**  Generated with [Codex](https://openai.com/codex); posted by Codex (gpt-6-luna) on behalf of kylez. --------- Signed-off-by: Kyle Zemek <[email protected]> Co-authored-by: Codex (gpt-6-luna) <[email protected]>
…vigation budget (#3445) ## Why Two Chromium journeys time out at `page.goto` in the Flake check lane because each test starts its own cold Vite dev server and navigates at once. | Test | Sightings | |---|---| | `diffs.spec.mjs`, "diff preview expands in both layouts and keeps focus and scroll containment" | Flake check, Chromium: [11 Nov, 9/12](https://github.com/block/buzz-app/actions/runs/43120252998/job/130859347639), [11 Nov, 3/10](https://github.com/block/buzz-app/actions/runs/43117562130/job/130849891867), [12 Nov, 2/9](https://github.com/block/buzz-app/actions/runs/43125377816/job/130876901453), [12 Nov, 3/9](https://github.com/block/buzz-app/actions/runs/43125831951/job/130878888421) | | `plugin-cli-reload.spec.mjs`, "CLI reload replaces an enabled folder plugin without a Settings action" | Flake check, Chromium: [10 Nov, 5/9, passed on re-run](https://github.com/block/buzz-app/actions/runs/42831586958/job/129883380086) | ## Root cause Both specs created a Vite dev server inside each test. The diffs fixture reaches the bundled plugins, the message row, the thread panel, the conversation service, the relay session and the global stylesheet, about 1,800 modules. The conversation page is about 1,900. The worker-level warm-up from #3055 lives in `source-fixture.mjs` and only covers specs that take their base URL from it. The Flake check lane runs `--repeat-each=10` with two workers, and every repeat is a fresh worker. Each repeat therefore paid for a cold Node process, a cold Tailwind build and a cold module graph. In the failing traces 1,671 to 1,707 module requests were still in flight when the 15 s navigation timeout fired, with the global stylesheet alone taking about 9 s. The page itself was requested once, so this was not a dependency re-optimisation. ## Fix - **`diffs.spec.mjs`**: build the page once per worker with `previewFixtures` and drop the per-test dev server, as the typeahead and voice-notes specs do. The spec has no server middleware and no pixel assertions, so the static bundle is enough. `previewFixtures` keeps development React. Most of the diff is the test body moving out of the server's `try` block by one indent level. - **`plugin-cli-reload.spec.mjs`**: it needs its proof middleware, so it keeps the dev server and warms up the conversation entry right after `listen()`, as `conversation.spec.mjs` does. The navigation timeout is unchanged. No assertions changed and no browser cases were added or removed. ## Evidence Local runs on an idle 32-core Linux host, Chromium, a fresh worker per repeat, `bin/pnpm test:browser:ci --project chromium --no-deps --reporter=list`. | Scope | Result | |---|---| | `diffs.spec.mjs`, `--workers=1 --repeat-each=5` | 5/5 passed, 2.9 s each, 32.7 s total | | `diffs.spec.mjs`, `--workers=2 --repeat-each=4` | 4/4 passed, 14.9 s total | | `plugin-cli-reload.spec.mjs`, `--workers=1 --repeat-each=5` | 5/5 passed, 12.6 to 14.5 s each | | `plugin-cli-reload.spec.mjs`, `--workers=2 --repeat-each=4` | 4/4 passed, 13.4 to 13.8 s each | The native fixture for the reload spec was built first with `bin/cargo build --locked -p buzzodz-plugins --example fixture-bridge --bin buzzodz`. Before the change, during triage on the same host: the unchanged diffs spec passed 5/5 on a quiet host, failed 5/5 at a load average near 25, and failed 4/4 with two workers. A standalone cold navigation took 5.4 to 7.7 s idle, 14.1 s on two cores and 15.3 s on one core. Passing repeats of the diffs test in the failing CI jobs took about 17.8 s; hosted timings after the change are only known once CI runs this PR. ## Not reproduced, not run - The cold-navigation timeout reproduced only under load or with two concurrent cold workers, not on an idle host. The runs on this branch were on an idle host, so they guard against regression rather than reproduce CI timing. - WebKit was not run on this branch. - No human has tested this yet, so `buzz-review-completed` is not set. ## Left alone Other specs still serve fixture pages from a per-test cold dev server, including `window-chrome`, `composer-attachments`, `image-strip`, `image-zoom` and `message-edit`. They have not shown this timeout in CI and are candidates for the same treatment in their own change. Open draft #2746 edits other parts of `plugin-cli-reload.spec.mjs`; a trial merge against this change is clean. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Signed-off-by: mtoohey <[email protected]> Signed-off-by: Matt Toohey <[email protected]> Co-authored-by: Claude Fable 5.1 <[email protected]>
Stacked on #2212. That PR makes Buzz require a valid signature from some publisher. This PR lets a user or an organization restrict which publishers are accepted, and adds an opt-in self-developed mode so you can sign your own plugins with your Buzz identity for development and testing.
Configuration
An optional
plugin-trust.jsonsits beside the profile folders, next tomanaged-plugins.jsonandrepository-access.json.BUZZ_PLUGIN_TRUSToverrides its path.{ "version": 1, "trustedPublishers": [{ "publisher": "npub1…", "name": "Block plugins" }], "allowSelfDeveloped": true }allowSelfDeveloped, the saved Buzz human identity (the keybuzzodz plugin signuses) is also trusted. Unsigned plugins never run, even withBUZZ_REQUIRE_PLUGIN_SIGNATURES=0.BUZZ_PLUGIN_TRUSTpath, or a dangling symlinktrust addcan create the explicit file.Blocked plugins stay installed and listed, with the reason and their publisher key, which is the value to register.
CLI
Each change prints the resulting policy and lists the installed plugins that can no longer run.
Implementation
crates/plugin-manager/src/trust.rshandles parsing, loading, the check and the atomic update. It follows theaccess.rsandmanaged.rsfile pattern.Manager::artifact, which covers enable, rollback,module,contentandhost_grantsinstall_artifact, the path every import takesreloadcatalog(), which setserror. The desktop already refuses to activate a plugin whose catalog entry has an error, and it polls every second, so trust edits apply live.allowSelfDevelopedreads the saved identity from secure storage only when a plugin's publisher isn't listed, at most once per process. Lasting failures stay cached so the 1-second catalog poll can't re-prompt; a missing identity or busy storage is retried.sign_release_savedand was refactored out of it; behavior is unchanged.Not in this PR (follow-ups)
BUZZ_DEV_CREDENTIAL_HELPER: the self-developed identity read usesfind_human, the same asplugin sign, not the dev helper.Known limits (consistent with the sibling config files)
atomic_writereplaces a symlinked trust file with a regular file.Validation
bin/cargo test -p buzzodz-plugins: 134 tests pass, including 8 new unit tests intrust.rsand two end-to-end tests that drive thebuzzodzbinary. That test covers register by hex and by npub, the block and unblock paths (install, enable, reload,module,host_grants, catalog), unsigned plugins under=0, fail-closed on an unusable file, refused edits, and keeping a label.cargo clippy -p buzzodz-plugins --all-targets -D warningsis clean.BUZZ_PLUGIN_TRUSTpath failed open. That's fixed in the second commit, with tests for the missing explicit file, dangling symlinks, and BigBoss's install, enable and load repro.Human test steps
BUZZODZ_HOME=… buzzodz plugin listand note an installed external plugin'sPublisher:.buzzodz plugin trust add <some other npub>. The plugin disappears from the running app within about a second, and Settings shows "Publisher … is not trusted".buzzodz plugin trust add <that plugin's publisher>. It comes back.buzzodz plugin signon a local build, thenbuzzodz plugin trust self-developed onand install the build. It runs.plugin-trust.json. Everything returns to Make plugin signature enforcement opt-in #2212's behavior.🤖 Generated with Claude Code