Files
openclaw/src/secrets/resolve.test.ts
Peter Steinberger edecdbd05e refactor(config): config-surface reduction tranche 3 — product consolidations (review request) (#111527)
* refactor(config): consolidate media model lists

* refactor(config): unify memory configuration

* refactor(config): consolidate TTS ownership

* refactor(config): move typing policy to agents

* refactor(config): retire product-level config surfaces

* refactor(config): share scoped tool policy type

* chore(config): refresh generated baselines

* fix(config): honor agent typing overrides

* fix(config): migrate sibling config consumers

* refactor(infra): keep base64url decoder private

* fix(config): strip invalid legacy TTS values

* chore(config): refresh rebased baseline hash

* fix(doctor): route legacy messages.tts.realtime voice to talk during tts move

* refactor(config): polish final layout names

* refactor(config): freeze retired tuning defaults

* feat(config): add fast mode default symmetry

* refactor(config): key agent entries by id

* docs(config): update final layout reference

* test(config): cover final layout migrations

* chore(config): refresh final layout baselines

* fix(config): align final layout runtime readers

* fix(config): align remaining readers

* fix(config): stabilize final layout migrations

* fix(config): finalize config projection proof

* fix(config): address final layout review

* docs(release): preserve historical config names

* fix(config): complete keyed agent migration

* fix(config): close final migration gaps

* fix(config): finish full-branch review

* fix(config): complete runtime secret detection

* fix(config): close final review findings

* fix(config): finish canonical docs and heartbeat migration

* fix(config): integrate latest main after rebase

* refactor(env): isolate test-only controls

* refactor(env): isolate build and development controls

* refactor(env): collapse process identity indirection

* refactor(env): remove duplicate config and temp aliases

* docs(env): define the operator-facing allowlist

* ci(env): ratchet production variable count

* fix(env): remove stale provider helper import

* fix(env): make ratchet sorting explicit

* test(env): keep test seam in dead-code audit

* test(env): cover ratchet growth and boundary; document surface budgets

* docs(config): document tier-eval consolidations

* docs(config): clarify speech preference ownership

* test(memory): align retired tuning fixtures

* refactor(memory): freeze engine heuristics

* refactor(config): apply tier-eval tranche

* refactor(tts): move persona shaping to providers

* refactor(compaction): move prompt policy to providers

* test(config): align hookified prompt fixtures

* chore(deadcode): classify test-only exports

* chore(github): remove unused spawn helper

* chore(deadcode): classify queue diagnostics

* chore(deadcode): remove unused lane snapshot export

* chore(plugin-sdk): ratchet consolidated surface

* fix(config): integrate latest main after rebase
2026-07-21 20:28:43 -07:00

850 lines
27 KiB
TypeScript

/** Tests SecretRef provider resolution for env, file, and exec sources. */
import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { afterAll, beforeAll, describe, expect, it, vi } from "vitest";
import type { OpenClawConfig } from "../config/config.js";
import { MAX_TIMER_TIMEOUT_MS } from "../shared/number-coercion.js";
import {
killPidIfAlive,
readPidFile,
waitForPidToExit,
writeForkingNoOutputScript,
} from "../test-utils/process-tree.js";
import { INVALID_EXEC_SECRET_REF_IDS } from "../test-utils/secret-ref-test-vectors.js";
import { withMockedWindowsPlatform } from "../test-utils/vitest-spies.js";
import {
isMissingSecretRefResolutionError,
resolveSecretRefString,
resolveSecretRefValue,
resolveSecretRefValues,
} from "./resolve.js";
async function writeSecureFile(filePath: string, content: string, mode = 0o600): Promise<void> {
await fs.mkdir(path.dirname(filePath), { recursive: true });
const tempPath = `${filePath}.tmp-${process.pid}-${Date.now()}-${Math.random()
.toString(16)
.slice(2)}`;
try {
await fs.writeFile(tempPath, content, "utf8");
await fs.chmod(tempPath, mode);
await fs.rename(tempPath, filePath);
} catch (err) {
await fs.rm(tempPath, { force: true }).catch(() => {});
throw err;
}
}
describe("secret ref resolver", () => {
const isWindows = process.platform === "win32";
function itPosix(name: string, fn: () => Promise<void> | void) {
it.skipIf(isWindows)(name, fn);
}
let fixtureRoot = "";
let caseId = 0;
let execProtocolV1ScriptPath = "";
let execPlainScriptPath = "";
let execProtocolV2ScriptPath = "";
let execMissingIdScriptPath = "";
let execInheritedErrorScriptPath = "";
let execProviderErrorScriptPath = "";
let execUnsafeProviderErrorScriptPath = "";
let execInvalidJsonScriptPath = "";
let execFastExitScriptPath = "";
const createCaseDir = async (label: string): Promise<string> => {
const dir = path.join(fixtureRoot, `${label}-${caseId++}`);
await fs.mkdir(dir);
return dir;
};
type ExecProviderConfig = {
source: "exec";
command: string;
passEnv?: string[];
jsonOnly?: boolean;
allowSymlinkCommand?: boolean;
trustedDirs?: string[];
env?: Record<string, string>;
args?: string[];
timeoutMs?: number;
noOutputTimeoutMs?: number;
};
type FileProviderConfig = {
source: "file";
path: string;
mode: "json" | "singleValue";
timeoutMs?: number;
allowInsecurePath?: boolean;
};
function createExecProviderConfig(
command: string,
overrides: Partial<ExecProviderConfig> = {},
): ExecProviderConfig {
return {
source: "exec",
command,
passEnv: ["PATH"],
...overrides,
};
}
async function resolveExecSecret(
command: string,
overrides: Partial<ExecProviderConfig> = {},
): Promise<string> {
return resolveSecretRefString(
{ source: "exec", provider: "execmain", id: "openai/api-key" },
{
config: {
secrets: {
providers: {
execmain: createExecProviderConfig(command, overrides),
},
},
},
},
);
}
function createFileProviderConfig(
filePath: string,
overrides: Partial<FileProviderConfig> = {},
): FileProviderConfig {
return {
source: "file",
path: filePath,
mode: "json",
...overrides,
};
}
beforeAll(async () => {
fixtureRoot = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-secrets-resolve-"));
const sharedExecDir = path.join(fixtureRoot, "shared-exec");
await fs.mkdir(sharedExecDir, { recursive: true });
execProtocolV1ScriptPath = path.join(sharedExecDir, "resolver-v1.sh");
await writeSecureFile(
execProtocolV1ScriptPath,
[
"#!/bin/sh",
'printf \'{"protocolVersion":1,"values":{"openai/api-key":"value:openai/api-key"}}\'',
].join("\n"),
0o700,
);
execPlainScriptPath = path.join(sharedExecDir, "resolver-plain.sh");
await writeSecureFile(
execPlainScriptPath,
["#!/bin/sh", "printf 'plain-secret'"].join("\n"),
0o700,
);
execProtocolV2ScriptPath = path.join(sharedExecDir, "resolver-v2.sh");
await writeSecureFile(
execProtocolV2ScriptPath,
["#!/bin/sh", 'printf \'{"protocolVersion":2,"values":{"openai/api-key":"x"}}\''].join("\n"),
0o700,
);
execMissingIdScriptPath = path.join(sharedExecDir, "resolver-missing-id.sh");
await writeSecureFile(
execMissingIdScriptPath,
["#!/bin/sh", 'printf \'{"protocolVersion":1,"values":{}}\''].join("\n"),
0o700,
);
execInheritedErrorScriptPath = path.join(sharedExecDir, "resolver-inherited-error.sh");
await writeSecureFile(
execInheritedErrorScriptPath,
[
"#!/bin/sh",
'printf \'{"protocolVersion":1,"values":{"toString":"resolved"},"errors":{}}\'',
].join("\n"),
0o700,
);
execProviderErrorScriptPath = path.join(sharedExecDir, "resolver-error.sh");
await writeSecureFile(
execProviderErrorScriptPath,
[
"#!/bin/sh",
'printf \'{"protocolVersion":1,"values":{},"errors":{"openai/api-key":{"code":"NOT_FOUND","message":"provider-private-detail-7f3c"}}}\'',
].join("\n"),
0o700,
);
execUnsafeProviderErrorScriptPath = path.join(sharedExecDir, "resolver-unsafe-error.sh");
await writeSecureFile(
execUnsafeProviderErrorScriptPath,
[
"#!/bin/sh",
'printf \'{"protocolVersion":1,"values":{},"errors":{"openai/api-key":{"code":"PROVIDERPRIVATEDETAIL9C2E"}}}\'',
].join("\n"),
0o700,
);
execInvalidJsonScriptPath = path.join(sharedExecDir, "resolver-invalid-json.sh");
await writeSecureFile(
execInvalidJsonScriptPath,
["#!/bin/sh", "printf 'not-json'"].join("\n"),
0o700,
);
execFastExitScriptPath = path.join(sharedExecDir, "resolver-fast-exit.sh");
await writeSecureFile(execFastExitScriptPath, ["#!/bin/sh", "exit 0"].join("\n"), 0o700);
});
afterAll(async () => {
if (!fixtureRoot) {
return;
}
await fs.rm(fixtureRoot, { recursive: true, force: true });
});
it("resolves env refs via implicit default env provider", async () => {
const config: OpenClawConfig = {};
const value = await resolveSecretRefString(
{ source: "env", provider: "default", id: "OPENAI_API_KEY" },
{
config,
env: { OPENAI_API_KEY: "sk-env-value" }, // pragma: allowlist secret
},
);
expect(value).toBe("sk-env-value");
});
it("classifies only matching absent env refs as missing", async () => {
const ref = { source: "env", provider: "default", id: "MISSING_API_KEY" } as const;
const missingError = await resolveSecretRefValue(ref, { config: {}, env: {} }).catch(
(error: unknown) => error,
);
expect(isMissingSecretRefResolutionError({ ref, error: missingError })).toBe(true);
expect(
isMissingSecretRefResolutionError({
ref: { ...ref, id: "OTHER_API_KEY" },
error: missingError,
}),
).toBe(false);
const policyError = await resolveSecretRefValue(ref, {
config: {
secrets: {
providers: {
default: { source: "env", allowlist: ["OTHER_API_KEY"] },
},
},
},
env: { MISSING_API_KEY: "test-missing-api-key" },
}).catch((error: unknown) => error);
expect(isMissingSecretRefResolutionError({ ref, error: policyError })).toBe(false);
});
it("classifies missing refs under a configured default provider alias", async () => {
const ref = { source: "env", provider: "primary", id: "MISSING_API_KEY" } as const;
const error = await resolveSecretRefValue(ref, {
config: {
secrets: {
defaults: { env: "primary" },
providers: { primary: { source: "env" } },
},
},
env: {},
}).catch((caught: unknown) => caught);
expect(isMissingSecretRefResolutionError({ ref, error })).toBe(true);
});
it("does not rewrite an explicit default provider to a configured alias", async () => {
const ref = { source: "env", provider: "default", id: "MISSING_API_KEY" } as const;
const error = await resolveSecretRefValue(ref, {
config: {
secrets: {
defaults: { env: "primary" },
providers: { primary: { source: "env" } },
},
},
env: {},
}).catch((caught: unknown) => caught);
expect(error).toBeInstanceOf(Error);
expect((error as Error).message).toContain('Secret provider "default" is not configured');
expect(isMissingSecretRefResolutionError({ ref, error })).toBe(false);
});
itPosix("resolves file refs in json mode", async () => {
const root = await createCaseDir("file");
const filePath = path.join(root, "secrets.json");
await writeSecureFile(
filePath,
JSON.stringify({
providers: {
openai: {
apiKey: "sk-file-value", // pragma: allowlist secret
},
},
}),
);
const value = await resolveSecretRefString(
{ source: "file", provider: "filemain", id: "/providers/openai/apiKey" },
{
config: {
secrets: {
providers: {
filemain: createFileProviderConfig(filePath),
},
},
},
},
);
expect(value).toBe("sk-file-value");
});
itPosix("classifies an out-of-bounds file pointer as a missing ref", async () => {
const root = await createCaseDir("file-missing-index");
const filePath = path.join(root, "secrets.json");
await writeSecureFile(filePath, JSON.stringify({ providers: [] }));
const ref = { source: "file", provider: "filemain", id: "/providers/0" } as const;
const error = await resolveSecretRefValue(ref, {
config: {
secrets: {
providers: {
filemain: createFileProviderConfig(filePath),
},
},
},
}).catch((caught: unknown) => caught);
expect(isMissingSecretRefResolutionError({ ref, error })).toBe(true);
});
itPosix("resolves exec refs with protocolVersion 1 response", async () => {
const value = await resolveExecSecret(execProtocolV1ScriptPath);
expect(value).toBe("value:openai/api-key");
});
itPosix("surfaces bounded exec error codes without provider-supplied detail", async () => {
const error = await resolveExecSecret(execProviderErrorScriptPath).catch(
(caught: unknown) => caught,
);
expect(error).toBeInstanceOf(Error);
expect((error as Error).message).toBe(
'Exec provider "execmain" failed for id "openai/api-key" (NOT_FOUND).',
);
expect((error as Error).message).not.toContain("provider-private-detail-7f3c");
});
itPosix(
"classifies omitted and NOT_FOUND exec ids as missing but keeps other errors fail-closed",
async () => {
const ref = { source: "exec", provider: "execmain", id: "openai/api-key" } as const;
const configFor = (command: string): OpenClawConfig => ({
secrets: {
providers: {
execmain: createExecProviderConfig(command),
},
},
});
const omittedError = await resolveSecretRefValue(ref, {
config: configFor(execMissingIdScriptPath),
}).catch((error: unknown) => error);
const missingError = await resolveSecretRefValue(ref, {
config: configFor(execProviderErrorScriptPath),
}).catch((error: unknown) => error);
const providerError = await resolveSecretRefValue(ref, {
config: configFor(execUnsafeProviderErrorScriptPath),
}).catch((error: unknown) => error);
expect(isMissingSecretRefResolutionError({ ref, error: omittedError })).toBe(true);
expect(isMissingSecretRefResolutionError({ ref, error: missingError })).toBe(true);
expect(isMissingSecretRefResolutionError({ ref, error: providerError })).toBe(false);
},
);
itPosix("suppresses exec error codes outside the bounded format", async () => {
const error = await resolveExecSecret(execUnsafeProviderErrorScriptPath).catch(
(caught: unknown) => caught,
);
expect(error).toBeInstanceOf(Error);
expect((error as Error).message).toBe(
'Exec provider "execmain" failed for id "openai/api-key".',
);
expect((error as Error).message).not.toContain("PROVIDERPRIVATEDETAIL9C2E");
});
itPosix("clamps oversized exec provider timeouts", async () => {
const setTimeoutSpy = vi.spyOn(globalThis, "setTimeout");
const value = await resolveExecSecret(execProtocolV1ScriptPath, {
timeoutMs: Number.MAX_SAFE_INTEGER,
noOutputTimeoutMs: Number.MAX_SAFE_INTEGER,
});
expect(value).toBe("value:openai/api-key");
expect(setTimeoutSpy).toHaveBeenCalledWith(expect.any(Function), MAX_TIMER_TIMEOUT_MS);
});
itPosix("uses timeoutMs as the default no-output timeout for exec providers", async () => {
const root = await createCaseDir("exec-delay");
const scriptPath = path.join(root, "resolver-delay.sh");
// Keep the fixture cheap to start so this stays deterministic under a busy test run.
await writeSecureFile(
scriptPath,
[
"#!/bin/sh",
"sleep 0.03",
'printf \'{"protocolVersion":1,"values":{"delayed":"ok"}}\'',
].join("\n"),
0o700,
);
const value = await resolveSecretRefString(
{ source: "exec", provider: "execmain", id: "delayed" },
{
config: {
secrets: {
providers: {
execmain: {
source: "exec",
command: scriptPath,
passEnv: ["PATH"],
timeoutMs: 1500,
},
},
},
},
},
);
expect(value).toBe("ok");
});
itPosix("kills forked exec provider children on no-output timeout", async () => {
const root = await createCaseDir("exec-fork-timeout");
const scriptPath = await writeForkingNoOutputScript(root);
const pidPath = path.join(root, "forked.pid");
let childPid: number | undefined;
const nativeSetTimeout = globalThis.setTimeout;
const noOutputTimeouts: Array<() => void> = [];
const setTimeoutSpy = vi
.spyOn(globalThis, "setTimeout")
.mockImplementation((callback, delay, ...args) => {
if (delay === 1_000) {
noOutputTimeouts.push(() => callback(...args));
return nativeSetTimeout(() => undefined, 60_000);
}
return nativeSetTimeout(callback, delay, ...args);
});
try {
const resultPromise = resolveExecSecret(scriptPath, {
env: { NODE_BINARY: process.execPath, PID_FILE: pidPath },
// Preserve production-like startup headroom; the test fires the
// re-armed timer only after the readiness byte arrives.
noOutputTimeoutMs: 1_000,
timeoutMs: 10_000,
});
await vi.waitFor(() => {
expect(noOutputTimeouts.length).toBeGreaterThanOrEqual(2);
});
childPid = await readPidFile(pidPath);
noOutputTimeouts.at(-1)?.();
await expect(resultPromise).rejects.toThrow('Exec provider "execmain" produced no output');
expect(await waitForPidToExit(childPid, 5_000)).toBe(true);
} finally {
setTimeoutSpy.mockRestore();
killPidIfAlive(childPid);
}
});
itPosix("supports non-JSON single-value exec output when jsonOnly is false", async () => {
const value = await resolveExecSecret(execPlainScriptPath, { jsonOnly: false });
expect(value).toBe("plain-secret");
});
itPosix(
"tolerates stdin write errors when exec provider exits before consuming a large request",
async () => {
const refs = Array.from({ length: 256 }, (_, index) => ({
source: "exec" as const,
provider: "execmain",
id: `openai/${String(index).padStart(3, "0")}/${"x".repeat(240)}`,
}));
await expect(
resolveSecretRefValues(refs, {
config: {
secrets: {
providers: {
execmain: {
source: "exec",
command: execFastExitScriptPath,
},
},
},
},
}),
).rejects.toThrow('Exec provider "execmain" returned empty stdout.');
},
);
it("enforces the built-in per-provider reference limit", async () => {
const refs = Array.from({ length: 513 }, (_, index) => ({
source: "env" as const,
provider: "default",
id: `SECRET_${index}`,
}));
await expect(resolveSecretRefValues(refs, { config: {} })).rejects.toThrow(
'Secret provider "default" exceeded maxRefsPerProvider (512).',
);
});
itPosix("rejects symlink command paths", async () => {
const root = await createCaseDir("exec-link-reject");
const symlinkPath = path.join(root, "resolver-link.mjs");
await fs.symlink(execPlainScriptPath, symlinkPath);
await expect(resolveExecSecret(symlinkPath, { jsonOnly: false })).rejects.toThrow(
"must not be a symlink",
);
});
itPosix("stays fail-closed when the retired symlink opt-out is present", async () => {
const root = await createCaseDir("exec-link-allow");
const symlinkPath = path.join(root, "resolver-link.mjs");
await fs.symlink(execPlainScriptPath, symlinkPath);
await expect(
resolveExecSecret(symlinkPath, {
jsonOnly: false,
allowSymlinkCommand: true,
}),
).rejects.toThrow("must not be a symlink");
});
itPosix(
"rejects Homebrew-style symlinked exec commands even with the retired opt-out",
async () => {
const root = await createCaseDir("homebrew");
const binDir = path.join(root, "opt", "homebrew", "bin");
const cellarDir = path.join(root, "opt", "homebrew", "Cellar", "node", "25.0.0", "bin");
await fs.mkdir(binDir, { recursive: true });
await fs.mkdir(cellarDir, { recursive: true });
const targetCommand = path.join(cellarDir, "node");
const symlinkCommand = path.join(binDir, "node");
await writeSecureFile(
targetCommand,
[
"#!/bin/sh",
'suffix="${1:-missing}"',
'printf \'{"protocolVersion":1,"values":{"openai/api-key":"%s:openai/api-key"}}\' "$suffix"',
].join("\n"),
0o700,
);
await fs.symlink(targetCommand, symlinkCommand);
await expect(
resolveExecSecret(symlinkCommand, {
args: ["brew"],
allowSymlinkCommand: true,
}),
).rejects.toThrow("must not be a symlink");
},
);
itPosix("rejects symlinks before trusted-directory evaluation", async () => {
const root = await createCaseDir("exec-link-trusted");
const symlinkPath = path.join(root, "resolver-link.mjs");
await fs.symlink(execPlainScriptPath, symlinkPath);
await expect(
resolveExecSecret(symlinkPath, {
jsonOnly: false,
allowSymlinkCommand: true,
trustedDirs: [root],
}),
).rejects.toThrow("must not be a symlink");
});
itPosix("rejects exec refs when protocolVersion is not 1", async () => {
await expect(resolveExecSecret(execProtocolV2ScriptPath)).rejects.toThrow(
"protocolVersion must be 1",
);
});
itPosix("rejects exec refs when response omits requested id", async () => {
await expect(resolveExecSecret(execMissingIdScriptPath)).rejects.toThrow(
'response missing id "openai/api-key"',
);
});
itPosix("rejects exec refs when missing response id is inherited", async () => {
await expect(
resolveSecretRefValue(
{ source: "exec", provider: "execmain", id: "toString" },
{
config: {
secrets: {
providers: {
execmain: createExecProviderConfig(execMissingIdScriptPath),
},
},
},
},
),
).rejects.toThrow('response missing id "toString"');
});
itPosix("ignores inherited exec response errors", async () => {
await expect(
resolveSecretRefValue(
{ source: "exec", provider: "execmain", id: "toString" },
{
config: {
secrets: {
providers: {
execmain: createExecProviderConfig(execInheritedErrorScriptPath),
},
},
},
},
),
).resolves.toBe("resolved");
});
itPosix("rejects exec refs with invalid JSON when jsonOnly is true", async () => {
await expect(resolveExecSecret(execInvalidJsonScriptPath, { jsonOnly: true })).rejects.toThrow(
"returned invalid JSON",
);
});
itPosix("supports file singleValue mode with id=value", async () => {
const root = await createCaseDir("file-single-value");
const filePath = path.join(root, "token.txt");
await writeSecureFile(filePath, "raw-token-value\n");
const value = await resolveSecretRefString(
{ source: "file", provider: "rawfile", id: "value" },
{
config: {
secrets: {
providers: {
rawfile: createFileProviderConfig(filePath, {
mode: "singleValue",
}),
},
},
},
},
);
expect(value).toBe("raw-token-value");
});
itPosix("times out file provider reads when timeoutMs elapses", async () => {
const root = await createCaseDir("file-timeout");
const filePath = path.join(root, "secrets.json");
await writeSecureFile(
filePath,
JSON.stringify({
providers: {
openai: {
apiKey: "sk-file-value", // pragma: allowlist secret
},
},
}),
);
const sampleHandle = await fs.open(filePath, "r");
const fileHandlePrototype = Object.getPrototypeOf(sampleHandle) as {
read: typeof sampleHandle.read;
};
await sampleHandle.close();
const readSpy = vi
.spyOn(fileHandlePrototype, "read")
.mockImplementation(() => new Promise(() => {}) as never);
try {
await expect(
resolveSecretRefString(
{ source: "file", provider: "filemain", id: "/providers/openai/apiKey" },
{
config: {
secrets: {
providers: {
filemain: createFileProviderConfig(filePath, {
timeoutMs: 5,
}),
},
},
},
},
),
).rejects.toThrow('File provider "filemain" timed out');
} finally {
readSpy.mockRestore();
}
});
it("rejects misconfigured provider source mismatches", async () => {
await expect(
resolveSecretRefValue(
{ source: "exec", provider: "default", id: "abc" },
{
config: {
secrets: {
providers: {
default: {
source: "env",
},
},
},
},
},
),
).rejects.toThrow('has source "env" but ref requests "exec"');
});
it("rejects invalid exec ids before provider resolution", async () => {
for (const id of INVALID_EXEC_SECRET_REF_IDS) {
await expect(
resolveSecretRefValue(
{ source: "exec", provider: "vault", id },
{
config: {},
},
),
).rejects.toThrow(/Exec secret reference id must match|Secret reference id is empty/);
}
});
it("rejects invalid env, file, and provider refs before provider resolution", async () => {
await expect(
resolveSecretRefValue(
{ source: "env", provider: "default", id: "bad id" },
{
config: {},
},
),
).rejects.toThrow("Env secret reference id must match");
await expect(
resolveSecretRefValue(
{ source: "file", provider: "default", id: "providers/openai/apiKey" },
{
config: {},
},
),
).rejects.toThrow("File secret reference id must be an absolute JSON pointer");
await expect(
resolveSecretRefValue(
{ source: "env", provider: "Default", id: "OPENAI_API_KEY" },
{
config: {},
},
),
).rejects.toThrow("Secret reference provider must match");
});
it("strips UTF-8 BOM from file provider payload before JSON parse", async () => {
const dir = await createCaseDir("bom-file");
const filePath = path.join(dir, "secrets-with-bom.json");
// Write JSON with UTF-8 BOM prefix (EF BB BF)
const bom = "\uFEFF";
await writeSecureFile(filePath, `${bom}{"apiKey":"sk-test-123"}`);
const value = await resolveSecretRefString(
{ source: "file", provider: "filemain", id: "/apiKey" },
{
config: {
secrets: {
providers: {
filemain: createFileProviderConfig(filePath),
},
},
},
},
);
expect(value).toBe("sk-test-123");
});
it("strips UTF-8 BOM from file provider singleValue mode", async () => {
const dir = await createCaseDir("bom-single");
const filePath = path.join(dir, "secret-with-bom.txt");
const bom = "\uFEFF";
await writeSecureFile(filePath, `${bom}my-secret-value\n`);
const value = await resolveSecretRefString(
{ source: "file", provider: "filemain", id: "value" },
{
config: {
secrets: {
providers: {
filemain: createFileProviderConfig(filePath, { mode: "singleValue" }),
},
},
},
},
);
expect(value).toBe("my-secret-value");
});
it("fails closed on Windows when file provider ACL source is unknown", async () => {
await withMockedWindowsPlatform(async () => {
const dir = await createCaseDir("win-acl");
const filePath = path.join(dir, "secrets.json");
await writeSecureFile(filePath, '{"token":"abc123"}');
await expect(
resolveSecretRefString(
{ source: "file", provider: "filemain", id: "/token" },
{
config: {
secrets: {
providers: {
filemain: createFileProviderConfig(filePath),
},
},
},
},
),
).rejects.toThrow(/ACL verification unavailable on Windows/);
});
});
it("stays fail-closed when the retired Windows ACL opt-out is present", async () => {
await withMockedWindowsPlatform(async () => {
const dir = await createCaseDir("win-acl-opt-out");
const filePath = path.join(dir, "secrets.json");
await writeSecureFile(filePath, '{"token":"abc123"}');
await expect(
resolveSecretRefString(
{ source: "file", provider: "filemain", id: "/token" },
{
config: {
secrets: {
providers: {
filemain: createFileProviderConfig(filePath, { allowInsecurePath: true }),
},
},
},
},
),
).rejects.toThrow(/ACL verification unavailable on Windows/);
});
});
it("fails closed on Windows when exec provider ACL source is unknown", async () => {
await withMockedWindowsPlatform(async () => {
await expect(resolveExecSecret(execProtocolV1ScriptPath)).rejects.toThrow(
/ACL verification unavailable on Windows/,
);
});
});
});