openai / openai/codex

app-server external exec-server selected with externalSandbox, but model tool execution falls back to app-server host identity

Open
#41,563 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

app-server bug CLI sandbox tool-calls windows-os
Dominant language
Rust
Stars
125k
Forks
19.4k
PR merge metrics
PR metrics pending

Description

Summary

With Codex CLI/app-server 0.144.5 on Windows, an explicit external
exec-server environment can be registered and contacted successfully, but a
model-requested tool process still executes under the app-server host identity
instead of the exec-server environment's restricted OS identity.

This prevents a secure embedding topology where model authentication and
coordination run in a trusted process while all engineering tools run under a
separate restricted Windows identity.

Environment

  • Codex CLI/app-server: 0.144.5
  • Platform: Windows
  • Trusted app-server identity: warddesk\brandon
  • Restricted exec-server/Workshop identity: CodexSandboxOffline
  • deferred_executor enabled
  • executor capability discovery enabled
  • explicit environment selected on both thread/start and turn/start
  • turn sandbox policy: externalSandbox
  • exec-server transport: loopback WebSocket
  • disposable fixture workspace only

No model credentials were supplied to the restricted identity.

Expected behavior

When an explicit external environment is selected for a thread and turn, every
model-requested engineering process, filesystem action, and tool operation
should be dispatched through that environment's exec-server and execute with
the exec-server environment's OS/security identity.

If the external environment cannot handle an operation, the operation or turn
should fail. It should not silently fall back to execution under the app-server
host identity.

Actual behavior

  1. environment/add succeeds.

  2. environment/info successfully contacts the restricted exec-server.

  3. Deferred-executor capabilities are enabled.

  4. The external environment is supplied explicitly on thread/start.

  5. The same environment is supplied explicitly on turn/start.

  6. The turn selects:

    {
      "type": "externalSandbox",
      "networkAccess": "restricted"
    }
    
  7. A real model-backed turn requests the safe command whoami.exe.

  8. The resulting process reports:

    warddesk\brandon
    

    rather than:

    CodexSandboxOffline
    

The tool therefore executes with the trusted coordinator's OS authority rather
than the selected external environment's restricted authority.

Minimal reproduction

Start the exec-server under a distinct restricted Windows identity:

codex exec-server --strict-config --listen ws://127.0.0.1:0

The launcher used for the reproduction independently verifies that ordinary
commands launched through this restricted worker run as
CodexSandboxOffline.

Start app-server under the trusted coordinator identity:

codex `
  -c features.deferred_executor=true `
  -c features.executor_capability_discovery=true `
  app-server --listen stdio://

Perform the normal initialization handshake:

{
  "method": "initialize",
  "id": 1,
  "params": {
    "clientInfo": {
      "name": "external-executor-identity-repro",
      "title": "External Executor Identity Reproduction",
      "version": "1"
    },
    "capabilities": {
      "experimentalApi": true
    }
  }
}
{"method":"initialized"}

Register the URL printed by exec-server:

{
  "method": "environment/add",
  "id": 2,
  "params": {
    "environmentId": "restricted-workshop",
    "execServerUrl": "ws://127.0.0.1:<port>"
  }
}

Confirm that app-server can reach it:

{
  "method": "environment/info",
  "id": 3,
  "params": {
    "environmentId": "restricted-workshop"
  }
}

Start an ephemeral thread with the environment selected:

{
  "method": "thread/start",
  "id": 4,
  "params": {
    "cwd": "C:\\fixture\\workshop",
    "runtimeWorkspaceRoots": ["C:\\fixture\\workshop"],
    "approvalPolicy": "never",
    "sandbox": "read-only",
    "ephemeral": true,
    "environments": [
      {
        "environmentId": "restricted-workshop",
        "cwd": "C:\\fixture\\workshop",
        "runtimeWorkspaceRoots": ["C:\\fixture\\workshop"]
      }
    ],
    "dynamicTools": [],
    "selectedCapabilityRoots": []
  }
}

Start the turn, selecting the environment again and requesting only an identity
probe:

{
  "method": "turn/start",
  "id": 5,
  "params": {
    "threadId": "<thread-id>",
    "input": [
      {
        "type": "text",
        "text": "Run whoami.exe and report only its stdout.",
        "text_elements": []
      }
    ],
    "sandboxPolicy": {
      "type": "externalSandbox",
      "networkAccess": "restricted"
    },
    "environments": [
      {
        "environmentId": "restricted-workshop",
        "cwd": "C:\\fixture\\workshop",
        "runtimeWorkspaceRoots": ["C:\\fixture\\workshop"]
      }
    ]
  }
}

Observe the completed command's stdout or an equivalent safe process-identity
receipt. In this reproduction it is the app-server identity
warddesk\brandon, not CodexSandboxOffline.

Secondary protocol compatibility finding

Types and JSON schemas generated from the same 0.144.5 binary with:

codex app-server generate-json-schema --experimental --out <temporary-directory>

include the environment/status method.

However, sending:

{
  "method": "environment/status",
  "id": 6,
  "params": {
    "environmentId": "restricted-workshop"
  }
}

is rejected by the running app-server as an unknown method.

Is this:

  • a version-skew defect;
  • a generated-schema mismatch;
  • an unsupported or deprecated method;
  • or a compatibility condition consumers are expected to detect?

Security relevance

This is not being reported as a general Codex sandbox vulnerability.

The specific problem is that a selected external execution environment does not
provide the expected execution boundary in this embedding topology. Model
authentication and coordination intentionally run in a trusted process, while
engineering tools must run under a separate restricted OS identity.

Silent host-local fallback makes that separation impossible to assert safely.

Requested behavior

Equivalent guarantees to the following would address the integration blocker:

  1. Every model-requested shell/filesystem/tool operation for a selected external
    environment is routed through its bound exec-server.
  2. externalSandbox never silently runs an engineering tool under the
    app-server host identity.
  3. Routing failure fails the operation or turn closed.
  4. Structured execution evidence must identify the selected environment and
    provide independently verifiable execution provenance sufficient to establish
    that the tool ran through the selected external exec-server rather than
    falling back to the app-server host. Where available, include actual
    process/security identity together with exec-server/session, tool
    request/result, and turn/session identities.
  5. The exec-server transport supports session-bound authentication or an
    equivalently protected local channel.
  6. Generated capability/schema advertisement agrees with runtime method
    support.

Related reports

  • #33820 is materially related because an apply_patch delete reaches
    host-local filesystem instead of the selected external environment. That
    report is narrower and is not established as identical to this
    process-identity result.
  • #33391 is related to executor identity evidence but does not report this
    routing fallback.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start at the app-server environment/add, thread/start, and turn/start entry points and trace externalSandbox tool routing. Reproduce with the separate exec-server identities and a whoami.exe request, then check environment/status against generated schemas. Done means selected external operations cannot silently run on the app-server host, routing failures are closed, execution provenance is verifiable, and advertised methods match runtime support.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
backend-api-design, security
Issue type
Bug
Difficulty
5/5
Estimated time
Over a week
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.