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/web/auth_handlers.go +16−1

@@ -100,6 +100,16 @@ func (w *Web) LoginBegin(rw http.ResponseWriter, r *http.Request) {
100 100 return
101 101 }
102 102 name := strings.TrimSpace(body.Username)
103 // A name that could not be an account is answered with the same decoy as an
104 // unknown account, but it must not reach the per-account limiter or the
105 // ceremony store at whatever length the client chose.
106 if err := identity.ValidateName(name); err != nil {
107 if !w.limit(rw, r, w.limits.beginAccount, "login:invalid") {
108 return
109 }
110 w.beginDecoyLogin(rw, "unknown")
111 return
112 }
103 113 if !w.limit(rw, r, w.limits.beginAccount, "login:"+name) {
104 114 return
105 115 }
@@ -498,6 +508,11 @@ func (w *Web) AddPasskeyBegin(rw http.ResponseWriter, r *http.Request) {
498 508 writeJSONError(rw, http.StatusBadRequest, err.Error())
499 509 return
500 510 }
511 label := strings.TrimSpace(body.Label)
512 if err := identity.ValidateCredentialLabel(label); err != nil {
513 writeJSONError(rw, http.StatusBadRequest, err.Error())
514 return
515 }
501 516 user, err := w.waUser(pr.account)
502 517 if err != nil {
503 518 writeJSONError(rw, http.StatusInternalServerError, "cannot read the account")
@@ -512,7 +527,7 @@ func (w *Web) AddPasskeyBegin(rw http.ResponseWriter, r *http.Request) {
512 527 }
513 528 if err := w.beginCeremony(rw, ceremony{
514 529 Kind: "passkey", Data: *data, Name: pr.account.Name,
515 Label: strings.TrimSpace(body.Label),
530 Label: label,
516 531 }, creation); err != nil {
517 532 writeJSONError(rw, http.StatusInternalServerError, "cannot store the ceremony")
518 533 }