P2 minor set: password off argv, configurable share gate, POST logout, log path check

- Navidrome password is now passed to curl via stdin (--data-urlencode "p@-")
  in navidrome-scan.sh and pipeline-status.sh, so it no longer appears in
  ps/proc. Verified the query sent is identical and a live scan still triggers.
- MIN_ARTIST_DIRS (the share-health gate) is now a setting, threaded through to
  the pipeline env, so a user with a small library can lower it instead of the
  scan/sync being permanently blocked by the hardcoded 500.
- /auth/logout is now POST-only (with a nav form + aria-label), so a drive-by
  GET can't log the user out; enforced allowed_email already landed separately.
- view_log now confirms the run's log_path resolves under the logs dir before
  serving it (defense in depth).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
andrew
2026-07-10 15:50:06 -06:00
parent 2641a1b874
commit 2c86d7973f
9 changed files with 40 additions and 11 deletions
+1 -1
View File
@@ -55,7 +55,7 @@ async def auth_callback(request: Request):
return RedirectResponse(url="/")
@router.get("/logout")
@router.post("/logout")
async def logout(request: Request):
request.session.clear()
end_session_endpoint = None
+8 -1
View File
@@ -99,7 +99,14 @@ async def view_log(run_id: int, user: dict = Depends(require_auth), db=Depends(g
raise HTTPException(404, "no such run/log")
from pathlib import Path
path = Path(run.log_path)
from app.settings import settings
# Containment check: only ever serve files from under the logs dir. Paths
# are app-generated so this is defense in depth against a malformed row.
logs_dir = settings.logs_dir.resolve()
path = Path(run.log_path).resolve()
if logs_dir != path and logs_dir not in path.parents:
raise HTTPException(404, "no such run/log")
if not path.exists():
raise HTTPException(404, "log file no longer exists")
return path.read_text(errors="replace")