fix: resolve relative folder paths against anime dir in delete_series

The folder column in anime_series stores the relative folder name
(e.g. 'Beyblade Burst (2016)'), not an absolute path. The old
delete_series code called os.path.abspath(folder_path) directly,
which joins a relative path against the process current working
directory. Inside the container the FastAPI app runs with CWD=/app
while the anime directory is /data, so 'Beyblade Burst (2016)'
resolved to '/app/Beyblade Burst (2016)' and the is_safe_path check
correctly (but unhelpfully) flagged it as outside the /data base,
skipping the folder delete while still removing the row.

Fix: resolve relative folder paths against the configured anime
directory before validating against the base. Absolute paths still
work unchanged.

Also harden is_safe_path() the same way so a relative target is
treated as relative to base_path, not to the process CWD. Path
traversal ('../etc/passwd') is still rejected.

Adds two regression tests:
- is_safe_path with chdir to '/' and relative target resolves
  against the base
- delete_series with chdir to '/', relative folder in DB,
  succeeds and removes the folder
This commit is contained in:
2026-08-28 21:01:10 +02:00
parent 7df9a84ae9
commit 14f12e55e7
4 changed files with 139 additions and 12 deletions

View File

@@ -213,6 +213,34 @@ class TestIsSafePath:
"/anime/Attack on Titan/Season 1/Episode 1"
)
def test_relative_target_resolved_against_base(self):
"""Relative targets resolve against the base, not the process CWD.
Regression test: previously `os.path.abspath(target_path)` would
join a relative target against the process's current working
directory. When the CWD differed from `base_path` (e.g. the
FastAPI app running with CWD=/app while the anime directory is
/data), a relative folder name like "Beyblade Burst (2016)"
would be resolved to "/app/Beyblade Burst (2016)" and
incorrectly rejected as outside the base. The helper now
treats a relative target as relative to `base_path`.
"""
with tempfile.TemporaryDirectory() as tmpdir:
base = os.path.abspath(tmpdir)
# Simulate a process CWD different from base
old_cwd = os.getcwd()
try:
os.chdir("/")
# Relative target inside base should be safe
assert is_safe_path(base, "Beyblade Burst (2016)")
# Nested relative target should also be safe
assert is_safe_path(base, "Beyblade Burst (2016)/Season 1")
# Relative traversal (../) must still be rejected even
# when resolved against the base
assert not is_safe_path(base, "../etc/passwd")
finally:
os.chdir(old_cwd)
class TestCreateSafeFolder:
"""Test create_safe_folder function."""