#1473·RD-Agent

Bug: FBWorkspace.inject_files can write files outside workspace_path

Author: yifanxiong272Created Sep 2, 2026Updated Sep 2, 2026
Labelsbug

Bug Description

FBWorkspace.inject_files joins caller-provided file keys directly with workspace_path and then writes the result without checking that the resolved path remains inside the workspace.

A file key containing parent-directory components can therefore write outside the workspace folder:

python
workspace.inject_files(
    **{
        "../escape.txt": "escaped",
    }
)

This creates:

<workspace_path>/../escape.txt

instead of rejecting the path or otherwise preventing the write.

The method's docstring describes the operation as injecting code "into the folder", and normal callers pass dynamic filename-to-code dictionaries through this API. A filename key should not be able to escape workspace_path.

To Reproduce

Steps to reproduce the behavior:

  1. Check out RD-Agent main at commit:
2c878f9d2453dced35061165786d1f31bbff0ab6
  1. Install RD-Agent from source and install pytest:
bash
python -m pip install -e .
python -m pip install pytest
  1. Create test/test_fbworkspace_inject_files_path_traversal.py:
python
from rdagent.core.experiment import FBWorkspace


def test_inject_files_does_not_write_outside_workspace(tmp_path):
    workspace = FBWorkspace()
    workspace.workspace_path = tmp_path / "workspace"
    workspace.file_dict = {}

    workspace.inject_files(
        **{
            "inside.txt": "inside",
        }
    )
    assert (
        workspace.workspace_path / "inside.txt"
    ).read_text() == "inside"

    outside = tmp_path / "escape.txt"

    try:
        workspace.inject_files(
            **{
                "../escape.txt": "escaped",
            }
        )
    except ValueError:
        pass

    assert not outside.exists()
  1. Run:
bash
python -m pytest \
  test/test_fbworkspace_inject_files_path_traversal.py \
  -q
  1. Observe that the test fails because escape.txt is created outside workspace_path.

Expected Behavior

inject_files should never create, overwrite, or delete files outside workspace_path.

For a key such as:

../escape.txt

the method should reject the input, for example by raising ValueError, or otherwise guarantee that no filesystem operation occurs outside the workspace.

Normal relative paths inside the workspace, such as:

inside.txt
subdir/file.py

should continue to work.

Screenshot

Not applicable. This is a deterministic filesystem-level reproduction.

Environment

Note: Users can run rdagent collect_info to get system information and paste it directly here.

  • Name of current operating system: macOS
  • Processor architecture: arm64
  • System, version, and hardware information: macOS 15.7.3, arm64
  • Version number of the system: 15.7.3
  • Python version: 3.13.2
  • Container ID: Not applicable
  • Container Name: Not applicable
  • Container Status: Not applicable
  • Image ID used by the container: Not applicable
  • Image tag used by the container: Not applicable
  • Container port mapping: Not applicable
  • Container Label: Not applicable
  • Startup Commands: Not applicable
  • RD-Agent version: main@2c878f9d2453dced35061165786d1f31bbff0ab6
  • Package version: Source checkout

Additional Notes

The current implementation builds the target path directly from the untrusted key:

python
target_file_path = self.workspace_path / k

It then writes or deletes that path:

python
if v == self.DEL_KEY:
    if target_file_path.exists():
        target_file_path.unlink()
    self.file_dict.pop(k, None)
else:
    self.file_dict[k] = v
    target_file_path.parent.mkdir(parents=True, exist_ok=True)
    target_file_path.write_text(v)

Because Path / "../escape.txt" is still allowed by the filesystem, this escapes the workspace once resolved.

A possible fix is to resolve the candidate path and verify that it is contained by the resolved workspace root before either the write or delete branch performs filesystem operations:

python
workspace_root = self.workspace_path.resolve()
target_file_path = (workspace_root / k).resolve()

if not target_file_path.is_relative_to(workspace_root):
    raise ValueError(
        f"File path escapes workspace: {k}"
    )

Regression coverage should include:

  • ordinary files such as file.py;
  • nested relative files such as subdir/file.py;
  • parent-directory paths such as ../escape.txt;
  • absolute paths;
  • both write and DEL_KEY delete operations;
  • preservation of file_dict behavior for accepted paths.

Targeted issue and pull-request searches found no existing report for this FBWorkspace.inject_files workspace-escape root.