Fix stale model provider API config recovery#72542
Conversation
🔒 Aisle Security AnalysisWe found 3 potential security issue(s) in this PR:
1. 🟡 Log/CRLF injection via unsanitized model provider ID in startup warning
Description
Vulnerable code: params.log.warn(
`gateway: skipped model provider ${providerId}; configured provider api is invalid. ...`,
);RecommendationSanitize untrusted strings before logging. Use the existing terminal/log sanitizers (e.g. import { sanitizeTerminalText } from "../terminal/safe-text.js";
for (const providerId of providerIds) {
const safeProviderId = sanitizeTerminalText(providerId);
params.log.warn(
`gateway: skipped model provider ${safeProviderId}; configured provider api is invalid. ` +
`Run "openclaw doctor --fix" to repair the config.`,
);
}Optionally, also enforce a safe provider-id character set at schema validation time (e.g., 2. 🟡 Prototype pollution risk when cloning config model providers via object spread
DescriptionThe gateway startup path attempts to recover from invalid If a user-controlled config file contains a provider id key such as This occurs before the pruned config is revalidated ( Vulnerable code: const nextProviders = { ...providers };Impact depends on downstream use of the polluted object, but prototype pollution can lead to unexpected property resolution across the process and, in some cases, security-sensitive logic bypasses. RecommendationAvoid spreading untrusted objects into normal Example fix: import { isBlockedObjectKey } from "../config/prototype-keys.js";
const nextProviders: Record<string, unknown> = Object.create(null);
for (const [key, value] of Object.entries(providers)) {
if (isBlockedObjectKey(key)) continue;
nextProviders[key] = value;
}Alternatively: const nextProviders = Object.assign(Object.create(null), providers);Then perform deletions on 3. 🟡 Prototype pollution risk in legacy config normalizers when copying provider maps via object spread
DescriptionLegacy config normalization copies the If a config contains provider IDs (object keys) like Vulnerable code: const nextProviders: Record<string, unknown> = { ...rawProviders };Similar risk applies when spreading RecommendationWhen cloning config objects that may originate from untrusted/hand-edited files, avoid Use a null-prototype target and skip blocked keys: import { isBlockedObjectKey } from "../../../config/prototype-keys.js";
const nextProviders: Record<string, unknown> = Object.create(null);
for (const [k, v] of Object.entries(rawProviders)) {
if (isBlockedObjectKey(k)) continue;
nextProviders[k] = v;
}
const nextProvider: Record<string, unknown> = Object.create(null);
for (const [k, v] of Object.entries(rawProvider)) {
if (isBlockedObjectKey(k)) continue;
nextProvider[k] = v;
}This prevents Analyzed PR: #72542 at commit Last updated on: 2026-04-27T03:44:04Z |
Greptile SummaryThis PR addresses stale Confidence Score: 4/5Safe to merge; logic is conservative and well-tested, with one minor P2 observation about the allowedValues guard. No P0/P1 issues found. The only finding is a P2 style concern: the allowedValues guard in resolveInvalidModelProviderApiIssueProviderId is effectively a no-op due to the tight coupling between MODEL_APIS and the Zod schema, and it has no test coverage for the non-undefined path. All other logic (normalizer, pruning, re-validation, auth persistence flag) is correct and consistently handles edge cases. src/gateway/server-startup-config.ts — specifically the allowedValues guard at line 77. Prompt To Fix All With AIThis is a comment left during a code review.
Path: src/gateway/server-startup-config.ts
Line: 77-78
Comment:
**`allowedValues` guard can silently bypass the recovery path**
The condition `!issue.allowedValues.every((value) => MODEL_API_VALUES.has(value))` returns `null` (preventing graceful skip) if the validator's `allowedValues` list contains any value not present in `MODEL_API_VALUES`. Because `ModelApiSchema` is defined as `z.enum(MODEL_APIS)` and `MODEL_API_VALUES` is built from the same `MODEL_APIS` constant, the two sets are always identical at compile time. In practice this guard is a no-op today, but if `allowedValues` is ever populated from a different code path (e.g., a version of the validator with newer enum entries loaded at runtime), the recovery silently falls back to last-known-good instead of skipping the provider. The test confirms this risk by omitting `allowedValues` entirely from the mock issues, so the guard has no test coverage. Consider documenting the invariant or removing the guard if it adds no safety value.
How can I resolve this? If you propose a fix, please make it concise.Reviews (1): Last reviewed commit: "docs(changelog): note model provider api..." | Re-trigger Greptile |
fd6e50d to
19d22c9
Compare
5a68116 to
6cecdfa
Compare
Summary
models.providers.*.api = "openai"values toopenai-completionsin doctor/runtime compat.Tests
pnpm test src/commands/doctor-legacy-config.migrations.test.ts src/gateway/server-startup-config.recovery.test.tspnpm check:changedFixes #72477