feat: add /tools runtime availability view#54088
Conversation
🔒 Aisle Security AnalysisWe found 2 potential security issue(s) in this PR:
Vulnerabilities1. 🟡 IDOR / information disclosure in
|
| Property | Value |
|---|---|
| Severity | Medium |
| CWE | CWE-639 |
| Location | src/gateway/server-methods/tools-effective.ts:32-44 |
Description
The new Gateway RPC method tools.effective authorizes requests only by operator scope (it is placed under operator.read) and then returns the runtime-effective tool inventory for whatever session is identified by the caller-supplied sessionKey.
There is no check that the RPC caller is allowed to access that session (no ownership/binding to the websocket connection/client identity). As a result:
- Any client with access to the
operator.readscope can querytools.effectivefor any validsessionKey. - The response reveals operational capabilities for that session (tool list grouped by
core/plugin/channel, pluspluginId/channelIdidentifiers and the effectiveprofile). - The handler returns a distinct error for missing vs existing keys (
unknown session key "..."), which makessessionKeyprobing/enumeration easier.
Vulnerable code:
const loaded = loadSessionEntry(params.sessionKey);
if (!loaded.entry) {
params.respond(
false,
undefined,
errorShape(ErrorCodes.INVALID_REQUEST, `unknown session key "${params.sessionKey}"`),
);
return null;
}
...
resolveEffectiveToolInventory({ sessionKey: params.sessionKey, ... })Recommendation
Bind session access to the authenticated caller, and avoid using a bare sessionKey as the sole authorization factor.
Suggested fixes (choose one or combine):
-
Enforce session authorization: require that the caller is subscribed/authorized for that session, or that the session belongs to the caller’s identity.
-
Tighten scope: move
tools.effectiveto an admin-only scope (e.g.operator.admin) if it is intended for privileged diagnostics. -
Harden error handling: return a generic not-found/unauthorized message for invalid or unauthorized keys to reduce enumeration signal.
Example (sketch):
// After loading session entry:
if (!loaded.entry) {
respond(false, undefined, errorShape(ErrorCodes.NOT_FOUND, "session not found"));
return;
}
// Authorization check (pseudo):
if (!context.sessionAcl.canRead(client, loaded.canonicalKey ?? params.sessionKey)) {
respond(false, undefined, errorShape(ErrorCodes.UNAUTHORIZED, "not authorized"));
return;
}2. 🟡 Denial-of-service/side-effects: tools inventory computation loads plugins and executes tool factories
| Property | Value |
|---|---|
| Severity | Medium |
| CWE | CWE-400 |
| Location | src/agents/tools-effective-inventory.ts:157-184 |
Description
resolveEffectiveToolInventory builds the “effective” tools list by calling createOpenClawCodingTools(...). This call constructs all tools including plugin tools, which triggers plugin discovery/module loading and runs each plugin tool factory.
Impact:
- A caller can repeatedly invoke
/tools(chat command) or the gateway methodtools.effectiveto force:- filesystem scanning + dynamic module loading (
jiti) during plugin loading - execution of plugin tool
factory(context)functions (which may perform arbitrary I/O during construction)
- filesystem scanning + dynamic module loading (
- This is a classic expensive introspection endpoint that can be abused for resource exhaustion (CPU/IO/memory) or unintended side effects, even though no tool is being executed.
- The inventory path also sets
allowGatewaySubagentBinding: true, meaning plugin runtime is initialized in a more permissive mode than necessary for a read-only inventory operation.
Vulnerable code:
const effectiveTools = createOpenClawCodingTools({
...,
allowGatewaySubagentBinding: true,
});Why this is grounded:
createOpenClawCodingToolsincludes plugin tools viaresolvePluginTools, which callsloadOpenClawPlugins(...)and then runsentry.factory(context)for each plugin tool.loadOpenClawPluginsusesfsandjiti(dynamic module loading), which are expensive and potentially unbounded operations to expose in an on-demand inventory endpoint.
Attack surface:
- Gateway:
tools.effectiveendpoint. - Chat:
/toolscommand (authorized senders).
Even if access is limited to “authorized senders”, this still creates a DoS vector from any authorized user/session and makes overall system robustness depend on plugin factory behavior.
Recommendation
Make inventory resolution side-effect free and inexpensive:
-
Do not instantiate tools to list inventory. Instead:
- return a static/core catalog + plugin manifests metadata (names/labels/descriptions) without running factories
- or add a
createOpenClawCodingTools({ mode: 'inventory' })that prevents plugin tool factories from executing and skips heavy initialization.
-
If plugin metadata must be computed dynamically, add:
- caching of the computed inventory per
(agentId, sessionKey, modelProvider, modelId, groupId, accountId, …)with a short TTL - rate limiting / request budgeting on
tools.effectiveand/tools
- caching of the computed inventory per
-
For inventory calls, set least privilege:
- avoid
allowGatewaySubagentBinding: trueunless strictly required.
- avoid
Example approach (sketch):
// inventory-only: never call plugin factories
const tools = listCoreToolDescriptors();
const pluginTools = listPluginToolDescriptorsFromRegistry({ load: 'metadata_only' });
return merge(tools, pluginTools);Analyzed PR: #54088 at commit 38632e7
Last updated on: 2026-03-25T02:16:09Z
Greptile SummaryThis PR replaces the catalog-backed Issues found:
Confidence Score: 4/5
Prompt To Fix All With AIThis is a comment left during a code review.
Path: src/gateway/server-methods/tools-effective.ts
Line: 41-82
Comment:
**`senderIsOwner` not propagated — owner-only tools silently hidden**
`resolveTrustedToolsEffectiveContext` extracts model, channel, and threading context from the session, but never derives `senderIsOwner`. Because `resolveEffectiveToolInventory` receives `senderIsOwner: undefined` (which is treated as falsy), any tools guarded by `ownerOnly: true` (e.g. the `cron` tool) will be excluded from the "Available Right Now" inventory, even though the operator viewing the Control UI panel is the owner. This directly undermines the PR's goal of showing an accurate runtime inventory.
The gateway context is already trusted (gated on `operator_read` scope), so it is safe to derive owner status here. A simple fix is to propagate it from session metadata or default to `true` for operator-authenticated gateway requests:
```ts
return {
cfg: loaded.cfg,
agentId: sessionAgentId,
senderIsOwner: loaded.entry.senderIsOwner ?? true, // operator gateway is owner-trusted
// ... rest of fields
};
```
And then pass it into the `resolveEffectiveToolInventory` call:
```ts
resolveEffectiveToolInventory({
// ... existing fields
senderIsOwner: trustedContext.senderIsOwner,
})
```
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/gateway/protocol/schema/agents-models-skills.ts
Line: 299-313
Comment:
**`rawDescription` missing from `ToolsEffectiveEntrySchema`**
`EffectiveToolInventoryEntry` (the runtime type) includes a `rawDescription` field, and `resolveEffectiveToolInventory` populates it on every entry. The gateway handler responds with the raw return value of that function, so `rawDescription` is present in the wire payload even though it is absent from `ToolsEffectiveEntrySchema` (which declares `additionalProperties: false`).
The inconsistency means: the schema does not describe the actual response shape, the UI TypeScript type (`ToolsEffectiveEntry`) does not expose `rawDescription`, and any future client that wants verbose descriptions via the gateway would need to rely on an undocumented field.
Either add `rawDescription` to the schema (since clients could use it for verbose modes) or strip it from the gateway response before sending. Leaking internal fields through a schema that declares `additionalProperties: false` is confusing for consumers.
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/agents/tools-effective-inventory.ts
Line: 86-107
Comment:
**Doc-block sentinel strings duplicated across files**
The hardcoded sentinel set (`"ACTIONS:"`, `"JOB SCHEMA (FOR ADD ACTION):"`, `"JOB SCHEMA:"`, `"SESSION TARGET OPTIONS:"`, etc.) is defined twice — once here in `isDocBlockStart` and again in `stripVerboseToolDocTail` inside `src/auto-reply/status.ts`. If a new sentinel is added to one place it is easy to miss the other, which would cause the compact summary to strip sections that the verbose formatter still exposes (or vice versa).
Consider extracting both the sentinel set and the detection logic into a shared utility (e.g. `src/agents/tool-description-utils.ts`) and importing it in both locations.
How can I resolve this? If you propose a fix, please make it concise.Reviews (1): Last reviewed commit: "feat: make /tools show what the agent ca..." | Re-trigger Greptile |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 88649340ff
ℹ️ 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".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 2587c2cc70
ℹ️ 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".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 2da6c5b43b
ℹ️ 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".
ce43992 to
88c9044
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ac778cfa1f
ℹ️ 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".
02e93ec to
38632e7
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 38632e7c4e
ℹ️ 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".
| const toolsRequestKey = `${resolvedAgentId}:${state.sessionKey}`; | ||
| if ( | ||
| state.toolsEffectiveResultKey !== toolsRequestKey || | ||
| state.toolsEffectiveError |
There was a problem hiding this comment.
Invalidate effective-tools cache when model changes
The refresh guard keys tools.effective only by agentId + sessionKey, so opening the Tools panel after a model switch in the same session can reuse stale availability data and skip reloading. tools.effective resolves modelProvider/modelId from session state (resolveSessionModelRef) and downstream filtering depends on model compatibility (for example native web-search models change which tools are exposed), so model changes can legitimately change the inventory without changing the session key.
Useful? React with 👍 / 👎.
…nto add-json-schema-to-cli * 'add-json-schema-to-cli' of github.com:kvokka/openclaw: (58 commits) test: make vitest config tests platform-aware fix: refresh available tools when the session model changes (openclaw#54184) fix: prefer freshest transcript session owners test: disable Vitest fs cache on Windows fix: prefer deterministic transcript session keys fix: prefer deterministic session usage targets fix: prefer deterministic session id resume targets feat: add /tools runtime availability view (openclaw#54088) fix: enforce sandbox visibility for session_status ids fix: drop spawned visibility list caps fix: verify exact spawned session visibility fix: enforce spawned session visibility in key resolve fix: resolve exact session ids without fuzzy limits Discord: resolve /think autocomplete from session model (openclaw#49176) chore(agents): normalize pi embedded runner imports feat(gateway): make openai compatibility agent-first test(parallel): force unit-fast batch planning test(browser): stabilize default browser detection mocks fix: prefer current parents in session rows fix: prefer current subagent owners in session rows ...
* test(memory): lock qmd status counts regression * feat: make /tools show what the agent can use right now * fix: sync web ui slash commands with the shared registry * feat: add profile and unavailable counts to /tools * refine: keep /tools focused on available tools * fix: resolve /tools review regressions * fix: honor model compat in /tools inventory * fix: sync generated protocol models for /tools * fix: restore canonical slash command names * fix: avoid ci lint drift in google helper exports * perf: stop computing unused /tools unavailable counts * docs: clarify /tools runtime behavior
* test(memory): lock qmd status counts regression * feat: make /tools show what the agent can use right now * fix: sync web ui slash commands with the shared registry * feat: add profile and unavailable counts to /tools * refine: keep /tools focused on available tools * fix: resolve /tools review regressions * fix: honor model compat in /tools inventory * fix: sync generated protocol models for /tools * fix: restore canonical slash command names * fix: avoid ci lint drift in google helper exports * perf: stop computing unused /tools unavailable counts * docs: clarify /tools runtime behavior
* test(memory): lock qmd status counts regression * feat: make /tools show what the agent can use right now * fix: sync web ui slash commands with the shared registry * feat: add profile and unavailable counts to /tools * refine: keep /tools focused on available tools * fix: resolve /tools review regressions * fix: honor model compat in /tools inventory * fix: sync generated protocol models for /tools * fix: restore canonical slash command names * fix: avoid ci lint drift in google helper exports * perf: stop computing unused /tools unavailable counts * docs: clarify /tools runtime behavior
* test(memory): lock qmd status counts regression * feat: make /tools show what the agent can use right now * fix: sync web ui slash commands with the shared registry * feat: add profile and unavailable counts to /tools * refine: keep /tools focused on available tools * fix: resolve /tools review regressions * fix: honor model compat in /tools inventory * fix: sync generated protocol models for /tools * fix: restore canonical slash command names * fix: avoid ci lint drift in google helper exports * perf: stop computing unused /tools unavailable counts * docs: clarify /tools runtime behavior
* test(memory): lock qmd status counts regression * feat: make /tools show what the agent can use right now * fix: sync web ui slash commands with the shared registry * feat: add profile and unavailable counts to /tools * refine: keep /tools focused on available tools * fix: resolve /tools review regressions * fix: honor model compat in /tools inventory * fix: sync generated protocol models for /tools * fix: restore canonical slash command names * fix: avoid ci lint drift in google helper exports * perf: stop computing unused /tools unavailable counts * docs: clarify /tools runtime behavior
* test(memory): lock qmd status counts regression * feat: make /tools show what the agent can use right now * fix: sync web ui slash commands with the shared registry * feat: add profile and unavailable counts to /tools * refine: keep /tools focused on available tools * fix: resolve /tools review regressions * fix: honor model compat in /tools inventory * fix: sync generated protocol models for /tools * fix: restore canonical slash command names * fix: avoid ci lint drift in google helper exports * perf: stop computing unused /tools unavailable counts * docs: clarify /tools runtime behavior
* test(memory): lock qmd status counts regression * feat: make /tools show what the agent can use right now * fix: sync web ui slash commands with the shared registry * feat: add profile and unavailable counts to /tools * refine: keep /tools focused on available tools * fix: resolve /tools review regressions * fix: honor model compat in /tools inventory * fix: sync generated protocol models for /tools * fix: restore canonical slash command names * fix: avoid ci lint drift in google helper exports * perf: stop computing unused /tools unavailable counts * docs: clarify /tools runtime behavior
* test(memory): lock qmd status counts regression * feat: make /tools show what the agent can use right now * fix: sync web ui slash commands with the shared registry * feat: add profile and unavailable counts to /tools * refine: keep /tools focused on available tools * fix: resolve /tools review regressions * fix: honor model compat in /tools inventory * fix: sync generated protocol models for /tools * fix: restore canonical slash command names * fix: avoid ci lint drift in google helper exports * perf: stop computing unused /tools unavailable counts * docs: clarify /tools runtime behavior
* test(memory): lock qmd status counts regression * feat: make /tools show what the agent can use right now * fix: sync web ui slash commands with the shared registry * feat: add profile and unavailable counts to /tools * refine: keep /tools focused on available tools * fix: resolve /tools review regressions * fix: honor model compat in /tools inventory * fix: sync generated protocol models for /tools * fix: restore canonical slash command names * fix: avoid ci lint drift in google helper exports * perf: stop computing unused /tools unavailable counts * docs: clarify /tools runtime behavior
* test(memory): lock qmd status counts regression * feat: make /tools show what the agent can use right now * fix: sync web ui slash commands with the shared registry * feat: add profile and unavailable counts to /tools * refine: keep /tools focused on available tools * fix: resolve /tools review regressions * fix: honor model compat in /tools inventory * fix: sync generated protocol models for /tools * fix: restore canonical slash command names * fix: avoid ci lint drift in google helper exports * perf: stop computing unused /tools unavailable counts * docs: clarify /tools runtime behavior
* test(memory): lock qmd status counts regression * feat: make /tools show what the agent can use right now * fix: sync web ui slash commands with the shared registry * feat: add profile and unavailable counts to /tools * refine: keep /tools focused on available tools * fix: resolve /tools review regressions * fix: honor model compat in /tools inventory * fix: sync generated protocol models for /tools * fix: restore canonical slash command names * fix: avoid ci lint drift in google helper exports * perf: stop computing unused /tools unavailable counts * docs: clarify /tools runtime behavior
Summary
/toolscould mislead users by reflecting catalog/config guesses instead of the tools the active agent could actually use in the current session, and native command surfaces did not expose the same useful modes as text chat./toolsand the Control UI runtime section to that live inventory, made/toolscompact by default with a more detailed mode, and added a nativemodeargument so Discord/native surfaces can access the same behavior.Change Type (select all)
Scope (select all touched areas)
Linked Issue/PR
Root Cause / Regression History (if applicable)
/toolsand UI runtime surfaces must be session-backed rather than catalog-backed.git blame, prior PR, issue, or refactor if known):/toolsalready flowed through the info-command path, but it intersected config/catalog semantics rather than a server-derived runtime snapshot.Regression Test Plan (if applicable)
src/agents/tools-effective-inventory.test.ts,src/gateway/server-methods/tools-effective.test.ts,src/auto-reply/reply/commands-info.tools.test.ts,src/auto-reply/status.tools.test.ts,ui/src/ui/controllers/agents.test.ts,ui/src/ui/views/agents-panels-tools-skills.browser.test.ts/toolsand the Control UI runtime section should reflect the tools available to the active agent in the active session, preserve plugin/channel ownership metadata, and keep compact vs detailed rendering stable across command surfaces.User-visible / Behavior Changes
/toolsnow answers “what can this agent use right now in this conversation?” instead of showing a config-derived guess./toolsdefaults to a compact inventory and supports a more detailed mode./toolsdetail via a structuredmodeargument.Security Impact (required)
Yes/No) NoYes/No) NoYes/No) NoYes/No) NoYes/No) NoYes, explain risk + mitigation: N/ARepro + Verification
Environment
Steps
/toolsand verify the response matches the tools available to the active agent in that session./toolson Discord/Telegram and verify compact vs detailed behavior.Expected
/toolsand the Control UI runtime section show the tools the current agent can actually use in the active session./toolsmodes as text surfaces.Actual
/toolsoutput and Control UI runtime inventory./tools, and auth/config gating issues were resolved in the dev profile used for manual checks.Evidence
Human Verification (required)
/toolsoutput reflects runtime-effective inventory; Control UI “Available Right Now” section renders session-backed tools; native/toolsregistration appears on Telegram/Discord after running this checkout and removing local auth/config blockers.tsc --noEmitremains blocked by unrelated pre-existing fetch-mock typing failures outside this PR; I did not run a full end-to-end multi-provider regression suite.Review Conversations
If a bot review conversation is addressed by this PR, resolve that conversation yourself. Do not leave bot review conversation cleanup for maintainers.
Compatibility / Migration
Yes/No) YesYes/No) NoYes/No) NoFailure Recovery (if this breaks)
/toolsbehavior and runtime tools panel behavior./Users/thoffman/openclaw2/src/agents/tools-effective-inventory.ts,/Users/thoffman/openclaw2/src/gateway/server-methods/tools-effective.ts,/Users/thoffman/openclaw2/src/auto-reply/reply/commands-info.ts,/Users/thoffman/openclaw2/src/auto-reply/status.ts,/Users/thoffman/openclaw2/ui/src/ui/views/agents-panels-tools-skills.ts/toolsshowing stale or empty inventories, Control UI runtime tools not updating on session change, native/toolsmode handling diverging from text behavior.Risks and Mitigations
displaySummaryplus safe fallback handling, and kept detailed rendering separate from raw tool docs.