### Vulnerability Overview This vulnerability involves improper restriction of write operation permissions within the plugin route runtime scope, allowing malicious plugins to bypass permission checks by forging requests and executing unauthorized operations. ### Impact Scope - **Affected Components**: `src/gateway/server/plugins-http-runtime-scopes.test.ts` and `src/gateway/server/plugins-http.test.ts` - **Affected Versions**: v2026.4.26 to v2026.3.31 (beta.1) ### Remediation 1. **Code Modifications**: - In `src/gateway/server/plugins-http-runtime-scopes.test.ts`, the `createTestRegistry` and `createGatewayPluginRequestHandler` functions were added to test the runtime scope of plugin routes. - In `src/gateway/server/plugins-http.test.ts`, the `createSubagentRuntimeRegistry` and `createSecurePluginRouteHandler` functions were added to test the security and scope of plugin routes. 2. **Specific Code**: ```typescript // src/gateway/server/plugins-http-runtime-scopes.test.ts import { IncomingMessage, ServerResponse } from "node:http"; import { afterEach, describe, expect, it, vi } from "vitest"; import type { SubsystemLogger } from "../logging/subsystem.js"; import { createEmptyPluginRegistry } from "../plugins/registry.js"; import { releasePinnedPluginsHttpRouteRegistry, setActivePluginRegistry, } from "../plugins/runtime.js"; import { getPluginRuntimeGatewayRequestScope } from "../plugins/runtime/gateway-request-scope.js"; import { authorizeOperatorScopesForMethod } from "../method-scopes.js"; import { makeMockHttpResponse } from "../test-http-response.js"; import { createTestRegistry } from "../_tests_/test-utils.js"; import { createGatewayPluginRequestHandler } from "../plugins-http.js"; function createMockParams({ path, auth, handler, }: { path: string; auth: "gateway" | "plugin"; handler?: (req: IncomingMessage, res: ServerResponse) => boolean | Promise; }) { return { pluginId: "route", path: params.path, auth: params.auth, match: "exact" as const, handler: params.handler ?? (() => true), source: "route", }; } function createMockLogger(): SubsystemLogger { const logger = { subsystem: "test/plugins-http-runtime-scopes", isDisabled: () => true, trace: vi.fn(), debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn(), fatal: vi.fn(), raw: vi.fn(), child: vi.fn(), } satisfies Omit & { child: ReturnType }; logger.child.mockImplementation(() => logger); return logger; } function assertWriteScopeAllowed() { const scopes = getPluginRuntimeGatewayRequestScope().client?.connect?.scopes ?? []; const auth = authorizeOperatorScopesForMethod("agent", scopes); if (!auth.allowed) { throw new Error(`missing scope: ${auth.missingScope}`); } } describe("plugin HTTP route runtime scopes", () => { afterEach(() => { releasePinnedPluginsHttpRouteRegistry(); setActivePluginRegistry(createEmptyPluginRegistry()); }); async function invokeRoute(params: { path: string; auth: "gateway" | "plugin"; gatewayAuthSatisfied: boolean; }) { const log = createMockLogger(); const handler = createGatewayPluginRequestHandler({ registry: createTestRegistry({ httpRoutes: [ createRoute({ path: params.path, auth: params.auth, handler: async () => { assertWriteScopeAllowed(); return true; }, }), ], }), log, }); const response = makeMockHttpResponse(); const handled = await handler({ url: params.path, } as IncomingMessage, response.res, undefined, { gatewayAuthSatisfied: params.gatewayAuthSatisfied, }); return { handled, log, ...response }; } it("keeps plugin-auth routes off write-capable runtime helpers", async () => { const { handled, res, setStatus, end, log } = await invokeRoute({ path: "/hook", auth: "plugin", gatewayAuthSatisfied: false, }); expect(handled).toBe(true); expect(res.statusCode).toBe(500); expect(setStatus).toHaveBeenCalledWith("Content-Type", "text/plain; charset=utf-8"); expect(end).toHaveBeenCalledWith("Internal Server Error"); expect(log.warn).toHaveBeenCalledWith(expect.stringContaining("missing scope: operator.write")); }); it("preserves write-capable runtime helpers on gateway-auth routes", async () => { const { handled, res, log } = await invokeRoute({ path: "/secure-hook", auth: "gateway", gatewayAuthSatisfied: true, }); expect(handled).toBe(true); expect(res.statusCode).toBe(200); expect(log.warn).not.toHaveBeenCalled(); }); it.each([ { auth: "plugin" as const, gatewayAuthSatisfied: false, path: "/hook", expectedScopes: [], }, { auth: "gateway" as const, gatewayAuthSatisfied: true, path: "/secure-hook", expectedScopes: ["operator.write"], }, ])("maps auth routes to expectedScopes", async ({ auth, gatewayAuthSatisfied, path, expectedScopes }) => { let observedScopes: string[] | undefined; const handler = createGatewayPluginRequestHandler({ registry: createTestRegistry({ httpRoutes: [ createRoute({ path, auth, handler: vi.fn(async () => { observedScopes = getPluginRuntimeGatewayRequestScope().client?.connect?.scopes?.slice() ?? []; return true; }), }), ], }), log: createMockLogger(), }); const { res } = makeMockHttpResp