vole
Clone
Read only, no credentials:
https://rodent.software/git/vole.git
Write over SSH with a registered key:
git clone ssh://git@0.0.0.0:2222/vole.git
A free and open-source forge that lives in your Git repo.
Harden the web transport and bound the pages that run git
701232cf37157ff392e3b35a3279735d72e585d4 · Keenan Nemetz · 12d ago · parent 6bfc4294
Harden the web transport and bound the pages that run git Generated clone and LFS download links were derived from the request's Host header, with X-Forwarded-Proto believed from anyone. Those links are copied out of a page and pasted into a terminal, and the LFS hrefs are followed by a client, so a forged Host chose where somebody else's download went. Links now use public_url when it is configured, which is how a production instance is meant to be set up; without it the request origin is all there is, and forwarded headers are honoured only when git_http_client_ip_header is set, because that is the instance's statement that a proxy it trusts rewrites them. Site links follow the forge's scheme instead of always saying http, so an HTTPS forge does not link to a mixed-content site. The security headers now close the rest of the surface: object-src is none, inline styles are no longer allowed (no template or generated markup contains one), and Permissions-Policy, COOP and CORP are set. The CSRF check gained a second lock: when the browser says where a request came from, that origin must be this instance, on form posts and on the JSON ceremony endpoints alike. A missing Origin and Referer is not evidence, so only a request that names somewhere else is refused. The pages that shell out to git are bounded. Search, compare, blame, file history, commit history and both archive formats take a slot from a pool of eight and run under a 30-second deadline, which is attached to the git handle so the process is killed rather than left running after the reader has gone. Past a five-second wait for a slot the page answers 503 with Retry-After. Plain tar now takes the archive slot too: it is cheaper than gzip, not free, and both stream for as long as the client reads. Tests: clone URLs ignore a forged Host when public_url is set and honour forwarded headers only behind a declared proxy; LFS hrefs likewise; site links follow the scheme; the CSP has no unsafe-inline and names object-src; a saturated git pool answers 503 with Retry-After; a cross-origin post is refused with a valid CSRF token, end to end; the JSON endpoints refuse a foreign origin. Docs: public_url now explains that it decides generated links and that forwarded headers are only trusted with the proxy header set; Sites gains a same-site risk section recommending a separate registrable domain when sites are not trusted.
docs/configuration.md +13−1
| @@ -71,7 +71,7 @@ vole admin account list | |||
| 71 | 71 | | `git_http_timeout_seconds` | `600` | How long one clone may run, Git process included | | |
| 72 | 72 | | `lease_seconds` | `60` | How long a runner may hold a job without checking in before it is given to someone else | | |
| 73 | 73 | | `max_jobs_per_repo` | `0` | How many jobs one repository may run at once; `0` means no limit | | |
| 74 | | `pages_domain` | empty | Domain suffix for websites, as in `<repo>.<pages_domain>`; empty disables sites | | ||
| 74 | | `pages_domain` | empty | Domain suffix for websites, as in `<repo>.<pages_domain>`; empty disables sites. Use a different registrable domain from the forge when sites are untrusted — see [Sites](sites.md#same-site-risk) | | ||
| 75 | 75 | | `pages_ref` | `"pages"` | Branch served as a website | | |
| 76 | 76 | | `pages_procs` | `4` | Number of Git reader processes per active site | | |
| 77 | 77 | | `pages_cache_bytes` | `33554432` (32 MiB) | Memory used to cache site files | | |
| @@ -146,6 +146,18 @@ created. It is also the WebAuthn relying-party origin: browser sign-in is enable | |||
| 146 | 146 | only when `public_url` is an `https://` URL. Vole does not change how it routes | |
| 147 | 147 | requests based on this setting. | |
| 148 | 148 | ||
| 149 | It is also where generated clone and LFS download links point. That is a security | ||
| 150 | property, not a convenience: without it those links follow the request's `Host` | ||
| 151 | header, which is a value the browser's request carries and an attacker can choose. | ||
| 152 | Copying a clone URL out of a page and into a terminal is normal use, so the URL a | ||
| 153 | page shows should be the instance's, not the caller's. Set `public_url` on any | ||
| 154 | instance other than a loopback preview. | ||
| 155 | |||
| 156 | Forwarded headers (`X-Forwarded-Proto`, `X-Forwarded-For`) are believed only when | ||
| 157 | `git_http_client_ip_header` is set, because that is the instance's statement that | ||
| 158 | a proxy it trusts is rewriting them. Without it, a client that sets a forwarding | ||
| 159 | header is describing itself, and Vole ignores it. | ||
| 160 | |||
| 149 | 161 | ## Automatic HTTPS with ACME | |
| 150 | 162 | ||
| 151 | 163 | Vole can obtain and renew its own TLS certificates, so a small public instance | |