Bug
No HTTP handler uses http.MaxBytesReader to limit request body size. All JSON body parsing goes through json.NewDecoder(r.Body) which reads the entire body into memory without any size constraint. While the daemon is loopback-only (127.0.0.1), a buggy or malicious client can send a multi-gigabyte request body, exhausting daemon memory and crashing the process.
Analyzed against: 96d1649 (current main)
Confidence: High — verified all decodeJSON/decodeJSONStrict call sites.
Affected Endpoints
All endpoints that parse a JSON request body:
POST /api/v1/projects (AddProject)
PUT /api/v1/projects/{id}/config (SetProjectConfig)
POST /api/v1/sessions (Spawn)
POST /api/v1/sessions/{sessionId}/send (Send)
PATCH /api/v1/sessions/{sessionId} (Rename)
POST /api/v1/sessions/{sessionId}/rollback (Rollback)
POST /api/v1/prs/{id}/resolve-comments (ResolveComments)
POST /api/v1/reviews/execute (TriggerReview)
POST /api/v1/reviews/{id}/send (SubmitReview)
The only application-level size check is maxPromptLen = 4096 on the prompt field in Spawn, but a body with megabytes of unknown JSON fields (which decodeJSON ignores) will still be fully read into memory before the prompt check runs.
Root Cause
backend/internal/httpd/controllers/projects.go (and sessions.go, reviews.go, prs.go) all use:
func decodeJSON(r *http.Request, v any) error {
return json.NewDecoder(r.Body).Decode(v)
}
No http.MaxBytesReader wrapping is applied to r.Body anywhere.
Reproduction
# Generate a 500MB JSON body
python3 -c "print('{\"prompt\": \"x\", \"junk\": \"' + 'A'*500000000 + '\"}')" > /tmp/big.json
# Send it to any endpoint
curl -X POST http://127.0.0.1:3001/api/v1/sessions \
-H 'Content-Type: application/json' \
--data-binary @/tmp/big.json
# Daemon memory spikes to ~500MB+ before the request is rejected
Impact
- Memory exhaustion: Daemon OOM crash from a single request
- Loopback mitigates but doesn't eliminate risk: Any process on the host can hit the daemon; also, simple POST requests from a web page (Content-Type
text/plain) bypass CORS preflight and the body is read before CORS rejection
Suggested Fix
Add a middleware or per-handler http.MaxBytesReader wrapper. A reasonable default for this daemon's payloads would be 1-4 MB:
func limitBody(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
r.Body = http.MaxBytesReader(w, r.Body, 4<<20) // 4 MB
next.ServeHTTP(w, r)
})
}
Bug
No HTTP handler uses
http.MaxBytesReaderto limit request body size. All JSON body parsing goes throughjson.NewDecoder(r.Body)which reads the entire body into memory without any size constraint. While the daemon is loopback-only (127.0.0.1), a buggy or malicious client can send a multi-gigabyte request body, exhausting daemon memory and crashing the process.Analyzed against:
96d1649(currentmain)Confidence: High — verified all
decodeJSON/decodeJSONStrictcall sites.Affected Endpoints
All endpoints that parse a JSON request body:
POST /api/v1/projects(AddProject)PUT /api/v1/projects/{id}/config(SetProjectConfig)POST /api/v1/sessions(Spawn)POST /api/v1/sessions/{sessionId}/send(Send)PATCH /api/v1/sessions/{sessionId}(Rename)POST /api/v1/sessions/{sessionId}/rollback(Rollback)POST /api/v1/prs/{id}/resolve-comments(ResolveComments)POST /api/v1/reviews/execute(TriggerReview)POST /api/v1/reviews/{id}/send(SubmitReview)The only application-level size check is
maxPromptLen = 4096on thepromptfield in Spawn, but a body with megabytes of unknown JSON fields (whichdecodeJSONignores) will still be fully read into memory before the prompt check runs.Root Cause
backend/internal/httpd/controllers/projects.go(and sessions.go, reviews.go, prs.go) all use:No
http.MaxBytesReaderwrapping is applied tor.Bodyanywhere.Reproduction
Impact
text/plain) bypass CORS preflight and the body is read before CORS rejectionSuggested Fix
Add a middleware or per-handler
http.MaxBytesReaderwrapper. A reasonable default for this daemon's payloads would be 1-4 MB: