Sign in

Search issue titles and bodies, change requests and comments. Private repositories you cannot read are not included.

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.

#58

Harden the web transport and bound the pages that run git

merged · by keenan 12d ago · merged into main at 701232cf by keenan 12d ago

keenan opened this 12d ago

Closes #12.

What was wrong

cloneBase and lfsObjectBase built URLs from r.Host and believed X-Forwarded-Proto from anyone. A clone URL is copied out of a page and pasted into a terminal, and an LFS batch href is followed by a client, so the Host header chose where somebody else’s download went. The CSP still allowed inline styles, there was no object-src, no Permissions-Policy, COOP or CORP, and the pages that shell out to git (search, compare, blame, history, archives) had no concurrency bound or deadline. Plain tar skipped the archive slot, and site links always used http://.

What this does

Canonical links. canonicalBase uses public_url when set. Without it the request origin is the only information there is, and forwarded headers are honoured only when git_http_client_ip_header is configured — that setting is the instance’s statement that a proxy it trusts rewrites them. A forged Host or X-Forwarded-Proto therefore cannot change a generated link on a configured instance. Applies to clone URLs, LFS batch hrefs and site URLs; site links now follow the forge’s scheme.

Headers. CSP: object-src 'none' and style-src 'self' (no template or generated markup contains an inline style, so 'unsafe-inline' was unused). Added Permissions-Policy, Cross-Origin-Opener-Policy: same-origin, Cross-Origin-Resource-Policy: same-origin.

Second CSRF lock. Origin/Referer must be this instance when the browser sends one, on form posts and on the JSON ceremony endpoints. A missing header is not evidence against a request; one that names another origin is refused even with a valid token.

Bounded git. A pool of 8 for search, compare, blame, file history, commit history and archives, with a 30 s deadline attached to the git.Repo so the process is killed (WithContext in core/git, via exec.CommandContext). A page waits up to 5 s for a slot, then answers 503 with Retry-After. Both archive formats take the slot now.

Notes

  • core/git gained WithContext; callers that do not set one behave exactly as before, so hooks, the lander and the runner are unaffected.
  • public_url being unset still falls back to the request origin, because a clone URL cannot be relative and there is no other information. That is documented as a loopback-preview arrangement; the acceptance criterion (host injection cannot change generated URLs) holds whenever the instance is configured, which is every deployment that is not a local preview.
  • Cross-Origin-Embedder-Policy is deliberately not set: it requires CORP on every subresource and would break the data: images and the confetti canvas for no gain here. COOP/CORP are what the issue asked for.
  • The CSP change was checked against the templates and the static JS: none of them writes a style attribute or a <style> element; the celebration script uses CSSOM (canvas.style...), which style-src does not govern.

Verification

  • gofmt, go vet ./..., go test -race ./... clean.
  • ./vole workflow check and ./vole workflow run verify pass.

Tests

  • TestCloneURLPrefersTheConfiguredOrigin (forged Host and forwarded headers lose), TestCloneURLFallsBackToTheRequestOrigin (forwarded headers only behind a declared proxy).
  • TestLFSObjectBasePrefersTheConfiguredOrigin, TestLFSObjectBaseHonoursForwardedHeadersOnlyBehindAProxy.
  • TestSiteURLFollowsTheForgeScheme.
  • TestSecurityHeadersCloseTheSurface (no unsafe-inline, object-src, COOP/CORP, Permissions-Policy).
  • TestGitSlotRefusesWhenSaturated (503 + Retry-After, slot reusable after release).
  • TestCrossOriginPostIsRefused (end to end: 403 with a valid token, session kept) and the foreign-origin case in TestJSONEndpointsRequireTheCSRFHeader.
  • TestSameOriginRequest (no header, same origin, foreign origin, referrer, Origin: null).

Docs: docs/configuration.md (public_url decides generated links; forwarded headers need the proxy setting), docs/sites.md (same-site risk of sharing a registrable domain with the forge).

Discussion (0)

No discussion yet.