Files
openclaw/src/config/types.secrets.ts

152 lines
3.7 KiB
TypeScript

export type SecretRefSource = "env" | "file" | "exec";
/**
* Stable identifier for a secret in a configured source.
* Examples:
* - env source: provider "default", id "OPENAI_API_KEY"
* - file source: provider "mounted-json", id "/providers/openai/apiKey"
* - exec source: provider "vault", id "openai/api-key"
*/
export type SecretRef = {
source: SecretRefSource;
provider: string;
id: string;
};
export type SecretInput = string | SecretRef;
export const DEFAULT_SECRET_PROVIDER_ALIAS = "default";
const ENV_SECRET_TEMPLATE_RE = /^\$\{([A-Z][A-Z0-9_]{0,127})\}$/;
function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === "object" && value !== null && !Array.isArray(value);
}
export function isSecretRef(value: unknown): value is SecretRef {
if (!isRecord(value)) {
return false;
}
if (Object.keys(value).length !== 3) {
return false;
}
return (
(value.source === "env" || value.source === "file" || value.source === "exec") &&
typeof value.provider === "string" &&
value.provider.trim().length > 0 &&
typeof value.id === "string" &&
value.id.trim().length > 0
);
}
function isLegacySecretRefWithoutProvider(
value: unknown,
): value is { source: SecretRefSource; id: string } {
if (!isRecord(value)) {
return false;
}
return (
(value.source === "env" || value.source === "file" || value.source === "exec") &&
typeof value.id === "string" &&
value.id.trim().length > 0 &&
value.provider === undefined
);
}
export function parseEnvTemplateSecretRef(
value: unknown,
provider = DEFAULT_SECRET_PROVIDER_ALIAS,
): SecretRef | null {
if (typeof value !== "string") {
return null;
}
const match = ENV_SECRET_TEMPLATE_RE.exec(value.trim());
if (!match) {
return null;
}
return {
source: "env",
provider: provider.trim() || DEFAULT_SECRET_PROVIDER_ALIAS,
id: match[1],
};
}
export function coerceSecretRef(
value: unknown,
defaults?: {
env?: string;
file?: string;
exec?: string;
},
): SecretRef | null {
if (isSecretRef(value)) {
return value;
}
if (isLegacySecretRefWithoutProvider(value)) {
const provider =
value.source === "env"
? (defaults?.env ?? DEFAULT_SECRET_PROVIDER_ALIAS)
: value.source === "file"
? (defaults?.file ?? DEFAULT_SECRET_PROVIDER_ALIAS)
: (defaults?.exec ?? DEFAULT_SECRET_PROVIDER_ALIAS);
return {
source: value.source,
provider,
id: value.id,
};
}
const envTemplate = parseEnvTemplateSecretRef(value, defaults?.env);
if (envTemplate) {
return envTemplate;
}
return null;
}
export type EnvSecretProviderConfig = {
source: "env";
/** Optional env var allowlist (exact names). */
allowlist?: string[];
};
export type FileSecretProviderMode = "singleValue" | "json";
export type FileSecretProviderConfig = {
source: "file";
path: string;
mode?: FileSecretProviderMode;
timeoutMs?: number;
maxBytes?: number;
};
export type ExecSecretProviderConfig = {
source: "exec";
command: string;
args?: string[];
timeoutMs?: number;
noOutputTimeoutMs?: number;
maxOutputBytes?: number;
jsonOnly?: boolean;
env?: Record<string, string>;
passEnv?: string[];
trustedDirs?: string[];
allowInsecurePath?: boolean;
allowSymlinkCommand?: boolean;
};
export type SecretProviderConfig =
| EnvSecretProviderConfig
| FileSecretProviderConfig
| ExecSecretProviderConfig;
export type SecretsConfig = {
providers?: Record<string, SecretProviderConfig>;
defaults?: {
env?: string;
file?: string;
exec?: string;
};
resolution?: {
maxProviderConcurrency?: number;
maxRefsPerProvider?: number;
maxBatchBytes?: number;
};
};