fix(models): normalize provider runtime selection#71259
Conversation
🔒 Aisle Security AnalysisWe found 2 potential security issue(s) in this PR:
1. 🟠 User-controlled runtime override can force CLI backend execution (host credentials) for canonical providers
Description
Impact:
Vulnerable code (routing decision): const agentRuntimeOverride = normalizeOptionalString(
params.getActiveSessionEntry()?.agentRuntimeOverride,
);
const cliExecutionProvider =
resolveCliRuntimeExecutionProvider({
provider,
cfg: runtimeConfig,
agentId: params.followupRun.run.agentId,
runtimeOverride: agentRuntimeOverride,
}) ?? provider;
if (isCliProvider(cliExecutionProvider, runtimeConfig)) {
...
const result = await runCliAgent({ provider: cliExecutionProvider, ... });
}RecommendationTreat switching to CLI backends as a privileged operation. Suggested defenses (combine as appropriate):
const wantsCli = isCliProvider(cliExecutionProvider, runtimeConfig);
if (wantsCli && !params.followupRun.run.senderIsOwner) {
throw new Error("CLI backends are restricted to owners/admins");
}
2. 🟡 Runtime override can pin unregistered harness and disable fallback, causing session-level DoS
DescriptionThe session-scoped Key points:
Vulnerable code (pinned runtime used as harness id): ...(agentRuntimeOverride && agentRuntimeOverride !== "auto" && agentRuntimeOverride !== "default"
? { agentHarnessId: agentRuntimeOverride }
: {}),Pinned-policy behavior that disables fallback: if (!agentHarnessId?.trim()) return undefined;
const runtime = normalizeEmbeddedAgentRuntime(agentHarnessId);
if (runtime === "auto") return undefined;
return { runtime, fallback: "none" };Throw on unregistered pinned harness: if (policy.fallback === "none") {
throw new Error(
`Requested agent harness "${runtime}" is not registered and PI fallback is disabled.`,
);
}RecommendationDo not treat user/session runtime overrides as a strict pin with Options:
Example (safe pinned policy): function resolvePinnedAgentHarnessPolicy(agentHarnessId?: string): AgentHarnessPolicy | undefined {
const raw = agentHarnessId?.trim();
if (!raw) return undefined;
const runtime = normalizeEmbeddedAgentRuntime(raw);
if (runtime === "auto") return undefined;
// Allow PI fallback for session overrides to avoid bricking sessions when a runtime is unavailable
return { runtime, fallback: "pi" };
}Additionally, consider generating Analyzed PR: #71259 at commit Last updated on: 2026-04-24T23:57:36Z |
Greptile SummaryThis PR reverses the codex-only PR #71193 and generalises the model/runtime split to Anthropic Claude CLI and Google Gemini CLI as well: canonical Confidence Score: 5/5Safe to merge; both remaining findings are minor defensive/style issues that don't affect current runtime behaviour. The core migration logic, CLI routing, session-override persistence, and Discord picker changes are all well-tested. The only findings are a missing extensions/anthropic/register.runtime.ts (defensive coding), src/config/schema.labels.ts (duplicate label) Prompt To Fix All With AIThis is a comment left during a code review.
Path: extensions/anthropic/register.runtime.ts
Line: 81-83
Comment:
`CLAUDE_CLI_CANONICAL_DEFAULT_MODEL_REF` slices `CLAUDE_CLI_DEFAULT_MODEL_REF` without first verifying it starts with `${CLAUDE_CLI_BACKEND_ID}/`, whereas the parallel `CLAUDE_CLI_CANONICAL_ALLOWLIST_REFS` computation directly above it correctly guards with `.startsWith(...)`. If `CLAUDE_CLI_DEFAULT_MODEL_REF` were ever changed to a canonical `anthropic/...` form, the slice would produce `anthropic/ic/claude-opus-4-7` or similar garbage silently.
```suggestion
const CLAUDE_CLI_CANONICAL_DEFAULT_MODEL_REF = CLAUDE_CLI_DEFAULT_MODEL_REF.startsWith(
`${CLAUDE_CLI_BACKEND_ID}/`,
)
? `anthropic/${CLAUDE_CLI_DEFAULT_MODEL_REF.slice(CLAUDE_CLI_BACKEND_ID.length + 1)}`
: CLAUDE_CLI_DEFAULT_MODEL_REF;
```
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/config/schema.labels.ts
Line: 75-76
Comment:
Both `agents.defaults.embeddedHarness` (the container object) and `agents.defaults.embeddedHarness.runtime` (the leaf field) are now labeled identically as `"Default Agent Runtime"`. In any flat config UI or help output that lists both entries, the two are indistinguishable. The old names ("Default Embedded Harness" / "Default Embedded Harness Runtime") were distinct; something like `"Default Agent Harness"` for the parent would avoid the collision.
```suggestion
"agents.defaults.embeddedHarness": "Default Agent Harness",
"agents.defaults.embeddedHarness.runtime": "Default Agent Runtime",
```
How can I resolve this? If you propose a fix, please make it concise.Reviews (1): Last reviewed commit: "fix(models): reverse codex-only runtime ..." | Re-trigger Greptile |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b1507f267c
ℹ️ 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".
996b280 to
ef64be3
Compare
1545408 to
72e6a72
Compare
|
Codex review: do not merge yet. I agree with the direction of splitting canonical provider/model selection from runtime selection, but the current PR still has a real trust-boundary bug around Blocking finding:
This is not just cosmetic. Runtime selection changes execution backend semantics. A chat user who is allowed to send model directives should not automatically be allowed to persist arbitrary runtime/harness overrides. The fix should validate the requested runtime before storing it, probably against the same runtime choices exposed to the picker plus configured agent policy, and reject/ignore unknown or unauthorized values. The execution path should also defensively refuse an unrecognized persisted override instead of treating it as a pinned harness id. Smaller cleanup findings, non-blocking after the runtime gate is fixed:
Also current checks are still red ( |
72e6a72 to
f5c687e
Compare
* fix(models): normalize provider runtime selection * fix(models): reverse codex-only runtime migration * fix(models): default runtime selection to pi * fix(status): label model runtime clearly * fix(status): align pi runtime label * fix(plugins): align tool result middleware runtime naming * fix(models): validate runtime overrides
* fix(models): normalize provider runtime selection * fix(models): reverse codex-only runtime migration * fix(models): default runtime selection to pi * fix(status): label model runtime clearly * fix(status): align pi runtime label * fix(plugins): align tool result middleware runtime naming * fix(models): validate runtime overrides
* fix(models): normalize provider runtime selection * fix(models): reverse codex-only runtime migration * fix(models): default runtime selection to pi * fix(status): label model runtime clearly * fix(status): align pi runtime label * fix(plugins): align tool result middleware runtime naming * fix(models): validate runtime overrides
* fix(models): normalize provider runtime selection * fix(models): reverse codex-only runtime migration * fix(models): default runtime selection to pi * fix(status): label model runtime clearly * fix(status): align pi runtime label * fix(plugins): align tool result middleware runtime naming * fix(models): validate runtime overrides
* fix(models): normalize provider runtime selection * fix(models): reverse codex-only runtime migration * fix(models): default runtime selection to pi * fix(status): label model runtime clearly * fix(status): align pi runtime label * fix(plugins): align tool result middleware runtime naming * fix(models): validate runtime overrides
* fix(models): normalize provider runtime selection * fix(models): reverse codex-only runtime migration * fix(models): default runtime selection to pi * fix(status): label model runtime clearly * fix(status): align pi runtime label * fix(plugins): align tool result middleware runtime naming * fix(models): validate runtime overrides
* fix(models): normalize provider runtime selection * fix(models): reverse codex-only runtime migration * fix(models): default runtime selection to pi * fix(status): label model runtime clearly * fix(status): align pi runtime label * fix(plugins): align tool result middleware runtime naming * fix(models): validate runtime overrides
Summary
codex/*,codex-cli/*,claude-cli/*,google-gemini-cli/*) into canonical provider/model refs plus explicit runtime selectionruntime: "auto"is now explicit opt-in for plugin harness auto-selectionOpenClaw Pi Defaultas the PI/default runtime option/statusto report sandbox path asExecution:and model runtime asRuntime: OpenClaw Pi Default,Runtime: OpenAI Codex, or the selected CLI/ACP backendruntime/runtimes, canonicalcodexid) while accepting the oldcodex-app-serverharness id as a compatibility alias/modelsprovider surfaces, while exposing runtime choices separately for Discord/channel model selectionValidation
OPENCLAW_LOCAL_CHECK=0 pnpm check:changedpassed before the final rebase; after rebasing onto currentorigin/main, the rerun passed typecheck/lint/import/guard lanes and reached extension tests, then hung invitest.extension-linewith no CPU and was stoppedpnpm plugin-sdk:api:genpnpm plugin-sdk:api:checkpnpm config:schema:checkpnpm check:no-conflict-markerstokenjuice wrap --raw -- pnpm check:docsenv OPENCLAW_LOCAL_CHECK=0 pnpm test src/agents/harness/tool-result-middleware.test.ts src/agents/codex-app-server.extensions.test.ts src/agents/pi-embedded-runner.extensions.test.ts extensions/codex/src/app-server/dynamic-tools.test.ts extensions/tokenjuice/index.test.ts extensions/tokenjuice/manifest.test.ts src/plugins/captured-registration.test.ts src/plugins/registry.test.tsenv OPENCLAW_LOCAL_CHECK=0 pnpm test src/auto-reply/reply/commands-status.test.tsenv OPENCLAW_LOCAL_CHECK=0 pnpm test src/auto-reply/status.test.ts src/status/status-message.test.tspnpm test src/agents/harness/selection.test.ts src/commands/doctor-legacy-config.migrations.test.ts src/auto-reply/reply/commands-models.test.ts extensions/discord/src/monitor/model-picker.test.ts extensions/discord/src/monitor/native-command.model-picker.test.ts src/config/schema.base.generated.test.ts src/config/schema.help.quality.test.ts src/agents/command/attempt-execution.cli.test.ts