Validate that a sandbox-side source path passed to `sandbox download` resolves under the sandbox writable root. Returns the cleaned, traversal-resolved path on success. Refuses any path that lexically escapes `/sandbox` (e.g. `/etc/passwd`, `/sandbox/../etc/passwd`) with a user-facing error. This is a lexical guard only — it does not follow symlinks. Call `resolve_sandbox_source_path` after this
(path: &str)
| 863 | /// to a subsequent SSH I/O operation, so a symlink such as |
| 864 | /// `/sandbox/etc-link -> /etc` cannot leak files outside the workspace. |
| 865 | fn validate_sandbox_source_path(path: &str) -> Result<String> { |
| 866 | if path.is_empty() { |
| 867 | return Err(miette::miette!("sandbox source path is empty")); |
| 868 | } |
| 869 | let cleaned = lexical_clean_absolute_path(path) |
| 870 | .ok_or_else(|| miette::miette!("sandbox source path must be absolute (got '{path}')"))?; |
| 871 | if !is_under_sandbox_workspace(&cleaned) { |
| 872 | return Err(miette::miette!( |
| 873 | "sandbox source path '{path}' is outside the sandbox workspace ({SANDBOX_WORKSPACE_ROOT})" |
| 874 | )); |
| 875 | } |
| 876 | Ok(cleaned) |
| 877 | } |
| 878 | |
| 879 | /// Pure helper: is `path` equal to `/sandbox` or a descendant of it? |
| 880 | fn is_under_sandbox_workspace(path: &str) -> bool { |