mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-26 16:41:49 +00:00
Secrets: reject exec SecretRef traversal ids across schema/runtime/gateway (#42370)
* Secrets: harden exec SecretRef validation and reload LKG coverage * Tests: harden exec fast-exit stdin regression case * Tests: align lifecycle daemon test formatting with oxfmt 0.36
This commit is contained in:
30
src/plugin-sdk/secret-input-schema.test.ts
Normal file
30
src/plugin-sdk/secret-input-schema.test.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
INVALID_EXEC_SECRET_REF_IDS,
|
||||
VALID_EXEC_SECRET_REF_IDS,
|
||||
} from "../test-utils/secret-ref-test-vectors.js";
|
||||
import { buildSecretInputSchema } from "./secret-input-schema.js";
|
||||
|
||||
describe("plugin-sdk secret input schema", () => {
|
||||
const schema = buildSecretInputSchema();
|
||||
|
||||
it("accepts plaintext and valid refs", () => {
|
||||
expect(schema.safeParse("sk-plain").success).toBe(true);
|
||||
expect(
|
||||
schema.safeParse({ source: "env", provider: "default", id: "OPENAI_API_KEY" }).success,
|
||||
).toBe(true);
|
||||
expect(
|
||||
schema.safeParse({ source: "file", provider: "filemain", id: "/providers/openai/apiKey" })
|
||||
.success,
|
||||
).toBe(true);
|
||||
for (const id of VALID_EXEC_SECRET_REF_IDS) {
|
||||
expect(schema.safeParse({ source: "exec", provider: "vault", id }).success, id).toBe(true);
|
||||
}
|
||||
});
|
||||
|
||||
it("rejects invalid exec refs", () => {
|
||||
for (const id of INVALID_EXEC_SECRET_REF_IDS) {
|
||||
expect(schema.safeParse({ source: "exec", provider: "vault", id }).success, id).toBe(false);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -1,12 +1,48 @@
|
||||
import { z } from "zod";
|
||||
import { ENV_SECRET_REF_ID_RE } from "../config/types.secrets.js";
|
||||
import {
|
||||
formatExecSecretRefIdValidationMessage,
|
||||
isValidExecSecretRefId,
|
||||
isValidFileSecretRefId,
|
||||
SECRET_PROVIDER_ALIAS_PATTERN,
|
||||
} from "../secrets/ref-contract.js";
|
||||
|
||||
export function buildSecretInputSchema() {
|
||||
const providerSchema = z
|
||||
.string()
|
||||
.regex(
|
||||
SECRET_PROVIDER_ALIAS_PATTERN,
|
||||
'Secret reference provider must match /^[a-z][a-z0-9_-]{0,63}$/ (example: "default").',
|
||||
);
|
||||
|
||||
return z.union([
|
||||
z.string(),
|
||||
z.object({
|
||||
source: z.enum(["env", "file", "exec"]),
|
||||
provider: z.string().min(1),
|
||||
id: z.string().min(1),
|
||||
}),
|
||||
z.discriminatedUnion("source", [
|
||||
z.object({
|
||||
source: z.literal("env"),
|
||||
provider: providerSchema,
|
||||
id: z
|
||||
.string()
|
||||
.regex(
|
||||
ENV_SECRET_REF_ID_RE,
|
||||
'Env secret reference id must match /^[A-Z][A-Z0-9_]{0,127}$/ (example: "OPENAI_API_KEY").',
|
||||
),
|
||||
}),
|
||||
z.object({
|
||||
source: z.literal("file"),
|
||||
provider: providerSchema,
|
||||
id: z
|
||||
.string()
|
||||
.refine(
|
||||
isValidFileSecretRefId,
|
||||
'File secret reference id must be an absolute JSON pointer (example: "/providers/openai/apiKey"), or "value" for singleValue mode.',
|
||||
),
|
||||
}),
|
||||
z.object({
|
||||
source: z.literal("exec"),
|
||||
provider: providerSchema,
|
||||
id: z.string().refine(isValidExecSecretRefId, formatExecSecretRefIdValidationMessage()),
|
||||
}),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user