perf: avoid broad registry enumeration for default models list#70883
perf: avoid broad registry enumeration for default models list#70883shakkernerd merged 3 commits intomainfrom
Conversation
🔒 Aisle Security AnalysisWe found 1 potential security issue(s) in this PR:
1. 🟡 External control of agent directory path leads to arbitrary filesystem reads during `models list`
Description
Vulnerable code (newly introduced path usage): const agentDir = resolveOpenClawAgentDir();
const authStorage = discoverAuthStorage(agentDir, { readOnly: true });
const registry = discoverModels(authStorage, agentDir, {
providerFilter: opts?.providerFilter,
});Related path source: const override = env.OPENCLAW_AGENT_DIR?.trim() || env.PI_CODING_AGENT_DIR?.trim();
if (override) {
return resolveUserPath(override, env);
}RecommendationConstrain and harden agent directory resolution when used for filesystem access:
Example (conceptual): import fs from "node:fs";
import path from "node:path";
import { resolveStateDir } from "../config/paths.js";
function resolveSafeAgentDir(env: NodeJS.ProcessEnv = process.env) {
const raw = resolveOpenClawAgentDir(env);
const real = fs.realpathSync(raw);
const allowedRoot = fs.realpathSync(path.join(resolveStateDir(env), "agents"));
if (!real.startsWith(allowedRoot + path.sep)) {
throw new Error("Refusing to load agent data outside state directory");
}
return real;
}Also consider rejecting non-directory paths and checking ownership/permissions to reduce symlink-based redirection risks. Analyzed PR: #70883 at commit Last updated on: 2026-04-24T02:29:25Z |
Greptile SummaryThis PR narrows the default Confidence Score: 5/5Safe to merge — the narrowed path is logically consistent with the broad path for the default case, all three key sets (discoveredKeys, availableKeys, registry) are populated correctly, and the test suite was updated to cover the new behavior. No P0 or P1 findings. The semantics of discoveredKeys and availableKeys are preserved: loadConfiguredListModelRegistry builds them using the same registry.find() call that appendConfiguredRows uses, so allowProviderAvailabilityFallback is always consistent. The if (!registry) guard is now dead code for the non-–all path but is a harmless safety net. No files require special attention. Reviews (1): Last reviewed commit: "test: cover default models list registry..." | Re-trigger Greptile |
84fa30f to
37b1e67
Compare
|
Landed via temp rebase onto current main.
Thanks @shakkernerd! |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 84fa30f038
ℹ️ 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".
| for (const entry of entries) { | ||
| const model = findConfiguredRegistryModel({ registry, entry, cfg }); |
There was a problem hiding this comment.
Skip non-filtered entries before registry find calls
When models list runs in configured mode with --provider, this loop still calls registry.find(...) for every configured entry before provider filtering happens later in appendConfiguredRows. That means a lookup failure from an unrelated provider can now abort the command even though that provider would not appear in output. Before this change, filtered-out entries were skipped before any per-entry find call, so --provider was insulated from other configured providers' lookup failures.
Useful? React with 👍 / 👎.
What changed
Default
openclaw models listnow resolves configured/default model rows without enumerating the full model registry.Before this change, the default list path still paid for broad registry work even though it only needed configured rows. That meant
models listcould spend several seconds in all-model discovery before printing a single configured/default model.This PR keeps the broad registry path for
models list --all, but narrows the default path to:registry.find()registry.hasConfiguredAuth(model)Before
Measured against
origin/mainafter build:Warm runs:
9.31s8.24sLocal output:
{ "count": 1, "models": [ { "key": "openai/gpt-5.5" } ] }After
Measured on this branch after build:
Warm runs:
4.15s2.97sLocal output remains the same configured row:
{ "count": 1, "models": [ { "key": "openai/gpt-5.5" } ] }Behavior notes
models list --allstill uses the broad registry path.models liststill hydrates configured rows through the registry.Verification
pnpm check:changednode openclaw.mjs models list --json