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/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.

Harden sign-in input bounds and private response caching

25343521418945b250da2fb29d27cffe34ee2a6e · Keenan Nemetz · 3d ago · parent b492b557

Harden sign-in input bounds and private response caching

Bound the account name in LoginBegin before it reaches the per-account rate
limiter or the ceremony store, and bound the passkey label and the credential
fields copied from an authenticator. Rate-limit keys are capped by hashing a
long value, forwarding headers used as addresses are capped, and expired limiter
entries are swept as the table grows.

Mark downloads and rendered pages private unless the repository is readable
anonymously. A private repository's artifact, archive, run artifact, raw file,
and HTML pages previously either carried a public cache directive or no
directive at all, so a shared cache could serve them to an anonymous reader.

Refs #209 (01M6S78Y7EQY596Y9TQRSNRVSE).
Refs #210 (01M6S7936QAYM9TMSCXCYP2CF9).

server/releases_test.go +21−1

@@ -358,6 +358,9 @@ func TestReleaseArtifactDownload(t *testing.T) {
358 358 if resp.StatusCode != 200 {
359 359 t.Fatalf("artifact download = %d", resp.StatusCode)
360 360 }
361 if cc := resp.Header.Get("Cache-Control"); !strings.Contains(cc, "public") {
362 t.Fatalf("public artifact Cache-Control = %q, want public", cc)
363 }
361 364 if !bytes.Equal(body, data) {
362 365 t.Fatalf("downloaded %q, want %q", body, data)
363 366 }
@@ -470,9 +473,26 @@ func TestReleaseArtifactsArePrivateForPrivateRepos(t *testing.T) {
470 473 if _, errs, err := f.git(work, "push", f.sshURL(f.repo), "refs/tags/v1.0.0:refs/tags/v1.0.0"); err != nil {
471 474 t.Fatalf("push tag: %s", errs)
472 475 }
473 if code, _ := f.get("/r/" + f.repo + "/releases/v1.0.0/artifacts/anything"); code != 404 {
476 attachArtifact(t, f, "v1.0.0", "tool", []byte("secret\n"))
477 if code, _ := f.get("/r/" + f.repo + "/releases/v1.0.0/artifacts/tool"); code != 404 {
474 478 t.Fatalf("private artifact = %d, want 404", code)
475 479 }
480
481 // An authorized reader may fetch the bytes, but a shared cache must not keep
482 // them and hand them to the next anonymous reader.
483 _, cookie := f.browserSession(t, f.ownerID)
484 for _, path := range []string{
485 "/r/" + f.repo + "/releases/v1.0.0/artifacts/tool",
486 "/r/" + f.repo + "/releases/v1.0.0/archive.tar",
487 } {
488 resp, _ := f.browserGet(t, path, cookie)
489 if resp.StatusCode != http.StatusOK {
490 t.Fatalf("%s = %d, want 200 for the owner", path, resp.StatusCode)
491 }
492 if cc := resp.Header.Get("Cache-Control"); strings.Contains(cc, "public") {
493 t.Fatalf("%s: Cache-Control = %q on a private repository", path, cc)
494 }
495 }
476 496 }
477 497
478 498 // pushMetaAs pushes a metadata change as a specific key, so a test can act as a