Documentation
¶
Overview ¶
Package passwordpwned checks whether a password has appeared in a known data breach, using the Have I Been Pwned (HIBP) Pwned Passwords API v3 (https://haveibeenpwned.com/API/v3#PwnedPasswords).
It uses the HIBP k-anonymity model: only the first 5 hex characters of the SHA-1 hash are sent over the network. The API returns all hash suffixes that share that 5-character prefix (typically 800 to 1,000 entries due to Add-Padding). The full hash match is resolved on the client side, so the password and its complete hash never leave the process.
Create a client with New and call Client.IsPwnedPassword:
c, err := passwordpwned.New()
if err != nil {
log.Fatal(err)
}
pwned, err := c.IsPwnedPassword(ctx, password)
if err != nil {
log.Fatal(err)
}
if pwned {
return errors.New("password has been compromised in a data breach, please choose another")
}
To enforce a threshold policy instead (e.g. NIST-style "reject only if seen more than N times"), use Client.PwnedCount:
count, err := c.PwnedCount(ctx, password)
if err != nil {
log.Fatal(err)
}
if count > maxAllowedBreaches {
return errors.New("password is too common, please choose another")
}
Features ¶
Requests set the Add-Padding header, so every response contains 800 to 1,000 entries regardless of the real match count. Responses are requested as brotli and decoded per their declared Content-Encoding (brotli, gzip, or identity); the decoded body is capped (see WithResponseSizeLimit) to guard against decompression-bomb style memory exhaustion. A 200 response that is not structurally valid range data (e.g. a captive-portal HTML page) is rejected with ErrMalformedResponse instead of being read as "not pwned". A httpretrier-backed retry policy handles transient network errors for read-only requests and honors a server-provided Retry-After header. Client.PwnedCount returns the raw breach count for threshold policies; Client.IsPwnedPassword returns a boolean. Client.HealthCheck performs a bounded range request to verify the endpoint is reachable. WithURL, WithTimeout, WithHTTPClient, WithUserAgent, WithRetryAttempts, WithRetryDelay, WithResponseSizeLimit, and WithPingTimeout configure the client. HTTPClient is an interface, so a mock HTTP client can be injected in tests.
Security Note ¶
The SHA-1 algorithm is used solely because the HIBP API requires it. The password is hashed locally; only the 5-character prefix is sent over TLS. This package does not store, log, or persist the password or its hash.
Index ¶
- Variables
- type Client
- type HTTPClient
- type Option
- func WithHTTPClient(hc HTTPClient) Option
- func WithPingTimeout(timeout time.Duration) Option
- func WithResponseSizeLimit(limit int64) Option
- func WithRetryAttempts(attempts uint) Option
- func WithRetryDelay(value time.Duration) Option
- func WithTimeout(timeout time.Duration) Option
- func WithURL(addr string) Option
- func WithUserAgent(s string) Option
Constants ¶
This section is empty.
Variables ¶
var ( // ErrInvalidURL is returned by [New] when the configured API base URL cannot // be parsed, lacks a scheme or host, or contains a query or fragment. ErrInvalidURL = errors.New("invalid API base URL") // ErrInvalidUserAgent is returned by [New] when the User-Agent is empty or // contains control characters: Go suppresses an empty User-Agent header // entirely (and the HIBP API rejects requests without one), while control // bytes are rejected by the HTTP transport on every request. ErrInvalidUserAgent = errors.New("invalid user agent") // ErrMalformedResponse is returned when the response body is empty, missing, // not structurally valid HIBP range data, or a matched hash suffix is not // followed by the expected ":<count>" sequence. ErrMalformedResponse = errors.New("malformed response body") // ErrUnexpectedStatus is returned when the API responds with a non-200 // status code; the code is included in the wrapped message. ErrUnexpectedStatus = errors.New("unexpected status code") // ErrResponseTooLarge is returned when the decoded response exceeds the // configured limit (see [WithResponseSizeLimit]), guarding against // decompression-bomb style memory exhaustion. ErrResponseTooLarge = errors.New("response body exceeds size limit") // ErrUnsupportedEncoding is returned when the server responds with a // Content-Encoding this client cannot decode. ErrUnsupportedEncoding = errors.New("unsupported response content-encoding") )
Sentinel errors returned by this package. Match them with errors.Is.
Functions ¶
This section is empty.
Types ¶
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
Client queries the HIBP Pwned Passwords API using the k-Anonymity range endpoint. Create one with New; the zero value is not usable.
func New ¶
New creates a Client with the given options applied over sensible defaults.
Defaults:
- API URL: https://api.pwnedpasswords.com
- Timeout: 30 s
- Ping timeout: 5 s (HealthCheck probe)
- User-Agent: nurago.passwordpwned/1
- Retry attempts: httpretrier.DefaultAttempts
- Max response: 8 MiB (decoded)
Use WithURL, WithTimeout, WithHTTPClient, WithUserAgent, WithRetryAttempts, WithRetryDelay, WithResponseSizeLimit, or WithPingTimeout to override individual settings.
All configuration is validated here: an invalid URL, an invalid User-Agent, or invalid retry settings return an error, so a successfully constructed Client cannot fail on configuration at call time.
func (*Client) HealthCheck ¶
HealthCheck verifies that the configured Pwned Passwords endpoint is reachable and healthy.
The HIBP range API has no dedicated ping endpoint, so the probe performs a lightweight GET on a fixed range prefix (without the Add-Padding header, to keep the response small) and reports any transport failure or non-200 status. The response body is not decoded: the status code is the readiness signal. The request is bounded by the ping timeout (see WithPingTimeout) and is never retried.
Note: each probe is a live request against the configured endpoint; the HIBP range API is unauthenticated and CDN-cached, so the cost is negligible at normal probe cadences.
func (*Client) IsPwnedPassword ¶
IsPwnedPassword reports whether password has appeared in a known data breach.
It is a thin wrapper over Client.PwnedCount: it returns true when the breach count is greater than zero.
Returns (true, nil) if the password is found in the breach database. Returns (false, nil) if the password is not found or its breach count is zero. Returns (false, err) on any network, encoding, or unexpected HTTP error.
func (*Client) PwnedCount ¶
PwnedCount returns how many times password appears in the HIBP Pwned Passwords breach database, or 0 if it is not present (including padding entries, which always report a count of 0). This lets callers enforce threshold policies (e.g. NIST-style "reject if seen more than N times") rather than a bare boolean.
The lookup uses the HIBP k-anonymity model:
- The SHA-1 hash of password is computed locally.
- Only the first 5 hex characters of the hash are sent to the HIBP API.
- The API returns all matching hash suffixes (padded to 800 to 1,000 entries).
- The full hash is matched client-side; the password never leaves the process.
Returns a non-nil error on any network, encoding, size-limit, or unexpected HTTP error; the count is 0 in that case.
type HTTPClient ¶
type HTTPClient interface {
// Do sends an HTTP request and returns an HTTP response.
Do(req *http.Request) (*http.Response, error)
}
HTTPClient is the minimal HTTP transport used by Client.
type Option ¶
type Option func(c *Client)
Option customizes passwordpwned client configuration.
func WithHTTPClient ¶
func WithHTTPClient(hc HTTPClient) Option
WithHTTPClient injects a custom HTTP client implementation. The injected client's own timeout applies (WithTimeout is ignored); if it has none, requests are bounded only by the caller's context.
func WithPingTimeout ¶
WithPingTimeout sets the per-probe timeout of Client.HealthCheck (default: 5 s). Non-positive values are ignored, keeping the default.
func WithResponseSizeLimit ¶
WithResponseSizeLimit caps the decoded response size (in bytes) to guard against decompression-bomb style memory exhaustion. Non-positive values are ignored, keeping the default limit. Useful when routing through a mirror that legitimately returns larger responses.
func WithRetryAttempts ¶
WithRetryAttempts sets the maximum number of total attempts, the initial request plus retries (default: httpretrier.DefaultAttempts, 4). Must be at least 1 (New rejects 0); a value of 1 disables retries.
func WithRetryDelay ¶
WithRetryDelay sets the base delay between a failed attempt and its retry (default: httpretrier.DefaultDelay, 1 s). Must be positive (New rejects non-positive values).
func WithTimeout ¶
WithTimeout sets the HTTP request timeout of the internally constructed HTTP client (default: 30 s). It has no effect when WithHTTPClient is used: configure the injected client's timeout directly. Non-positive values are ignored, keeping the default: net/http would treat them as "no timeout", leaving requests bounded only by the caller's context.
func WithURL ¶
WithURL overrides the default HIBP API base URL (default: https://api.pwnedpasswords.com). Useful for routing requests through a self-hosted mirror or a test server. A trailing slash is trimmed; a URL with a query or fragment is rejected by New. Hash-suffix matching is case-insensitive: responses from lowercase-hex mirrors are normalized before matching.
func WithUserAgent ¶
WithUserAgent overrides the User-Agent header used by API requests. The value must be non-empty and free of control characters (New rejects invalid values): Go suppresses an empty User-Agent header entirely, the HIBP API rejects requests without one, and the transport rejects control bytes.