Skip to content

Bug: FBWorkspace.inject_files can write files outside workspace_path聽#1473

Description

@yifanxiong272

馃悰 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:

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:
python -m pip install -e .
python -m pip install pytest
  1. Create test/test_fbworkspace_inject_files_path_traversal.py:
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:
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:

target_file_path = self.workspace_path / k

It then writes or deletes that path:

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:

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.

Activity

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

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions