diff --git a/src/conductor/executor/linkify.py b/src/conductor/executor/linkify.py index acd8a484..67d294fa 100644 --- a/src/conductor/executor/linkify.py +++ b/src/conductor/executor/linkify.py @@ -236,7 +236,7 @@ def _try_linkify_path(token: str, base_dir: Path | None) -> str | None: if base_dir is not None: try: resolved_base = base_dir.resolve() - candidate = (base_dir / stripped).resolve() + candidate = (base_dir / normalized).resolve() # Security: must be within base_dir (path-aware containment, not # string-prefix — `/foo/bar` must not match `/foo/barbaz/...`). if not candidate.is_relative_to(resolved_base): diff --git a/tests/test_executor/test_linkify.py b/tests/test_executor/test_linkify.py index ad890c39..1d040af6 100644 --- a/tests/test_executor/test_linkify.py +++ b/tests/test_executor/test_linkify.py @@ -100,6 +100,13 @@ def test_nested_path(self, tmp_path: Path) -> None: result = linkify_markdown("Plan at docs/projects/plan.md", base_dir=tmp_path) assert "[docs/projects/plan.md](docs/projects/plan.md)" in result + def test_windows_relative_path_separator(self, tmp_path: Path) -> None: + (tmp_path / "docs").mkdir() + (tmp_path / "docs" / "readme.md").write_text("hello") + + result = linkify_markdown(r"See docs\readme.md for details", base_dir=tmp_path) + assert r"[docs\readme.md](docs/readme.md)" in result + def test_nonexistent_file_not_linked(self, tmp_path: Path) -> None: result = linkify_markdown("See docs/missing.md for details", base_dir=tmp_path) assert "[docs/missing.md]" not in result