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:
1 parent
d39ee838ff
commit
aae5857dba
8 files changed
+122
-32
No files matched your search
+13
-4
@@ -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__":
|
||||
|
||||
Reference in new issue
Block a user