Skip to content

Data loss: sub-agent TemporaryDirectory cleanup traversed an active bind mount and deleted the host repository #33557

Open

Description

@Temmie-Tem

What version of Codex CLI is running?

codex-cli 0.144.3

What subscription do you have?

ChatGPT Pro 20x

Which model were you using?

gpt-5.6-sol, xhigh reasoning effort

What platform is your computer?

Linux (Kubuntu, ext4 filesystem; exact uname output unavailable from the preserved incident environment)

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

Codex CLI/TUI; terminal emulator and version unavailable from the preserved incident environment

Codex doctor report

Not available from the preserved incident environment. The installed system was shut down for evidence preservation and offline recovery.

What issue are you seeing?

Summary

A delegated Codex sub-agent deleted contents from a real local repository
during a host-only adversarial review. The delegated task explicitly said
READ-ONLY ADVERSARIAL REVIEW and Do not edit. Repository deletion was not
requested by the operator.

The sub-agent created a Python TemporaryDirectory, bind-mounted the real
repository below it, and did not install a guaranteed unmount path. An
unhandled exception then caused TemporaryDirectory.__exit__() to run
recursive cleanup while the bind mount was still active. Cleanup traversed the
mount and deleted contents from the real repository.

Environment

  • Codex CLI: 0.144.3
  • Interface: Codex CLI/TUI
  • Model: gpt-5.6-sol
  • Reasoning effort: xhigh
  • OS: Linux/Kubuntu, ext4
  • Sandbox: danger-full-access
  • Approval policy: never
  • Permission profile: disabled
  • Incident time: 2026-07-14 approximately 04:14:30 KST (UTC+09:00)

Failure sequence

real repository
  -> bind-mounted below Python TemporaryDirectory
  -> bind mount remained active
  -> an unhandled ValueError occurred
  -> TemporaryDirectory.__exit__ invoked cleanup
  -> shutil.rmtree traversed the active bind mount
  -> contents in the real repository were deleted

The retained tool log contains the direct traceback:

ValueError
  -> TemporaryDirectory.__exit__
  -> cleanup
  -> shutil.rmtree
  -> _rmtree_safe_fd_step
  -> PermissionError inside the mounted repository tree

The final PermissionError stopped cleanup only after it had already traversed
the mounted repository. Subsequent filesystem inspection found major repository
content missing.

Impact

Affected path, with the username redacted:

/home/<user>/dev/A90_5G_rooting

Observed missing content included:

  • .git
  • tests
  • docs
  • workspace/private
  • AGENTS.md
  • GOAL.md

Only a partial workspace tree remained locally.

The incident required stopping normal use of the installed Linux system,
booting a Live USB, keeping the source ext4 partition unmounted and
block-device read-only, and creating a complete 255743810560-byte partition
image with GNU ddrescue. Imaging completed with 100% rescued and zero physical
read errors. Logical undelete and exact reconstruction remain separate work.

This was a host-filesystem incident. No phone, ADB, Odin, or device-flashing
operation occurred.

What steps can reproduce the bug?

This must be reproduced only with a disposable filesystem image or disposable
test tree, never with a real workspace.

  1. Start Codex CLI with a disposable repository or disposable test tree as its working directory.
  2. Delegate a nominally read-only adversarial task to a sub-agent.
  3. In a private user/mount namespace, create a Python TemporaryDirectory.
  4. Create a child directory below it and bind-mount a disposable repository or
    test tree onto that child directory.
  5. Do not unmount the bind in a guaranteed finally block.
  6. Raise an exception inside the TemporaryDirectory context.
  7. Observe whether automatic cleanup crosses the active mount boundary and
    removes files from the mounted disposable tree.

Sanitized shape of the incident-producing code:

with tempfile.TemporaryDirectory(prefix="mount-cleanup-probe-") as name:
    temporary = Path(name)
    mounted_repo = temporary / "repo"
    mounted_repo.mkdir()
    mount_bind(disposable_repo, mounted_repo)

    # An exception occurred here.
    # There was no guaranteed unmount before leaving the context.

The important condition is that TemporaryDirectory cleanup runs while the
descendant bind mount remains active.

What is the expected behavior?

Codex should not generate or run a destructive mount-namespace probe against a
real workspace when the delegated task is explicitly read-only.

At minimum, agent and runtime safety controls should detect or prevent this
pattern:

  • a real workspace bind-mounted below an automatically cleaned temporary tree
  • recursive cleanup while the target or a descendant is an active mountpoint
  • mount creation without a guaranteed unmount in finally
  • destructive testing against a real repository instead of a disposable image
  • execution of filesystem-mutating commands after an explicit Do not edit
    instruction

A safe harness should:

  • use a disposable filesystem image or disposable copied tree
  • track every mount and unmount it in guaranteed cleanup
  • inspect /proc/self/mountinfo before any recursive deletion
  • refuse deletion when the cleanup target or any descendant is a mountpoint
  • require explicit confirmation before a test can expose a real workspace to
    recursive cleanup, even under danger-full-access and approval policy
    never

Additional information

Related issues — not exact duplicates

I searched the issue tracker for combinations of bind mount,
TemporaryDirectory, recursive cleanup, mountpoint, sub-agent deleted repository, and data loss. I did not find an existing issue describing this
exact failure mechanism: recursive cleanup traversing an active bind mount below
a temporary directory.

Evidence availability

The incident-producing session log was preserved outside the deleted
repository. Its SHA-256 matches the contemporaneous incident handoff. The log
contains the session metadata, exact tool call, timestamp, generated probe, and
cleanup traceback.

Session identifiers, the log hash, sanitized command excerpts, and recovery
records can be provided privately to OpenAI Support. Full session logs, private
repository contents, and the raw filesystem image will not be posted publicly.

Activity

  1. added
    CLIIssues related to the Codex CLI
    subagentIssues involving subagents or multi-agent features
    sandboxIssues related to permissions or sandboxing
    model-behaviorIssues related to behaviors exhibited by the model
    on Jul 16, 2026
  2. Temmie-Tem commented on Jul 16, 2026

    @Temmie-Tem
    Author

    Additional scope and evidence clarification

    I reviewed the preserved incident record and am adding only sanitized, high-level facts here:

    • The delegated review explicitly requested concrete namespace and bind-mount attack testing, while also explicitly requiring read-only operation and prohibiting file edits. Namespace testing was within scope; mutation or deletion of the live repository was not.
    • The incident command used an unprivileged user/mount namespace and did not invoke sudo or obtain host-root privileges. It placed a writable bind of the live repository below a Python TemporaryDirectory without a read-only remount, an unmount call, or a finally cleanup boundary.
    • A Python 3.14 path-validation exception caused TemporaryDirectory cleanup to run before namespace process exit. Recursive cleanup entered the active repository bind; later permission errors stopped cleanup only after traversal had begun.
    • Immediate follow-up checks in the same session confirmed that repository content present before the call was missing directly afterward.
    • The event has been observed once and has not been intentionally reproduced after the incident. At least three known later local commits were absent from the remote repository, and exact recovery of all local-only history and data is not guaranteed.

    This is not being reported as a sandbox escape or host privilege escalation. It is an unsafe cleanup/lifetime pattern applied to a live repository during an explicitly read-only delegated task. Exact prompts, paths, session identifiers, and log excerpts are being kept private and can be provided to OpenAI through an approved private support channel.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    CLIIssues related to the Codex CLIbugSomething isn't workingmodel-behaviorIssues related to behaviors exhibited by the modelsandboxIssues related to permissions or sandboxingsubagentIssues involving subagents or multi-agent features

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions