harden: stream-token host allowlist (SSRF), CSRF header on mutating routes, atomic JSON writes

- /stream/{token}: tokens are plain base64; allowlist https://hls.anidb.app
  so a session can't use the proxy for arbitrary URLs (new test)
- X-Aniweb:1 required on POST/DELETE except /login — SameSite=Lax is
  still sent on cross-site form POSTs, which could hit /account/delete
  (new test; SPA + tests send the header)
- tmp+os.replace writes for users/history/searches: a crash mid-write
  could corrupt the JSON -> load() returned {} -> silent data loss
- resume positions now read from full history (?all=1), not top-5
- pagehide bound once; savePos reads currentEp (no stale closures)
- account delete also removes the searches file; episodes TTL 5m
- ani-web.service: supervised restart (not installed; see header)
- gitignore pi-session dumps + .pytest_cache
This commit is contained in:
jozamudi committed 2026-08-23 23:22:09 +00:00
1 parent d39ee838ff
commit aae5857dba
8 files changed
+122 -32

No files matched your search

+13 -4
View File
@@ -51,12 +51,20 @@ def load() -> dict:
return {}
def atomic_write(path, text):
"""tmp + os.replace: a crash mid-write leaves the previous file intact
(in-place writes corrupted the file -> load() returned {} -> accounts
or history silently vanished)."""
tmp = path.parent / (path.name + ".tmp")
fd = os.open(tmp, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o600)
with os.fdopen(fd, "w") as f:
f.write(text)
os.replace(tmp, path)
def save(users: dict):
DATA.mkdir(exist_ok=True)
# ponytail: mode 0600 on create; won't tighten an existing file
fd = os.open(USERS, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o600)
with os.fdopen(fd, "w") as f:
json.dump(users, f, indent=1)
atomic_write(USERS, json.dumps(users, indent=1))
def add(username: str, password: str):
@@ -90,6 +98,7 @@ def remove(username: str):
users.pop(u, None)
save(users)
(DATA / "history" / f"{u}.json").unlink(missing_ok=True)
(DATA / "searches" / f"{u}.json").unlink(missing_ok=True)
if __name__ == "__main__":