security: harden web server, daemon socket, and write paths
Security review findings, fixed and verified: Critical - Fix unauthenticated arbitrary file read: the SPA catch-all route joined the raw URL path onto the dist dir without containment, so encoded '..' segments (/%2e%2e/etc/passwd) leaked any file readable by the root server. Resolve with realpath and reject paths outside the dist dir (fail-closed 404). High - Daemon socket: serve_start no longer accepts caller-chosen host/port. The socket is world-connectable (unprivileged CLI users), so callers could previously rebind the root web server to 0.0.0.0. The daemon now always binds the operator-configured address and reports it in the response; the CLI warns on mismatch. Medium - Remove the per-request max_delta_khz override from the API: the server-enforced safety cap is now authoritative. CLI direct paths (write, profile apply, verify) honor the configured cap; --max-delta still overrides for explicit root use. - Snapshot restore: confine filepath to the snapshot directory (realpath containment; blocks symlink escapes). - Login lockout: honor X-Forwarded-For only for peers listed in the new trusted_proxies config (rightmost untrusted hop), so the per-IP lockout works behind a reverse proxy. Spoofed headers from untrusted peers are ignored. - /api/shutdown: new allow_api_shutdown config (default true); shared systems can disable the API shutdown path. TLS (opt-in, like auth) - New ssl_certfile/ssl_keyfile config + CLI flags (serve start, service install/configure, --no-ssl to disable). When active: HTTPS for UI/API, wss:// for WebSockets, Secure session cookie, CLI auto-switches to https://. Cert/key paths are validated up front with a clear error instead of a silent uvicorn crash. Tests & docs - tests/test_security.py: standalone regression tests (no new deps) covering SPA containment, snapshot containment, cap removal, client-IP derivation, proxy normalization, TLS scheme detection, and daemon host/port hardening. - README + Usage-Guide: TLS section, new config keys, updated security notes.
This commit is contained in:
1 parent
8956fc9d7b
commit
39701c12ff
9 files changed
+648
-55
No files matched your search
@@ -121,6 +121,15 @@ def restore(gpu, snapshot_dir: str, filepath: str | None = None) -> bool:
|
||||
print(f"Snapshot file not found: {filepath}")
|
||||
return False
|
||||
|
||||
# Contain the path inside the snapshot directory — callers (in
|
||||
# particular the HTTP API) must not be able to point the restore at
|
||||
# arbitrary files on the filesystem.
|
||||
snap_dir = os.path.realpath(snapshot_dir)
|
||||
resolved = os.path.realpath(filepath)
|
||||
if not resolved.startswith(snap_dir + os.sep):
|
||||
print(f"Snapshot path outside snapshot directory: {filepath}")
|
||||
return False
|
||||
|
||||
try:
|
||||
with open(filepath, "rb") as f:
|
||||
raw = f.read()
|
||||
|
||||
Reference in new issue
Block a user