Skip to content

Worker/hub tier: job_id path traversal, uncapped WS frames and HTTP bodies, auth hardening gaps #5

Description

@thedataengineer

Three unbounded-input problems in the worker/hub tier:

  1. Worker-side path traversal via job_id. worker.py:100 builds adapter = self._work_root / (msg.get("job_id") or "") from a hub-controlled websocket frame with no validation, then passes it to load(msg["base_model"], adapter=str(adapter)) at worker.py:110. A job_id of ../../.. escapes the work root. Workers trust the hub by design, but the id should still be validated as a single safe path segment (no separators, no ..).

  2. No cap on websocket frame size. ws.py:63-73 recv_frame reads a 64-bit payload length and allocates it via _read_exact with no maximum. Either side of the hub↔worker socket can trigger unbounded memory allocation with one frame header. A configurable cap (even a generous 512 MB default for adapter tarballs) turns this into a clean protocol error.

  3. No cap on HTTP request bodies. serve.py:1191 _body() reads Content-Length uncapped, and the artifact upload at serve.py:1426 does self.rfile.read(length) straight into memory. Same fix: reject bodies over a limit with a 413.

Related hardening in the same area, lower severity:

  • Path checks on static assets are ".." substring denylists (serve.py:1216, serve.py:1226) and dataset ids flow raw into self.root / f"{ds_id}.json" (serve.py:1250, serve.py:333). Safe today only because BaseHTTPRequestHandler does not URL-decode self.path. Resolve-and-contain (Path.resolve().is_relative_to(root)) is the robust form.
  • POST /v1/login (serve.py:1343) has no rate limit or lockout, so unlimited password guessing is possible when auth is enabled.
  • No token revocation: _valid_token (serve.py:1141) checks only expiry and signature, so a leaked 12h token cannot be invalidated except by changing the password.
  • The HF token is written world-readable: serve.py:477 writes settings.json with the default umask, and there is no chmod/0o600 anywhere in the package. Same for tokens.json (serve.py:495). Both should be written 0600.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions