feat(plugins): install pinned instance plugins and load only verified bytes (#1873) #1924

Merged
jason.woltje merged 2 commits from org-pl1 into refactor 2026-11-11 06:20:44 +00:00
Owner

PL1 (#1873): installed plugins, pinned by hash, as approved in roles-and-rails design section 13.

  • The host owner installs a package into <dataRoot>/plugins/<name> from a directory, a zip or a local git repo at a full commit. The CLI writes a plugins.lock.json pin and makes the files read-only. Organizations, Console users, testers and agents get no install or remove verb.
  • plugin.json is strict and never runs code. Declarations are namespaced and checked against P1's registry. Core names, hard waits, unknown fields and a non-null signature refuse.
  • Every import re-hashes the whole package against the lock. A bad pin drops that plugin and keeps core and the other plugins working.
  • A process-wide latch ties each install path to its first verified pin. A changed pin refuses plugin-restart-required instead of running stale cached modules. A resolve guard refuses bare third-party imports, files outside the package and executable data URLs.
  • An org registry merges only verified, enabled declarations: tools, routed actions, credential services and template metadata. A package with several kinds is active only when the owner enables all of them.
  • Plugin tools go through the broker on every call. Sandbox tools run in a fresh bubblewrap with no home, config, workspace or network. A disabled or missing package refuses launch and running calls with plugin-disabled and only the validated name.

No third-party plugin ships. Template import, hiring, signatures and a marketplace stay out of scope.

Lane: Sol. Reviews: Opus build (changes 5) and daybreak-blue security (changes 1, same P1). The fix round fixed both P1s, the P2 and two P3s. The P3 about synchronous whole-package hashing is deferred: it needs an async snapshot design. A daybreak-blue re-check of the fix commit found one P2: plugin code can register its own node:module hook, or start a worker or child process, and get around the resolve guard. Lead ruling: accepted risk under R10, not a blocker. The design already says approved plugin code runs as the instance uid and a pin does not make code safe (roles-and-rails design line 536). The guard stops accidental unpinned imports, and it is not a boundary against hostile code. In a sandbox tool nothing outside the package is mounted, so a bypass reaches nothing new. Follow-up: run host plugin code out of process if hostile plugins come into scope. The re-check found no latch race and no refusal leak, and pinned.test passed 28/0.

Rebased by the Lead onto 15225c5e1. That rebase conflicted in organization/src/role.mjs with #1920 (safe names), and the Lead resolved it by keeping both changes: the plugin-name allowance on unknown actions plus the restored action name in the always-gated refusal. Then rebased onto 85858938f with range-diff all =. The Lead gate on 754a94758 covered 583 files, every refusal-redaction and last-values file, and test-config and test-task: 6030/2. Both reds are Case 1 load timing (P10 inner and the row276 legacy timeout). Case 1 alone on 99ce579e3: 55/1, only row253 C4 P10 inner, which also fails on base (known red, fix in flight).

PL1 (#1873): installed plugins, pinned by hash, as approved in roles-and-rails design section 13. - The host owner installs a package into `<dataRoot>/plugins/<name>` from a directory, a zip or a local git repo at a full commit. The CLI writes a `plugins.lock.json` pin and makes the files read-only. Organizations, Console users, testers and agents get no install or remove verb. - `plugin.json` is strict and never runs code. Declarations are namespaced and checked against P1's registry. Core names, hard waits, unknown fields and a non-null signature refuse. - Every import re-hashes the whole package against the lock. A bad pin drops that plugin and keeps core and the other plugins working. - A process-wide latch ties each install path to its first verified pin. A changed pin refuses `plugin-restart-required` instead of running stale cached modules. A resolve guard refuses bare third-party imports, files outside the package and executable data URLs. - An org registry merges only verified, enabled declarations: tools, routed actions, credential services and template metadata. A package with several kinds is active only when the owner enables all of them. - Plugin tools go through the broker on every call. Sandbox tools run in a fresh bubblewrap with no home, config, workspace or network. A disabled or missing package refuses launch and running calls with `plugin-disabled` and only the validated name. No third-party plugin ships. Template import, hiring, signatures and a marketplace stay out of scope. Lane: Sol. Reviews: Opus build (changes 5) and daybreak-blue security (changes 1, same P1). The fix round fixed both P1s, the P2 and two P3s. The P3 about synchronous whole-package hashing is deferred: it needs an async snapshot design. A daybreak-blue re-check of the fix commit found one P2: plugin code can register its own `node:module` hook, or start a worker or child process, and get around the resolve guard. Lead ruling: accepted risk under R10, not a blocker. The design already says approved plugin code runs as the instance uid and a pin does not make code safe (roles-and-rails design line 536). The guard stops accidental unpinned imports, and it is not a boundary against hostile code. In a sandbox tool nothing outside the package is mounted, so a bypass reaches nothing new. Follow-up: run host plugin code out of process if hostile plugins come into scope. The re-check found no latch race and no refusal leak, and pinned.test passed 28/0. Rebased by the Lead onto 15225c5e1. That rebase conflicted in `organization/src/role.mjs` with #1920 (safe names), and the Lead resolved it by keeping both changes: the plugin-name allowance on unknown actions plus the restored action name in the always-gated refusal. Then rebased onto 85858938f with range-diff all `=`. The Lead gate on 754a94758 covered 583 files, every refusal-redaction and last-values file, and test-config and test-task: 6030/2. Both reds are Case 1 load timing (P10 inner and the row276 legacy timeout). Case 1 alone on 99ce579e3: 55/1, only `row253 C4 P10 inner`, which also fails on base (known red, fix in flight).
jason.woltje added 2 commits 2026-11-11 06:20:37 +00:00
jason.woltje merged commit 99ce579e3c into refactor 2026-11-11 06:20:44 +00:00
Sign in to join this conversation.