Context
The broker enforces cumulative-exposure session limits, but there is no request-rate limiter on either inbound transport. A grep -rniE '429|Retry-After|rate.?limit|RateLimit|TooManyRequests|slowapi|token.?bucket' over nautilus/transport/ returns nothing.
Confirmed against the code:
POST /v1/request (nautilus/transport/fastapi_app.py:274) accepts unbounded request volume.
- The
nautilus_request MCP tool (nautilus/transport/mcp_server.py:232) is equally unbounded.
ApiConfig (nautilus/config/models.py:294) exposes only host, port, keys — no rate-limit field.
The roadmap (design-docs/05-ecosystem-roadmap.md:516,587) specifies only write-side capability gates (max_writes_per_minute / max_invocations_per_minute) as future work, not a read-path limiter. (Note: the rate_limit: 100/hour in design-docs/04-architecture-operations.md:216 is a per-source outbound throttle on the threat_intel REST source — a different concern, not an inbound limiter.)
Impact: a single runaway agent can flood POST /v1/request, exhausting adapter pools and inflating the append-only audit log (audit.jsonl is already ~1.3MB) with no backstop, and starving other agents.
Task
Add an optional rate limiter (fixed-window or token-bucket) keyed by agent_id (optionally by API key/source), off by default.
- Add a
rate_limit field to ApiConfig (requests-per-window, window, key strategy). Absent config ⇒ no limiting.
- Enforce in the FastAPI
POST /v1/request path (and its /v1/query alias) returning HTTP 429 + Retry-After.
- Mirror the limit in the MCP
nautilus_request tool, returning a structured throttle error.
- Emit a
rate_limited audit event via the existing sparse-event path (AuditLogger.emit_event, nautilus/audit/logger.py:186) so throttling is observable.
Design decisions to settle before implementation (hence needs-design): in-memory vs shared/distributed bucket (multi-worker uvicorn), exact key strategy (agent vs api_key vs both), and the MCP error shape.
Where
nautilus/transport/fastapi_app.py (request handler ~_handle_request/post_request, lines 252-298)
nautilus/transport/mcp_server.py (nautilus_request tool, line 232)
nautilus/config/models.py (ApiConfig, line 294)
nautilus/audit/logger.py (emit_event, line 186) for the audit event
Acceptance criteria
Size: M
Context
The broker enforces cumulative-exposure session limits, but there is no request-rate limiter on either inbound transport. A
grep -rniE '429|Retry-After|rate.?limit|RateLimit|TooManyRequests|slowapi|token.?bucket'overnautilus/transport/returns nothing.Confirmed against the code:
POST /v1/request(nautilus/transport/fastapi_app.py:274) accepts unbounded request volume.nautilus_requestMCP tool (nautilus/transport/mcp_server.py:232) is equally unbounded.ApiConfig(nautilus/config/models.py:294) exposes onlyhost,port,keys— no rate-limit field.The roadmap (
design-docs/05-ecosystem-roadmap.md:516,587) specifies only write-side capability gates (max_writes_per_minute/max_invocations_per_minute) as future work, not a read-path limiter. (Note: therate_limit: 100/hourindesign-docs/04-architecture-operations.md:216is a per-source outbound throttle on thethreat_intelREST source — a different concern, not an inbound limiter.)Impact: a single runaway agent can flood
POST /v1/request, exhausting adapter pools and inflating the append-only audit log (audit.jsonlis already ~1.3MB) with no backstop, and starving other agents.Task
Add an optional rate limiter (fixed-window or token-bucket) keyed by
agent_id(optionally by API key/source), off by default.rate_limitfield toApiConfig(requests-per-window, window, key strategy). Absent config ⇒ no limiting.POST /v1/requestpath (and its/v1/queryalias) returning HTTP 429 +Retry-After.nautilus_requesttool, returning a structured throttle error.rate_limitedaudit event via the existing sparse-event path (AuditLogger.emit_event,nautilus/audit/logger.py:186) so throttling is observable.Design decisions to settle before implementation (hence
needs-design): in-memory vs shared/distributed bucket (multi-worker uvicorn), exact key strategy (agent vs api_key vs both), and the MCP error shape.Where
nautilus/transport/fastapi_app.py(request handler ~_handle_request/post_request, lines 252-298)nautilus/transport/mcp_server.py(nautilus_requesttool, line 232)nautilus/config/models.py(ApiConfig, line 294)nautilus/audit/logger.py(emit_event, line 186) for the audit eventAcceptance criteria
rate_limitconfig ⇒ no limiting; existing behavior unchanged (covered by a-m unittest).Retry-Afteron REST, structured throttle error on MCP.rate_limitedaudit entry is written when a request is throttled.uv run ruff check,ruff format --check, andpyrightpass; new code covered by-m unittests.Size: M