Skip to content

fix(security): audit web search keys for all bundled providers#56540

Merged
hydro13 merged 1 commit intoopenclaw:mainfrom
hydro13:fix/web-search-audit-missing-providers
Mar 28, 2026
Merged

fix(security): audit web search keys for all bundled providers#56540
hydro13 merged 1 commit intoopenclaw:mainfrom
hydro13:fix/web-search-audit-missing-providers

Conversation

@hydro13
Copy link
Copy Markdown
Member

@hydro13 hydro13 commented Mar 28, 2026

Summary

  • hasWebSearchKey() was hardcoded to only check Brave and Perplexity credentials
  • Replace with provider-aware check using resolveBundledPluginWebSearchProviders()
  • Gemini, Grok/XAI, Kimi, Moonshot, and OpenRouter credentials are now recognized by the audit
  • Add focused regression tests for each provider

Root Cause

The audit function hasWebSearchKey() in src/security/audit-extra.sync.ts was still hardcoded to legacy surfaces (Brave config/env + Perplexity config/env). Web search has moved to provider-owned metadata and plugin config paths, so newer providers were invisible to the audit.

Change Type

  • Bug fix
  • Security hardening

Testing

All 15 tests pass including 6 new regression tests:

  • Gemini plugin config
  • Gemini env key (GEMINI_API_KEY)
  • Grok env key (XAI_API_KEY)
  • Kimi env key (KIMI_API_KEY)
  • Moonshot env key (MOONSHOT_API_KEY)
  • OpenRouter env fallback (OPENROUTER_API_KEY)

Fixes #34509

@hydro13 hydro13 requested a review from a team as a code owner March 28, 2026 17:29
@openclaw-barnacle openclaw-barnacle Bot added size: S maintainer Maintainer-authored PR labels Mar 28, 2026
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Mar 28, 2026

Greptile Summary

This PR fixes a security audit gap where hasWebSearchKey() was hardcoded to check only Brave and Perplexity credentials, making newer web-search providers (Gemini, Grok/XAI, Kimi, Moonshot, OpenRouter) invisible to the audit. The fix replaces the hardcoded checks with a provider-aware loop over resolveBundledPluginWebSearchProviders(), and adds a small helper hasConfiguredCredentialValue to normalise the credential check across string and non-string values.

Key points:

  • The core refactor is clean and correctly extends coverage to all registered bundled providers without duplicating provider-specific knowledge in the audit layer.
  • Six new integration-style tests validate the end-to-end path through collectSmallModelRiskFindingsisWebSearchEnabledhasWebSearchKey for the newly supported providers.
  • Regression coverage for the previously hardcoded Brave and Perplexity env-var checks is absent from the new test suite — if their envVars registrations change, there is no test to catch it.
  • The ?? fallback in getConfiguredCredentialValue?.(cfg) ?? getCredentialValue(search) causes the legacy cfg.tools.web.search path to be consulted even for providers that define getConfiguredCredentialValue but return null/undefined for it. This appears intentional for backward compatibility but is not documented."

Confidence Score: 5/5

Safe to merge — all remaining findings are non-blocking P2 suggestions around test coverage and operator semantics.

No P0 or P1 issues found. The new implementation is logically sound and all changed code paths have test coverage for the new providers. The two P2 notes (missing Brave/Perplexity regression tests and the ?? operator behaviour) are improvement suggestions that do not affect correctness of the shipped fix.

No files require special attention, though adding Brave and Perplexity cases to the new test suite would improve regression safety.

Important Files Changed

Filename Overview
src/security/audit-extra.sync.ts Replaces hardcoded Brave/Perplexity credential checks in hasWebSearchKey with a provider-aware loop over resolveBundledPluginWebSearchProviders. Logic is correct; the ?? fallback to getCredentialValue(search) intentionally preserves legacy-path coverage for migrated providers.
src/security/audit-extra.sync.test.ts Adds six new regression cases for the newly supported providers (Gemini, Grok, Kimi, Moonshot, OpenRouter). Does not include regression tests for the two previously hardcoded providers (Brave, Perplexity) whose paths are now exercised through the generic mechanism.
Prompt To Fix All With AI
This is a comment left during a code review.
Path: src/security/audit-extra.sync.ts
Line: 343-344

Comment:
**`??` falls through on both absent and `null`/`undefined` return values**

The current expression:

```ts
provider.getConfiguredCredentialValue?.(cfg) ?? provider.getCredentialValue(search)
```

will fall through to `getCredentialValue(search)` not only when `getConfiguredCredentialValue` is absent (optional chaining returns `undefined`), but also when it is **defined** and explicitly returns `null` or `undefined` (e.g., "no credential at this config path"). This means the legacy `cfg.tools.web.search` path is always consulted as a fallback even for providers that have fully migrated to `getConfiguredCredentialValue`.

If the intent is backward-compat double-checking, that is fine and working as designed. However, if the intent is "use only the new path once the method is defined", the check should use a ternary instead:

```ts
const configuredCredential = provider.getConfiguredCredentialValue
  ? provider.getConfiguredCredentialValue(cfg)
  : provider.getCredentialValue(search);
```

A comment clarifying the intended dual-path behaviour would prevent future confusion regardless.

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: src/security/audit-extra.sync.test.ts
Line: 59-107

Comment:
**Missing regression tests for Brave and Perplexity**

The PR replaces the previously hardcoded checks for `BRAVE_API_KEY` and `PERPLEXITY_API_KEY` with the generic `resolveBundledPluginWebSearchProviders` mechanism, but no regression test cases cover these two providers in the new `collectSmallModelRiskFindings` suite.

If the Brave or Perplexity provider's `envVars` array is ever accidentally modified (or the provider's plugin ID changes), the regression would be silent — neither the old hardcoded path nor any new test would catch it. Consider adding equivalent `it.each` entries for `BRAVE_API_KEY` and `PERPLEXITY_API_KEY` to match the pattern used for the six new providers.

How can I resolve this? If you propose a fix, please make it concise.

Reviews (1): Last reviewed commit: "fix(security): audit web search keys for..." | Re-trigger Greptile

Comment thread src/security/audit-extra.sync.ts Outdated
Comment on lines +343 to +344
const configuredCredential =
provider.getConfiguredCredentialValue?.(cfg) ?? provider.getCredentialValue(search);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 ?? falls through on both absent and null/undefined return values

The current expression:

provider.getConfiguredCredentialValue?.(cfg) ?? provider.getCredentialValue(search)

will fall through to getCredentialValue(search) not only when getConfiguredCredentialValue is absent (optional chaining returns undefined), but also when it is defined and explicitly returns null or undefined (e.g., "no credential at this config path"). This means the legacy cfg.tools.web.search path is always consulted as a fallback even for providers that have fully migrated to getConfiguredCredentialValue.

If the intent is backward-compat double-checking, that is fine and working as designed. However, if the intent is "use only the new path once the method is defined", the check should use a ternary instead:

const configuredCredential = provider.getConfiguredCredentialValue
  ? provider.getConfiguredCredentialValue(cfg)
  : provider.getCredentialValue(search);

A comment clarifying the intended dual-path behaviour would prevent future confusion regardless.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/security/audit-extra.sync.ts
Line: 343-344

Comment:
**`??` falls through on both absent and `null`/`undefined` return values**

The current expression:

```ts
provider.getConfiguredCredentialValue?.(cfg) ?? provider.getCredentialValue(search)
```

will fall through to `getCredentialValue(search)` not only when `getConfiguredCredentialValue` is absent (optional chaining returns `undefined`), but also when it is **defined** and explicitly returns `null` or `undefined` (e.g., "no credential at this config path"). This means the legacy `cfg.tools.web.search` path is always consulted as a fallback even for providers that have fully migrated to `getConfiguredCredentialValue`.

If the intent is backward-compat double-checking, that is fine and working as designed. However, if the intent is "use only the new path once the method is defined", the check should use a ternary instead:

```ts
const configuredCredential = provider.getConfiguredCredentialValue
  ? provider.getConfiguredCredentialValue(cfg)
  : provider.getCredentialValue(search);
```

A comment clarifying the intended dual-path behaviour would prevent future confusion regardless.

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +59 to +107
it.each([
{
name: "gemini plugin config",
cfg: {
...baseCfg,
plugins: {
entries: {
google: { enabled: true, config: { webSearch: { apiKey: "AIza-test" } } },
},
},
} satisfies OpenClawConfig,
env: {},
},
{
name: "gemini env key",
cfg: baseCfg,
env: { GEMINI_API_KEY: "AIza-test" },
},
{
name: "grok env key",
cfg: baseCfg,
env: { XAI_API_KEY: "xai-test" },
},
{
name: "kimi env key",
cfg: baseCfg,
env: { KIMI_API_KEY: "sk-kimi-test" },
},
{
name: "moonshot env key",
cfg: baseCfg,
env: { MOONSHOT_API_KEY: "sk-moonshot-test" },
},
{
name: "openrouter env fallback",
cfg: baseCfg,
env: { OPENROUTER_API_KEY: "sk-or-v1-test" },
},
])("$name", ({ cfg, env }) => {
const [finding] = collectSmallModelRiskFindings({
cfg,
env,
});

expect(finding?.checkId).toBe("models.small_params");
expect(finding?.severity).toBe("critical");
expect(finding?.detail).toContain("web_search");
});
});
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Missing regression tests for Brave and Perplexity

The PR replaces the previously hardcoded checks for BRAVE_API_KEY and PERPLEXITY_API_KEY with the generic resolveBundledPluginWebSearchProviders mechanism, but no regression test cases cover these two providers in the new collectSmallModelRiskFindings suite.

If the Brave or Perplexity provider's envVars array is ever accidentally modified (or the provider's plugin ID changes), the regression would be silent — neither the old hardcoded path nor any new test would catch it. Consider adding equivalent it.each entries for BRAVE_API_KEY and PERPLEXITY_API_KEY to match the pattern used for the six new providers.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/security/audit-extra.sync.test.ts
Line: 59-107

Comment:
**Missing regression tests for Brave and Perplexity**

The PR replaces the previously hardcoded checks for `BRAVE_API_KEY` and `PERPLEXITY_API_KEY` with the generic `resolveBundledPluginWebSearchProviders` mechanism, but no regression test cases cover these two providers in the new `collectSmallModelRiskFindings` suite.

If the Brave or Perplexity provider's `envVars` array is ever accidentally modified (or the provider's plugin ID changes), the regression would be silent — neither the old hardcoded path nor any new test would catch it. Consider adding equivalent `it.each` entries for `BRAVE_API_KEY` and `PERPLEXITY_API_KEY` to match the pattern used for the six new providers.

How can I resolve this? If you propose a fix, please make it concise.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 70d016675a

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/security/audit-extra.sync.ts Outdated
if (typeof value === "string") {
return value.trim().length > 0;
}
return value !== undefined && value !== null;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Validate credential shape before treating it as configured

hasConfiguredCredentialValue currently treats any non-null/undefined non-string as a valid credential. That makes values like false, 0, or unresolved secret-ref objects count as “configured,” so hasWebSearchKey can report web_search exposure even when no usable API key exists at runtime. This creates false critical models.small_params findings for malformed or unresolved credentials; the check should mirror runtime credential normalization (non-empty string or resolvable secret input) instead of accepting arbitrary objects/primitives.

Useful? React with 👍 / 👎.

hasWebSearchKey() was hardcoded to only check Brave and Perplexity
credentials. Replace with provider-aware check using
resolveBundledPluginWebSearchProviders() so Gemini, Grok/XAI, Kimi,
Moonshot, and OpenRouter credentials are recognized by the audit.

Add focused regression tests for each provider.

Fixes openclaw#34509
@hydro13 hydro13 force-pushed the fix/web-search-audit-missing-providers branch from 70d0166 to 07b57da Compare March 28, 2026 17:48
@openclaw-barnacle openclaw-barnacle Bot added the scripts Repository scripts label Mar 28, 2026
@hydro13 hydro13 merged commit 31112d5 into openclaw:main Mar 28, 2026
36 checks passed
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 07b57daaa6

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

return Boolean(
search?.apiKey || search?.perplexity?.apiKey || env.BRAVE_API_KEY || env.PERPLEXITY_API_KEY,
);
return hasBundledWebSearchCredential({ config: cfg, env });
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Cache web-search key detection outside the model loop

hasWebSearchKey now calls hasBundledWebSearchCredential, which resolves bundled providers through plugin-resolution paths instead of a constant-time field/env check. collectSmallModelRiskFindings invokes this for every small model, so a config with multiple small-model entries repeatedly re-runs bundled provider resolution work (including manifest/runtime registry traversal), turning this audit path into avoidable synchronous overhead; computing this once per audit run (or memoizing in hasWebSearchKey) would keep the check cheap and prevent latency spikes in larger configs.

Useful? React with 👍 / 👎.

hydro13 added a commit that referenced this pull request Mar 28, 2026
alexcode-cc pushed a commit to alexcode-cc/clawdbot that referenced this pull request Mar 30, 2026
…law#56540)

hasWebSearchKey() was hardcoded to only check Brave and Perplexity
credentials. Replace with provider-aware check using
resolveBundledPluginWebSearchProviders() so Gemini, Grok/XAI, Kimi,
Moonshot, and OpenRouter credentials are recognized by the audit.

Add focused regression tests for each provider.

Fixes openclaw#34509
alexjiang1 pushed a commit to alexjiang1/openclaw that referenced this pull request Mar 31, 2026
…law#56540)

hasWebSearchKey() was hardcoded to only check Brave and Perplexity
credentials. Replace with provider-aware check using
resolveBundledPluginWebSearchProviders() so Gemini, Grok/XAI, Kimi,
Moonshot, and OpenRouter credentials are recognized by the audit.

Add focused regression tests for each provider.

Fixes openclaw#34509
pgondhi987 pushed a commit to pgondhi987/openclaw that referenced this pull request Mar 31, 2026
…law#56540)

hasWebSearchKey() was hardcoded to only check Brave and Perplexity
credentials. Replace with provider-aware check using
resolveBundledPluginWebSearchProviders() so Gemini, Grok/XAI, Kimi,
Moonshot, and OpenRouter credentials are recognized by the audit.

Add focused regression tests for each provider.

Fixes openclaw#34509
lovewanwan pushed a commit to lovewanwan/openclaw that referenced this pull request Apr 28, 2026
…law#56540)

hasWebSearchKey() was hardcoded to only check Brave and Perplexity
credentials. Replace with provider-aware check using
resolveBundledPluginWebSearchProviders() so Gemini, Grok/XAI, Kimi,
Moonshot, and OpenRouter credentials are recognized by the audit.

Add focused regression tests for each provider.

Fixes openclaw#34509
Tardisyuan pushed a commit to Tardisyuan/openclaw that referenced this pull request Apr 30, 2026
…law#56540)

hasWebSearchKey() was hardcoded to only check Brave and Perplexity
credentials. Replace with provider-aware check using
resolveBundledPluginWebSearchProviders() so Gemini, Grok/XAI, Kimi,
Moonshot, and OpenRouter credentials are recognized by the audit.

Add focused regression tests for each provider.

Fixes openclaw#34509
ogt-redknie pushed a commit to ogt-redknie/OPENX that referenced this pull request May 2, 2026
…law#56540)

hasWebSearchKey() was hardcoded to only check Brave and Perplexity
credentials. Replace with provider-aware check using
resolveBundledPluginWebSearchProviders() so Gemini, Grok/XAI, Kimi,
Moonshot, and OpenRouter credentials are recognized by the audit.

Add focused regression tests for each provider.

Fixes openclaw#34509
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request May 9, 2026
…law#56540)

hasWebSearchKey() was hardcoded to only check Brave and Perplexity
credentials. Replace with provider-aware check using
resolveBundledPluginWebSearchProviders() so Gemini, Grok/XAI, Kimi,
Moonshot, and OpenRouter credentials are recognized by the audit.

Add focused regression tests for each provider.

Fixes openclaw#34509
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

maintainer Maintainer-authored PR scripts Repository scripts size: S

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Web search key audit misses Gemini/Grok/Kimi providers

1 participant