MemPalace/mempalace · warning · ValueError
invalid Content-Length
Error message
invalid Content-Length
What it means
ValueError('invalid Content-Length') raised in the daemon's HTTP request handler (_read_json, mempalace/daemon.py:936) when the Content-Length header is negative. A negative length would make self.rfile.read(-1) read until the client closes the connection, blocking a worker thread and bypassing the MAX_BODY_BYTES cap — so it is rejected explicitly as a hardening measure. This error is produced inside the daemon server; a client that sends the malformed header receives a 400 response carrying this message.
Source
Thrown at mempalace/daemon.py:936
timeout = 10
def log_message(self, fmt, *args): # pragma: no cover - stdlib access logging noise
return
def _authorized(self) -> bool:
auth = self.headers.get("Authorization")
if auth and secrets.compare_digest(auth, f"Bearer {token}"):
return True
_json_response(self, 401, {"error": "unauthorized"})
return False
def _read_json(self) -> dict[str, Any]:
length = int(self.headers.get("Content-Length", "0") or "0")
# Reject a negative Content-Length explicitly: self.rfile.read(-1)
# would read until the client closes the connection, blocking the
# worker and bypassing the MAX_BODY_BYTES cap (an auth-gated DoS).
if length < 0:
raise ValueError("invalid Content-Length")
if length > MAX_BODY_BYTES:
raise ValueError("request body too large")
raw = self.rfile.read(length)
return json.loads(raw.decode("utf-8")) if raw else {}
def do_GET(self):
if not self._authorized():
return
try:
self._handle_get()
except Exception as exc: # noqa: BLE001 - malformed query/DB error → 400
_json_response(self, 400, {"error": str(exc)})
def _handle_get(self):
parsed = urlparse(self.path)
if parsed.path == "/health":
_json_response(
self,View on GitHub (pinned to 06cb6987f0)
Solutions
- If you are the client author: send a valid non-negative Content-Length (or omit the body and use GET).
- If you see this in daemon logs: identify the client sending malformed headers; it already has the bearer token, so treat it as a buggy or compromised client and investigate.
- Use urllib/requests-style clients that compute Content-Length automatically instead of raw sockets.
Example fix
# before (raw socket) sock.sendall(b"POST /jobs HTTP/1.1\r\nContent-Length: -1\r\n\r\n") # after import json, urllib.request req = urllib.request.Request(url, data=json.dumps(payload).encode(), method="POST")
Defensive patterns
Strategy: validation
Validate before calling
# client-side: never send a negative Content-Length body = json.dumps(payload).encode() assert len(body) >= 0 # any real serializer produces this; raw sockets don't
Try / catch
# server-side: the handler already converts this to a 400 JSON response
try:
data = self._read_json()
except ValueError as exc:
_json_response(self, 400, {"error": str(exc)}) Prevention
- Use standard HTTP libraries (urllib, requests) that compute Content-Length for you.
- Never hand-craft Content-Length headers in tests or proxies.
- Monitor daemon 400s — repeated malformed headers from an authenticated client indicate a bug or compromise.
When it happens
Trigger: A hand-rolled HTTP client sending 'Content-Length: -1'; a corrupted/truncated request replayed by a proxy or test harness; negative Content-Length crafted by an authenticated caller (it is behind bearer auth, so it is an auth-gated DoS being prevented, not an anonymous one).
Common situations: Testing the daemon with custom sockets or curl with miscomputed lengths; buggy middleware that rewrites headers; security scanners probing the local endpoint after reading the token.
Related errors
- request body too large
- daemon endpoint pid is not alive
- daemon returned non-JSON response: {raw[:200]!r}
- daemon is not running
- daemon is not running; job {args.job_id} is {job['state']}
AI-assisted analysis of MemPalace/mempalace@06cb6987f0 (2026-08-15).
Data as JSON: /api/errors/d5509560cdfeae85.
Report an issue: GitHub.