{"record":{"id":"6f36a66a84bf54b8","repo":"ory/kratos","slug":"errunexpectedstatuscode","errorCode":"ErrUnexpectedStatusCode","errorMessage":"ErrUnexpectedStatusCode wrapped: %d (unexpected status code from HIBP API)","messagePattern":"ErrUnexpectedStatusCode wrapped: (.+?) \\(unexpected status code from HIBP API\\)","errorType":"error_code","errorClass":"wrapped sentinel error","httpStatus":null,"severity":"warning","filePath":"selfservice/strategy/password/validator.go","lineNumber":139,"sourceCode":"\t}\n\treturn greatestLength\n}\n\nfunc (s *DefaultPasswordValidator) fetch(ctx context.Context, hpw []byte, apiDNSName string) (int64, error) {\n\tprefix := fmt.Sprintf(\"%X\", hpw)[0:5]\n\tloc := fmt.Sprintf(\"https://%s/range/%s\", apiDNSName, prefix)\n\treq, err := retryablehttp.NewRequestWithContext(ctx, \"GET\", loc, nil)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tres, err := s.reg.HTTPClient(ctx, httpx.ResilientClientWithConnectionTimeout(time.Second)).Do(req)\n\tif err != nil {\n\t\treturn 0, errors.Wrapf(ErrNetworkFailure, \"%s\", err)\n\t}\n\tdefer func() { _ = res.Body.Close() }()\n\n\tif res.StatusCode != http.StatusOK {\n\t\treturn 0, errors.Wrapf(ErrUnexpectedStatusCode, \"%d\", res.StatusCode)\n\t}\n\n\tvar thisCount int64\n\n\tsc := bufio.NewScanner(res.Body)\n\tfor sc.Scan() {\n\t\trow := sc.Text()\n\t\tresult := strings.Split(strings.TrimSpace(row), \":\")\n\n\t\t// We assume a count of 1. HIBP API sometimes responds without the\n\t\t// colon, so we just assume that the leak count is one.\n\t\t//\n\t\t// See https://github.com/ory/kratos/issues/2145\n\t\tcount := int64(1)\n\t\tif len(result) == 2 {\n\t\t\tcount, err = strconv.ParseInt(strings.ReplaceAll(result[1], \",\", \"\"), 10, 64)\n\t\t\tif err != nil {\n\t\t\t\treturn 0, errors.WithStack(herodot.ErrUpstreamError().WithReasonf(\"Expected password hash to contain a count formatted as int but got: %s\", result[1]))","sourceCodeStart":121,"sourceCodeEnd":157,"githubUrl":"https://github.com/ory/kratos/blob/b86338da04a040247a07f46100a86dcfb3875909/selfservice/strategy/password/validator.go#L121-L157","documentation":"After the HIBP range request succeeds at the transport level, fetch checks that the HTTP status is 200. Any other status code (e.g. 429 rate limit, 5xx) is wrapped in ErrUnexpectedStatusCode. HIBP only returns the hash-suffix list on 200, so any other code means the result is unusable.","triggerScenarios":"fetch receives a response with status != 200 from the HIBP API — most commonly HTTP 429 when the unauthenticated rate limit is exceeded, or 5xx during HIBP outages.","commonSituations":"High-traffic signup flows hammering the HIBP API without caching, deployments sharing IPs behind NAT hitting rate limits, HIBP service degradation.","solutions":["Set ignore_network_errors: true (also covers this error class) so password validation degrades gracefully.","Rely on the built-in hash cache (s.hashes) and ensure the instance is long-lived rather than restarted per request.","Check the wrapped status code: 429 implies rate limiting — reduce request volume or add a paid HIBP key via an API proxy.","Retry later; if persistent, monitor status.api.pwnedpasswords.com for outages."],"exampleFix":null,"handlingStrategy":"fallback","validationCode":null,"typeGuard":null,"tryCatchPattern":"if errors.Is(err, strategy.ErrUnexpectedStatusCode) {\n  log.WithError(err).Warn(\"HIBP returned non-200; skipping breach check\")\n  if cfg.IgnoreNetworkErrors { return allowed }\n}","preventionTips":["Set ignore_network_errors: true so 429/5xx from HIBP don't block signups.","Keep instances warm so the prefix cache absorbs repeated lookups.","Add alerting on the wrapped status code to detect persistent 429/5xx.","Throttle signup-triggered validations if running at high volume."],"tags":["hibp","http","status-code","rate-limit","password"],"backgroundTag":"http-non-200-response","analyzedSha":"b86338da04a040247a07f46100a86dcfb3875909","analyzedAt":"2026-09-07T15:58:15.934Z","contentChangedAt":"2026-09-07T15:58:15.934Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}