diff --git a/scripts/ci/materialize_base_rust_dependencies.py b/scripts/ci/materialize_base_rust_dependencies.py index 6feec9cedf..ef946f42ae 100644 --- a/scripts/ci/materialize_base_rust_dependencies.py +++ b/scripts/ci/materialize_base_rust_dependencies.py @@ -94,49 +94,50 @@ def _is_workspace_manifest(content: bytes) -> bool: return "workspace" in parsed -def _select_vendor_root( +def _select_vendor_roots( repo_root: pathlib.Path, base_sha: str, cargo_paths: list[str] -) -> str | None: - """Return the single directory ``cargo vendor`` should be invoked from, or ``None``. - - Only one topology is supported: a single Cargo workspace root, or a single standalone - crate with no workspace. Any other shape (independent multi-root layouts) fails closed - rather than guess which root's lock file is authoritative -- the same restraint - ``materialize_base_python_requirements.py`` takes with uv workspaces. +) -> list[str]: + """Return every directory whose base lock must be vendored, primary root first. + + A base tree may legitimately hold several lock roots: the standard cargo-fuzz + layout declares ``[workspace]`` in both the repository root and ``fuzz/`` so the + fuzz crate opts out of the parent workspace, and the two locks resolve *different* + crate sets. Selecting one root and dropping the rest would silently vendor an + incomplete closure, so every root is vendored into one shared directory via + ``cargo vendor --sync`` and every lock is asserted with ``--locked``. + + What still fails closed is a root that cannot be reconciled at all: a manifest + declaring a workspace with no sibling ``Cargo.lock``, or a lock with no sibling + ``Cargo.toml``. Those are unresolvable rather than merely plural. """ - manifests = [path for path in cargo_paths if path.endswith("Cargo.toml")] - locks = {path.rsplit("/", 1)[0] if "/" in path else "." for path in cargo_paths if path.endswith("Cargo.lock")} - workspace_dirs: list[str] = [] - for manifest_path in manifests: + manifests = { + (path.rsplit("/", 1)[0] if "/" in path else ".") + for path in cargo_paths + if path.endswith("Cargo.toml") + } + locks = { + (path.rsplit("/", 1)[0] if "/" in path else ".") + for path in cargo_paths + if path.endswith("Cargo.lock") + } + for manifest_path in sorted(path for path in cargo_paths if path.endswith("Cargo.toml")): content = _git(repo_root, "show", f"{base_sha}:{manifest_path}") - if _is_workspace_manifest(content): - manifest_dir = manifest_path.rsplit("/", 1)[0] if "/" in manifest_path else "." - workspace_dirs.append(manifest_dir) - - if len(workspace_dirs) == 1: - (root,) = workspace_dirs - if root in locks: - return root - raise RuntimeError( - f"base Cargo workspace root {root} has no sibling Cargo.lock" - ) - if len(workspace_dirs) > 1: - raise RuntimeError( - "base tree declares more than one Cargo workspace root; " - "Rust dependency vendoring needs exactly one" - ) - if len(locks) == 1: - (root,) = locks - manifest_path = "Cargo.toml" if root == "." else f"{root}/Cargo.toml" - if manifest_path in manifests: - return root - raise RuntimeError(f"base Cargo.lock at {root} has no sibling Cargo.toml") - if len(locks) > 1: - raise RuntimeError( - "base tree has more than one Cargo.lock with no single workspace root; " - "Rust dependency vendoring needs exactly one" - ) - return None + if not _is_workspace_manifest(content): + continue + manifest_dir = manifest_path.rsplit("/", 1)[0] if "/" in manifest_path else "." + if manifest_dir not in locks: + raise RuntimeError( + f"base Cargo workspace root {manifest_dir} has no sibling Cargo.lock" + ) + for lock_dir in sorted(locks): + if lock_dir not in manifests: + raise RuntimeError(f"base Cargo.lock at {lock_dir} has no sibling Cargo.toml") + if not locks: + return [] + # Deterministic order with the repository root first when it is one of the roots, + # so the primary --manifest-path is stable across runs and hosts. + ordered = sorted(locks, key=lambda root: (root != ".", root)) + return ordered def _placeholder_target_paths(manifest_content: bytes) -> list[str]: @@ -180,8 +181,14 @@ def _reconstruct_base_tree( destination.write_bytes(content) if destination.name == "Cargo.toml": for target_path in _placeholder_target_paths(content): + target_relative_path = pathlib.PurePosixPath(target_path) + if target_relative_path.is_absolute() or ".." in target_relative_path.parts: + raise RuntimeError( + "Cargo target path must stay inside its manifest root: " + f"{target_path}" + ) target_destination = destination.parent / pathlib.Path( - *pathlib.PurePosixPath(target_path).parts + *target_relative_path.parts ) target_destination.parent.mkdir(parents=True, exist_ok=True) if not target_destination.exists(): @@ -189,18 +196,30 @@ def _reconstruct_base_tree( def _run_cargo_vendor( - manifest_path: pathlib.Path, vendor_dir: pathlib.Path + manifest_path: pathlib.Path, + vendor_dir: pathlib.Path, + sync_manifests: list[pathlib.Path] | None = None, ) -> subprocess.CompletedProcess[bytes]: - """Run ``cargo vendor`` for one reconstructed base manifest and return the result.""" + """Vendor the union of the base manifests, asserting every lock stays unchanged. + + ``--sync`` adds each further root's manifest to the same vendor directory, so no + root's dependencies are dropped. ``--locked`` makes cargo refuse to re-resolve: + without it a lock that disagrees with its manifest would be quietly updated and + the vendored set would no longer be the committed closure. + """ + command = [ + "cargo", + "vendor", + "--locked", + "--manifest-path", + str(manifest_path), + "--versioned-dirs", + ] + for sync_manifest in sync_manifests or []: + command.extend(["--sync", str(sync_manifest)]) + command.append(str(vendor_dir)) return subprocess.run( - [ - "cargo", - "vendor", - "--manifest-path", - str(manifest_path), - "--versioned-dirs", - str(vendor_dir), - ], + command, check=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE, @@ -235,19 +254,28 @@ def materialize( resolved_repo = repo_root.resolve() cargo_paths = _regular_cargo_blob_paths(resolved_repo, base_sha) - vendor_root = _select_vendor_root(resolved_repo, base_sha, cargo_paths) + vendor_roots = _select_vendor_roots(resolved_repo, base_sha, cargo_paths) manifest: list[str] = [] - if vendor_root is not None: + if vendor_roots: + primary_root, *additional_roots = vendor_roots + + def _manifest_for(root: str, base: pathlib.Path) -> pathlib.Path: + return base / ("Cargo.toml" if root == "." else f"{root}/Cargo.toml") + + def _lock_for(root: str) -> str: + return "Cargo.lock" if root == "." else f"{root}/Cargo.lock" + with tempfile.TemporaryDirectory() as work_dir: work_path = pathlib.Path(work_dir) _reconstruct_base_tree(resolved_repo, base_sha, cargo_paths, work_path) - manifest_path = work_path / ( - "Cargo.toml" if vendor_root == "." else f"{vendor_root}/Cargo.toml" - ) - lock_path = "Cargo.lock" if vendor_root == "." else f"{vendor_root}/Cargo.lock" + manifest_path = _manifest_for(primary_root, work_path) + sync_manifests = [_manifest_for(root, work_path) for root in additional_roots] + # Every root's lock is reported, so a failure names the whole vendored set + # rather than only the primary root. + lock_path = ", ".join(_lock_for(root) for root in vendor_roots) vendor_dir = output_dir / "vendor" try: - completed = _run_cargo_vendor(manifest_path, vendor_dir) + completed = _run_cargo_vendor(manifest_path, vendor_dir, sync_manifests) except (OSError, subprocess.TimeoutExpired) as exc: raise RuntimeError( f"could not run trusted cargo vendor for base manifest {lock_path}: " @@ -267,7 +295,7 @@ def materialize( vendor_dir_for_config.encode("utf-8"), ) (output_dir / "cargo-config.toml").write_bytes(config_text) - manifest = [lock_path] + manifest = [_lock_for(root) for root in vendor_roots] (output_dir / "manifest.json").write_text( json.dumps(manifest, indent=2, sort_keys=True) + "\n", diff --git a/tests/test_materialize_base_rust_dependencies.py b/tests/test_materialize_base_rust_dependencies.py index a44c1e7e06..b912c4ea4c 100644 --- a/tests/test_materialize_base_rust_dependencies.py +++ b/tests/test_materialize_base_rust_dependencies.py @@ -10,7 +10,7 @@ from scripts.ci import materialize_base_rust_dependencies as materializer -pytestmark = pytest.mark.skipif( +requires_cargo = pytest.mark.skipif( shutil.which("cargo") is None, reason="cargo is required to vendor a real dependency graph" ) @@ -38,7 +38,7 @@ def _commit_all(repo: Path) -> str: return git(repo, "rev-parse", "HEAD") -def _write_single_crate_workspace(repo: Path) -> None: +def _write_single_crate_workspace(repo: Path, *, generate_lock: bool = True) -> None: (repo / "Cargo.toml").write_text( '[workspace]\nmembers = ["crates/foo"]\nresolver = "2"\n', encoding="utf-8" ) @@ -52,9 +52,12 @@ def _write_single_crate_workspace(repo: Path) -> None: src_dir = crate_dir / "src" src_dir.mkdir() (src_dir / "lib.rs").write_text("pub fn x() {}\n", encoding="utf-8") - subprocess.run( - ["cargo", "generate-lockfile"], cwd=repo, check=True, capture_output=True - ) + if generate_lock: + subprocess.run( + ["cargo", "generate-lockfile"], cwd=repo, check=True, capture_output=True + ) + else: + (repo / "Cargo.lock").write_text("version = 3\n", encoding="utf-8") def test_no_tracked_cargo_lock_skips_gracefully(tmp_path: Path) -> None: @@ -72,6 +75,7 @@ def test_no_tracked_cargo_lock_skips_gracefully(tmp_path: Path) -> None: assert not (output_dir / "vendor").exists() +@requires_cargo def test_vendors_a_single_workspace_offline_afterward(tmp_path: Path) -> None: """A workspace's locked dependency closure vendors, and cargo then builds offline from it.""" repo = tmp_path / "repo" @@ -103,6 +107,7 @@ def test_vendors_a_single_workspace_offline_afterward(tmp_path: Path) -> None: assert build.returncode == 0, build.stderr +@requires_cargo def test_pr_added_dependency_not_in_base_lock_is_not_materialized(tmp_path: Path) -> None: """Vendoring reads only the validated base commit, never a later PR-controlled lock.""" repo = tmp_path / "repo" @@ -125,21 +130,128 @@ def test_pr_added_dependency_not_in_base_lock_is_not_materialized(tmp_path: Path assert "ryu" not in vendored_crates -def test_multiple_workspace_roots_fail_closed(tmp_path: Path) -> None: - """An ambiguous multi-root layout refuses to guess which lock is authoritative.""" +def test_multiple_workspace_roots_are_vendored_as_a_union(tmp_path: Path) -> None: + """Several base lock roots are all vendored, because their closures differ. + + The standard cargo-fuzz layout declares ``[workspace]`` in the repository root and + in ``fuzz/`` so the fuzz crate opts out of the parent workspace, and the two locks + resolve different crate sets. Selecting one root and dropping the rest would vendor + an incomplete closure, which is why this is a union rather than a refusal. + """ repo = tmp_path / "repo" _init_repo(repo) for name in ("a", "b"): crate_dir = repo / name - (crate_dir).mkdir() + crate_dir.mkdir() (crate_dir / "Cargo.toml").write_text( f'[workspace]\nmembers = ["{name}-crate"]\n', encoding="utf-8" ) (crate_dir / "Cargo.lock").write_text("# empty lock\n", encoding="utf-8") base_sha = _commit_all(repo) - with pytest.raises(RuntimeError, match="more than one Cargo workspace root"): - materializer.materialize(repo, base_sha, tmp_path / "out") + roots = materializer._select_vendor_roots(repo, base_sha, ["a/Cargo.toml", "a/Cargo.lock", "b/Cargo.toml", "b/Cargo.lock"]) + assert roots == ["a", "b"] + + +@requires_cargo +def test_root_and_fuzz_vendor_distinct_crates_and_reject_changed_or_missing_lock( + tmp_path: Path, monkeypatch: pytest.MonkeyPatch +) -> None: + """The real two-root Cargo path retains both closures and rejects lock drift.""" + monkeypatch.setenv("CARGO_HOME", str(tmp_path / "cargo-home")) + dependencies = {} + for name in ("rootdep", "fuzzdep"): + dependency = tmp_path / name + _init_repo(dependency) + (dependency / "Cargo.toml").write_text( + f'[package]\nname = "{name}"\nversion = "0.1.0"\nedition = "2021"\n', + encoding="utf-8", + ) + (dependency / "src").mkdir() + (dependency / "src" / "lib.rs").write_text("pub fn marker() {}\n", encoding="utf-8") + _commit_all(dependency) + dependencies[name] = dependency.as_uri() + + repo = tmp_path / "repo" + _init_repo(repo) + for directory, package, dependency in ( + (repo, "root", "rootdep"), + (repo / "fuzz", "fuzz", "fuzzdep"), + ): + directory.mkdir(exist_ok=True) + (directory / "Cargo.toml").write_text( + f'[package]\nname = "{package}"\nversion = "0.1.0"\nedition = "2021"\n' + f'[workspace]\n[dependencies]\n{dependency} = {{ git = "{dependencies[dependency]}" }}\n', + encoding="utf-8", + ) + (directory / "src").mkdir() + (directory / "src" / "lib.rs").write_text("pub fn marker() {}\n", encoding="utf-8") + subprocess.run( + ["cargo", "generate-lockfile"], + cwd=directory, + check=True, + capture_output=True, + ) + base_sha = _commit_all(repo) + monkeypatch.setenv("CARGO_NET_OFFLINE", "true") + + output_dir = tmp_path / "out" + assert materializer.materialize(repo, base_sha, output_dir) == [ + "Cargo.lock", "fuzz/Cargo.lock" + ] + assert json.loads((output_dir / "manifest.json").read_text()) == [ + "Cargo.lock", "fuzz/Cargo.lock" + ] + vendored = {path.name.split("-")[0] for path in (output_dir / "vendor").iterdir()} + assert {"rootdep", "fuzzdep"} <= vendored + + fuzz_lock = repo / "fuzz" / "Cargo.lock" + lock_text = fuzz_lock.read_text(encoding="utf-8") + assert 'name = "fuzzdep"' in lock_text + fuzz_lock.write_text(lock_text.replace('name = "fuzzdep"', 'name = "otherdep"', 1)) + changed_sha = _commit_all(repo) + with pytest.raises(RuntimeError, match="cargo vendor failed.*fuzz/Cargo.lock"): + materializer.materialize(repo, changed_sha, tmp_path / "changed") + + fuzz_lock.unlink() + missing_sha = _commit_all(repo) + with pytest.raises(RuntimeError, match="fuzz.*no sibling Cargo.lock"): + materializer.materialize(repo, missing_sha, tmp_path / "missing") + + +def test_the_repository_root_is_the_primary_manifest_when_present() -> None: + """Ordering is deterministic and puts the repository root first.""" + paths = ["Cargo.toml", "Cargo.lock", "fuzz/Cargo.toml", "fuzz/Cargo.lock"] + import unittest.mock as mock + + with mock.patch.object(materializer, "_git", return_value=b"[workspace]\n"): + assert materializer._select_vendor_roots(Path("/unused"), "a" * 40, paths) == [ + ".", + "fuzz", + ] + + +def test_vendor_command_asserts_every_lock_and_syncs_every_root() -> None: + """The union must reach cargo as --sync, and every lock must be asserted. + + Without ``--locked`` cargo may re-resolve a lock that disagrees with its manifest, + so the vendored set would no longer be the committed closure; without ``--sync`` + only the primary root's dependencies would be vendored. + """ + import unittest.mock as mock + + with mock.patch.object(materializer.subprocess, "run") as runner: + runner.return_value = materializer.subprocess.CompletedProcess([], 0, b"", b"") + materializer._run_cargo_vendor( + Path("/work/Cargo.toml"), + Path("/out/vendor"), + [Path("/work/fuzz/Cargo.toml")], + ) + command = runner.call_args.args[0] + assert command[:3] == ["cargo", "vendor", "--locked"] + assert command[command.index("--manifest-path") + 1] == "/work/Cargo.toml" + assert command[command.index("--sync") + 1] == "/work/fuzz/Cargo.toml" + assert command[-1] == "/out/vendor" def test_main_reports_error_and_exits_nonzero_on_failure( @@ -188,14 +300,28 @@ def test_main_reports_success_with_no_rust_project( assert "Rust vendoring skipped" in capsys.readouterr().out +@pytest.mark.parametrize( + "vendor_args", + [[], ["--vendor-dir-for-config", "/opt/trusted/vendor"]], +) def test_main_reports_success_with_a_vendored_workspace( - tmp_path: Path, capsys: pytest.CaptureFixture[str] + tmp_path: Path, + capsys: pytest.CaptureFixture[str], + monkeypatch: pytest.MonkeyPatch, + vendor_args: list[str], ) -> None: """The CLI names the vendored base lock file on a successful run.""" repo = tmp_path / "repo" _init_repo(repo) - _write_single_crate_workspace(repo) + _write_single_crate_workspace(repo, generate_lock=False) base_sha = _commit_all(repo) + monkeypatch.setattr( + materializer, + "_run_cargo_vendor", + lambda *_a, **_k: subprocess.CompletedProcess( + args=["cargo", "vendor"], returncode=0, stdout=b"directory = 'vendor'\n", stderr=b"" + ), + ) exit_code = materializer.main( [ @@ -205,6 +331,7 @@ def test_main_reports_success_with_a_vendored_workspace( base_sha, "--output-dir", str(tmp_path / "out"), + *vendor_args, ] ) @@ -242,7 +369,7 @@ def test_symlinked_cargo_toml_is_excluded(tmp_path: Path) -> None: """A tracked symlink named ``Cargo.toml`` is never treated as a candidate manifest.""" repo = tmp_path / "repo" _init_repo(repo) - _write_single_crate_workspace(repo) + _write_single_crate_workspace(repo, generate_lock=False) (repo / "linked-crate").symlink_to("crates/foo") base_sha = _commit_all(repo) @@ -266,7 +393,7 @@ def test_select_vendor_root_workspace_without_sibling_lock_raises( materializer, "_git", lambda *_a, **_k: b'[workspace]\nmembers = ["crates/foo"]\n' ) with pytest.raises(RuntimeError, match="no sibling Cargo.lock"): - materializer._select_vendor_root(Path("/unused"), "a" * 40, ["Cargo.toml"]) + materializer._select_vendor_roots(Path("/unused"), "a" * 40, ["Cargo.toml"]) def test_select_vendor_root_returns_single_standalone_crate( @@ -274,10 +401,10 @@ def test_select_vendor_root_returns_single_standalone_crate( ) -> None: """A single crate with no ``[workspace]`` table is its own vendor root.""" monkeypatch.setattr(materializer, "_git", lambda *_a, **_k: b'[package]\nname = "foo"\n') - root = materializer._select_vendor_root( + roots = materializer._select_vendor_roots( Path("/unused"), "a" * 40, ["crate-a/Cargo.toml", "crate-a/Cargo.lock"] ) - assert root == "crate-a" + assert roots == ["crate-a"] def test_select_vendor_root_single_lock_without_manifest_raises( @@ -286,20 +413,23 @@ def test_select_vendor_root_single_lock_without_manifest_raises( """A standalone ``Cargo.lock`` with no sibling ``Cargo.toml`` fails closed.""" monkeypatch.setattr(materializer, "_git", lambda *_a, **_k: b"") with pytest.raises(RuntimeError, match="no sibling Cargo.toml"): - materializer._select_vendor_root(Path("/unused"), "a" * 40, ["crate-a/Cargo.lock"]) + materializer._select_vendor_roots(Path("/unused"), "a" * 40, ["crate-a/Cargo.lock"]) -def test_select_vendor_root_multiple_locks_without_workspace_raises( +def test_select_vendor_roots_covers_independent_standalone_crates( monkeypatch: pytest.MonkeyPatch, ) -> None: - """Two independent standalone crates with no shared workspace root fail closed.""" + """Two independent crates are both vendored; neither lock may be dropped. + + ``--sync`` vendors any set of manifests into one directory, so nesting is not + required for the union to be correct and there is nothing left to guess. + """ monkeypatch.setattr(materializer, "_git", lambda *_a, **_k: b'[package]\nname = "x"\n') - with pytest.raises(RuntimeError, match="more than one Cargo.lock"): - materializer._select_vendor_root( - Path("/unused"), - "a" * 40, - ["crate-a/Cargo.toml", "crate-a/Cargo.lock", "crate-b/Cargo.toml", "crate-b/Cargo.lock"], - ) + assert materializer._select_vendor_roots( + Path("/unused"), + "a" * 40, + ["crate-a/Cargo.toml", "crate-a/Cargo.lock", "crate-b/Cargo.toml", "crate-b/Cargo.lock"], + ) == ["crate-a", "crate-b"] def test_placeholder_target_paths_covers_explicit_lib_and_bin_entries() -> None: @@ -328,7 +458,7 @@ def test_reconstruct_base_tree_does_not_overwrite_an_existing_placeholder( """Running placeholder synthesis twice for the same manifest is a no-op the second time.""" repo = tmp_path / "repo" _init_repo(repo) - _write_single_crate_workspace(repo) + _write_single_crate_workspace(repo, generate_lock=False) base_sha = _commit_all(repo) cargo_paths = materializer._regular_cargo_blob_paths(repo, base_sha) @@ -346,7 +476,7 @@ def test_run_cargo_vendor_propagates_missing_binary(tmp_path: Path) -> None: """A missing ``cargo`` executable surfaces as a materialize() ``RuntimeError``.""" repo = tmp_path / "repo" _init_repo(repo) - _write_single_crate_workspace(repo) + _write_single_crate_workspace(repo, generate_lock=False) base_sha = _commit_all(repo) with pytest.MonkeyPatch.context() as monkeypatch: @@ -365,7 +495,7 @@ def test_materialize_surfaces_cargo_vendor_failure_detail( """A non-zero ``cargo vendor`` exit is reported with its captured stderr detail.""" repo = tmp_path / "repo" _init_repo(repo) - _write_single_crate_workspace(repo) + _write_single_crate_workspace(repo, generate_lock=False) base_sha = _commit_all(repo) monkeypatch.setattr( @@ -385,7 +515,7 @@ def test_materialize_surfaces_cargo_vendor_failure_with_no_stderr( """A non-zero ``cargo vendor`` exit with empty stderr still names the exit status.""" repo = tmp_path / "repo" _init_repo(repo) - _write_single_crate_workspace(repo) + _write_single_crate_workspace(repo, generate_lock=False) base_sha = _commit_all(repo) monkeypatch.setattr( diff --git a/tests/test_materialize_base_rust_path_safety.py b/tests/test_materialize_base_rust_path_safety.py new file mode 100644 index 0000000000..cd726ffebb --- /dev/null +++ b/tests/test_materialize_base_rust_path_safety.py @@ -0,0 +1,52 @@ +from __future__ import annotations + +import subprocess +from pathlib import Path + +import pytest + +from scripts.ci import materialize_base_rust_dependencies as materializer + + +def _git(repo: Path, *arguments: str) -> str: + """Run Git for the materializer path-safety fixture.""" + return subprocess.run( + ["git", "-C", str(repo), *arguments], + check=True, + capture_output=True, + text=True, + ).stdout.strip() + + +@pytest.mark.parametrize("absolute_target", [False, True]) +def test_reconstruct_base_tree_rejects_target_path_outside_manifest_root( + tmp_path: Path, absolute_target: bool +) -> None: + """A trusted manifest cannot make placeholder synthesis escape its root.""" + escaped_path = tmp_path / ("absolute-escaped.rs" if absolute_target else "escaped.rs") + target_path = str(escaped_path) if absolute_target else "zzz/../../escaped.rs" + repo = tmp_path / "repo" + repo.mkdir() + _git(repo, "init") + _git(repo, "config", "user.name", "Test") + _git(repo, "config", "user.email", "test@example.invalid") + (repo / "Cargo.toml").write_text( + '[package]\nname = "probe"\nversion = "0.1.0"\n' + f'[lib]\npath = "{target_path}"\n', + encoding="utf-8", + ) + (repo / "Cargo.lock").write_text("# fixture lock\n", encoding="utf-8") + _git(repo, "add", "-A") + _git(repo, "commit", "-m", "path safety fixture") + base_sha = _git(repo, "rev-parse", "HEAD") + work_dir = tmp_path / "work" + + with pytest.raises(RuntimeError, match="target path must stay inside its manifest root"): + materializer._reconstruct_base_tree( + repo, + base_sha, + ["Cargo.toml", "Cargo.lock"], + work_dir, + ) + + assert not escaped_path.exists()