diff --git a/api/workspace.py b/api/workspace.py index 74d1c53c..5e83d26b 100644 --- a/api/workspace.py +++ b/api/workspace.py @@ -163,6 +163,8 @@ def _remote_terminal_workspace_candidate(path: str | Path) -> Path | None: raw = _strip_surrounding_quotes(str(path)).strip() if not raw: return None + if '\x00' in raw or '\x00' in cwd: + return None normalized_raw = _normalize_posix_path(raw) normalized_cwd = _normalize_posix_path(cwd) if normalized_raw is not None and normalized_cwd is not None: diff --git a/tests/test_remote_terminal_workspace.py b/tests/test_remote_terminal_workspace.py index d3d99202..fb2fcbb1 100644 --- a/tests/test_remote_terminal_workspace.py +++ b/tests/test_remote_terminal_workspace.py @@ -90,3 +90,23 @@ def test_var_home_workspaces_stay_allowed_before_system_root_blocklist(monkeypat monkeypatch.setattr(workspace, "_workspace_access_error", lambda _candidate: None) assert validator(str(candidate)) == candidate + + +def test_remote_terminal_workspace_rejects_embedded_nullbyte_in_raw_path(monkeypatch): + """Embedded null bytes in remote workspace path should be rejected.""" + monkeypatch.setattr(api_config, "get_config", lambda: _remote_config()) + + # Path with embedded null byte + nullbyte_path = f"{REMOTE_CWD}/projects\x00/demo" + + assert workspace._remote_terminal_workspace_candidate(nullbyte_path) is None + + +def test_remote_terminal_workspace_rejects_embedded_nullbyte_in_cwd(monkeypatch): + """Embedded null bytes in remote terminal cwd should be rejected.""" + monkeypatch.setattr(api_config, "get_config", lambda: _remote_config(terminal={"backend": "ssh", "cwd": f"{REMOTE_CWD}\x00/malicious"})) + + # Normal path, but remote cwd contains null byte + normal_path = f"{REMOTE_CWD}/projects/demo" + + assert workspace._remote_terminal_workspace_candidate(normal_path) is None