vole
Clone
Read only, no credentials:
https://rodent.software/vole.git
Write over SSH with a registered key:
git clone ssh://git@rodent.software:2222/vole.git
A free and open-source forge that lives in your Git repo.
Bound the anonymous Git HTTP read path
86991e7020a418a08a5e3a36ce8f296561392e3b · Keenan Nemetz · 13d ago · parent e05f1c48
Bound the anonymous Git HTTP read path The transport that needs no account had no bound on anything: the request body was capped only at "not negative", the backend ran without a request context or deadline, the path check accepted any suffix after `git-upload-pack` and handed it to the CGI as `PATH_INFO`, and the server set no `ReadTimeout` and no `MaxHeaderBytes`. Bound what the read side can spend, which is every dimension available to a fetch and none of the ones a push would need: a 2 MiB body cap with a required `Content-Length`, exactly two endpoints with one method and one service each so the CGI never sees an unvalidated path, `exec.CommandContext` plus `WaitDelay` plus an explicit kill so a disconnect or an expired budget stops git instead of leaving it running, and a concurrency semaphore with a per-client bound that answers 503 with Retry-After rather than queueing. The client address is the connection's own unless the operator names a proxy header, because behind a proxy the connection address is the proxy. Both limits and the header name are configuration, `git_http` turns the transport off entirely, and `status` lists them so an operator can see what is in force. `WriteTimeout` is deliberately not set: clone, archives, LFS objects and site files are streamed, so those flows are bounded where they are produced.
forge/config/config.go +26
| @@ -33,6 +33,27 @@ type Config struct { | |||
| 33 | 33 | SSHAddr string `toml:"ssh_addr"` | |
| 34 | 34 | // MaxBlobBytes rejects individual objects in pre-receive. | |
| 35 | 35 | MaxBlobBytes int64 `toml:"max_blob_bytes"` | |
| 36 | // GitHTTP enables the anonymous smart-HTTP read transport: clone, fetch and | ||
| 37 | // the LFS downloads that share its URLs. It is on by default because reading a | ||
| 38 | // public repository without an account is the point of a public repository. | ||
| 39 | // An instance whose repositories are all internal can switch it off and get a | ||
| 40 | // smaller anonymous surface; pushes are on SSH either way. | ||
| 41 | GitHTTP bool `toml:"git_http"` | ||
| 42 | // GitHTTPMaxConcurrent bounds simultaneous reads on that transport. Past it a | ||
| 43 | // clone is refused with 503 and Retry-After rather than queued, because a | ||
| 44 | // queue is another unbounded thing to hold. | ||
| 45 | GitHTTPMaxConcurrent int `toml:"git_http_max_concurrent"` | ||
| 46 | // GitHTTPMaxPerClient bounds simultaneous reads from one address. Zero | ||
| 47 | // disables the per-client rule and leaves only the concurrency bound. | ||
| 48 | GitHTTPMaxPerClient int `toml:"git_http_max_per_client"` | ||
| 49 | // GitHTTPClientIPHeader names the request header a trusted proxy sets with the | ||
| 50 | // client address, e.g. "X-Forwarded-For". Empty means the connection address is | ||
| 51 | // the client, which is right when nothing is in front of Vole. Behind a proxy | ||
| 52 | // without this, every visitor shares one identity and one per-client budget. | ||
| 53 | GitHTTPClientIPHeader string `toml:"git_http_client_ip_header"` | ||
| 54 | // GitHTTPTimeoutSeconds bounds one read, including the git process behind it. | ||
| 55 | // A big clone over a slow link is minutes; a stalled one is hours. | ||
| 56 | GitHTTPTimeoutSeconds int `toml:"git_http_timeout_seconds"` | ||
| 36 | 57 | // ReceiveMaxBytes caps a whole push, enforced by git's own | |
| 37 | 58 | // receive.maxInputSize in each repo rather than by buffering in the proxy. | |
| 38 | 59 | ReceiveMaxBytes int64 `toml:"receive_max_bytes"` | |
| @@ -106,6 +127,11 @@ func defaults() *Config { | |||
| 106 | 127 | MaxBlobBytes: 100 << 20, // 100 MiB per blob | |
| 107 | 128 | ReceiveMaxBytes: 1 << 30, // 1 GiB per push | |
| 108 | 129 | ||
| 130 | GitHTTP: true, | ||
| 131 | GitHTTPMaxConcurrent: 16, | ||
| 132 | GitHTTPMaxPerClient: 8, | ||
| 133 | GitHTTPTimeoutSeconds: 600, | ||
| 134 | |||
| 109 | 135 | LFSObjectMaxBytes: 2 << 30, // 2 GiB per LFS object | |
| 110 | 136 | LFSWarnBytes: 5 << 20, // flag binaries over 5 MiB that are not in LFS | |
| 111 | 137 | ||