fix(node-pairing): require operator.admin to approve browser.proxy nodes (#104491)

Approving a node whose pairing surface advertises browser.proxy only required
operator.write, while invoking browser.proxy already requires operator.admin
(server-methods/nodes.ts). resolveNodePairApprovalScopes bumped the approval
scope to operator.admin only for NODE_SYSTEM_RUN_COMMANDS, so a write-scoped
operator could trust a browser.proxy-capable node that later routes bundled
browser-tool traffic. Add NODE_BROWSER_PROXY_COMMAND to the admin-approval set
so approval scope matches the invoke-time gate.
This commit is contained in:
Yuval Dinodia
2026-07-11 12:58:00 -04:00
committed by GitHub
parent 834c146958
commit 68c18caca0
2 changed files with 11 additions and 2 deletions

View File

@@ -10,6 +10,13 @@ describe("resolveNodePairApprovalScopes", () => {
]);
});
it("requires operator.admin for browser.proxy commands", () => {
expect(resolveNodePairApprovalScopes(["browser.proxy"])).toEqual([
"operator.pairing",
"operator.admin",
]);
});
it("requires operator.write for non-exec commands", () => {
expect(resolveNodePairApprovalScopes(["canvas.present"])).toEqual([
"operator.pairing",

View File

@@ -1,5 +1,5 @@
// Maps node pairing command declarations to required operator scopes.
import { NODE_SYSTEM_RUN_COMMANDS } from "./node-commands.js";
import { NODE_BROWSER_PROXY_COMMAND, NODE_SYSTEM_RUN_COMMANDS } from "./node-commands.js";
/** Operator scopes required to approve a pending node pairing surface. */
export type NodeApprovalScope = "operator.pairing" | "operator.write" | "operator.admin";
@@ -8,13 +8,15 @@ const OPERATOR_PAIRING_SCOPE: NodeApprovalScope = "operator.pairing";
const OPERATOR_WRITE_SCOPE: NodeApprovalScope = "operator.write";
const OPERATOR_ADMIN_SCOPE: NodeApprovalScope = "operator.admin";
const ADMIN_APPROVAL_COMMANDS = [...NODE_SYSTEM_RUN_COMMANDS, NODE_BROWSER_PROXY_COMMAND];
/** Map declared node commands to the least operator scopes needed for approval. */
export function resolveNodePairApprovalScopes(commands: unknown): NodeApprovalScope[] {
const normalized = Array.isArray(commands)
? commands.filter((command): command is string => typeof command === "string")
: [];
if (
normalized.some((command) => NODE_SYSTEM_RUN_COMMANDS.some((allowed) => allowed === command))
normalized.some((command) => ADMIN_APPROVAL_COMMANDS.some((allowed) => allowed === command))
) {
return [OPERATOR_PAIRING_SCOPE, OPERATOR_ADMIN_SCOPE];
}