#64 serve: git upload-pack has no timeout, no concurrency cap, and fully buffers the pack
/repo.bundle/git-upload-pack spawns git upload-pack per POST with no timeout, no concurrency limit, and buffers the entire pack in RAM (wait_with_output() → Vec<u8> → respond).
Problems:
- No timeout: nothing bounds the child. The 5 s socket read timeout applies to our reads from the client, not to git finishing; a client that dribbles its want-list or stalls mid-pack keeps a thread + a
gitprocess alive for as long as it likes. The 1 MiB request cap only bounds the request. - No concurrency cap: N parallel clones = N
git upload-packprocesses + N full packs in memory. Two or three concurrent clones of a large repo can OOM the container (default Docker memory limits are generous, Cloudflare-free-boxes are not). - Whole-response buffering:
respond()takes&[u8], so the pack is fully materialized before the first byte is written — no streaming, noContent-Length-free chunked path available in the currentrespond. - Client disconnects are not detected until wait() returns.
Note this is the smart path and is serve-only (dumb /repo.git is static and unaffected), so a deployment that never advertises /repo.bundle still exposes it — the route exists whenever www/repo.bundle exists, which build always writes.
Fix direction:
- spawn
upload-pack, stream stdout to the socket while writing the pack (droprespond's buffering for this route), kill the child on write error - enforce a wall-clock timeout (e.g. 60 s) and a concurrency semaphore; 503 when full
- consider gating the route behind a flag (it is a convenience, not the canonical clone path since #54)
Acceptance:
- a stalled client cannot keep a git process alive past the timeout
- concurrent clones are capped and the cap is documented
- memory use does not scale with pack size for a single clone
Uunknown ·
Fixed in 80497982-9d71-46ca-a8aa-5f4ded049800 (see PR body): bounded 64 connections with immediate 503 + 10s head deadline (#63), 4 concurrent upload-pack runs with 120s timeout and temp-file streaming (#64), 5-minute full-map sweep + 100k key cap in RateLimiter (#65).