openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
888 lines (887 loc) • 35.1 kB
JavaScript
import { c as normalizeOptionalString } from "./string-coerce-mnp54Vah.js";
import { i as formatErrorMessage } from "./errors-BXgSefBE.js";
import { _ as uniqueStrings } from "./string-normalization-WNUDCpXX.js";
import { i as isPathInside, m as FsSafeError } from "./path-BlG8lhgR.js";
import { s as readSecureFile } from "./fs-safe-aqmM_n6V.js";
import { s as shouldRejectHardlinkedPluginFiles } from "./discovery-BDSfxwT6.js";
import { a as inspectPathPermissions, d as safeStat } from "./permissions-ya3cPkFH.js";
import { c as isRecord, p as resolveUserPath } from "./utils-CCC-BEJH.js";
import { t as loadPluginManifestRegistry } from "./manifest-registry-BywZIeAq.js";
import { s as normalizePluginsConfig } from "./config-state-CikNNz7T.js";
import { n as isActivatedManifestOwner } from "./manifest-owner-policy-DxLYM2ma.js";
import { c as secretRefKey, i as isValidExecSecretRefId, n as SINGLE_VALUE_FILE_REF_ID, o as isValidSecretProviderAlias, r as formatExecSecretRefIdValidationMessage, s as resolveDefaultSecretProviderAlias } from "./ref-contract-BAa3qHwr.js";
import { r as getCurrentPluginMetadataSnapshot } from "./current-plugin-metadata-snapshot-CfA_n2Nc.js";
import { i as normalizePositiveTimerMs, n as isNonEmptyString, r as normalizePositiveInt } from "./shared-CuqTS6Vs.js";
import "./audit-fs-CBe_wA_B.js";
import "./scan-paths-Bve2UhXh.js";
import { t as runTasksWithConcurrency } from "./run-with-concurrency-DivrDqxu.js";
import { n as readJsonPointer } from "./json-pointer-CTPEa7-l.js";
import fs from "node:fs";
import path from "node:path";
import fs$1 from "node:fs/promises";
import { spawn } from "node:child_process";
//#region src/secrets/provider-integrations.ts
/** Materializes trusted plugin secret-provider integrations into exec provider configs. */
const NODE_COMMAND_PLACEHOLDER = "${node}";
const PLUGIN_INTEGRATION_PROVIDER_ID_MAX_LENGTH = 128;
function isPathInsideOrEqual(rootDir, candidate) {
const relative = path.relative(path.resolve(rootDir), path.resolve(candidate));
return relative === "" || relative.length > 0 && !relative.startsWith("..") && !path.isAbsolute(relative);
}
function resolvePluginRelativePath(value, pluginRoot) {
const resolved = path.resolve(pluginRoot, value);
return isPathInsideOrEqual(pluginRoot, resolved) ? resolved : void 0;
}
function isPluginRelativeEntrypoint(value) {
return value.startsWith("./");
}
function resolveArg(arg, pluginRoot) {
if (!arg.startsWith("./") && !arg.startsWith("../")) return arg;
return resolvePluginRelativePath(arg, pluginRoot);
}
function withNodeCommandTrustedDir(command, pluginRoot) {
return command === NODE_COMMAND_PLACEHOLDER ? [...new Set([path.dirname(process.execPath), pluginRoot])] : [pluginRoot];
}
function isSecurePosixPathStat(stat) {
if (process.platform === "win32") return true;
if ((stat.mode & 18) !== 0) return false;
if (typeof process.getuid !== "function" || typeof stat.uid !== "number") return true;
const uid = process.getuid();
return stat.uid === uid || stat.uid === 0;
}
function pathSegmentsBetween(rootDir, targetDir) {
const relative = path.relative(rootDir, targetDir);
if (relative === "") return [];
if (relative.startsWith("..") || path.isAbsolute(relative)) return;
return relative.split(path.sep).filter(Boolean);
}
function isSecurePluginEntrypointPath(params) {
if (params.allowInsecurePath || process.platform === "win32") return true;
const originalSegments = pathSegmentsBetween(path.resolve(params.pluginRoot), path.dirname(path.resolve(params.resolvedEntrypoint)));
const realpathSegments = pathSegmentsBetween(params.pluginRootRealpath, path.dirname(params.entrypointRealpath));
if (!originalSegments || !realpathSegments) return false;
let originalDir = path.resolve(params.pluginRoot);
for (const [index, segment] of ["", ...originalSegments].entries()) {
if (segment) originalDir = path.join(originalDir, segment);
const stat = fs.lstatSync(originalDir);
if (index === 0 && stat.isSymbolicLink()) continue;
if (!stat.isDirectory() || stat.isSymbolicLink() || !isSecurePosixPathStat(stat)) return false;
}
let realpathDir = params.pluginRootRealpath;
for (const segment of ["", ...realpathSegments]) {
if (segment) realpathDir = path.join(realpathDir, segment);
const stat = fs.lstatSync(realpathDir);
if (!stat.isDirectory() || !isSecurePosixPathStat(stat)) return false;
}
return true;
}
function resolveNodeEntrypointArg(params) {
const entrypoint = params.integration.args?.[0];
if (!entrypoint || !isPluginRelativeEntrypoint(entrypoint)) return;
let pluginRootRealpath;
try {
pluginRootRealpath = fs.realpathSync(params.pluginRoot);
} catch {
return;
}
const resolved = resolvePluginRelativePath(entrypoint, params.pluginRoot);
if (!resolved) return;
let stat;
try {
stat = fs.lstatSync(resolved);
} catch {
return;
}
if (!stat.isFile() || stat.isSymbolicLink()) return;
if (params.rejectHardlinks && stat.nlink > 1) return;
if (params.integration.allowInsecurePath !== true && !isSecurePosixPathStat(stat)) return;
try {
const realpath = fs.realpathSync(resolved);
if (!isPathInsideOrEqual(pluginRootRealpath, realpath)) return;
if (!isSecurePluginEntrypointPath({
pluginRoot: params.pluginRoot,
pluginRootRealpath,
resolvedEntrypoint: resolved,
entrypointRealpath: realpath,
allowInsecurePath: params.integration.allowInsecurePath === true
})) return;
return realpath;
} catch {
return;
}
}
function materializeExecProviderConfig(integration, record, env) {
const pluginRoot = record.rootDir;
if (integration.command !== NODE_COMMAND_PLACEHOLDER) return;
const nodeEntrypoint = resolveNodeEntrypointArg({
integration,
pluginRoot,
rejectHardlinks: shouldRejectHardlinkedPluginFiles({
origin: record.origin,
rootDir: pluginRoot,
env
})
});
if (!nodeEntrypoint) return;
const args = integration.args?.map((arg, index) => nodeEntrypoint && index === 0 ? nodeEntrypoint : resolveArg(arg, pluginRoot)).filter((arg) => arg !== void 0);
if (integration.args && args?.length !== integration.args.length) return;
const trustedDirs = withNodeCommandTrustedDir(integration.command, pluginRoot);
return {
source: "exec",
command: process.execPath,
...args ? { args } : {},
...integration.timeoutMs !== void 0 ? { timeoutMs: integration.timeoutMs } : {},
...integration.noOutputTimeoutMs !== void 0 ? { noOutputTimeoutMs: integration.noOutputTimeoutMs } : {},
...integration.maxOutputBytes !== void 0 ? { maxOutputBytes: integration.maxOutputBytes } : {},
...integration.jsonOnly === false ? { jsonOnly: false } : {},
...integration.env ? { env: integration.env } : {},
...integration.passEnv ? { passEnv: integration.passEnv } : {},
trustedDirs,
...integration.command === NODE_COMMAND_PLACEHOLDER || integration.allowInsecurePath ? { allowInsecurePath: true } : {}
};
}
function canExposeSecretProviderIntegrations(params) {
if (params.record.origin !== "bundled" && params.record.origin !== "global") return false;
return isActivatedManifestOwner({
plugin: params.record,
normalizedConfig: params.normalizedConfig,
rootConfig: params.config
});
}
function integrationDisplayName(record, integrationId, integration) {
return normalizeOptionalString(integration.displayName) ?? normalizeOptionalString(record.name) ?? integrationId;
}
function createPluginIntegrationProviderConfig(params) {
return {
source: "exec",
pluginIntegration: {
pluginId: params.pluginId,
integrationId: params.integrationId
}
};
}
function isValidPluginIntegrationProviderId(value) {
return value.length > 0 && value.length <= PLUGIN_INTEGRATION_PROVIDER_ID_MAX_LENGTH;
}
/** Narrows a secret provider config to the plugin-integration exec shape. */
function isPluginIntegrationSecretProviderConfig(value) {
return typeof value === "object" && value !== null && "source" in value && value.source === "exec" && "pluginIntegration" in value && typeof value.pluginIntegration === "object" && value.pluginIntegration !== null && "pluginId" in value.pluginIntegration && typeof value.pluginIntegration.pluginId === "string" && value.pluginIntegration.pluginId.trim().length > 0 && "integrationId" in value.pluginIntegration && typeof value.pluginIntegration.integrationId === "string" && value.pluginIntegration.integrationId.trim().length > 0;
}
/** Materializes an active trusted plugin secret-provider integration into an exec provider. */
/** Resolves a trusted plugin secret-provider integration into executable provider config. */
function resolveSecretProviderIntegrationConfig(params) {
const config = params.config ?? {};
const normalizedConfig = normalizePluginsConfig(config.plugins);
const env = params.env ?? process.env;
const { pluginId, integrationId } = params.providerConfig.pluginIntegration;
if (!isValidSecretProviderAlias(params.providerAlias)) return {
ok: false,
reason: `provider alias "${params.providerAlias}" is invalid`
};
const record = params.manifestRegistry.plugins.find((candidate) => candidate.id === pluginId);
if (!record) return {
ok: false,
reason: `plugin "${pluginId}" is not installed`
};
if (!canExposeSecretProviderIntegrations({
record,
normalizedConfig,
config
})) return {
ok: false,
reason: `plugin "${pluginId}" is not active or is not from a trusted install origin`
};
const integration = record.secretProviderIntegrations?.[integrationId];
if (!integration) return {
ok: false,
reason: `plugin "${record.id}" does not declare secret provider integration "${integrationId}"`
};
const materialized = materializeExecProviderConfig(integration, record, env);
if (!materialized) return {
ok: false,
reason: `plugin "${record.id}" integration "${integrationId}" could not be materialized`
};
return {
ok: true,
providerConfig: materialized
};
}
/** Lists plugin secret-provider presets available to interactive configure flows. */
function listSecretProviderIntegrationPresets(params) {
const presets = [];
const config = params.config ?? {};
const normalizedConfig = normalizePluginsConfig(config.plugins);
const env = params.env ?? process.env;
for (const record of params.manifestRegistry.plugins) {
if (!canExposeSecretProviderIntegrations({
record,
normalizedConfig,
config
})) continue;
for (const [integrationId, integration] of Object.entries(record.secretProviderIntegrations ?? {})) {
const providerAlias = normalizeOptionalString(integration.providerAlias) ?? integrationId;
if (!isValidSecretProviderAlias(providerAlias) || !isValidPluginIntegrationProviderId(record.id) || !isValidPluginIntegrationProviderId(integrationId)) continue;
if (!materializeExecProviderConfig(integration, record, env)) continue;
presets.push({
id: integrationId,
pluginId: record.id,
providerAlias,
displayName: integrationDisplayName(record, integrationId, integration),
...integration.description ? { description: integration.description } : {},
providerConfig: createPluginIntegrationProviderConfig({
pluginId: record.id,
integrationId
})
});
}
}
return presets.toSorted((left, right) => `${left.displayName}:${left.providerAlias}`.localeCompare(`${right.displayName}:${right.providerAlias}`));
}
//#endregion
//#region src/secrets/resolve.ts
/** Resolves SecretRef values from env, file, and exec secret providers. */
const DEFAULT_PROVIDER_CONCURRENCY = 4;
const DEFAULT_MAX_REFS_PER_PROVIDER = 512;
const DEFAULT_MAX_BATCH_BYTES = 256 * 1024;
const DEFAULT_FILE_MAX_BYTES = 1024 * 1024;
const DEFAULT_FILE_TIMEOUT_MS = 5e3;
const DEFAULT_EXEC_TIMEOUT_MS = 5e3;
const DEFAULT_EXEC_MAX_OUTPUT_BYTES = 1024 * 1024;
const WINDOWS_ABS_PATH_PATTERN = /^[A-Za-z]:[\\/]/;
const WINDOWS_UNC_PATH_PATTERN = /^\\\\[^\\]+\\[^\\]+/;
/** Error for failures that affect an entire configured secret provider. */
/** Error emitted when a configured secret provider cannot resolve a ref. */
var SecretProviderResolutionError = class extends Error {
constructor(params) {
super(params.message, params.cause !== void 0 ? { cause: params.cause } : void 0);
this.scope = "provider";
this.name = "SecretProviderResolutionError";
this.source = params.source;
this.provider = params.provider;
}
};
/** Error for failures limited to one SecretRef id under a provider. */
var SecretRefResolutionError = class extends Error {
constructor(params) {
super(params.message, params.cause !== void 0 ? { cause: params.cause } : void 0);
this.scope = "ref";
this.name = "SecretRefResolutionError";
this.source = params.source;
this.provider = params.provider;
this.refId = params.refId;
}
};
/** Type guard for provider-scoped secret resolution failures. */
function isProviderScopedSecretResolutionError(value) {
return value instanceof SecretProviderResolutionError;
}
function isSecretResolutionError(value) {
return value instanceof SecretProviderResolutionError || value instanceof SecretRefResolutionError;
}
function providerResolutionError(params) {
return new SecretProviderResolutionError(params);
}
function refResolutionError(params) {
return new SecretRefResolutionError(params);
}
function throwUnknownProviderResolutionError(params) {
if (isSecretResolutionError(params.err)) throw params.err;
throw providerResolutionError({
source: params.source,
provider: params.provider,
message: formatErrorMessage(params.err),
cause: params.err
});
}
async function readFileStatOrThrow(pathname, label) {
const stat = await safeStat(pathname);
if (!stat.ok) throw new Error(`${label} is not readable: ${pathname}`);
if (stat.isDir) throw new Error(`${label} must be a file: ${pathname}`);
return stat;
}
function isAbsolutePathname(value) {
return path.isAbsolute(value) || WINDOWS_ABS_PATH_PATTERN.test(value) || WINDOWS_UNC_PATH_PATTERN.test(value);
}
function resolveResolutionLimits(config) {
const resolution = config.secrets?.resolution;
return {
maxProviderConcurrency: normalizePositiveInt(resolution?.maxProviderConcurrency, DEFAULT_PROVIDER_CONCURRENCY),
maxRefsPerProvider: normalizePositiveInt(resolution?.maxRefsPerProvider, DEFAULT_MAX_REFS_PER_PROVIDER),
maxBatchBytes: normalizePositiveInt(resolution?.maxBatchBytes, DEFAULT_MAX_BATCH_BYTES)
};
}
function toProviderKey(source, provider) {
return `${source}:${provider}`;
}
function resolveConfiguredProvider(params) {
const { ref, config } = params;
const providerConfig = config.secrets?.providers?.[ref.provider];
if (!providerConfig) {
if (ref.source === "env" && ref.provider === resolveDefaultSecretProviderAlias(config, "env")) return { source: "env" };
throw providerResolutionError({
source: ref.source,
provider: ref.provider,
message: `Secret provider "${ref.provider}" is not configured (ref: ${ref.source}:${ref.provider}:${ref.id}).`
});
}
if (providerConfig.source !== ref.source) throw providerResolutionError({
source: ref.source,
provider: ref.provider,
message: `Secret provider "${ref.provider}" has source "${providerConfig.source}" but ref requests "${ref.source}".`
});
if (isPluginIntegrationSecretProviderConfig(providerConfig)) {
const resolved = resolveSecretProviderIntegrationConfig({
manifestRegistry: params.manifestRegistry ?? getCurrentPluginMetadataSnapshot({
config,
env: params.env,
allowWorkspaceScopedSnapshot: true
})?.manifestRegistry ?? loadPluginManifestRegistry({
config,
env: params.env
}),
providerAlias: ref.provider,
providerConfig,
config,
env: params.env
});
if (!resolved.ok) throw providerResolutionError({
source: ref.source,
provider: ref.provider,
message: `Secret provider "${ref.provider}" plugin integration is unavailable: ${resolved.reason}.`
});
return resolved.providerConfig;
}
return providerConfig;
}
async function assertSecurePath(params) {
if (!isAbsolutePathname(params.targetPath)) throw new Error(`${params.label} must be an absolute path.`);
let effectivePath = params.targetPath;
let stat = await readFileStatOrThrow(effectivePath, params.label);
if (stat.isSymlink) {
if (!params.allowSymlinkPath) throw new Error(`${params.label} must not be a symlink: ${effectivePath}`);
try {
effectivePath = await fs$1.realpath(effectivePath);
} catch {
throw new Error(`${params.label} symlink target is not readable: ${params.targetPath}`);
}
if (!isAbsolutePathname(effectivePath)) throw new Error(`${params.label} resolved symlink target must be an absolute path.`);
stat = await readFileStatOrThrow(effectivePath, params.label);
if (stat.isSymlink) throw new Error(`${params.label} symlink target must not be a symlink: ${effectivePath}`);
}
if (params.trustedDirs && params.trustedDirs.length > 0) {
if (!params.trustedDirs.map((entry) => resolveUserPath(entry)).some((dir) => isPathInside(dir, effectivePath))) throw new Error(`${params.label} is outside trustedDirs: ${effectivePath}`);
}
if (params.allowInsecurePath) return effectivePath;
const perms = await inspectPathPermissions(effectivePath);
if (!perms.ok) throw new Error(`${params.label} permissions could not be verified: ${effectivePath}`);
const writableByOthers = perms.worldWritable || perms.groupWritable;
const readableByOthers = perms.worldReadable || perms.groupReadable;
if (writableByOthers || !params.allowReadableByOthers && readableByOthers) throw new Error(`${params.label} permissions are too open: ${effectivePath}`);
if (process.platform === "win32" && perms.source === "unknown") throw new Error(`${params.label} ACL verification unavailable on Windows for ${effectivePath}. Set allowInsecurePath=true for this provider to bypass this check when the path is trusted.`);
if (process.platform !== "win32" && typeof process.getuid === "function" && stat.uid != null) {
const uid = process.getuid();
if (stat.uid !== uid) throw new Error(`${params.label} must be owned by the current user (uid=${uid}): ${effectivePath}`);
}
return effectivePath;
}
async function readFileProviderPayload(params) {
const cacheKey = params.providerName;
const cache = params.cache;
const cachedFilePayload = cache?.filePayloadByProvider?.get(cacheKey);
if (cachedFilePayload) return await cachedFilePayload;
const filePath = resolveUserPath(params.providerConfig.path);
const readPromise = (async () => {
const timeoutMs = normalizePositiveTimerMs(params.providerConfig.timeoutMs, DEFAULT_FILE_TIMEOUT_MS);
const maxBytes = normalizePositiveInt(params.providerConfig.maxBytes, DEFAULT_FILE_MAX_BYTES);
try {
const { buffer: payload } = await readSecureFile({
filePath,
label: `secrets.providers.${params.providerName}.path`,
io: {
maxBytes,
timeoutMs
},
permissions: { allowInsecure: params.providerConfig.allowInsecurePath }
});
const text = payload.toString("utf8").replace(/^\uFEFF/, "");
if (params.providerConfig.mode === "singleValue") return text.replace(/\r?\n$/, "");
const parsed = JSON.parse(text);
if (!isRecord(parsed)) throw new Error(`File provider "${params.providerName}" payload is not a JSON object.`);
return parsed;
} catch (error) {
if (error instanceof FsSafeError && error.code === "timeout") throw new Error(`File provider "${params.providerName}" timed out after ${timeoutMs}ms.`, { cause: error });
throw error;
}
})();
if (cache) {
cache.filePayloadByProvider ??= /* @__PURE__ */ new Map();
cache.filePayloadByProvider.set(cacheKey, readPromise);
}
return await readPromise;
}
async function resolveEnvRefs(params) {
const resolved = /* @__PURE__ */ new Map();
const allowlist = params.providerConfig.allowlist ? new Set(params.providerConfig.allowlist) : null;
for (const ref of params.refs) {
if (allowlist && !allowlist.has(ref.id)) throw refResolutionError({
source: "env",
provider: params.providerName,
refId: ref.id,
message: `Environment variable "${ref.id}" is not allowlisted in secrets.providers.${params.providerName}.allowlist.`
});
const envValue = params.env[ref.id];
if (!isNonEmptyString(envValue)) throw refResolutionError({
source: "env",
provider: params.providerName,
refId: ref.id,
message: `Environment variable "${ref.id}" is missing or empty.`
});
resolved.set(ref.id, envValue);
}
return resolved;
}
async function resolveFileRefs(params) {
let payload;
try {
payload = await readFileProviderPayload({
providerName: params.providerName,
providerConfig: params.providerConfig,
cache: params.cache
});
} catch (err) {
throwUnknownProviderResolutionError({
source: "file",
provider: params.providerName,
err
});
}
const mode = params.providerConfig.mode ?? "json";
const resolved = /* @__PURE__ */ new Map();
if (mode === "singleValue") {
for (const ref of params.refs) {
if (ref.id !== "value") throw refResolutionError({
source: "file",
provider: params.providerName,
refId: ref.id,
message: `singleValue file provider "${params.providerName}" expects ref id "${SINGLE_VALUE_FILE_REF_ID}".`
});
resolved.set(ref.id, payload);
}
return resolved;
}
for (const ref of params.refs) try {
resolved.set(ref.id, readJsonPointer(payload, ref.id, { onMissing: "throw" }));
} catch (err) {
throw refResolutionError({
source: "file",
provider: params.providerName,
refId: ref.id,
message: formatErrorMessage(err),
cause: err
});
}
return resolved;
}
function isIgnorableStdinWriteError(error) {
if (typeof error !== "object" || error === null || !("code" in error)) return false;
const code = String(error.code);
return code === "EPIPE" || code === "ERR_STREAM_DESTROYED";
}
async function runExecResolver(params) {
return await new Promise((resolve, reject) => {
const child = spawn(params.command, params.args, {
cwd: params.cwd,
env: params.env,
stdio: [
"pipe",
"pipe",
"pipe"
],
shell: false,
windowsHide: true
});
let settled = false;
let stdout = "";
let stderr = "";
let timedOut = false;
let noOutputTimedOut = false;
let outputBytes = 0;
let noOutputTimer = null;
const timeoutTimer = setTimeout(() => {
timedOut = true;
child.kill("SIGKILL");
}, params.timeoutMs);
const clearTimers = () => {
clearTimeout(timeoutTimer);
if (noOutputTimer) {
clearTimeout(noOutputTimer);
noOutputTimer = null;
}
};
const armNoOutputTimer = () => {
if (noOutputTimer) clearTimeout(noOutputTimer);
noOutputTimer = setTimeout(() => {
noOutputTimedOut = true;
child.kill("SIGKILL");
}, params.noOutputTimeoutMs);
};
const append = (chunk, target) => {
const text = typeof chunk === "string" ? chunk : chunk.toString("utf8");
outputBytes += Buffer.byteLength(text, "utf8");
if (outputBytes > params.maxOutputBytes) {
child.kill("SIGKILL");
if (!settled) {
settled = true;
clearTimers();
reject(/* @__PURE__ */ new Error(`Exec provider output exceeded maxOutputBytes (${params.maxOutputBytes}).`));
}
return;
}
if (target === "stdout") stdout += text;
else stderr += text;
armNoOutputTimer();
};
armNoOutputTimer();
child.on("error", (error) => {
if (settled) return;
settled = true;
clearTimers();
reject(error);
});
child.stdout?.on("data", (chunk) => append(chunk, "stdout"));
child.stderr?.on("data", (chunk) => append(chunk, "stderr"));
child.on("close", (code, signal) => {
if (settled) return;
settled = true;
clearTimers();
resolve({
stdout,
stderr,
code,
signal,
termination: noOutputTimedOut ? "no-output-timeout" : timedOut ? "timeout" : "exit"
});
});
const handleStdinError = (error) => {
if (isIgnorableStdinWriteError(error) || settled) return;
settled = true;
clearTimers();
reject(error instanceof Error ? error : new Error(String(error)));
};
child.stdin?.on("error", handleStdinError);
try {
child.stdin?.end(params.input);
} catch (error) {
handleStdinError(error);
}
});
}
function parseExecValues(params) {
const trimmed = params.stdout.trim();
if (!trimmed) throw providerResolutionError({
source: "exec",
provider: params.providerName,
message: `Exec provider "${params.providerName}" returned empty stdout.`
});
let parsed;
if (!params.jsonOnly && params.ids.length === 1) try {
parsed = JSON.parse(trimmed);
} catch {
return { [params.ids[0]]: trimmed };
}
else try {
parsed = JSON.parse(trimmed);
} catch {
throw providerResolutionError({
source: "exec",
provider: params.providerName,
message: `Exec provider "${params.providerName}" returned invalid JSON.`
});
}
if (!isRecord(parsed)) {
if (!params.jsonOnly && params.ids.length === 1 && typeof parsed === "string") return { [params.ids[0]]: parsed };
throw providerResolutionError({
source: "exec",
provider: params.providerName,
message: `Exec provider "${params.providerName}" response must be an object.`
});
}
if (parsed.protocolVersion !== 1) throw providerResolutionError({
source: "exec",
provider: params.providerName,
message: `Exec provider "${params.providerName}" protocolVersion must be 1.`
});
const responseValues = parsed.values;
if (!isRecord(responseValues)) throw providerResolutionError({
source: "exec",
provider: params.providerName,
message: `Exec provider "${params.providerName}" response missing "values".`
});
const responseErrors = isRecord(parsed.errors) ? parsed.errors : null;
const out = {};
for (const id of params.ids) {
if (responseErrors && id in responseErrors) {
const entry = responseErrors[id];
if (isRecord(entry) && typeof entry.message === "string" && entry.message.trim()) throw refResolutionError({
source: "exec",
provider: params.providerName,
refId: id,
message: `Exec provider "${params.providerName}" failed for id "${id}" (${entry.message.trim()}).`
});
throw refResolutionError({
source: "exec",
provider: params.providerName,
refId: id,
message: `Exec provider "${params.providerName}" failed for id "${id}".`
});
}
if (!(id in responseValues)) throw refResolutionError({
source: "exec",
provider: params.providerName,
refId: id,
message: `Exec provider "${params.providerName}" response missing id "${id}".`
});
out[id] = responseValues[id];
}
return out;
}
async function resolveExecRefs(params) {
const ids = uniqueStrings(params.refs.map((ref) => ref.id));
if (ids.length > params.limits.maxRefsPerProvider) throw providerResolutionError({
source: "exec",
provider: params.providerName,
message: `Exec provider "${params.providerName}" exceeded maxRefsPerProvider (${params.limits.maxRefsPerProvider}).`
});
const commandPath = resolveUserPath(params.providerConfig.command);
let secureCommandPath;
try {
secureCommandPath = await assertSecurePath({
targetPath: commandPath,
label: `secrets.providers.${params.providerName}.command`,
trustedDirs: params.providerConfig.trustedDirs,
allowInsecurePath: params.providerConfig.allowInsecurePath,
allowReadableByOthers: true,
allowSymlinkPath: params.providerConfig.allowSymlinkCommand
});
} catch (err) {
throwUnknownProviderResolutionError({
source: "exec",
provider: params.providerName,
err
});
}
const requestPayload = {
protocolVersion: 1,
provider: params.providerName,
ids
};
const input = JSON.stringify(requestPayload);
if (Buffer.byteLength(input, "utf8") > params.limits.maxBatchBytes) throw providerResolutionError({
source: "exec",
provider: params.providerName,
message: `Exec provider "${params.providerName}" request exceeded maxBatchBytes (${params.limits.maxBatchBytes}).`
});
const childEnv = {};
for (const key of params.providerConfig.passEnv ?? []) {
const value = params.env[key];
if (value !== void 0) childEnv[key] = value;
}
for (const [key, value] of Object.entries(params.providerConfig.env ?? {})) childEnv[key] = value;
const timeoutMs = normalizePositiveTimerMs(params.providerConfig.timeoutMs, DEFAULT_EXEC_TIMEOUT_MS);
const noOutputTimeoutMs = normalizePositiveTimerMs(params.providerConfig.noOutputTimeoutMs, timeoutMs);
const maxOutputBytes = normalizePositiveInt(params.providerConfig.maxOutputBytes, DEFAULT_EXEC_MAX_OUTPUT_BYTES);
const jsonOnly = params.providerConfig.jsonOnly ?? true;
let result;
try {
result = await runExecResolver({
command: secureCommandPath,
args: params.providerConfig.args ?? [],
cwd: path.dirname(secureCommandPath),
env: childEnv,
input,
timeoutMs,
noOutputTimeoutMs,
maxOutputBytes
});
} catch (err) {
throwUnknownProviderResolutionError({
source: "exec",
provider: params.providerName,
err
});
}
if (result.termination === "timeout") throw providerResolutionError({
source: "exec",
provider: params.providerName,
message: `Exec provider "${params.providerName}" timed out after ${timeoutMs}ms.`
});
if (result.termination === "no-output-timeout") throw providerResolutionError({
source: "exec",
provider: params.providerName,
message: `Exec provider "${params.providerName}" produced no output for ${noOutputTimeoutMs}ms.`
});
if (result.code !== 0) throw providerResolutionError({
source: "exec",
provider: params.providerName,
message: `Exec provider "${params.providerName}" exited with code ${String(result.code)}.`
});
let values;
try {
values = parseExecValues({
providerName: params.providerName,
ids,
stdout: result.stdout,
jsonOnly
});
} catch (err) {
throwUnknownProviderResolutionError({
source: "exec",
provider: params.providerName,
err
});
}
const resolved = /* @__PURE__ */ new Map();
for (const id of ids) resolved.set(id, values[id]);
return resolved;
}
async function resolveProviderRefs(params) {
try {
if (params.providerConfig.source === "env") return await resolveEnvRefs({
refs: params.refs,
providerName: params.providerName,
providerConfig: params.providerConfig,
env: params.options.env ?? process.env
});
if (params.providerConfig.source === "file") return await resolveFileRefs({
refs: params.refs,
providerName: params.providerName,
providerConfig: params.providerConfig,
cache: params.options.cache
});
if (params.providerConfig.source === "exec") {
if (isPluginIntegrationSecretProviderConfig(params.providerConfig)) throw providerResolutionError({
source: params.source,
provider: params.providerName,
message: `Secret provider "${params.providerName}" plugin integration was not materialized before exec resolution.`
});
return await resolveExecRefs({
refs: params.refs,
providerName: params.providerName,
providerConfig: params.providerConfig,
env: params.options.env ?? process.env,
limits: params.limits
});
}
throw providerResolutionError({
source: params.source,
provider: params.providerName,
message: `Unsupported secret provider source "${String(params.providerConfig.source)}".`
});
} catch (err) {
return throwUnknownProviderResolutionError({
source: params.source,
provider: params.providerName,
err
});
}
}
/** Resolves a batch of SecretRefs, grouped by provider for bounded provider concurrency. */
async function resolveSecretRefValues(refs, options) {
if (refs.length === 0) return /* @__PURE__ */ new Map();
const limits = resolveResolutionLimits(options.config);
const uniqueRefs = /* @__PURE__ */ new Map();
for (const ref of refs) {
const id = ref.id.trim();
if (!id) throw new Error("Secret reference id is empty.");
if (ref.source === "exec" && !isValidExecSecretRefId(id)) throw new Error(`${formatExecSecretRefIdValidationMessage()} (ref: ${ref.source}:${ref.provider}:${id}).`);
uniqueRefs.set(secretRefKey(ref), {
...ref,
id
});
}
const grouped = /* @__PURE__ */ new Map();
for (const ref of uniqueRefs.values()) {
const key = toProviderKey(ref.source, ref.provider);
const existing = grouped.get(key);
if (existing) {
existing.refs.push(ref);
continue;
}
grouped.set(key, {
source: ref.source,
providerName: ref.provider,
refs: [ref]
});
}
const taskResults = await runTasksWithConcurrency({
tasks: [...grouped.values()].map((group) => async () => {
if (group.refs.length > limits.maxRefsPerProvider) throw providerResolutionError({
source: group.source,
provider: group.providerName,
message: `Secret provider "${group.providerName}" exceeded maxRefsPerProvider (${limits.maxRefsPerProvider}).`
});
const providerConfig = resolveConfiguredProvider({
ref: group.refs[0],
config: options.config,
env: options.env ?? process.env,
manifestRegistry: options.manifestRegistry
});
return {
group,
values: await resolveProviderRefs({
refs: group.refs,
source: group.source,
providerName: group.providerName,
providerConfig,
options,
limits
})
};
}),
limit: limits.maxProviderConcurrency,
errorMode: "stop"
});
if (taskResults.hasError) throw taskResults.firstError;
const resolved = /* @__PURE__ */ new Map();
for (const result of taskResults.results) for (const ref of result.group.refs) {
if (!result.values.has(ref.id)) throw refResolutionError({
source: result.group.source,
provider: result.group.providerName,
refId: ref.id,
message: `Secret provider "${result.group.providerName}" did not return id "${ref.id}".`
});
resolved.set(secretRefKey(ref), result.values.get(ref.id));
}
return resolved;
}
/** Resolves one SecretRef, using the optional shared runtime cache. */
/** Resolves one SecretRef to an unknown value using configured provider state. */
async function resolveSecretRefValue(ref, options) {
const cache = options.cache;
const key = secretRefKey(ref);
const cachedResolvedValue = cache?.resolvedByRefKey?.get(key);
if (cachedResolvedValue) return await cachedResolvedValue;
const promise = (async () => {
const resolved = await resolveSecretRefValues([ref], options);
if (!resolved.has(key)) throw refResolutionError({
source: ref.source,
provider: ref.provider,
refId: ref.id,
message: `Secret reference "${key}" resolved to no value.`
});
return resolved.get(key);
})();
if (cache) {
cache.resolvedByRefKey ??= /* @__PURE__ */ new Map();
cache.resolvedByRefKey.set(key, promise);
}
return await promise;
}
/** Resolves one SecretRef and requires a non-empty string result. */
async function resolveSecretRefString(ref, options) {
const resolved = await resolveSecretRefValue(ref, options);
if (!isNonEmptyString(resolved)) throw new Error(`Secret reference "${ref.source}:${ref.provider}:${ref.id}" resolved to a non-string or empty value.`);
return resolved;
}
//#endregion
export { resolveSecretRefValue as a, listSecretProviderIntegrationPresets as c, resolveSecretRefString as i, resolveSecretProviderIntegrationConfig as l, SecretRefResolutionError as n, resolveSecretRefValues as o, isProviderScopedSecretResolutionError as r, isPluginIntegrationSecretProviderConfig as s, SecretProviderResolutionError as t };