Skip to content

Commit fe680e4

Browse files
committed
fix(cli): scope web command secret refs
1 parent 230806e commit fe680e4

32 files changed

Lines changed: 2288 additions & 2087 deletions

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ Docs: https://docs.openclaw.ai
121121
- CLI/configure: let model-only section setup enter provider auth directly instead of first asking where the Gateway runs, unblocking OAuth/token setup in terminals where that unrelated prompt is unresponsive. Fixes #39223. Thanks @LevityLeads.
122122
- Providers/Anthropic-messages: extract `reasoning_content` from `thinking` blocks during assistant replay so proxy providers that route through the Anthropic-messages transport preserve reasoning context across tool-call follow-up turns. Thanks @Sunnyone2three.
123123
- Agents/GitHub Copilot: normalize replayed Responses tool-call IDs before dispatch so resumed sessions with historical overlong tool IDs continue instead of failing Copilot schema validation. (#82750) Thanks @galiniliev.
124-
- CLI/web: resolve provider-scoped web search/fetch SecretRefs for `infer web ... --provider ...` while leaving unrelated plugin secrets untouched. Fixes #82621. Thanks @leno23.
124+
- CLI/infer: resolve plugin-scoped web search and fetch SecretRefs on the exact command credential surface, keeping non-selected and unrelated plugin secrets inactive. Fixes #82621. (#82699) Thanks @leno23.
125125
- Providers/Anthropic Vertex: resolve installed provider public surfaces from package-local `dist/`, restoring `anthropic-vertex/*` model calls after plugin externalization. Fixes #82781. Thanks @0L1v3DaD.
126126
- Gateway/exec approvals: bind path-shaped allowlists, safe-bin trust, skill auto-allow, Allow Always persistence, and approval audit metadata to the executable realpath so symlinked binaries cannot keep approvals after retargeting. Fixes #45595. Thanks @jasonftl.
127127
- Mac app: reorganize Settings around a grouped sidebar, with separate Connection and Exec Approvals pages so everyday permissions and app toggles are easier to scan.

docs/reference/secretref-credential-surface.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ Scope intent:
5454
- `plugins.entries.voice-call.config.streaming.providers.*.apiKey`
5555
- `plugins.entries.voice-call.config.tts.providers.*.apiKey`
5656
- `plugins.entries.voice-call.config.twilio.authToken`
57+
- `tools.web.search.*.apiKey`
5758
- `tools.web.search.apiKey`
5859
- `gateway.auth.password`
5960
- `gateway.auth.token`

docs/reference/secretref-user-supplied-credentials-matrix.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -645,6 +645,13 @@
645645
"secretShape": "secret_input",
646646
"optIn": true
647647
},
648+
{
649+
"id": "tools.web.search.*.apiKey",
650+
"configFile": "openclaw.json",
651+
"path": "tools.web.search.*.apiKey",
652+
"secretShape": "secret_input",
653+
"optIn": true
654+
},
648655
{
649656
"id": "tools.web.search.apiKey",
650657
"configFile": "openclaw.json",

extensions/firecrawl/src/firecrawl-fetch-provider-shared.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,19 @@ export const FIRECRAWL_WEB_FETCH_PROVIDER_SHARED = {
4444
getConfiguredCredentialValue: (config) =>
4545
(config?.plugins?.entries?.firecrawl?.config as { webFetch?: { apiKey?: unknown } } | undefined)
4646
?.webFetch?.apiKey,
47+
getConfiguredCredentialFallback: (config) => {
48+
const apiKey = (
49+
config?.plugins?.entries?.firecrawl?.config as
50+
| { webSearch?: { apiKey?: unknown } }
51+
| undefined
52+
)?.webSearch?.apiKey;
53+
return apiKey === undefined
54+
? undefined
55+
: {
56+
path: "plugins.entries.firecrawl.config.webSearch.apiKey",
57+
value: apiKey,
58+
};
59+
},
4760
setConfiguredCredentialValue: (configTarget, value) => {
4861
const plugins = ensureRecord(configTarget as unknown as Record<string, unknown>, "plugins");
4962
const entries = ensureRecord(plugins, "entries");

extensions/firecrawl/src/firecrawl-search-provider.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
} from "openclaw/plugin-sdk/provider-web-search-contract";
55

66
const FIRECRAWL_CREDENTIAL_PATH = "plugins.entries.firecrawl.config.webSearch.apiKey";
7+
const FIRECRAWL_FETCH_CREDENTIAL_PATH = "plugins.entries.firecrawl.config.webFetch.apiKey";
78

89
type FirecrawlClientModule = typeof import("./firecrawl-client.js");
910

@@ -14,6 +15,20 @@ function loadFirecrawlClientModule(): Promise<FirecrawlClientModule> {
1415
return firecrawlClientModulePromise;
1516
}
1617

18+
function getConfiguredFetchCredentialFallback(config?: {
19+
plugins?: { entries?: { firecrawl?: { config?: unknown } } };
20+
}) {
21+
const apiKey = (
22+
config?.plugins?.entries?.firecrawl?.config as { webFetch?: { apiKey?: unknown } } | undefined
23+
)?.webFetch?.apiKey;
24+
return apiKey === undefined
25+
? undefined
26+
: {
27+
path: FIRECRAWL_FETCH_CREDENTIAL_PATH,
28+
value: apiKey,
29+
};
30+
}
31+
1732
const GenericFirecrawlSearchSchema = {
1833
type: "object",
1934
properties: {
@@ -47,6 +62,7 @@ export function createFirecrawlWebSearchProvider(): WebSearchProviderPlugin {
4762
configuredCredential: { pluginId: "firecrawl" },
4863
selectionPluginId: "firecrawl",
4964
}),
65+
getConfiguredCredentialFallback: getConfiguredFetchCredentialFallback,
5066
createTool: (ctx) => ({
5167
description:
5268
"Search the web using Firecrawl. Returns structured results with snippets from Firecrawl Search. Use firecrawl_search for Firecrawl-specific knobs like sources or categories.",

extensions/firecrawl/src/firecrawl-tools.test.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,24 @@ describe("firecrawl tools", () => {
8686

8787
expect(provider.id).toBe("firecrawl");
8888
expect(provider.credentialPath).toBe("plugins.entries.firecrawl.config.webSearch.apiKey");
89+
expect(
90+
provider.getConfiguredCredentialFallback?.({
91+
plugins: {
92+
entries: {
93+
firecrawl: {
94+
config: {
95+
webFetch: {
96+
apiKey: { source: "env", provider: "default", id: "FIRECRAWL_API_KEY" },
97+
},
98+
},
99+
},
100+
},
101+
},
102+
} as never),
103+
).toEqual({
104+
path: "plugins.entries.firecrawl.config.webFetch.apiKey",
105+
value: { source: "env", provider: "default", id: "FIRECRAWL_API_KEY" },
106+
});
89107
const pluginEntry = applied.plugins?.entries?.firecrawl;
90108
if (!pluginEntry) {
91109
throw new Error("expected Firecrawl plugin entry");

extensions/firecrawl/web-search-contract-api.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55

66
export function createFirecrawlWebSearchProvider(): WebSearchProviderPlugin {
77
const credentialPath = "plugins.entries.firecrawl.config.webSearch.apiKey";
8+
const fetchCredentialPath = "plugins.entries.firecrawl.config.webFetch.apiKey";
89

910
return {
1011
id: "firecrawl",
@@ -24,6 +25,19 @@ export function createFirecrawlWebSearchProvider(): WebSearchProviderPlugin {
2425
configuredCredential: { pluginId: "firecrawl" },
2526
selectionPluginId: "firecrawl",
2627
}),
28+
getConfiguredCredentialFallback: (config) => {
29+
const apiKey = (
30+
config?.plugins?.entries?.firecrawl?.config as
31+
| { webFetch?: { apiKey?: unknown } }
32+
| undefined
33+
)?.webFetch?.apiKey;
34+
return apiKey === undefined
35+
? undefined
36+
: {
37+
path: fetchCredentialPath,
38+
value: apiKey,
39+
};
40+
},
2741
createTool: () => null,
2842
};
2943
}

scripts/repro/cli-web-search-secret-refs-live-proof.mjs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* Run: TAVILY_API_KEY=resolved-live-proof pnpm exec tsx scripts/repro/cli-web-search-secret-refs-live-proof.mjs
55
*/
66
import { resolveCommandConfigWithSecrets } from "../../src/cli/command-config-resolution.js";
7-
import { getWebSearchCommandSecretTargetIds } from "../../src/cli/command-secret-targets.js";
7+
import { getCapabilityWebSearchCommandSecretTargetIds } from "../../src/cli/command-secret-targets.js";
88

99
const unresolvedConfig = {
1010
tools: { web: { search: { provider: "tavily", enabled: true } } },
@@ -26,7 +26,7 @@ process.env.TAVILY_API_KEY = process.env.TAVILY_API_KEY ?? "resolved-live-proof"
2626
const { effectiveConfig, diagnostics } = await resolveCommandConfigWithSecrets({
2727
config: unresolvedConfig,
2828
commandName: "infer web search",
29-
targetIds: getWebSearchCommandSecretTargetIds(),
29+
targetIds: getCapabilityWebSearchCommandSecretTargetIds(),
3030
autoEnable: true,
3131
});
3232

@@ -38,5 +38,8 @@ console.log(
3838
"resolveCommandConfigWithSecrets apiKey is string =",
3939
typeof apiKey === "string" && apiKey.length > 0,
4040
);
41-
console.log("resolved apiKey remains redacted =", typeof apiKey === "string");
41+
console.log(
42+
"resolved apiKey prefix =",
43+
typeof apiKey === "string" ? `${apiKey.slice(0, 8)}…` : apiKey,
44+
);
4245
console.log("diagnostics count =", diagnostics.length);

0 commit comments

Comments
 (0)