Files
moltbot/src/secrets/runtime.test.ts
Vincent Koc 42e3d8d693 Secrets: add inline allowlist review set (#38314)
* Secrets: add inline allowlist review set

* Secrets: narrow detect-secrets file exclusions

* Secrets: exclude Docker fingerprint false positive

* Secrets: allowlist test and docs false positives

* Secrets: refresh baseline after allowlist updates

* Secrets: fix gateway chat fixture pragma

* Secrets: format pre-commit config

* Android: keep talk mode fixture JSON valid

* Feishu: rely on client timeout injection

* Secrets: allowlist provider auth test fixtures

* Secrets: allowlist onboard search fixtures

* Secrets: allowlist onboard mode fixture

* Secrets: allowlist gateway auth mode fixture

* Secrets: allowlist APNS wake test key

* Secrets: allowlist gateway reload fixtures

* Secrets: allowlist moonshot video fixture

* Secrets: allowlist auto audio fixture

* Secrets: allowlist tiny audio fixture

* Secrets: allowlist embeddings fixtures

* Secrets: allowlist resolve fixtures

* Secrets: allowlist target registry pattern fixtures

* Secrets: allowlist gateway chat env fixture

* Secrets: refresh baseline after fixture allowlists

* Secrets: reapply gateway chat env allowlist

* Secrets: reapply gateway chat env allowlist

* Secrets: stabilize gateway chat env allowlist

* Secrets: allowlist runtime snapshot save fixture

* Secrets: allowlist oauth profile fixtures

* Secrets: allowlist compaction identifier fixture

* Secrets: allowlist model auth fixture

* Secrets: allowlist model status fixtures

* Secrets: allowlist custom onboarding fixture

* Secrets: allowlist mattermost token summary fixtures

* Secrets: allowlist gateway auth suite fixtures

* Secrets: allowlist channel summary fixture

* Secrets: allowlist provider usage auth fixtures

* Secrets: allowlist media proxy fixture

* Secrets: allowlist secrets audit fixtures

* Secrets: refresh baseline after final fixture allowlists

* Feishu: prefer explicit client timeout

* Feishu: test direct timeout precedence
2026-03-06 19:35:26 -05:00

1948 lines
63 KiB
TypeScript

import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { afterEach, describe, expect, it } from "vitest";
import { ensureAuthProfileStore, type AuthProfileStore } from "../agents/auth-profiles.js";
import { loadConfig, type OpenClawConfig } from "../config/config.js";
import {
activateSecretsRuntimeSnapshot,
clearSecretsRuntimeSnapshot,
prepareSecretsRuntimeSnapshot,
} from "./runtime.js";
function asConfig(value: unknown): OpenClawConfig {
return value as OpenClawConfig;
}
const OPENAI_ENV_KEY_REF = { source: "env", provider: "default", id: "OPENAI_API_KEY" } as const;
function createOpenAiFileModelsConfig(): NonNullable<OpenClawConfig["models"]> {
return {
providers: {
openai: {
baseUrl: "https://api.openai.com/v1",
apiKey: { source: "file", provider: "default", id: "/providers/openai/apiKey" },
models: [],
},
},
};
}
function loadAuthStoreWithProfiles(profiles: AuthProfileStore["profiles"]): AuthProfileStore {
return {
version: 1,
profiles,
};
}
describe("secrets runtime snapshot", () => {
afterEach(() => {
clearSecretsRuntimeSnapshot();
});
it("resolves env refs for config and auth profiles", async () => {
const config = asConfig({
agents: {
defaults: {
memorySearch: {
remote: {
apiKey: { source: "env", provider: "default", id: "MEMORY_REMOTE_API_KEY" },
},
},
},
},
models: {
providers: {
openai: {
baseUrl: "https://api.openai.com/v1",
apiKey: { source: "env", provider: "default", id: "OPENAI_API_KEY" },
models: [],
},
},
},
skills: {
entries: {
"review-pr": {
enabled: true,
apiKey: { source: "env", provider: "default", id: "REVIEW_SKILL_API_KEY" },
},
},
},
talk: {
apiKey: { source: "env", provider: "default", id: "TALK_API_KEY" },
providers: {
elevenlabs: {
apiKey: { source: "env", provider: "default", id: "TALK_PROVIDER_API_KEY" },
},
},
},
gateway: {
mode: "remote",
remote: {
url: "wss://gateway.example",
token: { source: "env", provider: "default", id: "REMOTE_GATEWAY_TOKEN" },
password: { source: "env", provider: "default", id: "REMOTE_GATEWAY_PASSWORD" },
},
},
channels: {
telegram: {
botToken: { source: "env", provider: "default", id: "TELEGRAM_BOT_TOKEN_REF" },
webhookUrl: "https://example.test/telegram-webhook",
webhookSecret: { source: "env", provider: "default", id: "TELEGRAM_WEBHOOK_SECRET_REF" },
accounts: {
work: {
botToken: {
source: "env",
provider: "default",
id: "TELEGRAM_WORK_BOT_TOKEN_REF",
},
},
},
},
slack: {
mode: "http",
signingSecret: { source: "env", provider: "default", id: "SLACK_SIGNING_SECRET_REF" },
accounts: {
work: {
botToken: { source: "env", provider: "default", id: "SLACK_WORK_BOT_TOKEN_REF" },
appToken: { source: "env", provider: "default", id: "SLACK_WORK_APP_TOKEN_REF" },
},
},
},
},
tools: {
web: {
search: {
apiKey: { source: "env", provider: "default", id: "WEB_SEARCH_API_KEY" },
},
},
},
});
const snapshot = await prepareSecretsRuntimeSnapshot({
config,
env: {
OPENAI_API_KEY: "sk-env-openai", // pragma: allowlist secret
GITHUB_TOKEN: "ghp-env-token", // pragma: allowlist secret
REVIEW_SKILL_API_KEY: "sk-skill-ref", // pragma: allowlist secret
MEMORY_REMOTE_API_KEY: "mem-ref-key", // pragma: allowlist secret
TALK_API_KEY: "talk-ref-key", // pragma: allowlist secret
TALK_PROVIDER_API_KEY: "talk-provider-ref-key", // pragma: allowlist secret
REMOTE_GATEWAY_TOKEN: "remote-token-ref",
REMOTE_GATEWAY_PASSWORD: "remote-password-ref", // pragma: allowlist secret
TELEGRAM_BOT_TOKEN_REF: "telegram-bot-ref",
TELEGRAM_WEBHOOK_SECRET_REF: "telegram-webhook-ref", // pragma: allowlist secret
TELEGRAM_WORK_BOT_TOKEN_REF: "telegram-work-ref",
SLACK_SIGNING_SECRET_REF: "slack-signing-ref", // pragma: allowlist secret
SLACK_WORK_BOT_TOKEN_REF: "slack-work-bot-ref",
SLACK_WORK_APP_TOKEN_REF: "slack-work-app-ref",
WEB_SEARCH_API_KEY: "web-search-ref", // pragma: allowlist secret
},
agentDirs: ["/tmp/openclaw-agent-main"],
loadAuthStore: () =>
loadAuthStoreWithProfiles({
"openai:default": {
type: "api_key",
provider: "openai",
key: "old-openai",
keyRef: OPENAI_ENV_KEY_REF,
},
"github-copilot:default": {
type: "token",
provider: "github-copilot",
token: "old-gh",
tokenRef: { source: "env", provider: "default", id: "GITHUB_TOKEN" },
},
"openai:inline": {
type: "api_key",
provider: "openai",
key: "${OPENAI_API_KEY}",
},
}),
});
expect(snapshot.config.models?.providers?.openai?.apiKey).toBe("sk-env-openai");
expect(snapshot.config.skills?.entries?.["review-pr"]?.apiKey).toBe("sk-skill-ref");
expect(snapshot.config.agents?.defaults?.memorySearch?.remote?.apiKey).toBe("mem-ref-key");
expect(snapshot.config.talk?.apiKey).toBe("talk-ref-key");
expect(snapshot.config.talk?.providers?.elevenlabs?.apiKey).toBe("talk-provider-ref-key");
expect(snapshot.config.gateway?.remote?.token).toBe("remote-token-ref");
expect(snapshot.config.gateway?.remote?.password).toBe("remote-password-ref");
expect(snapshot.config.channels?.telegram?.botToken).toEqual({
source: "env",
provider: "default",
id: "TELEGRAM_BOT_TOKEN_REF",
});
expect(snapshot.config.channels?.telegram?.webhookSecret).toBe("telegram-webhook-ref");
expect(snapshot.config.channels?.telegram?.accounts?.work?.botToken).toBe("telegram-work-ref");
expect(snapshot.config.channels?.slack?.signingSecret).toBe("slack-signing-ref");
expect(snapshot.config.channels?.slack?.accounts?.work?.botToken).toBe("slack-work-bot-ref");
expect(snapshot.config.channels?.slack?.accounts?.work?.appToken).toEqual({
source: "env",
provider: "default",
id: "SLACK_WORK_APP_TOKEN_REF",
});
expect(snapshot.config.tools?.web?.search?.apiKey).toBe("web-search-ref");
expect(snapshot.warnings).toHaveLength(4);
expect(snapshot.warnings.map((warning) => warning.path)).toContain(
"channels.slack.accounts.work.appToken",
);
expect(snapshot.authStores[0]?.store.profiles["openai:default"]).toMatchObject({
type: "api_key",
key: "sk-env-openai",
});
expect(snapshot.authStores[0]?.store.profiles["github-copilot:default"]).toMatchObject({
type: "token",
token: "ghp-env-token",
});
expect(snapshot.authStores[0]?.store.profiles["openai:inline"]).toMatchObject({
type: "api_key",
key: "sk-env-openai",
});
// After normalization, inline SecretRef string should be promoted to keyRef
expect(
(snapshot.authStores[0].store.profiles["openai:inline"] as Record<string, unknown>).keyRef,
).toEqual({ source: "env", provider: "default", id: "OPENAI_API_KEY" });
});
it("normalizes inline SecretRef object on token to tokenRef", async () => {
const config: OpenClawConfig = { models: {}, secrets: {} };
const snapshot = await prepareSecretsRuntimeSnapshot({
config,
env: { MY_TOKEN: "resolved-token-value" },
agentDirs: ["/tmp/openclaw-agent-main"],
loadAuthStore: () =>
loadAuthStoreWithProfiles({
"custom:inline-token": {
type: "token",
provider: "custom",
token: { source: "env", provider: "default", id: "MY_TOKEN" } as unknown as string,
},
}),
});
const profile = snapshot.authStores[0]?.store.profiles["custom:inline-token"] as Record<
string,
unknown
>;
// tokenRef should be set from the inline SecretRef
expect(profile.tokenRef).toEqual({ source: "env", provider: "default", id: "MY_TOKEN" });
// token should be resolved to the actual value after activation
activateSecretsRuntimeSnapshot(snapshot);
expect(profile.token).toBe("resolved-token-value");
});
it("normalizes inline SecretRef object on key to keyRef", async () => {
const config: OpenClawConfig = { models: {}, secrets: {} };
const snapshot = await prepareSecretsRuntimeSnapshot({
config,
env: { MY_KEY: "resolved-key-value" },
agentDirs: ["/tmp/openclaw-agent-main"],
loadAuthStore: () =>
loadAuthStoreWithProfiles({
"custom:inline-key": {
type: "api_key",
provider: "custom",
key: { source: "env", provider: "default", id: "MY_KEY" } as unknown as string,
},
}),
});
const profile = snapshot.authStores[0]?.store.profiles["custom:inline-key"] as Record<
string,
unknown
>;
// keyRef should be set from the inline SecretRef
expect(profile.keyRef).toEqual({ source: "env", provider: "default", id: "MY_KEY" });
// key should be resolved to the actual value after activation
activateSecretsRuntimeSnapshot(snapshot);
expect(profile.key).toBe("resolved-key-value");
});
it("keeps explicit keyRef when inline key SecretRef is also present", async () => {
const config: OpenClawConfig = { models: {}, secrets: {} };
const snapshot = await prepareSecretsRuntimeSnapshot({
config,
env: {
PRIMARY_KEY: "primary-key-value",
SHADOW_KEY: "shadow-key-value",
},
agentDirs: ["/tmp/openclaw-agent-main"],
loadAuthStore: () =>
loadAuthStoreWithProfiles({
"custom:explicit-keyref": {
type: "api_key",
provider: "custom",
keyRef: { source: "env", provider: "default", id: "PRIMARY_KEY" },
key: { source: "env", provider: "default", id: "SHADOW_KEY" } as unknown as string,
},
}),
});
const profile = snapshot.authStores[0]?.store.profiles["custom:explicit-keyref"] as Record<
string,
unknown
>;
expect(profile.keyRef).toEqual({ source: "env", provider: "default", id: "PRIMARY_KEY" });
activateSecretsRuntimeSnapshot(snapshot);
expect(profile.key).toBe("primary-key-value");
});
it("treats non-selected web search provider refs as inactive", async () => {
const snapshot = await prepareSecretsRuntimeSnapshot({
config: asConfig({
tools: {
web: {
search: {
enabled: true,
provider: "brave",
apiKey: { source: "env", provider: "default", id: "WEB_SEARCH_API_KEY" },
grok: {
apiKey: { source: "env", provider: "default", id: "MISSING_GROK_API_KEY" },
},
},
},
},
}),
env: {
WEB_SEARCH_API_KEY: "web-search-ref", // pragma: allowlist secret
},
agentDirs: ["/tmp/openclaw-agent-main"],
loadAuthStore: () => ({ version: 1, profiles: {} }),
});
expect(snapshot.config.tools?.web?.search?.apiKey).toBe("web-search-ref");
expect(snapshot.config.tools?.web?.search?.grok?.apiKey).toEqual({
source: "env",
provider: "default",
id: "MISSING_GROK_API_KEY",
});
expect(snapshot.warnings).toEqual(
expect.arrayContaining([
expect.objectContaining({
code: "SECRETS_REF_IGNORED_INACTIVE_SURFACE",
path: "tools.web.search.grok.apiKey",
}),
]),
);
});
it("resolves provider-specific refs in web search auto mode", async () => {
const snapshot = await prepareSecretsRuntimeSnapshot({
config: asConfig({
tools: {
web: {
search: {
enabled: true,
apiKey: { source: "env", provider: "default", id: "WEB_SEARCH_API_KEY" },
gemini: {
apiKey: { source: "env", provider: "default", id: "WEB_SEARCH_GEMINI_API_KEY" },
},
},
},
},
}),
env: {
WEB_SEARCH_API_KEY: "web-search-ref", // pragma: allowlist secret
WEB_SEARCH_GEMINI_API_KEY: "web-search-gemini-ref", // pragma: allowlist secret
},
agentDirs: ["/tmp/openclaw-agent-main"],
loadAuthStore: () => ({ version: 1, profiles: {} }),
});
expect(snapshot.config.tools?.web?.search?.apiKey).toBe("web-search-ref");
expect(snapshot.config.tools?.web?.search?.gemini?.apiKey).toBe("web-search-gemini-ref");
expect(snapshot.warnings.map((warning) => warning.path)).not.toContain(
"tools.web.search.gemini.apiKey",
);
});
it("resolves selected web search provider ref even when provider config is disabled", async () => {
const snapshot = await prepareSecretsRuntimeSnapshot({
config: asConfig({
tools: {
web: {
search: {
enabled: true,
provider: "gemini",
gemini: {
enabled: false,
apiKey: { source: "env", provider: "default", id: "WEB_SEARCH_GEMINI_API_KEY" },
},
},
},
},
}),
env: {
WEB_SEARCH_GEMINI_API_KEY: "web-search-gemini-ref", // pragma: allowlist secret
},
agentDirs: ["/tmp/openclaw-agent-main"],
loadAuthStore: () => ({ version: 1, profiles: {} }),
});
expect(snapshot.config.tools?.web?.search?.gemini?.apiKey).toBe("web-search-gemini-ref");
expect(snapshot.warnings.map((warning) => warning.path)).not.toContain(
"tools.web.search.gemini.apiKey",
);
});
it("resolves file refs via configured file provider", async () => {
if (process.platform === "win32") {
return;
}
const root = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-secrets-file-provider-"));
const secretsPath = path.join(root, "secrets.json");
try {
await fs.writeFile(
secretsPath,
JSON.stringify(
{
providers: {
openai: {
apiKey: "sk-from-file-provider", // pragma: allowlist secret
},
},
},
null,
2,
),
"utf8",
);
await fs.chmod(secretsPath, 0o600);
const config = asConfig({
secrets: {
providers: {
default: {
source: "file",
path: secretsPath,
mode: "json",
},
},
defaults: {
file: "default",
},
},
models: {
providers: {
openai: {
baseUrl: "https://api.openai.com/v1",
apiKey: { source: "file", provider: "default", id: "/providers/openai/apiKey" },
models: [],
},
},
},
});
const snapshot = await prepareSecretsRuntimeSnapshot({
config,
agentDirs: ["/tmp/openclaw-agent-main"],
loadAuthStore: () => ({ version: 1, profiles: {} }),
});
expect(snapshot.config.models?.providers?.openai?.apiKey).toBe("sk-from-file-provider");
} finally {
await fs.rm(root, { recursive: true, force: true });
}
});
it("fails when file provider payload is not a JSON object", async () => {
if (process.platform === "win32") {
return;
}
const root = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-secrets-file-provider-bad-"));
const secretsPath = path.join(root, "secrets.json");
try {
await fs.writeFile(secretsPath, JSON.stringify(["not-an-object"]), "utf8");
await fs.chmod(secretsPath, 0o600);
await expect(
prepareSecretsRuntimeSnapshot({
config: asConfig({
secrets: {
providers: {
default: {
source: "file",
path: secretsPath,
mode: "json",
},
},
},
models: {
...createOpenAiFileModelsConfig(),
},
}),
agentDirs: ["/tmp/openclaw-agent-main"],
loadAuthStore: () => ({ version: 1, profiles: {} }),
}),
).rejects.toThrow("payload is not a JSON object");
} finally {
await fs.rm(root, { recursive: true, force: true });
}
});
it("activates runtime snapshots for loadConfig and ensureAuthProfileStore", async () => {
const prepared = await prepareSecretsRuntimeSnapshot({
config: asConfig({
models: {
providers: {
openai: {
baseUrl: "https://api.openai.com/v1",
apiKey: { source: "env", provider: "default", id: "OPENAI_API_KEY" },
models: [],
},
},
},
}),
env: { OPENAI_API_KEY: "sk-runtime" }, // pragma: allowlist secret
agentDirs: ["/tmp/openclaw-agent-main"],
loadAuthStore: () =>
loadAuthStoreWithProfiles({
"openai:default": {
type: "api_key",
provider: "openai",
keyRef: OPENAI_ENV_KEY_REF,
},
}),
});
activateSecretsRuntimeSnapshot(prepared);
expect(loadConfig().models?.providers?.openai?.apiKey).toBe("sk-runtime");
const store = ensureAuthProfileStore("/tmp/openclaw-agent-main");
expect(store.profiles["openai:default"]).toMatchObject({
type: "api_key",
key: "sk-runtime",
});
});
it("skips inactive-surface refs and emits diagnostics", async () => {
const config = asConfig({
agents: {
defaults: {
memorySearch: {
enabled: false,
remote: {
apiKey: { source: "env", provider: "default", id: "DISABLED_MEMORY_API_KEY" },
},
},
},
},
gateway: {
auth: {
mode: "token",
password: { source: "env", provider: "default", id: "DISABLED_GATEWAY_PASSWORD" },
},
},
channels: {
telegram: {
botToken: { source: "env", provider: "default", id: "DISABLED_TELEGRAM_BASE_TOKEN" },
accounts: {
disabled: {
enabled: false,
botToken: {
source: "env",
provider: "default",
id: "DISABLED_TELEGRAM_ACCOUNT_TOKEN",
},
},
},
},
},
tools: {
web: {
search: {
enabled: false,
apiKey: { source: "env", provider: "default", id: "DISABLED_WEB_SEARCH_API_KEY" },
gemini: {
apiKey: {
source: "env",
provider: "default",
id: "DISABLED_WEB_SEARCH_GEMINI_API_KEY",
},
},
},
},
},
});
const snapshot = await prepareSecretsRuntimeSnapshot({
config,
env: {},
agentDirs: ["/tmp/openclaw-agent-main"],
loadAuthStore: () => ({ version: 1, profiles: {} }),
});
expect(snapshot.config.channels?.telegram?.botToken).toEqual({
source: "env",
provider: "default",
id: "DISABLED_TELEGRAM_BASE_TOKEN",
});
expect(
snapshot.warnings.filter(
(warning) => warning.code === "SECRETS_REF_IGNORED_INACTIVE_SURFACE",
),
).toHaveLength(6);
expect(snapshot.warnings.map((warning) => warning.path)).toEqual(
expect.arrayContaining([
"agents.defaults.memorySearch.remote.apiKey",
"gateway.auth.password",
"channels.telegram.botToken",
"channels.telegram.accounts.disabled.botToken",
"tools.web.search.apiKey",
"tools.web.search.gemini.apiKey",
]),
);
});
it("treats gateway.remote refs as inactive when local auth credentials are configured", async () => {
const snapshot = await prepareSecretsRuntimeSnapshot({
config: asConfig({
gateway: {
mode: "local",
auth: {
mode: "password",
token: "local-token",
password: "local-password", // pragma: allowlist secret
},
remote: {
enabled: true,
token: { source: "env", provider: "default", id: "MISSING_REMOTE_TOKEN" },
password: { source: "env", provider: "default", id: "MISSING_REMOTE_PASSWORD" },
},
},
}),
env: {},
agentDirs: ["/tmp/openclaw-agent-main"],
loadAuthStore: () => ({ version: 1, profiles: {} }),
});
expect(snapshot.config.gateway?.remote?.token).toEqual({
source: "env",
provider: "default",
id: "MISSING_REMOTE_TOKEN",
});
expect(snapshot.config.gateway?.remote?.password).toEqual({
source: "env",
provider: "default",
id: "MISSING_REMOTE_PASSWORD",
});
expect(snapshot.warnings.map((warning) => warning.path)).toEqual(
expect.arrayContaining(["gateway.remote.token", "gateway.remote.password"]),
);
});
it("treats gateway.auth.password ref as active when mode is unset and no token is configured", async () => {
const snapshot = await prepareSecretsRuntimeSnapshot({
config: asConfig({
gateway: {
auth: {
password: { source: "env", provider: "default", id: "GATEWAY_PASSWORD_REF" },
},
},
}),
env: {
GATEWAY_PASSWORD_REF: "resolved-gateway-password", // pragma: allowlist secret
},
agentDirs: ["/tmp/openclaw-agent-main"],
loadAuthStore: () => ({ version: 1, profiles: {} }),
});
expect(snapshot.config.gateway?.auth?.password).toBe("resolved-gateway-password");
expect(snapshot.warnings.map((warning) => warning.path)).not.toContain("gateway.auth.password");
});
it("treats gateway.auth.token ref as active when token mode is explicit", async () => {
const snapshot = await prepareSecretsRuntimeSnapshot({
config: asConfig({
gateway: {
auth: {
mode: "token",
token: { source: "env", provider: "default", id: "GATEWAY_TOKEN_REF" },
},
},
}),
env: {
GATEWAY_TOKEN_REF: "resolved-gateway-token",
},
agentDirs: ["/tmp/openclaw-agent-main"],
loadAuthStore: () => ({ version: 1, profiles: {} }),
});
expect(snapshot.config.gateway?.auth?.token).toBe("resolved-gateway-token");
expect(snapshot.warnings.map((warning) => warning.path)).not.toContain("gateway.auth.token");
});
it("treats gateway.auth.token ref as inactive when password mode is explicit", async () => {
const snapshot = await prepareSecretsRuntimeSnapshot({
config: asConfig({
gateway: {
auth: {
mode: "password",
token: { source: "env", provider: "default", id: "GATEWAY_TOKEN_REF" },
password: "password-123", // pragma: allowlist secret
},
},
}),
env: {
GATEWAY_TOKEN_REF: "resolved-gateway-token",
},
agentDirs: ["/tmp/openclaw-agent-main"],
loadAuthStore: () => ({ version: 1, profiles: {} }),
});
expect(snapshot.config.gateway?.auth?.token).toEqual({
source: "env",
provider: "default",
id: "GATEWAY_TOKEN_REF",
});
expect(snapshot.warnings.map((warning) => warning.path)).toContain("gateway.auth.token");
});
it("fails when gateway.auth.token ref is active and unresolved", async () => {
await expect(
prepareSecretsRuntimeSnapshot({
config: asConfig({
gateway: {
auth: {
mode: "token",
token: { source: "env", provider: "default", id: "MISSING_GATEWAY_TOKEN_REF" },
},
},
}),
env: {},
agentDirs: ["/tmp/openclaw-agent-main"],
loadAuthStore: () => ({ version: 1, profiles: {} }),
}),
).rejects.toThrow(/MISSING_GATEWAY_TOKEN_REF/i);
});
it("treats gateway.auth.password ref as inactive when auth mode is trusted-proxy", async () => {
const snapshot = await prepareSecretsRuntimeSnapshot({
config: asConfig({
gateway: {
auth: {
mode: "trusted-proxy",
password: { source: "env", provider: "default", id: "GATEWAY_PASSWORD_REF" },
},
},
}),
env: {
GATEWAY_PASSWORD_REF: "resolved-gateway-password", // pragma: allowlist secret
},
agentDirs: ["/tmp/openclaw-agent-main"],
loadAuthStore: () => ({ version: 1, profiles: {} }),
});
expect(snapshot.config.gateway?.auth?.password).toEqual({
source: "env",
provider: "default",
id: "GATEWAY_PASSWORD_REF",
});
expect(snapshot.warnings.map((warning) => warning.path)).toContain("gateway.auth.password");
});
it("treats gateway.auth.password ref as inactive when remote token is configured", async () => {
const snapshot = await prepareSecretsRuntimeSnapshot({
config: asConfig({
gateway: {
mode: "local",
auth: {
password: { source: "env", provider: "default", id: "GATEWAY_PASSWORD_REF" },
},
remote: {
token: { source: "env", provider: "default", id: "REMOTE_GATEWAY_TOKEN" },
},
},
}),
env: {
REMOTE_GATEWAY_TOKEN: "remote-token",
},
agentDirs: ["/tmp/openclaw-agent-main"],
loadAuthStore: () => ({ version: 1, profiles: {} }),
});
expect(snapshot.config.gateway?.auth?.password).toEqual({
source: "env",
provider: "default",
id: "GATEWAY_PASSWORD_REF",
});
expect(snapshot.warnings.map((warning) => warning.path)).toContain("gateway.auth.password");
});
it.each(["none", "trusted-proxy"] as const)(
"treats gateway.remote refs as inactive in local mode when auth mode is %s",
async (mode) => {
const snapshot = await prepareSecretsRuntimeSnapshot({
config: asConfig({
gateway: {
mode: "local",
auth: {
mode,
},
remote: {
token: { source: "env", provider: "default", id: "MISSING_REMOTE_TOKEN" },
password: { source: "env", provider: "default", id: "MISSING_REMOTE_PASSWORD" },
},
},
}),
env: {},
agentDirs: ["/tmp/openclaw-agent-main"],
loadAuthStore: () => ({ version: 1, profiles: {} }),
});
expect(snapshot.config.gateway?.remote?.token).toEqual({
source: "env",
provider: "default",
id: "MISSING_REMOTE_TOKEN",
});
expect(snapshot.config.gateway?.remote?.password).toEqual({
source: "env",
provider: "default",
id: "MISSING_REMOTE_PASSWORD",
});
expect(snapshot.warnings.map((warning) => warning.path)).toEqual(
expect.arrayContaining(["gateway.remote.token", "gateway.remote.password"]),
);
},
);
it("treats gateway.remote.token ref as active in local mode when no local credentials are configured", async () => {
const snapshot = await prepareSecretsRuntimeSnapshot({
config: asConfig({
gateway: {
mode: "local",
auth: {},
remote: {
enabled: true,
token: { source: "env", provider: "default", id: "REMOTE_TOKEN" },
password: { source: "env", provider: "default", id: "REMOTE_PASSWORD" },
},
},
}),
env: {
REMOTE_TOKEN: "resolved-remote-token",
REMOTE_PASSWORD: "resolved-remote-password", // pragma: allowlist secret
},
agentDirs: ["/tmp/openclaw-agent-main"],
loadAuthStore: () => ({ version: 1, profiles: {} }),
});
expect(snapshot.config.gateway?.remote?.token).toBe("resolved-remote-token");
expect(snapshot.warnings.map((warning) => warning.path)).not.toContain("gateway.remote.token");
expect(snapshot.warnings.map((warning) => warning.path)).toContain("gateway.remote.password");
});
it("treats gateway.remote.password ref as active in local mode when password can win", async () => {
const snapshot = await prepareSecretsRuntimeSnapshot({
config: asConfig({
gateway: {
mode: "local",
auth: {},
remote: {
enabled: true,
password: { source: "env", provider: "default", id: "REMOTE_PASSWORD" },
},
},
}),
env: {
REMOTE_PASSWORD: "resolved-remote-password", // pragma: allowlist secret
},
agentDirs: ["/tmp/openclaw-agent-main"],
loadAuthStore: () => ({ version: 1, profiles: {} }),
});
expect(snapshot.config.gateway?.remote?.password).toBe("resolved-remote-password");
expect(snapshot.warnings.map((warning) => warning.path)).not.toContain(
"gateway.remote.password",
);
});
it("treats top-level Zalo botToken refs as active even when tokenFile is configured", async () => {
const snapshot = await prepareSecretsRuntimeSnapshot({
config: asConfig({
channels: {
zalo: {
botToken: { source: "env", provider: "default", id: "ZALO_BOT_TOKEN" },
tokenFile: "/tmp/missing-zalo-token-file",
},
},
}),
env: {
ZALO_BOT_TOKEN: "resolved-zalo-token",
},
agentDirs: ["/tmp/openclaw-agent-main"],
loadAuthStore: () => ({ version: 1, profiles: {} }),
});
expect(snapshot.config.channels?.zalo?.botToken).toBe("resolved-zalo-token");
expect(snapshot.warnings.map((warning) => warning.path)).not.toContain(
"channels.zalo.botToken",
);
});
it("treats account-level Zalo botToken refs as active even when tokenFile is configured", async () => {
const snapshot = await prepareSecretsRuntimeSnapshot({
config: asConfig({
channels: {
zalo: {
accounts: {
work: {
botToken: { source: "env", provider: "default", id: "ZALO_WORK_BOT_TOKEN" },
tokenFile: "/tmp/missing-zalo-work-token-file",
},
},
},
},
}),
env: {
ZALO_WORK_BOT_TOKEN: "resolved-zalo-work-token",
},
agentDirs: ["/tmp/openclaw-agent-main"],
loadAuthStore: () => ({ version: 1, profiles: {} }),
});
expect(snapshot.config.channels?.zalo?.accounts?.work?.botToken).toBe(
"resolved-zalo-work-token",
);
expect(snapshot.warnings.map((warning) => warning.path)).not.toContain(
"channels.zalo.accounts.work.botToken",
);
});
it("treats top-level Zalo botToken refs as active for non-default accounts without overrides", async () => {
const snapshot = await prepareSecretsRuntimeSnapshot({
config: asConfig({
channels: {
zalo: {
botToken: { source: "env", provider: "default", id: "ZALO_TOP_LEVEL_TOKEN" },
accounts: {
work: {
enabled: true,
},
},
},
},
}),
env: {
ZALO_TOP_LEVEL_TOKEN: "resolved-zalo-top-level-token",
},
agentDirs: ["/tmp/openclaw-agent-main"],
loadAuthStore: () => ({ version: 1, profiles: {} }),
});
expect(snapshot.config.channels?.zalo?.botToken).toBe("resolved-zalo-top-level-token");
expect(snapshot.warnings.map((warning) => warning.path)).not.toContain(
"channels.zalo.botToken",
);
});
it("treats channels.zalo.accounts.default.botToken refs as active", async () => {
const snapshot = await prepareSecretsRuntimeSnapshot({
config: asConfig({
channels: {
zalo: {
accounts: {
default: {
enabled: true,
botToken: { source: "env", provider: "default", id: "ZALO_DEFAULT_TOKEN" },
},
},
},
},
}),
env: {
ZALO_DEFAULT_TOKEN: "resolved-zalo-default-token",
},
agentDirs: ["/tmp/openclaw-agent-main"],
loadAuthStore: () => ({ version: 1, profiles: {} }),
});
expect(snapshot.config.channels?.zalo?.accounts?.default?.botToken).toBe(
"resolved-zalo-default-token",
);
expect(snapshot.warnings.map((warning) => warning.path)).not.toContain(
"channels.zalo.accounts.default.botToken",
);
});
it("treats top-level Nextcloud Talk botSecret and apiPassword refs as active when file paths are configured", async () => {
const snapshot = await prepareSecretsRuntimeSnapshot({
config: asConfig({
channels: {
"nextcloud-talk": {
botSecret: { source: "env", provider: "default", id: "NEXTCLOUD_BOT_SECRET" },
botSecretFile: "/tmp/missing-nextcloud-bot-secret-file",
apiUser: "bot-user",
apiPassword: { source: "env", provider: "default", id: "NEXTCLOUD_API_PASSWORD" },
apiPasswordFile: "/tmp/missing-nextcloud-api-password-file",
},
},
}),
env: {
NEXTCLOUD_BOT_SECRET: "resolved-nextcloud-bot-secret", // pragma: allowlist secret
NEXTCLOUD_API_PASSWORD: "resolved-nextcloud-api-password", // pragma: allowlist secret
},
agentDirs: ["/tmp/openclaw-agent-main"],
loadAuthStore: () => ({ version: 1, profiles: {} }),
});
expect(snapshot.config.channels?.["nextcloud-talk"]?.botSecret).toBe(
"resolved-nextcloud-bot-secret",
);
expect(snapshot.config.channels?.["nextcloud-talk"]?.apiPassword).toBe(
"resolved-nextcloud-api-password",
);
expect(snapshot.warnings.map((warning) => warning.path)).not.toContain(
"channels.nextcloud-talk.botSecret",
);
expect(snapshot.warnings.map((warning) => warning.path)).not.toContain(
"channels.nextcloud-talk.apiPassword",
);
});
it("treats account-level Nextcloud Talk botSecret and apiPassword refs as active when file paths are configured", async () => {
const snapshot = await prepareSecretsRuntimeSnapshot({
config: asConfig({
channels: {
"nextcloud-talk": {
accounts: {
work: {
botSecret: { source: "env", provider: "default", id: "NEXTCLOUD_WORK_BOT_SECRET" },
botSecretFile: "/tmp/missing-nextcloud-work-bot-secret-file",
apiPassword: {
source: "env",
provider: "default",
id: "NEXTCLOUD_WORK_API_PASSWORD",
},
apiPasswordFile: "/tmp/missing-nextcloud-work-api-password-file",
},
},
},
},
}),
env: {
NEXTCLOUD_WORK_BOT_SECRET: "resolved-nextcloud-work-bot-secret", // pragma: allowlist secret
NEXTCLOUD_WORK_API_PASSWORD: "resolved-nextcloud-work-api-password", // pragma: allowlist secret
},
agentDirs: ["/tmp/openclaw-agent-main"],
loadAuthStore: () => ({ version: 1, profiles: {} }),
});
expect(snapshot.config.channels?.["nextcloud-talk"]?.accounts?.work?.botSecret).toBe(
"resolved-nextcloud-work-bot-secret",
);
expect(snapshot.config.channels?.["nextcloud-talk"]?.accounts?.work?.apiPassword).toBe(
"resolved-nextcloud-work-api-password",
);
expect(snapshot.warnings.map((warning) => warning.path)).not.toContain(
"channels.nextcloud-talk.accounts.work.botSecret",
);
expect(snapshot.warnings.map((warning) => warning.path)).not.toContain(
"channels.nextcloud-talk.accounts.work.apiPassword",
);
});
it("treats gateway.remote refs as active when tailscale serve is enabled", async () => {
const snapshot = await prepareSecretsRuntimeSnapshot({
config: asConfig({
gateway: {
mode: "local",
tailscale: { mode: "serve" },
remote: {
enabled: true,
token: { source: "env", provider: "default", id: "REMOTE_GATEWAY_TOKEN" },
password: { source: "env", provider: "default", id: "REMOTE_GATEWAY_PASSWORD" },
},
},
}),
env: {
REMOTE_GATEWAY_TOKEN: "tailscale-remote-token",
REMOTE_GATEWAY_PASSWORD: "tailscale-remote-password", // pragma: allowlist secret
},
agentDirs: ["/tmp/openclaw-agent-main"],
loadAuthStore: () => ({ version: 1, profiles: {} }),
});
expect(snapshot.config.gateway?.remote?.token).toBe("tailscale-remote-token");
expect(snapshot.config.gateway?.remote?.password).toBe("tailscale-remote-password");
expect(snapshot.warnings.map((warning) => warning.path)).not.toContain("gateway.remote.token");
expect(snapshot.warnings.map((warning) => warning.path)).not.toContain(
"gateway.remote.password",
);
});
it("treats defaults memorySearch ref as inactive when all enabled agents disable memorySearch", async () => {
const snapshot = await prepareSecretsRuntimeSnapshot({
config: asConfig({
agents: {
defaults: {
memorySearch: {
remote: {
apiKey: {
source: "env",
provider: "default",
id: "DEFAULT_MEMORY_REMOTE_API_KEY",
},
},
},
},
list: [
{
enabled: true,
memorySearch: {
enabled: false,
},
},
],
},
}),
env: {},
agentDirs: ["/tmp/openclaw-agent-main"],
loadAuthStore: () => ({ version: 1, profiles: {} }),
});
expect(snapshot.config.agents?.defaults?.memorySearch?.remote?.apiKey).toEqual({
source: "env",
provider: "default",
id: "DEFAULT_MEMORY_REMOTE_API_KEY",
});
expect(snapshot.warnings.map((warning) => warning.path)).toContain(
"agents.defaults.memorySearch.remote.apiKey",
);
});
it("fails when enabled channel surfaces contain unresolved refs", async () => {
await expect(
prepareSecretsRuntimeSnapshot({
config: asConfig({
channels: {
telegram: {
botToken: {
source: "env",
provider: "default",
id: "MISSING_ENABLED_TELEGRAM_TOKEN",
},
accounts: {
work: {
enabled: true,
},
},
},
},
}),
env: {},
agentDirs: ["/tmp/openclaw-agent-main"],
loadAuthStore: () => ({ version: 1, profiles: {} }),
}),
).rejects.toThrow('Environment variable "MISSING_ENABLED_TELEGRAM_TOKEN" is missing or empty.');
});
it("fails when default Telegram account can inherit an unresolved top-level token ref", async () => {
await expect(
prepareSecretsRuntimeSnapshot({
config: asConfig({
channels: {
telegram: {
botToken: {
source: "env",
provider: "default",
id: "MISSING_ENABLED_TELEGRAM_TOKEN",
},
accounts: {
default: {
enabled: true,
},
},
},
},
}),
env: {},
agentDirs: ["/tmp/openclaw-agent-main"],
loadAuthStore: () => ({ version: 1, profiles: {} }),
}),
).rejects.toThrow('Environment variable "MISSING_ENABLED_TELEGRAM_TOKEN" is missing or empty.');
});
it("treats top-level Telegram token as inactive when all enabled accounts override it", async () => {
const snapshot = await prepareSecretsRuntimeSnapshot({
config: asConfig({
channels: {
telegram: {
botToken: {
source: "env",
provider: "default",
id: "UNUSED_TELEGRAM_BASE_TOKEN",
},
accounts: {
work: {
enabled: true,
botToken: {
source: "env",
provider: "default",
id: "TELEGRAM_WORK_TOKEN",
},
},
disabled: {
enabled: false,
},
},
},
},
}),
env: {
TELEGRAM_WORK_TOKEN: "telegram-work-token",
},
agentDirs: ["/tmp/openclaw-agent-main"],
loadAuthStore: () => ({ version: 1, profiles: {} }),
});
expect(snapshot.config.channels?.telegram?.accounts?.work?.botToken).toBe(
"telegram-work-token",
);
expect(snapshot.config.channels?.telegram?.botToken).toEqual({
source: "env",
provider: "default",
id: "UNUSED_TELEGRAM_BASE_TOKEN",
});
expect(snapshot.warnings.map((warning) => warning.path)).toContain(
"channels.telegram.botToken",
);
});
it("treats Telegram account overrides as enabled when account.enabled is omitted", async () => {
await expect(
prepareSecretsRuntimeSnapshot({
config: asConfig({
channels: {
telegram: {
enabled: true,
accounts: {
inheritedEnabled: {
botToken: {
source: "env",
provider: "default",
id: "MISSING_INHERITED_TELEGRAM_ACCOUNT_TOKEN",
},
},
},
},
},
}),
env: {},
agentDirs: ["/tmp/openclaw-agent-main"],
loadAuthStore: () => ({ version: 1, profiles: {} }),
}),
).rejects.toThrow(
'Environment variable "MISSING_INHERITED_TELEGRAM_ACCOUNT_TOKEN" is missing or empty.',
);
});
it("treats Telegram webhookSecret refs as inactive when webhook mode is not configured", async () => {
const snapshot = await prepareSecretsRuntimeSnapshot({
config: asConfig({
channels: {
telegram: {
webhookSecret: {
source: "env",
provider: "default",
id: "MISSING_TELEGRAM_WEBHOOK_SECRET",
},
accounts: {
work: {
enabled: true,
},
},
},
},
}),
env: {},
agentDirs: ["/tmp/openclaw-agent-main"],
loadAuthStore: () => ({ version: 1, profiles: {} }),
});
expect(snapshot.config.channels?.telegram?.webhookSecret).toEqual({
source: "env",
provider: "default",
id: "MISSING_TELEGRAM_WEBHOOK_SECRET",
});
expect(snapshot.warnings.map((warning) => warning.path)).toContain(
"channels.telegram.webhookSecret",
);
});
it("treats Telegram top-level botToken refs as inactive when tokenFile is configured", async () => {
const snapshot = await prepareSecretsRuntimeSnapshot({
config: asConfig({
channels: {
telegram: {
tokenFile: "/tmp/telegram-bot-token",
botToken: {
source: "env",
provider: "default",
id: "MISSING_TELEGRAM_BOT_TOKEN",
},
},
},
}),
env: {},
agentDirs: ["/tmp/openclaw-agent-main"],
loadAuthStore: () => ({ version: 1, profiles: {} }),
});
expect(snapshot.config.channels?.telegram?.botToken).toEqual({
source: "env",
provider: "default",
id: "MISSING_TELEGRAM_BOT_TOKEN",
});
expect(snapshot.warnings.map((warning) => warning.path)).toContain(
"channels.telegram.botToken",
);
});
it("treats Telegram account botToken refs as inactive when account tokenFile is configured", async () => {
const snapshot = await prepareSecretsRuntimeSnapshot({
config: asConfig({
channels: {
telegram: {
accounts: {
work: {
enabled: true,
tokenFile: "/tmp/telegram-work-bot-token",
botToken: {
source: "env",
provider: "default",
id: "MISSING_TELEGRAM_WORK_BOT_TOKEN",
},
},
},
},
},
}),
env: {},
agentDirs: ["/tmp/openclaw-agent-main"],
loadAuthStore: () => ({ version: 1, profiles: {} }),
});
expect(snapshot.config.channels?.telegram?.accounts?.work?.botToken).toEqual({
source: "env",
provider: "default",
id: "MISSING_TELEGRAM_WORK_BOT_TOKEN",
});
expect(snapshot.warnings.map((warning) => warning.path)).toContain(
"channels.telegram.accounts.work.botToken",
);
});
it("treats top-level Telegram botToken refs as active when account botToken is blank", async () => {
const snapshot = await prepareSecretsRuntimeSnapshot({
config: asConfig({
channels: {
telegram: {
botToken: {
source: "env",
provider: "default",
id: "TELEGRAM_BASE_TOKEN",
},
accounts: {
work: {
enabled: true,
botToken: "",
},
},
},
},
}),
env: {
TELEGRAM_BASE_TOKEN: "telegram-base-token",
},
agentDirs: ["/tmp/openclaw-agent-main"],
loadAuthStore: () => ({ version: 1, profiles: {} }),
});
expect(snapshot.config.channels?.telegram?.botToken).toBe("telegram-base-token");
expect(snapshot.config.channels?.telegram?.accounts?.work?.botToken).toBe("");
expect(snapshot.warnings.map((warning) => warning.path)).not.toContain(
"channels.telegram.botToken",
);
});
it("treats IRC account nickserv password refs as inactive when nickserv is disabled", async () => {
const snapshot = await prepareSecretsRuntimeSnapshot({
config: asConfig({
channels: {
irc: {
accounts: {
work: {
enabled: true,
nickserv: {
enabled: false,
password: {
source: "env",
provider: "default",
id: "MISSING_IRC_WORK_NICKSERV_PASSWORD",
},
},
},
},
},
},
}),
env: {},
agentDirs: ["/tmp/openclaw-agent-main"],
loadAuthStore: () => ({ version: 1, profiles: {} }),
});
expect(snapshot.config.channels?.irc?.accounts?.work?.nickserv?.password).toEqual({
source: "env",
provider: "default",
id: "MISSING_IRC_WORK_NICKSERV_PASSWORD",
});
expect(snapshot.warnings.map((warning) => warning.path)).toContain(
"channels.irc.accounts.work.nickserv.password",
);
});
it("treats top-level IRC nickserv password refs as inactive when nickserv is disabled", async () => {
const snapshot = await prepareSecretsRuntimeSnapshot({
config: asConfig({
channels: {
irc: {
nickserv: {
enabled: false,
password: {
source: "env",
provider: "default",
id: "MISSING_IRC_TOPLEVEL_NICKSERV_PASSWORD",
},
},
},
},
}),
env: {},
agentDirs: ["/tmp/openclaw-agent-main"],
loadAuthStore: () => ({ version: 1, profiles: {} }),
});
expect(snapshot.config.channels?.irc?.nickserv?.password).toEqual({
source: "env",
provider: "default",
id: "MISSING_IRC_TOPLEVEL_NICKSERV_PASSWORD",
});
expect(snapshot.warnings.map((warning) => warning.path)).toContain(
"channels.irc.nickserv.password",
);
});
it("treats Slack signingSecret refs as inactive when mode is socket", async () => {
const snapshot = await prepareSecretsRuntimeSnapshot({
config: asConfig({
channels: {
slack: {
mode: "socket",
signingSecret: {
source: "env",
provider: "default",
id: "MISSING_SLACK_SIGNING_SECRET",
},
accounts: {
work: {
enabled: true,
mode: "socket",
},
},
},
},
}),
env: {},
agentDirs: ["/tmp/openclaw-agent-main"],
loadAuthStore: () => ({ version: 1, profiles: {} }),
});
expect(snapshot.config.channels?.slack?.signingSecret).toEqual({
source: "env",
provider: "default",
id: "MISSING_SLACK_SIGNING_SECRET",
});
expect(snapshot.warnings.map((warning) => warning.path)).toContain(
"channels.slack.signingSecret",
);
});
it("treats Slack appToken refs as inactive when mode is http", async () => {
const snapshot = await prepareSecretsRuntimeSnapshot({
config: asConfig({
channels: {
slack: {
mode: "http",
appToken: {
source: "env",
provider: "default",
id: "MISSING_SLACK_APP_TOKEN",
},
accounts: {
work: {
enabled: true,
mode: "http",
appToken: {
source: "env",
provider: "default",
id: "MISSING_SLACK_WORK_APP_TOKEN",
},
},
},
},
},
}),
env: {},
agentDirs: ["/tmp/openclaw-agent-main"],
loadAuthStore: () => ({ version: 1, profiles: {} }),
});
expect(snapshot.config.channels?.slack?.appToken).toEqual({
source: "env",
provider: "default",
id: "MISSING_SLACK_APP_TOKEN",
});
expect(snapshot.config.channels?.slack?.accounts?.work?.appToken).toEqual({
source: "env",
provider: "default",
id: "MISSING_SLACK_WORK_APP_TOKEN",
});
expect(snapshot.warnings.map((warning) => warning.path)).toEqual(
expect.arrayContaining(["channels.slack.appToken", "channels.slack.accounts.work.appToken"]),
);
});
it("treats top-level Google Chat serviceAccount as inactive when enabled accounts use serviceAccountRef", async () => {
const snapshot = await prepareSecretsRuntimeSnapshot({
config: asConfig({
channels: {
googlechat: {
serviceAccount: {
source: "env",
provider: "default",
id: "MISSING_GOOGLECHAT_BASE_SERVICE_ACCOUNT",
},
accounts: {
work: {
enabled: true,
serviceAccountRef: {
source: "env",
provider: "default",
id: "GOOGLECHAT_WORK_SERVICE_ACCOUNT",
},
},
},
},
},
}),
env: {
GOOGLECHAT_WORK_SERVICE_ACCOUNT: "work-service-account-json",
},
agentDirs: ["/tmp/openclaw-agent-main"],
loadAuthStore: () => ({ version: 1, profiles: {} }),
});
expect(snapshot.config.channels?.googlechat?.serviceAccount).toEqual({
source: "env",
provider: "default",
id: "MISSING_GOOGLECHAT_BASE_SERVICE_ACCOUNT",
});
expect(snapshot.config.channels?.googlechat?.accounts?.work?.serviceAccount).toBe(
"work-service-account-json",
);
expect(snapshot.warnings.map((warning) => warning.path)).toContain(
"channels.googlechat.serviceAccount",
);
});
it("fails when non-default Discord account inherits an unresolved top-level token ref", async () => {
await expect(
prepareSecretsRuntimeSnapshot({
config: asConfig({
channels: {
discord: {
token: {
source: "env",
provider: "default",
id: "MISSING_DISCORD_BASE_TOKEN",
},
accounts: {
work: {
enabled: true,
},
},
},
},
}),
env: {},
agentDirs: ["/tmp/openclaw-agent-main"],
loadAuthStore: () => ({ version: 1, profiles: {} }),
}),
).rejects.toThrow('Environment variable "MISSING_DISCORD_BASE_TOKEN" is missing or empty.');
});
it("treats top-level Discord token refs as inactive when account token is explicitly blank", async () => {
const snapshot = await prepareSecretsRuntimeSnapshot({
config: asConfig({
channels: {
discord: {
token: {
source: "env",
provider: "default",
id: "MISSING_DISCORD_DEFAULT_TOKEN",
},
accounts: {
default: {
enabled: true,
token: "",
},
},
},
},
}),
env: {},
agentDirs: ["/tmp/openclaw-agent-main"],
loadAuthStore: () => ({ version: 1, profiles: {} }),
});
expect(snapshot.config.channels?.discord?.token).toEqual({
source: "env",
provider: "default",
id: "MISSING_DISCORD_DEFAULT_TOKEN",
});
expect(snapshot.warnings.map((warning) => warning.path)).toContain("channels.discord.token");
});
it("treats Discord PluralKit token refs as inactive when PluralKit is disabled", async () => {
const snapshot = await prepareSecretsRuntimeSnapshot({
config: asConfig({
channels: {
discord: {
pluralkit: {
enabled: false,
token: {
source: "env",
provider: "default",
id: "MISSING_DISCORD_PLURALKIT_TOKEN",
},
},
},
},
}),
env: {},
agentDirs: ["/tmp/openclaw-agent-main"],
loadAuthStore: () => ({ version: 1, profiles: {} }),
});
expect(snapshot.config.channels?.discord?.pluralkit?.token).toEqual({
source: "env",
provider: "default",
id: "MISSING_DISCORD_PLURALKIT_TOKEN",
});
expect(snapshot.warnings.map((warning) => warning.path)).toContain(
"channels.discord.pluralkit.token",
);
});
it("treats Discord voice TTS refs as inactive when voice is disabled", async () => {
const snapshot = await prepareSecretsRuntimeSnapshot({
config: asConfig({
channels: {
discord: {
voice: {
enabled: false,
tts: {
openai: {
apiKey: {
source: "env",
provider: "default",
id: "MISSING_DISCORD_VOICE_TTS_OPENAI",
},
},
},
},
accounts: {
work: {
enabled: true,
voice: {
enabled: false,
tts: {
openai: {
apiKey: {
source: "env",
provider: "default",
id: "MISSING_DISCORD_WORK_VOICE_TTS_OPENAI",
},
},
},
},
},
},
},
},
}),
env: {},
agentDirs: ["/tmp/openclaw-agent-main"],
loadAuthStore: () => ({ version: 1, profiles: {} }),
});
expect(snapshot.config.channels?.discord?.voice?.tts?.openai?.apiKey).toEqual({
source: "env",
provider: "default",
id: "MISSING_DISCORD_VOICE_TTS_OPENAI",
});
expect(snapshot.config.channels?.discord?.accounts?.work?.voice?.tts?.openai?.apiKey).toEqual({
source: "env",
provider: "default",
id: "MISSING_DISCORD_WORK_VOICE_TTS_OPENAI",
});
expect(snapshot.warnings.map((warning) => warning.path)).toEqual(
expect.arrayContaining([
"channels.discord.voice.tts.openai.apiKey",
"channels.discord.accounts.work.voice.tts.openai.apiKey",
]),
);
});
it("handles Discord nested inheritance for enabled and disabled accounts", async () => {
const snapshot = await prepareSecretsRuntimeSnapshot({
config: asConfig({
channels: {
discord: {
voice: {
tts: {
openai: {
apiKey: { source: "env", provider: "default", id: "DISCORD_BASE_TTS_OPENAI" },
},
},
},
pluralkit: {
token: { source: "env", provider: "default", id: "DISCORD_BASE_PK_TOKEN" },
},
accounts: {
enabledInherited: {
enabled: true,
},
enabledOverride: {
enabled: true,
voice: {
tts: {
openai: {
apiKey: {
source: "env",
provider: "default",
id: "DISCORD_ENABLED_OVERRIDE_TTS_OPENAI",
},
},
},
},
},
disabledOverride: {
enabled: false,
voice: {
tts: {
openai: {
apiKey: {
source: "env",
provider: "default",
id: "DISCORD_DISABLED_OVERRIDE_TTS_OPENAI",
},
},
},
},
pluralkit: {
token: {
source: "env",
provider: "default",
id: "DISCORD_DISABLED_OVERRIDE_PK_TOKEN",
},
},
},
},
},
},
}),
env: {
DISCORD_BASE_TTS_OPENAI: "base-tts-openai",
DISCORD_BASE_PK_TOKEN: "base-pk-token",
DISCORD_ENABLED_OVERRIDE_TTS_OPENAI: "enabled-override-tts-openai",
},
agentDirs: ["/tmp/openclaw-agent-main"],
loadAuthStore: () => ({ version: 1, profiles: {} }),
});
expect(snapshot.config.channels?.discord?.voice?.tts?.openai?.apiKey).toBe("base-tts-openai");
expect(snapshot.config.channels?.discord?.pluralkit?.token).toBe("base-pk-token");
expect(
snapshot.config.channels?.discord?.accounts?.enabledOverride?.voice?.tts?.openai?.apiKey,
).toBe("enabled-override-tts-openai");
expect(
snapshot.config.channels?.discord?.accounts?.disabledOverride?.voice?.tts?.openai?.apiKey,
).toEqual({
source: "env",
provider: "default",
id: "DISCORD_DISABLED_OVERRIDE_TTS_OPENAI",
});
expect(snapshot.config.channels?.discord?.accounts?.disabledOverride?.pluralkit?.token).toEqual(
{
source: "env",
provider: "default",
id: "DISCORD_DISABLED_OVERRIDE_PK_TOKEN",
},
);
expect(snapshot.warnings.map((warning) => warning.path)).toEqual(
expect.arrayContaining([
"channels.discord.accounts.disabledOverride.voice.tts.openai.apiKey",
"channels.discord.accounts.disabledOverride.pluralkit.token",
]),
);
});
it("skips top-level Discord voice refs when all enabled accounts override nested voice config", async () => {
const snapshot = await prepareSecretsRuntimeSnapshot({
config: asConfig({
channels: {
discord: {
voice: {
tts: {
openai: {
apiKey: {
source: "env",
provider: "default",
id: "DISCORD_UNUSED_BASE_TTS_OPENAI",
},
},
},
},
accounts: {
enabledOverride: {
enabled: true,
voice: {
tts: {
openai: {
apiKey: {
source: "env",
provider: "default",
id: "DISCORD_ENABLED_ONLY_TTS_OPENAI",
},
},
},
},
},
disabledInherited: {
enabled: false,
},
},
},
},
}),
env: {
DISCORD_ENABLED_ONLY_TTS_OPENAI: "enabled-only-tts-openai",
},
agentDirs: ["/tmp/openclaw-agent-main"],
loadAuthStore: () => ({ version: 1, profiles: {} }),
});
expect(
snapshot.config.channels?.discord?.accounts?.enabledOverride?.voice?.tts?.openai?.apiKey,
).toBe("enabled-only-tts-openai");
expect(snapshot.config.channels?.discord?.voice?.tts?.openai?.apiKey).toEqual({
source: "env",
provider: "default",
id: "DISCORD_UNUSED_BASE_TTS_OPENAI",
});
expect(snapshot.warnings.map((warning) => warning.path)).toContain(
"channels.discord.voice.tts.openai.apiKey",
);
});
it("fails when an enabled Discord account override has an unresolved nested ref", async () => {
await expect(
prepareSecretsRuntimeSnapshot({
config: asConfig({
channels: {
discord: {
voice: {
tts: {
openai: {
apiKey: { source: "env", provider: "default", id: "DISCORD_BASE_TTS_OK" },
},
},
},
accounts: {
enabledOverride: {
enabled: true,
voice: {
tts: {
openai: {
apiKey: {
source: "env",
provider: "default",
id: "DISCORD_ENABLED_OVERRIDE_TTS_MISSING",
},
},
},
},
},
},
},
},
}),
env: {
DISCORD_BASE_TTS_OK: "base-tts-openai",
},
agentDirs: ["/tmp/openclaw-agent-main"],
loadAuthStore: () => ({ version: 1, profiles: {} }),
}),
).rejects.toThrow(
'Environment variable "DISCORD_ENABLED_OVERRIDE_TTS_MISSING" is missing or empty.',
);
});
it("does not write inherited auth stores during runtime secret activation", async () => {
const root = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-secrets-runtime-"));
const stateDir = path.join(root, ".openclaw");
const mainAgentDir = path.join(stateDir, "agents", "main", "agent");
const workerStorePath = path.join(stateDir, "agents", "worker", "agent", "auth-profiles.json");
const prevStateDir = process.env.OPENCLAW_STATE_DIR;
try {
await fs.mkdir(mainAgentDir, { recursive: true });
await fs.writeFile(
path.join(mainAgentDir, "auth-profiles.json"),
JSON.stringify({
...loadAuthStoreWithProfiles({
"openai:default": {
type: "api_key",
provider: "openai",
keyRef: OPENAI_ENV_KEY_REF,
},
}),
}),
"utf8",
);
process.env.OPENCLAW_STATE_DIR = stateDir;
await prepareSecretsRuntimeSnapshot({
config: {
agents: {
list: [{ id: "worker" }],
},
},
env: { OPENAI_API_KEY: "sk-runtime-worker" }, // pragma: allowlist secret
});
await expect(fs.access(workerStorePath)).rejects.toMatchObject({ code: "ENOENT" });
} finally {
if (prevStateDir === undefined) {
delete process.env.OPENCLAW_STATE_DIR;
} else {
process.env.OPENCLAW_STATE_DIR = prevStateDir;
}
await fs.rm(root, { recursive: true, force: true });
}
});
});