evOo-local/packages/contracts/src/bot-secrets.ts
cynarAI 6c7eed7580 Initial clean evOo-local from f00577b
Fresh history cut from cynarAI/evoo antigravity-handoff/project-timeline
(Voice First + project timeline). Brand docs/titles as evOo; keep @rakazo/*
packages as tech debt. Excludes node_modules, caches, secrets, voice blobs.
2026-09-20 17:15:16 +02:00

70 lines
2.0 KiB
TypeScript

import { z } from "zod";
export const BotSecretName = z.string().regex(/^[a-z][a-z0-9_]{0,63}$/);
const SecretHeaderName = z
.string()
.regex(/^[!#$%&'*+.^_`|~0-9A-Za-z-]{1,120}$/)
.refine(
(name) =>
!/^(host|connection|content-length|content-type|transfer-encoding|te|trailer|upgrade|cookie|origin|referer|accept|proxy-.*|sec-.*|.*forwarded.*)$/i.test(
name,
),
"Unsupported credential header",
);
export const BotSecretAuth = z.discriminatedUnion("type", [
z.object({ type: z.literal("bearer") }),
z.object({ type: z.literal("header"), name: SecretHeaderName }),
z.object({
type: z.literal("basic"),
username: z
.string()
.min(1)
.max(200)
.regex(/^[^:\r\n]+$/),
}),
]);
export const BotSecretDestination = z.object({
name: BotSecretName,
origin: z
.string()
.max(2048)
.refine((value) => {
try {
const url = new URL(value);
return (
url.protocol === "https:" &&
!url.username &&
!url.password &&
!url.search &&
!url.hash &&
url.pathname === "/"
);
} catch {
return false;
}
}, "Expected an HTTPS origin without a path, credentials, query, or fragment"),
auth: BotSecretAuth,
});
export type BotSecretDestination = z.infer<typeof BotSecretDestination>;
/** Written atomically with the protected value, distinct from action approval. */
export const BotSecretSubmission = z.object({ credentialSaved: BotSecretDestination });
export const SecretHttpRequest = z
.object({
name: BotSecretName,
url: z.string().max(8192),
method: z.enum(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD"]).default("GET"),
body: z.string().max(100_000).optional(),
contentType: z
.enum(["application/json", "application/x-www-form-urlencoded", "text/plain"])
.default("application/json"),
})
.refine(
(request) => request.body === undefined || !["GET", "HEAD"].includes(request.method),
"This method cannot have a body",
);