diff --git a/docs/cli/nodes.md b/docs/cli/nodes.md index ef2b6c43ba77..38c2304ccfc9 100644 --- a/docs/cli/nodes.md +++ b/docs/cli/nodes.md @@ -46,6 +46,7 @@ These commands drive the gateway-owned `node.pair.*` store, separate from device - commandless request: `operator.pairing` - ordinary node commands: `operator.pairing` + `operator.write` - admin-sensitive commands (`system.run`, `system.run.prepare`, `system.which`, `browser.proxy`, `browser.proxy.upload.v1`, `fs.listDir`, and `system.execApprovals.get/set`): `operator.pairing` + `operator.admin` +- These requirements classify node commands relayed through `node.invoke`. The top-level Gateway `fs.listDir` RPC needs `operator.write` for workspace-contained host browsing and `operator.admin` when `nodeId` is present. - `remove` scope: `operator.pairing` can remove non-operator node rows; a device-token caller revoking its own node role on a mixed-role device additionally needs `operator.admin`. ## Invoke diff --git a/docs/gateway/operator-scopes.md b/docs/gateway/operator-scopes.md index 8d306e36b5ee..55f98c571f38 100644 --- a/docs/gateway/operator-scopes.md +++ b/docs/gateway/operator-scopes.md @@ -79,6 +79,7 @@ The result is used for both `hello.auth.scopes` and Gateway method authorization. Identity grants are session-only: they do not create or modify pairing records or request a device scope upgrade. Token, password, and no-auth connections carry no verified identity and receive no grant. +Identity grants apply only to `operator`-role connections; `node`-role connections never receive them. ## Method scope is only the first gate @@ -89,8 +90,18 @@ dispatch so authorization failures have one canonical structured response: - `agent` needs `operator.write` for ordinary turns and `operator.admin` for `/new` or `/reset` session lifecycle commands. - `node.invoke` needs `operator.write` for ordinary relay commands and - `operator.admin` for `browser.proxy`, `browser.proxy.upload.v1`, `fs.listDir`, - and `terminal.upload`. + `operator.admin` when relaying `browser.proxy`, `browser.proxy.upload.v1`, + `fs.listDir`, or `terminal.upload` to a node. +- The top-level `fs.listDir` RPC needs `operator.write` for Gateway-host + requests and `operator.admin` when `nodeId` targets a node. Its handler limits + non-admin Gateway-host browsing to configured agent workspaces. +- `sessions.create` needs `operator.write` for ordinary creation, including a + `projectId`, and `operator.admin` for incognito sessions or any `execNode` + request. For non-admin callers, the handler limits `cwd` to configured agent + workspaces; `projectId` cannot be combined with `cwd` or `execNode`. +- `worktrees.branches` needs `operator.write`. Its handler limits non-admin + callers to workspace-contained paths or registered-project roots; other host + paths require `operator.admin`. - `talk.config` needs `operator.read`; `includeSecrets: true` also needs `operator.talk.secrets`. - `talk.client.*`, `talk.session.*`, `talk.speak`, and `talk.mode` need @@ -101,6 +112,15 @@ dispatch so authorization failures have one canonical structured response: Persisting a selected model as the configured agent default is also admin-only. +Project RPCs use these scopes: + +| Method | Required scope and additional gate | +| -------------------------------------- | --------------------------------------------------------------------------------------------- | +| `projects.list` | `operator.read`; only callers satisfying `operator.write` receive `repoRoot` and `originUrl`. | +| `projects.add` | `operator.write` and the `controlPlaneWrite` method flag. | +| `projects.register`, `projects.remove` | `operator.admin`. | +| `projects.searchRemote` | `operator.read`. | + Some handlers then apply stricter checks based on the concrete thing being approved or mutated: @@ -191,6 +211,9 @@ command list: | ordinary node commands | `operator.pairing` + `operator.write` | | `system.run`, `system.run.prepare`, `system.which`, `browser.proxy`, `browser.proxy.upload.v1`, `fs.listDir`, or `system.execApprovals.get/set` | `operator.pairing` + `operator.admin` | +Here, `fs.listDir` is the node command declared for relay through `node.invoke`, +not the top-level Gateway RPC described above. + Approving a node declaration records its command surface. For `computer.act`, the node advertises that surface only after Computer Control is enabled locally; once the pairing update is approved, invoking it through `node.invoke` requires diff --git a/docs/gateway/pairing.md b/docs/gateway/pairing.md index 871f5c9034df..842a7a624dca 100644 --- a/docs/gateway/pairing.md +++ b/docs/gateway/pairing.md @@ -116,6 +116,10 @@ Notes: `system.which`, `browser.proxy`, `browser.proxy.upload.v1`, `fs.listDir`, or `system.execApprovals.get/set`: `operator.pairing` + `operator.admin` +Here, `fs.listDir` is the node command relayed through `node.invoke`. The +top-level Gateway `fs.listDir` RPC needs `operator.write` for +workspace-contained host browsing and `operator.admin` when `nodeId` is present. + Node pairing approval records the trusted capability surface. It does **not** pin the live node command surface per node. diff --git a/docs/gateway/protocol.md b/docs/gateway/protocol.md index 5dc89c475c46..461031e14b76 100644 --- a/docs/gateway/protocol.md +++ b/docs/gateway/protocol.md @@ -400,6 +400,10 @@ method scope (`operator.pairing`), based on the pending request's declared | ordinary commands | `operator.pairing` + `operator.write` | | includes `system.run`, `system.run.prepare`, `system.which`, `browser.proxy`, `browser.proxy.upload.v1`, `fs.listDir`, or `system.execApprovals.get/set` | `operator.pairing` + `operator.admin` | +In this table, `fs.listDir` is the node command relayed through `node.invoke`. +The top-level Gateway `fs.listDir` RPC needs `operator.write` for +workspace-contained host browsing and `operator.admin` when `nodeId` is present. + ### Caps/commands/permissions (node) Nodes declare capability claims at connect time: