Skip to content

fix(secrets): avoid re-materializing plaintext apiKey in agent models.json#28393

Closed
kelvinCB wants to merge 3 commits intoopenclaw:mainfrom
kelvinCB:fix/28359-no-plaintext-models-runtime
Closed

fix(secrets): avoid re-materializing plaintext apiKey in agent models.json#28393
kelvinCB wants to merge 3 commits intoopenclaw:mainfrom
kelvinCB:fix/28359-no-plaintext-models-runtime

Conversation

@kelvinCB
Copy link
Copy Markdown
Contributor

Summary

Fixes #28359.

When runtime secrets are active, loadConfig() may return a resolved runtime snapshot with plaintext provider keys. ensureOpenClawModelsJson() used that snapshot directly, which could persist plaintext API keys into ~/.openclaw/agents/*/agent/models.json.

What changed

  • Added getRuntimeConfigSourceSnapshot() export from config IO.
  • Updated ensureOpenClawModelsJson() to prefer the runtime source snapshot (pre-resolution config) when choosing explicit model providers.
    • Falls back to regular config when source snapshot is unavailable.
  • Added regression test:
    • sets a runtime resolved config containing plaintext apiKey
    • sets runtime source config containing SecretRef
    • verifies generated models.json keeps the SecretRef and does not persist plaintext.

Why this is safe

  • Behavior only changes when a runtime source snapshot exists.
  • Existing merge/normalize flows remain unchanged.
  • It narrows secret exposure on disk while preserving provider config semantics.

Validation

  • Ran: pnpm test src/agents/models-config.skips-writing-models-json-no-env-token.test.ts
  • Result: pass (5 tests)

@openclaw-barnacle openclaw-barnacle bot added agents Agent runtime and tooling size: S labels Feb 27, 2026
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Feb 27, 2026

Greptile Summary

Properly fixes the security issue where plaintext API keys could be persisted to ~/.openclaw/agents/*/agent/models.json when runtime secrets are active. The fix prefers the runtime source snapshot (containing SecretRef) over the resolved config (containing plaintext values) when writing provider configurations to disk.

Key Changes

  • Added getRuntimeConfigSourceSnapshot() export to access pre-resolution config
  • Updated ensureOpenClawModelsJson() to use source snapshot when available, falling back to resolved config only when source snapshot is unavailable
  • Added regression test validating that SecretRef is preserved and plaintext is not persisted

Technical Details

The solution correctly uses a fallback chain: sourceCfg?.models?.providers ?? cfg.models?.providers ?? {}, ensuring backwards compatibility when no runtime source snapshot exists. The test properly validates the fix using setRuntimeConfigSnapshot() with both resolved and source configs.

Note on Merge Mode

When mode is "merge" and an existing models.json contains string-based API keys, those will be preserved per the existing merge logic (lines 169-174 in models-config.ts). This is documented as intentional behavior to preserve user customizations.

Confidence Score: 4/5

  • This PR is safe to merge with minimal risk - it addresses a security issue without breaking existing functionality
  • The fix is well-implemented with proper fallback logic and test coverage. Score of 4 reflects high confidence with one documented limitation: merge mode preserves existing plaintext keys from disk, which is intentional per the PR description stating "Existing merge/normalize flows remain unchanged"
  • No files require special attention - all changes are straightforward and well-tested

Last reviewed commit: 3c7a7ec

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: 3c7a7eca9f

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

// with plaintext apiKey values. Prefer the source snapshot (with SecretRef inputs)
// when available so models.json does not persist resolved secrets to disk.
const sourceCfg = getRuntimeConfigSourceSnapshot();
const explicitProviders = (sourceCfg?.models?.providers ?? cfg.models?.providers ?? {}) as Record<
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Respect passed config when selecting explicit model providers

When a runtime snapshot is active, this now always prefers getRuntimeConfigSourceSnapshot() over the config argument, so callers that pass an explicit config object can no longer control models.providers. In gateway flows where ensureOpenClawModelsJson() is called with request-specific config (for example, code paths that pass params.cfg/effectiveCfg), provider additions or overrides in that config are silently ignored and models.json is generated from the global source snapshot instead, which can lead to missing/incorrect registry entries for that run.

Useful? React with 👍 / 👎.

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: 49cdf83073

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +118 to +119
const sourceCfg = getRuntimeConfigSourceSnapshot();
const explicitProviders = sourceCfg?.models?.providers ?? cfg.models?.providers ?? {};
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Respect passed config before source snapshot providers

ensureOpenClawModelsJson() now always reads explicitProviders from getRuntimeConfigSourceSnapshot() when a runtime snapshot exists, even when a caller explicitly passes a config argument; this changes precedence and can silently drop per-call provider overrides/additions, producing incorrect models.json contents and model resolution failures in runtime-secret sessions. Fresh evidence: this function is called with caller-supplied configs in production paths such as runImagePrompt (src/agents/tools/image-tool.ts), describeImageWithModel (src/media-understanding/providers/image.ts), and runEmbeddedPiAgent (src/agents/pi-embedded-runner/run.ts).

Useful? React with 👍 / 👎.

@openclaw-barnacle
Copy link
Copy Markdown

This pull request has been automatically marked as stale due to inactivity.
Please add updates or it will be closed.

@joshavant
Copy link
Copy Markdown
Contributor

Thanks for the work on this PR and for contributing to this fix path.

Closing this as superseded by #38955 (merged), which integrated the relevant fixes from this thread and completed the broader hardening scope for the secrets/models persistence cluster.

The merged work now covers source-snapshot persistence, marker/provenance auth handling, audit/apply expansion (including models.json/header residues), and write-mode hardening/tests/docs in one end-to-end fix.

Appreciate the contribution here. If there’s a remaining unique change from this PR that should still land, please call it out in a follow-up issue and reference #38955.

@joshavant joshavant closed this Mar 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agents Agent runtime and tooling size: S stale Marked as stale due to inactivity

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: SecretRef keys are re-materialized as plaintext in ~/.openclaw/agents/*/agent/models.json after runtime load

2 participants