feat: enhance symlink detection and cache invalidation

- Add `_entry_is_symlink` method to detect symlinks and Windows junctions
- Include first-level symlinks in fingerprint for better cache invalidation
- Re-enable preview path validation for security
- Update tests to verify retargeted symlinks trigger rescan
This commit is contained in:
Will Miao
2026-01-25 19:14:16 +08:00
parent 6142b3dc0c
commit c9e305397c
4 changed files with 117 additions and 43 deletions

View File

@@ -39,33 +39,32 @@ async def test_preview_handler_serves_preview_from_active_library(tmp_path):
assert response.status == 200
assert Path(response._path) == preview_file
# TODO: disable temporarily. Enable this once the symlink scan bug fixed
# async def test_preview_handler_forbids_paths_outside_active_library(tmp_path):
# allowed_root = tmp_path / "allowed"
# allowed_root.mkdir()
# forbidden_root = tmp_path / "forbidden"
# forbidden_root.mkdir()
# forbidden_file = forbidden_root / "sneaky.webp"
# forbidden_file.write_bytes(b"x")
async def test_preview_handler_forbids_paths_outside_active_library(tmp_path):
allowed_root = tmp_path / "allowed"
allowed_root.mkdir()
forbidden_root = tmp_path / "forbidden"
forbidden_root.mkdir()
forbidden_file = forbidden_root / "sneaky.webp"
forbidden_file.write_bytes(b"x")
# config = Config()
# config.apply_library_settings(
# {
# "folder_paths": {
# "loras": [str(allowed_root)],
# "checkpoints": [],
# "unet": [],
# "embeddings": [],
# }
# }
# )
config = Config()
config.apply_library_settings(
{
"folder_paths": {
"loras": [str(allowed_root)],
"checkpoints": [],
"unet": [],
"embeddings": [],
}
}
)
# handler = PreviewHandler(config=config)
# encoded_path = urllib.parse.quote(str(forbidden_file), safe="")
# request = make_mocked_request("GET", f"/api/lm/previews?path={encoded_path}")
handler = PreviewHandler(config=config)
encoded_path = urllib.parse.quote(str(forbidden_file), safe="")
request = make_mocked_request("GET", f"/api/lm/previews?path={encoded_path}")
# with pytest.raises(web.HTTPForbidden):
# await handler.serve_preview(request)
with pytest.raises(web.HTTPForbidden):
await handler.serve_preview(request)
async def test_config_updates_preview_roots_after_switch(tmp_path):