Skip to content

fix(gateway): normalize MCP schemas for Codex loopback#70763

Merged
steipete merged 3 commits into
mainfrom
fix/mcp-schema-codex-docker
Apr 23, 2026
Merged

fix(gateway): normalize MCP schemas for Codex loopback#70763
steipete merged 3 commits into
mainfrom
fix/mcp-schema-codex-docker

Conversation

@steipete

Copy link
Copy Markdown
Contributor

Summary

  • normalize MCP loopback object schemas so properties is always emitted, including no-argument tools with { type: "object" }
  • mark the generated OpenClaw Codex loopback MCP server with default_tools_approval_mode = "approve" instead of relying on global Codex approval settings
  • pin the Docker Codex CLI smoke to @openai/codex@0.124.0, keep the default on codex-cli/gpt-5.5, and make the Docker lane prefer staged Codex OAuth unless OPENCLAW_LIVE_CLI_BACKEND_AUTH=api-key is explicit
  • add a Docker-live schema probe plugin that verifies no-argument MCP schemas through real tools/list

Supersedes #69956. The schema fix is the minimal part of that PR; the already-landed auto-failover commits are intentionally not included here. Thanks @Chevron7Locked for the original MCP schema fix direction.

Verification

  • pnpm test src/agents/cli-backends.test.ts src/agents/cli-runner/bundle-mcp.test.ts src/gateway/gateway-cli-backend.live-helpers.test.ts src/gateway/server-startup.test.ts src/gateway/mcp-http.test.ts
  • OPENCLAW_VITEST_NO_OUTPUT_TIMEOUT_MS=180000 OPENCLAW_VITEST_NO_OUTPUT_RETRY=1 pnpm check:changed
  • OPENCLAW_SKIP_DOCKER_BUILD=1 OPENCLAW_LIVE_CLI_BACKEND_MCP_SCHEMA_PROBE=1 OPENCLAW_LIVE_CLI_BACKEND_USE_CI_SAFE_CODEX_CONFIG=0 OPENCLAW_LIVE_CLI_BACKEND_IMAGE_PROBE=0 OPENCLAW_LIVE_CLI_BACKEND_DEBUG=1 pnpm test:docker:live-cli-backend:codex

Docker proof used Codex CLI 0.124.0, model codex-cli/gpt-5.5, staged .codex/auth.json, live tools/list schema validation, direct loopback cron call, and model-driven cron tool call on the first attempt.

@aisle-research-bot

aisle-research-bot Bot commented Apr 23, 2026

Copy link
Copy Markdown

🔒 Aisle Security Analysis

We found 1 potential security issue(s) in this PR:

# Severity Title
1 🟠 High Codex MCP per-server auto-approval enabled for any openclaw loopback URL (potential tool-approval bypass + token exposure)
1. 🟠 Codex MCP per-server auto-approval enabled for any `openclaw` loopback URL (potential tool-approval bypass + token exposure)
Property Value
Severity High
CWE CWE-285
Location src/agents/cli-runner/bundle-mcp.ts:144-160

Description

bundle-mcp.ts unconditionally injects Codex's per-server tool approval setting default_tools_approval_mode = "approve" whenever an MCP server entry is named openclaw and its URL matches a loopback pattern.

  • Input/control surface: config.mcpServers is built by merging existing backend MCP config (--mcp-config) and workspace/plugin-provided bundle MCP config (loadEnabledBundleMcpConfig). These sources can be influenced by workspace content / third-party plugins.
  • Behavior change: Any server meeting the name+URL condition gets default_tools_approval_mode = "approve", which (per naming/intent) appears to disable approval prompts for tool use.
  • Impact: If an attacker can introduce an MCP server named openclaw pointing to a malicious localhost listener, Codex may treat that server as pre-approved for tool execution. Additionally, because Codex config uses bearer_token_env_var / env_http_headers, the Codex process will send sensitive values like OPENCLAW_MCP_TOKEN and OPENCLAW_MCP_SESSION_KEY to that loopback server.

Vulnerable code:

if (isOpenClawLoopbackMcpServer(name, server)) {
  next.default_tools_approval_mode = "approve";
}

Recommendation

Constrain auto-approval to the internally-generated OpenClaw MCP server only, rather than any config entry named openclaw.

Suggested mitigations (choose one or combine):

  1. Mark the injected OpenClaw server with an unforgeable flag during construction (e.g., internal: true in the in-memory config object), and only set default_tools_approval_mode when that flag is present.

  2. Disallow/ignore user/plugin overrides of the openclaw server name when bundleMcp is enabled (treat it as reserved), or generate a random/internal name not controllable by configuration.

  3. If you must match by URL, require stronger binding than loopback hostname, e.g. also require a specific port chosen by OpenClaw at runtime and not coming from merged user/plugin config.

Example (flag-based):

// when creating the OpenClaw-injected server
const injected = { url, headers, __openclaw_internal: true };

function normalizeCodexServerConfig(name: string, server: BundleMcpServerConfig) {
  const next: Record<string, unknown> = {};
  applyCommonServerConfig(next, server);

  if (server.__openclaw_internal === true) {
    next.default_tools_approval_mode = "approve";
  }// ...
}

Also consider not sending OpenClaw session credentials (OPENCLAW_MCP_TOKEN, session key) to any server unless it is confirmed to be the OpenClaw bridge endpoint.


Analyzed PR: #70763 at commit 3410e8d

Last updated on: 2026-04-23T20:47:43Z

@cursor

cursor Bot commented Apr 23, 2026

Copy link
Copy Markdown

PR Summary

Medium Risk
Changes MCP loopback tools/list schema normalization and alters Codex CLI bundle-MCP/server config and Docker auth behavior, which can affect live probes and tool bridging. Risk is moderate because it touches CI/live test harnesses and runtime MCP config shaping, but is narrowly scoped with updated tests.

Overview
Fixes MCP loopback tools/list schema output so any tool with { type: "object" } always includes properties: {}, avoiding Codex/MCP consumers choking on no-arg tool schemas.

Updates Codex CLI bundle-MCP injection to mark the generated openclaw loopback server with default_tools_approval_mode = "approve" (per-server) and documents this behavior.

Pins the Docker Codex CLI smoke lane to @openai/codex@0.124.0, adds service_tier="fast" Codex config overrides, switches the CI workflow’s Codex model default to codex-cli/gpt-5.5, and adjusts the Docker live script to prefer staged OAuth (clearing OPENAI_* unless OPENCLAW_LIVE_CLI_BACKEND_AUTH=api-key) while ensuring version-pinned npm installs.

Adds an optional Docker live schema-probe plugin + preflight assertion to validate real loopback tools/list schemas in CI when OPENCLAW_LIVE_CLI_BACKEND_MCP_SCHEMA_PROBE=1.

Reviewed by Cursor Bugbot for commit 3410e8d. Bugbot is set up for automated code reviews on this repo. Configure here.

@openclaw-barnacle openclaw-barnacle Bot added docs Improvements or additions to documentation gateway Gateway runtime scripts Repository scripts docker Docker and sandbox tooling agents Agent runtime and tooling extensions: openai labels Apr 23, 2026
@openclaw-barnacle openclaw-barnacle Bot added size: M maintainer Maintainer-authored PR labels Apr 23, 2026
@greptile-apps

greptile-apps Bot commented Apr 23, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes MCP schema normalization so that properties: {} is always emitted for { type: "object" } tool schemas — not just when the type was being coerced from something else. It also marks the OpenClaw loopback MCP server with default_tools_approval_mode = "approve" to prevent Codex from stalling on approval prompts, pins @openai/codex@0.124.0, and adds a Docker-live schema probe plugin that validates no-argument tool schemas via a real tools/list call. The core fix in mcp-http.schema.ts is correct and well-covered by the new unit test.

Confidence Score: 5/5

Safe to merge; the core schema fix is correct and the only open concern (IPv6 loopback) was flagged in a previous thread.

No new P0 or P1 findings beyond what was already raised. The mcp-http.schema.ts restructure correctly ensures properties is injected unconditionally for all object schemas, the test coverage is thorough, and the surrounding tooling changes (version pin, approval mode, service tier) appear intentional.

No files require special attention beyond the previously flagged IPv6 gap in src/agents/cli-runner/bundle-mcp.ts.

Reviews (2): Last reviewed commit: "docs: update config baseline" | Re-trigger Greptile

Comment on lines +144 to +150
function isOpenClawLoopbackMcpServer(name: string, server: BundleMcpServerConfig): boolean {
return (
name === "openclaw" &&
typeof server.url === "string" &&
/^https?:\/\/(?:127\.0\.0\.1|localhost):\d+\/mcp(?:[?#].*)?$/.test(server.url)
);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 IPv6 loopback not covered by isOpenClawLoopbackMcpServer

The regex in isOpenClawLoopbackMcpServer accepts 127.0.0.1 and localhost but not [::1]. The sibling normalizeOpenClawLoopbackUrl already handles [::1] as a third loopback variant. If the server ever binds to ::1, the default_tools_approval_mode = "approve" injection will be silently skipped, which could cause Codex to stall on approval prompts.

Suggested change
function isOpenClawLoopbackMcpServer(name: string, server: BundleMcpServerConfig): boolean {
return (
name === "openclaw" &&
typeof server.url === "string" &&
/^https?:\/\/(?:127\.0\.0\.1|localhost):\d+\/mcp(?:[?#].*)?$/.test(server.url)
);
}
function isOpenClawLoopbackMcpServer(name: string, server: BundleMcpServerConfig): boolean {
return (
name === "openclaw" &&
typeof server.url === "string" &&
/^https?:\/\/(?:127\.0\.0\.1|localhost|\[::1\]):\d+\/mcp(?:[?#].*)?$/.test(server.url)
);
}
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/agents/cli-runner/bundle-mcp.ts
Line: 144-150

Comment:
**IPv6 loopback not covered by `isOpenClawLoopbackMcpServer`**

The regex in `isOpenClawLoopbackMcpServer` accepts `127.0.0.1` and `localhost` but not `[::1]`. The sibling `normalizeOpenClawLoopbackUrl` already handles `[::1]` as a third loopback variant. If the server ever binds to `::1`, the `default_tools_approval_mode = "approve"` injection will be silently skipped, which could cause Codex to stall on approval prompts.

```suggestion
function isOpenClawLoopbackMcpServer(name: string, server: BundleMcpServerConfig): boolean {
  return (
    name === "openclaw" &&
    typeof server.url === "string" &&
    /^https?:\/\/(?:127\.0\.0\.1|localhost|\[::1\]):\d+\/mcp(?:[?#].*)?$/.test(server.url)
  );
}
```

How can I resolve this? If you propose a fix, please make it concise.

Comment thread scripts/test-live-cli-backend-docker.sh
@steipete steipete marked this pull request as draft April 23, 2026 20:37
@steipete steipete marked this pull request as ready for review April 23, 2026 20:37
@steipete steipete force-pushed the fix/mcp-schema-codex-docker branch from db9e187 to 3410e8d Compare April 23, 2026 20:40

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 3410e8d. Configure here.

function normalizeCodexServerConfig(
name: string,
server: BundleMcpServerConfig,
): Record<string, unknown> {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Parameter name shadows loop variable in same function

Low Severity

The newly added name parameter on normalizeCodexServerConfig shadows the existing for (const [name, value] of ...) loop variable on line 165. While the outer name (server config name) is only used before the loop and the inner name (header name) is used correctly within it, this shadowing creates a maintenance hazard — any future code added after the loop that intends to reference the server config name would silently get the last header name instead.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 3410e8d. Configure here.

@steipete steipete merged commit 235e17a into main Apr 23, 2026
71 checks passed
@steipete steipete deleted the fix/mcp-schema-codex-docker branch April 23, 2026 20:55
@steipete

Copy link
Copy Markdown
Contributor Author

Landed via rebase.

Verification:

  • pnpm config:docs:check
  • pnpm test src/agents/cli-backends.test.ts src/agents/cli-runner/bundle-mcp.test.ts src/gateway/gateway-cli-backend.live-helpers.test.ts src/gateway/server-startup.test.ts src/gateway/mcp-http.test.ts
  • OPENCLAW_VITEST_NO_OUTPUT_TIMEOUT_MS=180000 OPENCLAW_VITEST_NO_OUTPUT_RETRY=1 pnpm check:changed
  • CI green on 3410e8d605 (CI, Install Smoke incl. docker-e2e-fast/install-smoke, Blacksmith Testbox, Parity gate)

Merged as 235e17a08f.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agents Agent runtime and tooling docker Docker and sandbox tooling docs Improvements or additions to documentation extensions: openai gateway Gateway runtime maintainer Maintainer-authored PR scripts Repository scripts size: M

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant