fix: audit hardening — input validation, error handling, and robustness (#25)

* fix: audit hardening — input validation, error handling, and robustness

- immich_api: guard person["id"] with .get() + early return on missing field
- immich_api: include page number in pagination exception log
- immich_api: validate faces response is a list before indexing
- executor: wrap Image.open() in try/except for non-image HTTP responses
- executor: strip leading 'v' from Frigate version before parsing (v0.16.0 was misread)
- config: wrap FRIGATE_SCORE_CEILING float() parse in try/except with warning
- config: warn when both DATA_DIR and legacy CWD config files exist simultaneously
- scheduler: wrap PID file write in try/except so /tmp failures don't crash startup
- scheduler: clamp sleep to 60s max to bound recovery time after NTP clock jumps
- frigate_api: log unexpected non-list type in get_frigate_person_files at DEBUG

* fix: LIMIT env var crash and symlink guard on person output dir

- jobs: wrap int(LIMIT) parse in try/except — bad value (e.g. "30.5", "all")
  now logs a warning and falls back to the default instead of crashing
- executor: check for symlink before shutil.rmtree on person_dir — prevents
  following a symlink out of OUTPUT_DIR on a shared volume

* chore: bump version to 0.5.3
This commit is contained in:
2026-06-14 19:39:35 -04:00
committed by GitHub
parent 166729a17d
commit 2e08504682
8 changed files with 88 additions and 14 deletions
+5 -2
View File
@@ -31,7 +31,10 @@ def _run_scheduler() -> None:
print("Error: CRON_SCHEDULE environment variable is required.", flush=True)
sys.exit(1)
Path("/tmp/winnow.pid").write_text(str(os.getpid()))
try:
Path("/tmp/winnow.pid").write_text(str(os.getpid()))
except OSError as e:
print(f"Warning: could not write PID file: {e}", flush=True)
now = time.time()
cron = croniter(schedule, now)
@@ -53,7 +56,7 @@ def _run_scheduler() -> None:
print(f"winnow run failed: {e}", flush=True)
next_run = cron.get_next(float)
print(f"Next run: {time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(next_run))}", flush=True)
time.sleep(max(1, next_run - time.time()))
time.sleep(min(60, max(1, next_run - time.time())))
if __name__ == "__main__":