fix: mark env-ref providers as SecretRef-managed to prevent plaintext persistence (#42355)#42426
Conversation
- Extract path from image content blocks in tool results - This fixes Feishu image delivery when using the read tool - Previously, media paths were only extracted from MEDIA: tokens in text content, but read tool returns paths in image blocks Fixes issue where read tool image results lost media before final outbound payload assembly in Feishu channel. Closes openclaw#41744
- Extract MEDIA: tokens even when wrapped in code blocks - LLMs frequently format paths in code fences - Previously these were silently ignored causing delivery failures - Now tokens are extracted and delivered correctly Fixes openclaw#41966
… persistence - Add env-ref source to SecretRef-managed detection - Prevents API keys from being written as plaintext in models.json - Fixes heartbeat-triggered agent activation path - Ensures consistent SecretRef behavior across all activation paths Fixes openclaw#42355
|
Closing this PR because the author has more than 10 active PRs in this repo. Please reduce the active PR queue and reopen or resubmit once it is back under the limit. You can close your own PRs to get back under the limit. |
Greptile SummaryThis PR attempts to fix a security issue where provider API keys from environment variables were being persisted as plaintext in Key issues:
Confidence Score: 0/5
|
| const isSecretRefManaged = | ||
| (profileApiKey && profileApiKey.source !== "plaintext") || | ||
| (fromEnv && profileApiKey?.source === "env-ref"); |
There was a problem hiding this comment.
Incomplete security fix — env-ref case not marked when profileApiKey is undefined
The PR description explicitly states: "when API key is resolved from environment variable, profileApiKey may be undefined or have source env-ref". However, the new second clause only marks the provider as SecretRef-managed when profileApiKey?.source === "env-ref". If profileApiKey is undefined (the common env-var-only path), profileApiKey?.source evaluates to undefined, which does not equal "env-ref", so the whole clause is false:
fromEnvis truthy (an env var name string)profileApiKeyisundefinedfromEnv && profileApiKey?.source === "env-ref"evaluates totrue && false→false
The provider is therefore not added to secretRefManagedProviders, leaving the original plaintext-persistence bug intact for the most common env-var-only scenario.
The fix should mark as SecretRef-managed whenever fromEnv resolves to a value, regardless of whether profileApiKey is present:
| const isSecretRefManaged = | |
| (profileApiKey && profileApiKey.source !== "plaintext") || | |
| (fromEnv && profileApiKey?.source === "env-ref"); | |
| const isSecretRefManaged = | |
| (profileApiKey && profileApiKey.source !== "plaintext") || | |
| Boolean(fromEnv); |
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/agents/models-config.providers.ts
Line: 392-394
Comment:
**Incomplete security fix — env-ref case not marked when `profileApiKey` is `undefined`**
The PR description explicitly states: _"when API key is resolved from environment variable, `profileApiKey` may be **undefined** or have source `env-ref`"_. However, the new second clause only marks the provider as SecretRef-managed when `profileApiKey?.source === "env-ref"`. If `profileApiKey` is `undefined` (the common env-var-only path), `profileApiKey?.source` evaluates to `undefined`, which does **not** equal `"env-ref"`, so the whole clause is `false`:
- `fromEnv` is truthy (an env var name string)
- `profileApiKey` is `undefined`
- `fromEnv && profileApiKey?.source === "env-ref"` evaluates to `true && false` → `false`
The provider is therefore **not** added to `secretRefManagedProviders`, leaving the original plaintext-persistence bug intact for the most common env-var-only scenario.
The fix should mark as SecretRef-managed whenever `fromEnv` resolves to a value, regardless of whether `profileApiKey` is present:
```suggestion
const isSecretRefManaged =
(profileApiKey && profileApiKey.source !== "plaintext") ||
Boolean(fromEnv);
```
How can I resolve this? If you propose a fix, please make it concise.There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 124d1d8327
ℹ️ 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".
| (profileApiKey && profileApiKey.source !== "plaintext") || | ||
| (fromEnv && profileApiKey?.source === "env-ref"); |
There was a problem hiding this comment.
Mark env-only API keys as secret-managed
This condition still misses the env-only case the patch is trying to fix. When a provider key comes from fromEnv but there is no auth profile, profileApiKey is undefined, so (fromEnv && profileApiKey?.source === "env-ref") is false and the provider is not added to secretRefManagedProviders; because the first clause already covers all non-plaintext profile sources, the new second clause is effectively redundant. In heartbeat-triggered normalization, that leaves env-resolved keys eligible to be persisted as plaintext in models.json.
Useful? React with 👍 / 👎.
| foundMediaInFence = true; | ||
| } | ||
|
|
||
| const matches = Array.from(line.matchAll(MEDIA_TOKEN_RE)); |
There was a problem hiding this comment.
Remove duplicate
matches declaration
The same block declares const matches twice, which is a duplicate lexical declaration in one scope. This causes a parse/type-check failure (Identifier 'matches' has already been declared) before runtime, so consumers of splitMediaFromOutput cannot load this module and reply/tool media parsing paths break.
Useful? React with 👍 / 👎.
|
Thanks @uninhibite-scholar for the contribution and for moving this forward. This work is now covered by the merged superseding PR #42554: Appreciate the help here. |
Summary
This PR fixes a security issue where custom provider API keys configured via environment variables were being persisted as plaintext in agent-local
models.jsonduring heartbeat-triggered agent activation.Problem
The
secretRefManagedProvidersset was only populated when:But it missed the case where:
This caused:
Root Cause
In
src/agents/models-config.providers.ts, the logic only checkedprofileApiKey.source !== "plaintext", but when API key is resolved from environment variable,profileApiKeymay be undefined or have sourceenv-ref.Solution
Updated the SecretRef-managed detection to include env-ref sources:
Changes
Impact
Related
Closes #42355