Skip to content

fix(logging): redact environment values from logged CLI commands#175

Open
ryuhaneul wants to merge 3 commits into
PleasePrompto:mainfrom
ryuhaneul:fix/redact-secrets-in-cmd-logs
Open

fix(logging): redact environment values from logged CLI commands#175
ryuhaneul wants to merge 3 commits into
PleasePrompto:mainfrom
ryuhaneul:fix/redact-secrets-in-cmd-logs

Conversation

@ryuhaneul

Copy link
Copy Markdown
Contributor

Problem

Every CLI invocation logs the full command line, including the -e KEY=VALUE environment flags passed to the sandbox container. Since ~/.ductor/.env is injected into every CLI subprocess, every secret in it is written to the log in plaintext:

  • cli/claude_provider.pyCLI stream cmd / CLI cmd (INFO)
  • cli/codex_provider.pyCodex cmd (INFO)
  • cli/gemini_provider.pyGemini cmd (INFO)
  • cli/antigravity_provider.pyAntigravity send (DEBUG)
  • infra/docker.pydocker run cmd (DEBUG)

The existing "safe_cmd" logic only truncates values that follow a --flag; -e API_KEY=... is untouched, and most tokens are short enough to survive truncation anyway. Raising log_level to INFO or DEBUG for debugging therefore dumps API keys, tokens and passwords into agent.log (and its rotations).

What changed

A shared helper, cli/_log_redact.py:

redact_cmd_for_log(cmd: list[str]) -> list[str]
  • Masks the value of any KEY=VALUE argument whose key looks like an environment variable (uppercase / digits / underscore): -e GITHUB_TOKEN=ghp_…-e GITHUB_TOKEN=***.
  • Handles -e KEY=VALUE, -eKEY=VALUE, --env=KEY=VALUE, and bare inline assignments.
  • Fail-closed: anything that is not on an explicit non-sensitive allowlist gets masked, so a newly added secret is protected by default.
  • Keeps the keys and the command structure intact, so logs remain useful for debugging.
  • Preserves values for execution context that carries no secrets: DUCTOR_* (chat/topic/transport/agent/home/ports), TZ, PYTHONPATH, HOME, LANG, CONTAINER, Playwright browser path, and *_URL / *_HOST / *_PORT settings.
  • Returns a new list; the command actually executed is untouched.

Applied at every command-logging site (Claude, Codex, Gemini, Antigravity, Docker). Existing truncation and prompt-substitution behavior is preserved; masking is applied first, so a masked value is never truncated into something misleading.

Example

Before:

Codex cmd: docker exec -e GITHUB_TOKEN=ghp_REALSECRET -e MAIL_PASS=hunter2 -e DUCTOR_CHAT_ID=42 ductor-sandbox codex exec --model gpt-5

After:

Codex cmd: docker exec -e GITHUB_TOKEN=*** -e MAIL_PASS=*** -e DUCTOR_CHAT_ID=42 ductor-sandbox codex exec --model gpt-5

Testing

New tests cover: secret values absent from the rendered log line, key names preserved, allowlisted values preserved, command structure preserved, the inline/-e/--env= forms, and per-provider caplog assertions that no secret reaches the log record. Full suite: no new failures vs. base; ruff, ruff format, mypy and the i18n check pass.

Only fake tokens are used in the tests.

@ryuhaneul
ryuhaneul force-pushed the fix/redact-secrets-in-cmd-logs branch from 9ee7d7e to a544a43 Compare July 13, 2026 01:32
@ryuhaneul
ryuhaneul marked this pull request as draft July 13, 2026 02:56
@ryuhaneul
ryuhaneul marked this pull request as ready for review July 15, 2026 07:14
@ryuhaneul
ryuhaneul force-pushed the fix/redact-secrets-in-cmd-logs branch from a91422d to afbb507 Compare July 15, 2026 08:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant