Skip to content

v1.140.0

Choose a tag to compare

@github-actions github-actions released this 08 Jul 12:46
9646c97

v1.140.0

Major security-focused reworks of the jwt, passwordhash, and passwordpwned packages. All three ship breaking API changes — see the Breaking Changes section before upgrading.

jwt — self-contained HMAC engine, verifier-based API, and security hardening

  • Tokens are now signed and verified by a standard-library-only HMAC-SHA2 (HS256/HS384/HS512) implementation of the RFC 7515/7519 compact JWS, removing the github.com/golang-jwt/jwt/v5 build dependency (retained test-only for cross-validation). Restricting the surface to symmetric HMAC and verifying the signature before decoding claims makes alg=none and algorithm-confusion attacks structurally impossible.
  • Pin the accepted signing algorithm and require exp; validate nbf with optional clock-skew leeway; enforce iss and the full aud set when configured.
  • Bound how long a session can be kept alive by renewals via an auth_time claim preserved across renewals and a configurable maximum lifetime, so a stolen token cannot be renewed indefinitely.
  • Support HMAC key rotation through previous keys accepted for verification, validate minimum key length against the method hash size, and copy caller-supplied key material.
  • Cap the login request body (413 on overflow) and reject unknown or trailing JSON; return generic client messages while logging details server-side; distinguish invalid credentials (401) from credential-backend failures (500).
  • Emit RFC 6750 WWW-Authenticate challenges on 401 responses and accept case-insensitive, multi-space Bearer prefixes per RFC 7235.
  • New API: Authenticate, VerifyToken, IssueToken, Middleware with ClaimsFromContext, and options WithMaxBodyBytes, WithMaxSessionLifetime, WithClockSkewLeeway, WithPreviousKeys.
  • Fuzz tests for the login body and token parser, benchmarks for the issue and verify paths, and a runnable example; 100% statement coverage.

passwordhash — parameter envelope, rehash API, sentinel errors, and hardening

  • Any hash the package can mint it can also verify: cost options clamp to the same [floor, ceiling] range the verification path accepts, and hashing rejects out-of-envelope configurations.
  • Panics replaced with errors: invalid or zero-value Params now return ErrInvalidParams instead of panicking inside argon2.
  • Verification hardened against forged or corrupt blobs: encoded hashes larger than 16 KiB are rejected before decoding, deserialized parameters are validated against their bounds, and salt/key byte lengths are cross-checked against their declared sizes.
  • New PasswordNeedsRehash and EncryptPasswordNeedsRehash detect hashes minted with outdated parameters, enabling transparent re-hashing on the next successful login.
  • Default parallelism is a flat 4 lanes (RFC 9106 §4) instead of runtime.NumCPU(), so minted hashes are reproducible across heterogeneous hardware and a mixed fleet cannot enter a rehash loop; key and salt minimums for new hashes raised to 16 and 8 bytes.
  • Minimum-length policy is enforced only when hashing, not on verification, so raising the minimum no longer locks out existing users.
  • Sentinel-error taxonomy matchable with errors.Is: ErrPasswordTooShort, ErrPasswordTooLong, ErrInvalidParams, ErrInvalidHashData, ErrAlgoMismatch, ErrVersionMismatch, ErrInvalidPepperKey.
  • Derived keys and decrypted pepper material are wiped after comparison; fuzz, envelope round-trip, panic-safety, and boundary tests added; 100% coverage.

passwordpwned — breach-count API, hardened response validation, sentinel errors, and live health probe

  • New PwnedCount returns the raw breach count for NIST-style threshold policies ("reject only if seen more than N times"); IsPwnedPassword is now a thin count>0 wrapper over it.
  • Decoded responses are capped at 8 MiB (configurable via WithResponseSizeLimit) so a decompression bomb cannot exhaust memory, and are decoded according to their declared Content-Encoding (brotli, gzip, identity) instead of assuming brotli.
  • Fail loud on garbage 200s: the body must begin with a structurally valid <35-hex>:<count> range line, so a captive-portal page returns ErrMalformedResponse instead of silently reading as "not pwned"; hash-suffix matching is case-insensitive, so lowercase-hex mirrors no longer produce silent false negatives.
  • HealthCheck is now a live probe: a lightweight GET on a fixed range prefix bounded by a dedicated ping timeout (default 5 s, WithPingTimeout), never retried.
  • The HTTP retrier is built once in New — invalid retry settings fail at construction — and a server-provided Retry-After header is honored (capped at 60 s) on 429.
  • Sentinel-error taxonomy: ErrInvalidURL, ErrInvalidUserAgent, ErrMalformedResponse, ErrResponseTooLarge, ErrUnsupportedEncoding, ErrUnexpectedStatus.
  • Tests pin the k-anonymity contract (only the 5-character hash prefix ever leaves the process), plus fuzz targets over the response parser and body reader; 100% coverage.

Other

  • Fixed a flaky test timeout.
  • Updated dependencies (including golang.org/x/sync v0.22.0 and golang.org/x/sys v0.47.0).

Breaking Changes

  • jwt: New takes VerifyCredentialsFn func(username, password string) (bool, error) instead of UserHashFn — credential verification is fully delegated (pair it with pkg/passwordhash) and the package no longer performs bcrypt comparison itself. SigningMethod is now an HS256/HS384/HS512 enum, Claims carries native fields instead of embedding jwt.RegisteredClaims, and WithClaimSubject is removed (the sub claim is now the authenticated username).
  • passwordhash: default parallelism is a flat 4 instead of runtime.NumCPU(); WithKeyLen/WithSaltLen clamp to [16, 1024]/[8, 1024] and WithTime/WithMemory cap at 1024 passes/4 GiB (previously stored hashes remain verifiable unchanged). PasswordVerify and EncryptPasswordVerify no longer reject candidate passwords below the configured minimum length. Invalid Params configurations that previously panicked now return ErrInvalidParams.
  • passwordpwned: HealthCheck now performs a live request instead of always returning nil. New rejects previously accepted configurations: URLs with a query or fragment, an empty or control-character User-Agent, zero retry attempts, and non-positive retry delays. A 200 response whose body is not valid HIBP range data now returns ErrMalformedResponse instead of reporting the password as not pwned, and WithTimeout ignores non-positive values instead of disabling the client timeout.

Full Changelog: v1.139.0...v1.140.0