Skip to content

Regression: exec_command routes allowed TZ=... date command through forbidden zsh -lc shell wrapper #21571

@davibusanello

Description

@davibusanello

What version of Codex CLI is running?

0.128.0

What subscription do you have?

Pro

Which model were you using?

gpt-5.5

What platform is your computer?

Darwin 25.4.0 arm64 unknown

What terminal emulator and version are you using (if applicable)?

Ghostty

What issue are you seeing?

The command: TZ=America/Sao_Paulo date +%Y%m%dT%H%M%S%z is explicitly allowed by my active execpolicy rules when checked as the intended policy shape: ["TZ=America/Sao_Paulo", "date", "+%Y%m%dT%H%M%S%z"]

The policy helper returns:

  decision: allow
  matchedPrefix: ["TZ=America/Sao_Paulo", "date"]
  justification: "Timezone-specific date inspection is allowed."

However, during actual Codex execution, the command is submitted/rejected as a shell command-string launch: /bin/zsh -lc 'TZ=America/Sao_Paulo date +%Y%m%dT%H%M%S%z' or, with login=false: /bin/zsh -c 'TZ=America/Sao_Paulo date +%Y%m%dT%H%M%S%z'

That launch shape correctly matches my shell command-string forbid rule:

  pattern = [SHELLS, ["-c", "-lc"]]
  decision = "forbidden"
  justification = "Do not use shell command-string execution. If it becomes necessary to continue the task right now, stop and ask the user to run the exact command manually. Otherwise finish all other work first and present the command at the end."

So the local policy is internally consistent:

  • direct timezone-prefixed date shape is allowed

  • broad env wrapper is forbidden

  • shell command-string execution is forbidden

The regression is that Codex/model/tool-call emission is now routing a previously working allowed command through a forbidden shell-wrapper shape. This was working for days using the exact literal command TZ=America/Sao_Paulo date +%Y%m%dT%H%M%S%z and other allowed timezone prefixes, with no local rule/config/workflow changes.

What steps can reproduce the bug?

Uploaded thread: 019e0360-0c68-75a1-8888-b1844e63bc51

It's reproducible on both codex-cli and codex-app.

Additional reproducible steps:

~/.codex/rules/default.rules:

SHELLS = [
    "zsh",
    "/bin/zsh",
    "/usr/bin/zsh",
    "bash",
    "/bin/bash",
    "/usr/bin/bash",
    "sh",
    "/bin/sh",
    "/usr/bin/sh",
    "dash",
    "/bin/dash",
    "/usr/bin/dash",
    "ash",
    "/bin/ash",
    "/usr/bin/ash",
    "ksh",
    "/bin/ksh",
    "/usr/bin/ksh",
    "fish",
    "/bin/fish",
    "/usr/bin/fish",
]

# Block shell command-string execution.
prefix_rule(
    pattern = [SHELLS, ["-c", "-lc"]],
    decision = "forbidden",
    justification = "Do not use shell command-string execution. If it becomes necessary to continue the task right now, stop and ask the user to run the exact command manually. Otherwise finish all other work first and present the command at the end.",
    match = [
        "/bin/zsh -lc rg -n TODO",
        "bash -lc git status",
        "/bin/sh -c pwd",
        "zsh -c pwd",
        "/bin/zsh -lc TZ=America/Sao_Paulo date +%Y%m%dT%H%M%S%z",
    ],
)

# Allowed
prefix_rule(
    pattern = [[
        "TZ=America/Sao_Paulo",
        "TZ=UTC",
        "TZ=Etc/UTC",
        "TZ=America/New_York",
        "TZ=America/Chicago",
        "TZ=America/Denver",
        "TZ=America/Los_Angeles",
        "TZ=America/Toronto",
        "TZ=America/Vancouver",
        "TZ=Europe/London",
        "TZ=Europe/Berlin",
        "TZ=Europe/Paris",
        "TZ=Asia/Tokyo",
        "TZ=Asia/Shanghai",
        "TZ=Asia/Kolkata",
        "TZ=Australia/Sydney",
    ], "date"],
    decision = "allow",
    justification = "Timezone-specific date inspection is allowed.",
    match = [
        "TZ=America/Sao_Paulo date",
        "TZ=America/Sao_Paulo date +%Y%m%dT%H%M%S%z",
        "TZ=UTC date",
        "TZ=America/New_York date",
        "TZ=America/Toronto date",
        "TZ=America/Vancouver date",
        "TZ=Europe/London date",
        "TZ=Asia/Tokyo date",
    ],
)

Validating rules:

codex execpolicy check --pretty --rules ~/.codex/rules/default.rules -- /bin/zsh -lc TZ=America/Sao_Paulo date +%Y%m%dT%H%M%S%z

Result

{
  "matchedRules": [
    {
      "prefixRuleMatch": {
        "matchedPrefix": [
          "/bin/zsh",
          "-lc"
        ],
        "decision": "forbidden",
        "justification": "Do not use shell command-string execution. If it becomes necessary to continue the task right now, stop and ask the user to run the exact command manually. Otherwise finish all other work first and present the command at the end."
      }
    }
  ],
  "decision": "forbidden"
}
codex execpolicy check --pretty --rules ~/.codex/rules/default.rules -- TZ=America/Sao_Paulo date +%Y%m%dT%H%M%S%z

Result

{
  "matchedRules": [
    {
      "prefixRuleMatch": {
        "matchedPrefix": [
          "TZ=America/Sao_Paulo",
          "date"
        ],
        "decision": "allow",
        "justification": "Timezone-specific date inspection is allowed."
      }
    }
  ],
  "decision": "allow"
}

What is the expected behavior?

Codex should not route this simple allowed command through /bin/zsh -lc or /bin/zsh -c when shell command-string execution is forbidden by policy; it could also use the policy forbidden justification as feedback to the model. It should either:

  1. preserve a direct policy-visible command shape, or

  2. expose/use a true direct argv + env execution API, e.g.

    argv=["date", ["+%Y%m%dT%H%M%S%z"]], env=[{"TZ":["America/Sao_Paulo"]}], or

  3. avoid emitting inline env assignment when the active policy forbids shell wrappers.

Additional information

Important:

I am not requesting that Codex weaken or bypass the shell-wrapper forbid rule. The report is that the command emission/normalization changed and now violates the active policy boundary.

Versions:

  • codex-cli: v0.128.0

  • codex-app: v6.429.61741 (2429)

  • shell: zsh

  • first observed: 2026-04-29

  • returned working as expected: 2026-05-03

  • confirmed unexpected behavior again: 2026-05-07

Related issue class:

This resembles the known prefix_rule/shell-wrapper/env-prefix behavior class where env-prefix commands and /bin/zsh -lc wrapping cause policy matching to apply to the wrapper instead of the intended command.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingtool-callsIssues related to tool calling

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions