Security: request-size caps, concurrency limits, model-pull allowlist #3

Merged
enderofwings merged 3 commits from Athena/NexusOS:harden/3-resource-limits into main 2026-08-07 15:52:07 +00:00
Contributor

Summary

DoS guardrails for the unauthenticated local APIs (synapse/main.py, synapse/nexus_config.py):

  • Request body cap — middleware rejects bodies over NEXUS_MAX_REQUEST_MB (default 32 MB) with 413.
  • Upload caps/documents/upload enforces a decoded-size limit (NEXUS_MAX_UPLOAD_MB, default 20 MB) so a small base64 payload can't balloon in RAM, and PDF parsing stops at NEXUS_MAX_PDF_PAGES (default 500).
  • Concurrency limits — at most NEXUS_MAX_CONCURRENT_CHATS (4) simultaneous inference streams and NEXUS_MAX_CONCURRENT_UPLOADS (2) document ingests; excess requests get 429 instead of exhausting CPU/RAM.
  • Model-pull allowlist — opt-in NEXUS_MODEL_ALLOWLIST bounds which models /models/pull may download (a bare repo name matches all its tags). Empty default preserves current behaviour.

All limits are env-overridable with generous single-user defaults.

Testing

  • Oversized Content-Length gets 413 from the middleware; oversized decoded upload gets 413 from the endpoint.
  • PDF page cap verified against a generated many-page PDF.
  • Concurrency limiter unit-tested (acquire/release/slot, 429 when saturated).
  • Allowlist matching unit-tested (exact, repo-prefix, case-insensitivity, empty-list passthrough).
  • pytest smoke tests pass.

Stack note

This is PR 3 of 3, stacked on "SSRF guard + approval tokens" (which stacks on the network-boundary PR). Until those merge, this diff also shows their commits — review only the latest commit here, or merge in order.

## Summary DoS guardrails for the unauthenticated local APIs (`synapse/main.py`, `synapse/nexus_config.py`): - **Request body cap** — middleware rejects bodies over `NEXUS_MAX_REQUEST_MB` (default 32 MB) with 413. - **Upload caps** — `/documents/upload` enforces a decoded-size limit (`NEXUS_MAX_UPLOAD_MB`, default 20 MB) so a small base64 payload can't balloon in RAM, and PDF parsing stops at `NEXUS_MAX_PDF_PAGES` (default 500). - **Concurrency limits** — at most `NEXUS_MAX_CONCURRENT_CHATS` (4) simultaneous inference streams and `NEXUS_MAX_CONCURRENT_UPLOADS` (2) document ingests; excess requests get 429 instead of exhausting CPU/RAM. - **Model-pull allowlist** — opt-in `NEXUS_MODEL_ALLOWLIST` bounds which models `/models/pull` may download (a bare repo name matches all its tags). Empty default preserves current behaviour. All limits are env-overridable with generous single-user defaults. ## Testing - Oversized `Content-Length` gets 413 from the middleware; oversized decoded upload gets 413 from the endpoint. - PDF page cap verified against a generated many-page PDF. - Concurrency limiter unit-tested (acquire/release/slot, 429 when saturated). - Allowlist matching unit-tested (exact, repo-prefix, case-insensitivity, empty-list passthrough). - `pytest` smoke tests pass. ## Stack note This is **PR 3 of 3**, stacked on "SSRF guard + approval tokens" (which stacks on the network-boundary PR). Until those merge, this diff also shows their commits — review only the latest commit here, or merge in order.
Athena added 3 commits 2026-08-07 14:50:14 +00:00
The Synapse backend and memory service bound 0.0.0.0 with wildcard CORS and no
auth, exposing the full unauthenticated admin/data API to the LAN. Default the
uvicorn bind to 127.0.0.1 (NEXUS_BIND_HOST override), scope CORS to known local
origins instead of "*", and add TrustedHostMiddleware to reject foreign Host
headers (which defeats DNS-rebinding, something same-origin CORS cannot stop).

NEXUS_ALLOWED_HOSTS / NEXUS_ALLOWED_ORIGINS allow opt-in LAN exposure, intended
to be paired with real authentication.

Co-authored-by: Cursor <cursoragent@cursor.com>
Two tool/agent-layer hardening changes:

* fetch_url now resolves the target host and refuses to connect if any
  resolved address is loopback, private (RFC1918/ULA), link-local (incl. the
  169.254.169.254 cloud-metadata endpoint), multicast, reserved, or
  unspecified. IPv4-mapped IPv6 is unwrapped first, and the guard re-runs on
  every redirect hop so a public URL cannot 302 its way to an internal target.

* /chat/approve now requires a single-use token minted when the stream pauses
  for approval and delivered only in that stream's tool_request event, compared
  in constant time. Previously the pending approval was keyed solely on a
  client-supplied conversation_id, so anyone who could enumerate a
  conversation_id could approve another client's pending action.

The frontend threads the token from the tool_request event into the approve
call.

Co-authored-by: Cursor <cursoragent@cursor.com>
DoS/quota guardrails for the unauthenticated local APIs:

* Body-size middleware rejects oversized requests (Content-Length) before they
  are buffered/base64-decoded (NEXUS_MAX_REQUEST_MB, default 32).
* Document upload enforces a decoded-byte cap (NEXUS_MAX_UPLOAD_MB, default 20)
  and a PDF page-count cap (NEXUS_MAX_PDF_PAGES, default 500) as backstops for
  chunked bodies and pathological files.
* A counter-based in-flight limiter bounds concurrent chats and document
  ingests (NEXUS_MAX_CONCURRENT_CHATS/UPLOADS), returning 429 when saturated;
  the chat slot is held for the whole SSE stream and released on completion or
  client disconnect.
* /models/pull gains an opt-in allowlist (NEXUS_MODEL_ALLOWLIST); empty by
  default so behaviour is unchanged, otherwise a bare repo name permits all its
  tags.

Co-authored-by: Cursor <cursoragent@cursor.com>
enderofwings merged commit b5541d1c48 into main 2026-08-07 15:52:07 +00:00
Sign in to join this conversation.
No Reviewers
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: enderofwings/NexusOS#3