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).
forge/identity/passkey.go +39
| @@ -36,6 +36,20 @@ const ( | |||
| 36 | 36 | DefaultInviteTTL = 72 * time.Hour | |
| 37 | 37 | ||
| 38 | 38 | inviteTokenBytes = 32 | |
| 39 | |||
| 40 | // MaxCredentialLabel bounds the name a person gives a passkey. The label is | ||
| 41 | // shown on the account page and stored beside the credential, so it is a | ||
| 42 | // label, not prose. | ||
| 43 | MaxCredentialLabel = 128 | ||
| 44 | |||
| 45 | // Credential fields come from the authenticator and a client, so each is | ||
| 46 | // bounded before it reaches a ceremony or the roster. A value that is not a | ||
| 47 | // passkey shape is refused rather than stored at whatever size the client | ||
| 48 | // chose. | ||
| 49 | maxCredentialTransports = 16 | ||
| 50 | maxCredentialTransport = 32 | ||
| 51 | maxCredentialAttachment = 32 | ||
| 52 | maxCredentialAttestation = 64 | ||
| 39 | 53 | ) | |
| 40 | 54 | ||
| 41 | 55 | var ( | |
| @@ -394,6 +408,9 @@ func hashToken(token string) string { | |||
| 394 | 408 | } | |
| 395 | 409 | ||
| 396 | 410 | func validateCredential(c Credential) error { | |
| 411 | if err := ValidateCredentialLabel(c.Label); err != nil { | ||
| 412 | return err | ||
| 413 | } | ||
| 397 | 414 | switch { | |
| 398 | 415 | case len(c.ID) == 0: | |
| 399 | 416 | return errors.New("identity: passkey id is empty") | |
| @@ -403,9 +420,31 @@ func validateCredential(c Credential) error { | |||
| 403 | 420 | return errors.New("identity: passkey public key is empty") | |
| 404 | 421 | case len(c.PublicKey) > 8192: | |
| 405 | 422 | return errors.New("identity: passkey public key is too long") | |
| 423 | case len(c.AttestationType) > maxCredentialAttestation: | ||
| 424 | return fmt.Errorf("identity: passkey attestation type is too long") | ||
| 425 | case len(c.Transport) > maxCredentialTransports: | ||
| 426 | return fmt.Errorf("identity: passkey has %d transports, limit %d", len(c.Transport), maxCredentialTransports) | ||
| 427 | case len(c.Authenticator.Attachment) > maxCredentialAttachment: | ||
| 428 | return fmt.Errorf("identity: passkey attachment is too long") | ||
| 406 | 429 | case len(c.Authenticator.AAGUID) > 256: | |
| 407 | 430 | return errors.New("identity: passkey AAGUID is too long") | |
| 408 | 431 | } | |
| 432 | for _, transport := range c.Transport { | ||
| 433 | if len(transport) > maxCredentialTransport { | ||
| 434 | return fmt.Errorf("identity: passkey transport is %d bytes, limit %d", len(transport), maxCredentialTransport) | ||
| 435 | } | ||
| 436 | } | ||
| 437 | return nil | ||
| 438 | } | ||
| 439 | |||
| 440 | // ValidateCredentialLabel checks the label before a ceremony stores it. A label | ||
| 441 | // is display text, not prose, so it is capped where it is accepted rather than | ||
| 442 | // only when the finished credential is written. | ||
| 443 | func ValidateCredentialLabel(label string) error { | ||
| 444 | label = strings.TrimSpace(label) | ||
| 445 | if len(label) > MaxCredentialLabel { | ||
| 446 | return fmt.Errorf("identity: passkey label is %d bytes, limit %d", len(label), MaxCredentialLabel) | ||
| 447 | } | ||
| 409 | 448 | return nil | |
| 410 | 449 | } | |
| 411 | 450 | ||