fix: correct path traversal docstring, total_raw inflation, and config re-stat (v0.5.8)

- executor.py: fix _safe_person_dir docstring — realpath+startswith is the
  load-bearing traversal guard; islink is a supplementary early-exit for the
  symlink sub-case only. The previous comment "checking after realpath would be
  too late" implied islink was the primary guard, which is backwards.

- immich_api.py: move total_raw accumulation to after the dead-end-page break
  so all-garbage pages don't inflate the count and produce misleading
  "N total, 0 recent" output. Mixed pages (some valid, some non-dict) still
  count page_count so transient schema issues don't shrink MIN_FACE_COUNT below
  threshold. Add warning when a RequestException interrupts pagination mid-way
  so operators know total_raw is a lower bound.

- config.py: eliminate residual TOCTOU — change `if config_file.exists():` to
  `if _data_cfg_exists or config_file.exists():` so _data_cfg is never
  stat'd twice (the v0.5.7 fix cached the first check but not the second).
This commit is contained in:
2026-06-15 00:41:00 +00:00
parent 9685c310af
commit 1556d90bcc
6 changed files with 39 additions and 12 deletions
+5 -3
View File
@@ -42,9 +42,11 @@ def _safe_person_dir(output_dir: str, person_name: str) -> str:
"""Return the output subdirectory for a person, raising ValueError on path traversal.
os.path.join silently discards output_dir when person_name is absolute,
and '../..' sequences resolve outside the tree. Both are rejected here.
Symlinks on the raw (unresolved) path are also rejected — checking after
realpath would be too late because realpath follows the link first.
and '../..' sequences resolve outside the tree. Both are rejected by the
realpath+startswith guard, which is the load-bearing security check.
The islink check below provides an earlier, cleaner error message for the
symlink sub-case; it is redundant with (not a replacement for) the
realpath+startswith traversal check.
"""
raw = os.path.join(output_dir, person_name)
if os.path.islink(raw):