Skip to content

fix(daemon): validate JSON request size before parsing Add MAX_REQUEST - #89

Open
DeryFerd wants to merge 2 commits into
Growth-Circle:mainfrom
DeryFerd:fix/json-request-size-limit
Open

DeryFerd wants to merge 2 commits into
Growth-Circle:mainfrom
DeryFerd:fix/json-request-size-limit

Conversation

@DeryFerd

Copy link
Copy Markdown
Contributor

Problem

The daemon's request handling doesn't check incoming JSON line sizes before parsing. This creates a memory exhaustion vector where malicious or buggy clients can send arbitrarily large payloads and force the daemon to allocate huge buffers during deserialization.

The vulnerable path is in dispatch_request():

  1. Client connects (TCP or Unix socket)
  2. Daemon reads line with lines.next_line().await? or reader.lines()
  3. Line gets passed directly to serde_json::from_str::<RequestEnvelope>(line)
  4. If the JSON is massive, serde allocates accordingly before we even know if it's valid

This is a local attack surface issue. The daemon runs as a trusted process, but nothing stops a compromised client or a bug in the CLI from sending multi-GB requests. On Windows, where TCP is the default transport, this is slightly worse because TCP doesn't have the same filesystem permission constraints as Unix sockets.

What This PR Does

Adds a size check before JSON parsing:

const MAX_REQUEST_LINE_BYTES: usize = 10 * 1024 * 1024; // 10 MB

if line.len() > MAX_REQUEST_LINE_BYTES {
    return rejection_response("request_too_large", "request line exceeds maximum size...");
}

Requests over 10 MB get rejected with a clear error code and message. The check happens after the line is read but before serde_json touches it, so we're still reading the full line into memory once, but we avoid the deserialization overhead and structured allocation that happens inside serde.

Why 10 MB?

This is deliberately generous. Normal protocol requests are tiny:

  • MessageSend with a reasonable prompt: <50 KB
  • EventsSubscribe: <1 KB
  • SessionCreate: <500 bytes

Even a MessageSend with a 100,000-token context (way beyond typical usage) would still be under 1 MB as JSON. 10 MB gives us 10x headroom for weird edge cases while still preventing absurd payloads.

If someone legitimately needs to send >10 MB requests, that's a protocol design issue, not a limit we should silently accommodate.

Trade-offs

Why not use a streaming JSON parser?
We could switch to something like serde_json::Deserializer::from_reader() with a length-limited reader, but that's a bigger refactor. This PR is a targeted fix that addresses the immediate DoS vector without changing the protocol dispatch flow.

Why not reject at the transport layer?
Tokio's BufReader already buffers internally, so by the time we have a "line," we've already read it. We'd need to intercept at a lower level (custom AsyncRead wrapper) to avoid the allocation entirely. That's possible but adds complexity. This approach is simpler: we read the line, check its size, and bail early if it's ridiculous.

What about legitimate large payloads?
If we ever need chunked uploads, large file attachments, or streaming context, those should use a different protocol path (e.g., multipart protocol frames, separate file upload endpoint). The line-delimited JSON protocol isn't designed for that.

Validation

I ran the existing daemon manually and verified:

  • Normal requests (<1 KB) work fine
  • The constant is in place and the check compiles

I couldn't run the full test suite due to linker issues on Windows, but the change is straightforward: size check before parse, early rejection if oversized. The logic doesn't touch any other daemon behavior.

Related Work

This complements existing security PRs:

Those address auth and credential leakage. This addresses resource exhaustion. Together they harden the daemon against local threats.

Next Steps

After this PR:

  1. Consider adding similar checks for event payload sizes in EventLog::append_event
  2. Add integration test that verifies rejection behavior for oversized requests
  3. Document the limit in protocol specification or config reference

For now, this closes the immediate gap without requiring protocol changes or extensive refactoring.

…T_LINE_BYTES constant (10 MB) and reject requests exceeding this limit before JSON parsing. This prevents memory exhaustion from malicious or buggy clients sending extremely large payloads. The check happens early in dispatch_request, returning a request_too_large error with a clear message about the size limit.
@chatgpt-codex-connector

Copy link
Copy Markdown

Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits.
Credits must be used to enable repository wide code reviews.

…ity fixes for CI audit failures: - Upgrade quinn-proto from 0.11.14 to 0.11.15 Fixes: RUSTSEC-2026-0185 - Remote memory exhaustion from unbounded out-of-order stream reassembly (severity 7.5 high) - Force form-data to ^4.0.6 via pnpm overrides Fixes: GHSA-hmw2-7cc7-3qxx - CRLF injection in form-data via unescaped multipart field names and filenames (severity high) Both vulnerabilities existed in main branch dependencies and are not introduced by this PR. Fixing them here to unblock CI checks.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant