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

  1. If you are the client author: send a valid non-negative Content-Length (or omit the body and use GET).
  2. 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.
  3. 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

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


AI-assisted analysis of MemPalace/mempalace@06cb6987f0 (2026-08-15). Data as JSON: /api/errors/d5509560cdfeae85. Report an issue: GitHub.