Summary
_dump_debug_request writes the full forwarded request body — including the Authorization header value (which contains BYOK API keys) — to .codex-shim/last_request.json on disk. This file is world-readable by default on most Unix systems and is stored in the project checkout directory (a version-controlled location on many developer machines).
Evidence
codex_shim/server.py, _dump_debug_request():
def _dump_debug_request(slug: str, url: str, body: dict[str, Any]) -> None:
try:
dump_path = DEBUG_DIR / "last_request.json"
dump_path.parent.mkdir(parents=True, exist_ok=True)
payload = {"slug": slug, "url": url, "body": body}
full = json.dumps(payload, indent=2, default=str)
...
dump_path.write_text(full)
DEBUG_DIR is defined as:
DEBUG_DIR = Path(__file__).resolve().parents[1] / ".codex-shim"
This resolves to the project root's .codex-shim/ directory — directly inside the repository checkout. .gitignore lists .codex-shim/ so the file is not committed, but:
- The directory is readable by any user with access to the project directory (no
mode=0o700 passed to mkdir).
dump_path.write_text(full) uses the default umask — typically 0o644, making the file readable by group and world.
- The
body dict may include messages with the user's code context, tool outputs, and conversation history — sensitive work product beyond just API keys.
The function is called unconditionally on every forwarded _post_openai_chat and _post_openai_chat_as_anthropic request (not behind a debug flag), so this file is always written in production use.
Note: The HTTP headers (containing Authorization: Bearer <api_key>) are not included in the dumped payload (only slug, url, body are serialised), but the request body can contain conversation history and code context that should be considered private.
Why this matters
- Any local user with read access to the project directory can read the last conversation sent through the shim, including potentially sensitive code, credentials embedded in prompts, or security-related context.
- On CI/CD systems or shared developer machines, this file may be accessible to other users or processes.
- If a user accidentally runs
git add .codex-shim/ (the gitignore protects against git add . but not explicit adds), conversation content could be committed to a repository.
Root cause
The debug dump was added as a development aid but was never gated behind a debug flag. It runs unconditionally in production.
Recommended fix
- Gate the dump behind an environment variable:
if os.environ.get("CODEX_SHIM_DEBUG_DUMP"):.
- Create the
.codex-shim/ directory with mode 0o700: dump_path.parent.mkdir(parents=True, exist_ok=True, mode=0o700).
- Write the dump file with mode
0o600: use dump_path.open("w") with os.umask set, or dump_path.write_text(...); dump_path.chmod(0o600).
Acceptance criteria
_dump_debug_request is a no-op unless CODEX_SHIM_DEBUG_DUMP=1 is set.
- When the dump is enabled, the file is created with mode
0o600.
- The README documents the
CODEX_SHIM_DEBUG_DUMP flag as a debugging tool.
Suggested labels
security, privacy, bug
Priority
P2
Severity
Medium — local information disclosure of conversation content; API keys are not included in the dump payload.
Confidence
Confirmed — unconditional write with no permission hardening is explicit in the source.
Summary
_dump_debug_requestwrites the full forwarded request body — including theAuthorizationheader value (which contains BYOK API keys) — to.codex-shim/last_request.jsonon disk. This file is world-readable by default on most Unix systems and is stored in the project checkout directory (a version-controlled location on many developer machines).Evidence
codex_shim/server.py,_dump_debug_request():DEBUG_DIRis defined as:This resolves to the project root's
.codex-shim/directory — directly inside the repository checkout..gitignorelists.codex-shim/so the file is not committed, but:mode=0o700passed tomkdir).dump_path.write_text(full)uses the default umask — typically0o644, making the file readable by group and world.bodydict may includemessageswith the user's code context, tool outputs, and conversation history — sensitive work product beyond just API keys.The function is called unconditionally on every forwarded
_post_openai_chatand_post_openai_chat_as_anthropicrequest (not behind a debug flag), so this file is always written in production use.Note: The HTTP headers (containing
Authorization: Bearer <api_key>) are not included in the dumped payload (onlyslug,url,bodyare serialised), but the request body can contain conversation history and code context that should be considered private.Why this matters
git add .codex-shim/(the gitignore protects againstgit add .but not explicit adds), conversation content could be committed to a repository.Root cause
The debug dump was added as a development aid but was never gated behind a debug flag. It runs unconditionally in production.
Recommended fix
if os.environ.get("CODEX_SHIM_DEBUG_DUMP"):..codex-shim/directory with mode0o700:dump_path.parent.mkdir(parents=True, exist_ok=True, mode=0o700).0o600: usedump_path.open("w")withos.umaskset, ordump_path.write_text(...); dump_path.chmod(0o600).Acceptance criteria
_dump_debug_requestis a no-op unlessCODEX_SHIM_DEBUG_DUMP=1is set.0o600.CODEX_SHIM_DEBUG_DUMPflag as a debugging tool.Suggested labels
security, privacy, bug
Priority
P2
Severity
Medium — local information disclosure of conversation content; API keys are not included in the dump payload.
Confidence
Confirmed — unconditional write with no permission hardening is explicit in the source.