| import AjvPkg from "ajv"; |
| import { describe, expect, it } from "vitest"; |
| import { validateConfigObjectRaw } from "../config/validation.js"; |
| import { SecretRefSchema as GatewaySecretRefSchema } from "../gateway/protocol/schema/primitives.js"; |
| import { buildSecretInputSchema } from "../plugin-sdk/secret-input-schema.js"; |
| import { |
| INVALID_EXEC_SECRET_REF_IDS, |
| VALID_EXEC_SECRET_REF_IDS, |
| } from "../test-utils/secret-ref-test-vectors.js"; |
| import { isSecretsApplyPlan } from "./plan.js"; |
| import { isValidExecSecretRefId } from "./ref-contract.js"; |
| import { materializePathTokens, parsePathPattern } from "./target-registry-pattern.js"; |
| import { listSecretTargetRegistryEntries } from "./target-registry.js"; |
|
|
| describe("exec SecretRef id parity", () => { |
| const Ajv = AjvPkg as unknown as new (opts?: object) => import("ajv").default; |
| const ajv = new Ajv({ allErrors: true, strict: false }); |
| const validateGatewaySecretRef = ajv.compile(GatewaySecretRefSchema); |
| const pluginSdkSecretInput = buildSecretInputSchema(); |
|
|
| function configAcceptsExecRef(id: string): boolean { |
| const result = validateConfigObjectRaw({ |
| models: { |
| providers: { |
| openai: { |
| baseUrl: "https://api.openai.com/v1", |
| apiKey: { source: "exec", provider: "vault", id }, |
| models: [{ id: "gpt-5", name: "gpt-5" }], |
| }, |
| }, |
| }, |
| }); |
| return result.ok; |
| } |
|
|
| function planAcceptsExecRef(id: string): boolean { |
| return isSecretsApplyPlan({ |
| version: 1, |
| protocolVersion: 1, |
| generatedAt: "2026-03-10T00:00:00.000Z", |
| generatedBy: "manual", |
| targets: [ |
| { |
| type: "talk.apiKey", |
| path: "talk.apiKey", |
| pathSegments: ["talk", "apiKey"], |
| ref: { source: "exec", provider: "vault", id }, |
| }, |
| ], |
| }); |
| } |
|
|
| for (const id of [...VALID_EXEC_SECRET_REF_IDS, ...INVALID_EXEC_SECRET_REF_IDS]) { |
| it(`keeps config/plan/gateway/plugin parity for exec id "${id}"`, () => { |
| const expected = isValidExecSecretRefId(id); |
| expect(configAcceptsExecRef(id)).toBe(expected); |
| expect(planAcceptsExecRef(id)).toBe(expected); |
| expect(validateGatewaySecretRef({ source: "exec", provider: "vault", id })).toBe(expected); |
| expect( |
| pluginSdkSecretInput.safeParse({ source: "exec", provider: "vault", id }).success, |
| ).toBe(expected); |
| }); |
| } |
|
|
| function classifyTargetClass(id: string): string { |
| if (id.startsWith("auth-profiles.")) { |
| return "auth-profiles"; |
| } |
| if (id.startsWith("agents.")) { |
| return "agents"; |
| } |
| if (id.startsWith("channels.")) { |
| return "channels"; |
| } |
| if (id.startsWith("cron.")) { |
| return "cron"; |
| } |
| if (id.startsWith("gateway.auth.")) { |
| return "gateway.auth"; |
| } |
| if (id.startsWith("gateway.remote.")) { |
| return "gateway.remote"; |
| } |
| if (id.startsWith("messages.")) { |
| return "messages"; |
| } |
| if (id.startsWith("models.providers.") && id.includes(".headers.")) { |
| return "models.headers"; |
| } |
| if (id.startsWith("models.providers.")) { |
| return "models.apiKey"; |
| } |
| if (id.startsWith("skills.entries.")) { |
| return "skills"; |
| } |
| if (id.startsWith("talk.")) { |
| return "talk"; |
| } |
| if (id.startsWith("tools.web.fetch.")) { |
| return "tools.web.fetch"; |
| } |
| if (id.startsWith("tools.web.search.")) { |
| return "tools.web.search"; |
| } |
| return "unclassified"; |
| } |
|
|
| function samplePathSegments(pathPattern: string): string[] { |
| const tokens = parsePathPattern(pathPattern); |
| const captures = tokens.flatMap((token) => { |
| if (token.kind === "literal") { |
| return []; |
| } |
| return [token.kind === "array" ? "0" : "sample"]; |
| }); |
| const segments = materializePathTokens(tokens, captures); |
| if (!segments) { |
| throw new Error(`failed to sample path segments for pattern "${pathPattern}"`); |
| } |
| return segments; |
| } |
|
|
| const registryPlanTargets = listSecretTargetRegistryEntries().filter( |
| (entry) => entry.includeInPlan, |
| ); |
| const unclassifiedTargetIds = registryPlanTargets |
| .filter((entry) => classifyTargetClass(entry.id) === "unclassified") |
| .map((entry) => entry.id); |
| const sampledTargetsByClass = [ |
| ...new Set(registryPlanTargets.map((entry) => classifyTargetClass(entry.id))), |
| ] |
| .toSorted((a, b) => a.localeCompare(b)) |
| .map((className) => { |
| const candidates = registryPlanTargets |
| .filter((entry) => classifyTargetClass(entry.id) === className) |
| .toSorted((a, b) => a.id.localeCompare(b.id)); |
| const selected = candidates[0]; |
| if (!selected) { |
| throw new Error(`missing sampled target for class "${className}"`); |
| } |
| const pathSegments = samplePathSegments(selected.pathPattern); |
| return { |
| className, |
| id: selected.id, |
| type: selected.targetType, |
| configFile: selected.configFile, |
| pathSegments, |
| }; |
| }); |
|
|
| function planAcceptsExecRefForSample(params: { |
| type: string; |
| configFile: "openclaw.json" | "auth-profiles.json"; |
| pathSegments: string[]; |
| id: string; |
| }): boolean { |
| return isSecretsApplyPlan({ |
| version: 1, |
| protocolVersion: 1, |
| generatedAt: "2026-03-10T00:00:00.000Z", |
| generatedBy: "manual", |
| targets: [ |
| { |
| type: params.type, |
| path: params.pathSegments.join("."), |
| pathSegments: params.pathSegments, |
| ref: { source: "exec", provider: "vault", id: params.id }, |
| ...(params.configFile === "auth-profiles.json" ? { agentId: "main" } : {}), |
| }, |
| ], |
| }); |
| } |
|
|
| it("derives sampled class coverage from target registry metadata", () => { |
| expect(unclassifiedTargetIds).toEqual([]); |
| expect(sampledTargetsByClass.length).toBeGreaterThan(0); |
| }); |
|
|
| for (const sample of sampledTargetsByClass) { |
| it(`rejects traversal-segment exec ids for sampled class "${sample.className}" (example: "${sample.id}")`, () => { |
| expect( |
| planAcceptsExecRefForSample({ |
| type: sample.type, |
| configFile: sample.configFile, |
| pathSegments: sample.pathSegments, |
| id: "vault/openai/apiKey", |
| }), |
| ).toBe(true); |
| expect( |
| planAcceptsExecRefForSample({ |
| type: sample.type, |
| configFile: sample.configFile, |
| pathSegments: sample.pathSegments, |
| id: "vault/../apiKey", |
| }), |
| ).toBe(false); |
| }); |
| } |
| }); |
|
|