From 59b5d2f1c2dd95dfc7acadd0a2b9f152123f672a Mon Sep 17 00:00:00 2001 From: Dotta Date: Mon, 6 Jul 2026 13:47:05 +0000 Subject: [PATCH] Cover expired invocation scope and schema-scoped secret refs --- .../sdk/tests/host-client-factory.test.ts | 26 ++++++++++++++ .../__tests__/plugin-secrets-handler.test.ts | 36 +++++++++++++++++++ 2 files changed, 62 insertions(+) diff --git a/packages/plugins/sdk/tests/host-client-factory.test.ts b/packages/plugins/sdk/tests/host-client-factory.test.ts index eb79043e57d..b0866700ae8 100644 --- a/packages/plugins/sdk/tests/host-client-factory.test.ts +++ b/packages/plugins/sdk/tests/host-client-factory.test.ts @@ -172,4 +172,30 @@ describe("createHostClientHandlers invocation company scope", () => { ).rejects.toBeInstanceOf(InvocationScopeDeniedError); expect(searchAudit).not.toHaveBeenCalled(); }); + + it("fails closed when an authorization read arrives with an expired invocation scope", async () => { + const searchAudit = vi.fn(async () => []); + const services = { + authorization: { + searchAudit, + }, + } as unknown as HostServices; + const handlers = createHostClientHandlers({ + pluginId: "paperclip.test", + capabilities: ["authorization.audit.read"], + services, + }); + + await expect( + handlers["authorization.audit.search"]( + { companyId: "company-a" }, + { invalidInvocationScope: true }, + ), + ).rejects.toMatchObject({ + name: "InvocationScopeDeniedError", + code: PLUGIN_RPC_ERROR_CODES.INVOCATION_SCOPE_DENIED, + message: expect.stringContaining("missing, expired, or unknown invocation scope"), + }); + expect(searchAudit).not.toHaveBeenCalled(); + }); }); diff --git a/server/src/__tests__/plugin-secrets-handler.test.ts b/server/src/__tests__/plugin-secrets-handler.test.ts index ec89c8722c0..a480852e0bc 100644 --- a/server/src/__tests__/plugin-secrets-handler.test.ts +++ b/server/src/__tests__/plugin-secrets-handler.test.ts @@ -1,10 +1,46 @@ import { describe, expect, it } from "vitest"; import { createPluginSecretsHandler, + extractSecretRefPathsFromConfig, PLUGIN_SECRET_REFS_DISABLED_MESSAGE, } from "../services/plugin-secrets-handler.js"; describe("createPluginSecretsHandler", () => { + it("ignores UUID-looking metadata outside schema-declared secret-ref paths", () => { + const secretRef = "77777777-7777-4777-8777-777777777777"; + const refs = extractSecretRefPathsFromConfig( + { + credentials: { + apiKey: secretRef, + }, + metadata: { + leakedLookingUuid: "88888888-8888-4888-8888-888888888888", + }, + }, + { + type: "object", + properties: { + credentials: { + type: "object", + properties: { + apiKey: { type: "string", format: "secret-ref" }, + }, + }, + metadata: { + type: "object", + properties: { + leakedLookingUuid: { type: "string" }, + }, + }, + }, + }, + ); + + expect(Array.from(refs.entries())).toEqual([ + [secretRef, new Set(["credentials.apiKey"])], + ]); + }); + it("fails closed for plugin secret resolution until company scoping lands", async () => { const handler = createPluginSecretsHandler({ db: {} as never,