Secrets: honor caller env during runtime validation

This commit is contained in:
Gustavo Madeira Santana 2026-03-16 12:31:44 +00:00
parent e5282e6bda
commit 467dae53cf
No known key found for this signature in database
6 changed files with 62 additions and 20 deletions

View File

@ -3,14 +3,13 @@ import { resolveStateDir } from "../config/paths.js";
import { DEFAULT_AGENT_ID } from "../routing/session-key.js";
import { resolveUserPath } from "../utils.js";
export function resolveOpenClawAgentDir(): string {
const override =
process.env.OPENCLAW_AGENT_DIR?.trim() || process.env.PI_CODING_AGENT_DIR?.trim();
export function resolveOpenClawAgentDir(env: NodeJS.ProcessEnv = process.env): string {
const override = env.OPENCLAW_AGENT_DIR?.trim() || env.PI_CODING_AGENT_DIR?.trim();
if (override) {
return resolveUserPath(override);
return resolveUserPath(override, env);
}
const defaultAgentDir = path.join(resolveStateDir(), "agents", DEFAULT_AGENT_ID, "agent");
return resolveUserPath(defaultAgentDir);
const defaultAgentDir = path.join(resolveStateDir(env), "agents", DEFAULT_AGENT_ID, "agent");
return resolveUserPath(defaultAgentDir, env);
}
export function ensureOpenClawAgentEnv(): string {

View File

@ -327,12 +327,16 @@ export function resolveAgentIdByWorkspacePath(
return resolveAgentIdsByWorkspacePath(cfg, workspacePath)[0];
}
export function resolveAgentDir(cfg: OpenClawConfig, agentId: string) {
export function resolveAgentDir(
cfg: OpenClawConfig,
agentId: string,
env: NodeJS.ProcessEnv = process.env,
) {
const id = normalizeAgentId(agentId);
const configured = resolveAgentConfig(cfg, id)?.agentDir?.trim();
if (configured) {
return resolveUserPath(configured);
return resolveUserPath(configured, env);
}
const root = resolveStateDir(process.env);
const root = resolveStateDir(env);
return path.join(root, "agents", id, "agent");
}

View File

@ -4,6 +4,7 @@ import path from "node:path";
import { afterEach, beforeEach, describe, expect, it } from "vitest";
import { runSecretsApply } from "./apply.js";
import type { SecretsApplyPlan } from "./plan.js";
import { clearSecretsRuntimeSnapshot } from "./runtime.js";
const OPENAI_API_KEY_ENV_REF = {
source: "env",
@ -173,11 +174,13 @@ describe("secrets apply", () => {
let fixture: ApplyFixture;
beforeEach(async () => {
clearSecretsRuntimeSnapshot();
fixture = await createApplyFixture();
await seedDefaultApplyFixture(fixture);
});
afterEach(async () => {
clearSecretsRuntimeSnapshot();
await fs.rm(fixture.rootDir, { recursive: true, force: true });
});

View File

@ -1,5 +1,6 @@
import { afterEach, describe, expect, it, vi } from "vitest";
import type { OpenClawConfig } from "../config/config.js";
import * as webSearchProviders from "../plugins/web-search-providers.js";
import * as secretResolve from "./resolve.js";
import { createResolverContext } from "./runtime-shared.js";
import { resolveRuntimeWebTools } from "./runtime-web-tools.js";
@ -88,6 +89,32 @@ describe("runtime web tools resolution", () => {
vi.restoreAllMocks();
});
it("skips loading web search providers when search config is absent", async () => {
const providerSpy = vi.spyOn(webSearchProviders, "resolvePluginWebSearchProviders");
const { metadata } = await runRuntimeWebTools({
config: asConfig({
tools: {
web: {
fetch: {
firecrawl: {
apiKey: { source: "env", provider: "default", id: "FIRECRAWL_API_KEY_REF" },
},
},
},
},
}),
env: {
FIRECRAWL_API_KEY: "firecrawl-runtime-key", // pragma: allowlist secret
},
});
expect(providerSpy).not.toHaveBeenCalled();
expect(metadata.search.providerSource).toBe("none");
expect(metadata.fetch.firecrawl.active).toBe(true);
expect(metadata.fetch.firecrawl.apiKeySource).toBe("env");
});
it.each([
{
provider: "brave" as const,

View File

@ -315,11 +315,13 @@ export async function resolveRuntimeWebTools(params: {
const tools = isRecord(params.sourceConfig.tools) ? params.sourceConfig.tools : undefined;
const web = isRecord(tools?.web) ? tools.web : undefined;
const search = isRecord(web?.search) ? web.search : undefined;
const providers = resolvePluginWebSearchProviders({
config: params.sourceConfig,
env: params.context.env,
bundledAllowlistCompat: true,
});
const providers = search
? resolvePluginWebSearchProviders({
config: params.sourceConfig,
env: params.context.env,
bundledAllowlistCompat: true,
})
: [];
const searchMetadata: RuntimeWebSearchMetadata = {
providerSource: "none",

View File

@ -79,11 +79,14 @@ function clearActiveSecretsRuntimeState(): void {
clearRuntimeAuthProfileStoreSnapshots();
}
function collectCandidateAgentDirs(config: OpenClawConfig): string[] {
function collectCandidateAgentDirs(
config: OpenClawConfig,
env: NodeJS.ProcessEnv = process.env,
): string[] {
const dirs = new Set<string>();
dirs.add(resolveUserPath(resolveOpenClawAgentDir()));
dirs.add(resolveUserPath(resolveOpenClawAgentDir(env), env));
for (const agentId of listAgentIds(config)) {
dirs.add(resolveUserPath(resolveAgentDir(config, agentId)));
dirs.add(resolveUserPath(resolveAgentDir(config, agentId, env), env));
}
return [...dirs];
}
@ -92,7 +95,7 @@ function resolveRefreshAgentDirs(
config: OpenClawConfig,
context: SecretsRuntimeRefreshContext,
): string[] {
const configDerived = collectCandidateAgentDirs(config);
const configDerived = collectCandidateAgentDirs(config, context.env);
if (!context.explicitAgentDirs || context.explicitAgentDirs.length === 0) {
return configDerived;
}
@ -119,8 +122,12 @@ export async function prepareSecretsRuntimeSnapshot(params: {
const loadAuthStore = params.loadAuthStore ?? loadAuthProfileStoreForSecretsRuntime;
const candidateDirs = params.agentDirs?.length
? [...new Set(params.agentDirs.map((entry) => resolveUserPath(entry)))]
: collectCandidateAgentDirs(resolvedConfig);
? [
...new Set(
params.agentDirs.map((entry) => resolveUserPath(entry, params.env ?? process.env)),
),
]
: collectCandidateAgentDirs(resolvedConfig, params.env ?? process.env);
const authStores: Array<{ agentDir: string; store: AuthProfileStore }> = [];
for (const agentDir of candidateDirs) {