#14836: fix: strip apiKey from models.json to prevent credential exposure
agents
stale
size: XS
Cluster:
Model Authentication Enhancements
## Summary
When using environment variable syntax `${VAR}` for `apiKey` in `openclaw.json`, OpenClaw resolves it to plaintext at runtime and writes the resolved value to `~/.openclaw/agents/main/agent/models.json`, exposing credentials.
## Fix
Strip resolved `apiKey` values before writing to `models.json`, but preserve env var names (after `normalizeProviders` converts `${VAR}` → `VAR`):
```typescript
const sanitizedProviders: Record<string, ProviderConfig> = {};
for (const [key, provider] of Object.entries(normalizedProviders ?? {})) {
const { apiKey, ...rest } = provider;
// Keep apiKey only if it looks like an env var name (e.g., "AWS_BEARER_TOKEN_BEDROCK")
if (apiKey && typeof apiKey === "string" && /^[A-Z][A-Z0-9_]*$/.test(apiKey)) {
sanitizedProviders[key] = { ...rest, apiKey };
} else {
sanitizedProviders[key] = rest;
}
}
```
This ensures:
1. Plaintext resolved keys (e.g., `ABSK...`, `sk-...`) are stripped
2. Env var names (e.g., `AWS_BEARER_TOKEN_BEDROCK`) are preserved for registry compatibility
3. Existing tests continue to pass
## Testing
Tested locally — plaintext credentials no longer appear in `~/.openclaw/agents/main/agent/models.json`.
## Related Issues
- #11202 — apiKey in model catalog injected into LLM prompt context
- #14411 — Credential broker proposal
<!-- greptile_comment -->
<h2>Greptile Overview</h2>
<h3>Greptile Summary</h3>
This change updates `ensureOpenClawModelsJson` to sanitize provider configs before persisting `models.json`, stripping resolved plaintext `apiKey` values to avoid credential exposure, while preserving env-var *names* (matching `^[A-Z][A-Z0-9_]*$`) so runtime can still resolve secrets from the environment.
The write path remains the same (normalize → stringify → write if changed), but now writes `{ providers: sanitizedProviders }` instead of the raw normalized providers, reducing the chance of leaking secrets into the agent cache and downstream context.
<h3>Confidence Score: 5/5</h3>
- This PR is safe to merge with minimal risk.
- The change is localized to the models.json persistence layer and preserves existing behavior for env-var-name apiKey entries while preventing plaintext secret persistence; auth resolution already supports env-based and profile-based credentials.
- src/agents/models-config.ts
<sub>Last reviewed commit: aac88a4</sub>
<!-- greptile_other_comments_section -->
<!-- /greptile_comment -->
Most Similar PRs
#15756: [Security]: strip provider apiKey from models.json before prompt se...
by SecBear · 2026-02-13
87.0%
#12059: feat(agents): Add Azure AI Foundry credential support
by lisanyambere · 2026-02-08
81.2%
#9739: #9291 fix(models): preserve existing models in models.json when mer...
by ximzzzzz · 2026-02-05
78.5%
#13650: feat(agents): support env var overrides for default provider and model
by xrehpicx · 2026-02-10
77.4%
#16388: Fix: Show model selector during onboarding for all auth choices
by saurav470 · 2026-02-14
75.7%
#9163: Fix: Save Anthropic setup token to config file
by vishaltandale00 · 2026-02-04
75.7%
#21884: feat(models): auth improvements — status command, heuristics, multi...
by kckylechen1 · 2026-02-20
75.2%
#12839: feat(vault): add vault proxy mode for credential isolation
by sfo2001 · 2026-02-09
74.5%
#22069: fix(agents): add provider-specific hints for local model auth errors
by pierreeurope · 2026-02-20
73.9%
#12958: fix: block agent read access to sensitive config and credential files
by 000boil · 2026-02-10
73.7%