haveibeenpwned

package module
v1.0.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 18, 2026 License: MIT Imports: 16 Imported by: 0

README

haveibeenpwned

CI Docker Go Reference Go Report Card

A Go client for Troy Hunt's Have I Been Pwned API v3, including the Pwned Passwords k-anonymity range API.

Ships in four forms:

  • Go librarygithub.com/marco-montesines/haveibeenpwned
  • CLIhibp (cmd/hibp)
  • HTTP JSON APIhibp serve, published as a container image at ghcr.io/marco-montesines/haveibeenpwned
  • FrankenPHP extension — native PHP functions implemented in Go (frankenphp/)

Features

Method HIBP endpoint API key required
PwnedPasswordCount range/{first5HashChars} no
GetBreaches breaches no
GetBreachedSite breach/{name} no
GetDataClasses dataclasses no
GetBreachedAccount breachedaccount/{account} yes
GetPastedAccount pasteaccount/{account} yes

An API key for the account endpoints is available at haveibeenpwned.com/API/Key. Password checks use k-anonymity: only the first five characters of the password's SHA-1 hash ever leave your process, and responses are padded.

Library

Install
go get github.com/marco-montesines/haveibeenpwned
Quickstart
package main

import (
	"context"
	"fmt"
	"log"
	"os"

	hibp "github.com/marco-montesines/haveibeenpwned"
)

func main() {
	ctx := context.Background()
	client := hibp.New(os.Getenv("HIBP_API_KEY")) // key may be empty for unauthenticated endpoints

	// Has this password appeared in a breach? (no API key needed)
	count, err := client.PwnedPasswordCount(ctx, "P@ssw0rd")
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("password seen %d times in breaches\n", count)

	// All breaches for a domain (no API key needed).
	breaches, err := client.GetBreaches(ctx, "adobe.com")
	if err != nil {
		log.Fatal(err)
	}
	for _, b := range breaches {
		fmt.Printf("%s (%s): %d accounts\n", b.Title, b.BreachDate, b.PwnCount)
	}

	// Breaches for an account (requires API key). nil result = not breached.
	accountBreaches, err := client.GetBreachedAccount(ctx, "info@example.com", "", true, true)
	if err != nil {
		log.Fatal(err)
	}
	if accountBreaches == nil {
		fmt.Println("account not found in any breach")
	}
}

A runnable version lives in examples/quickstart.

Options
client := hibp.New(apiKey,
	hibp.WithHTTPClient(customHTTPClient),
	hibp.WithUserAgent("my-app/1.0"),
	hibp.WithBaseURL(proxyURL),                 // e.g. tests, corporate proxy
	hibp.WithPwnedPasswordsBaseURL(mirrorURL),
)
Error handling

Non-2xx API responses are returned as *hibp.HIBPErrorResponse, which preserves the HTTP status code and the Retry-After value on rate limits:

var apiErr *hibp.HIBPErrorResponse
if errors.As(err, &apiErr) && apiErr.Code == http.StatusTooManyRequests {
	time.Sleep(time.Duration(apiErr.RetryAfter) * time.Second)
}

A 404 on account lookups is not an error — it means "not breached" and yields a nil result with a nil error.

CLI

go install github.com/marco-montesines/haveibeenpwned/cmd/hibp@latest

hibp breaches -domain adobe.com          # all breaches for a domain
hibp breach Adobe                        # a single breach by name
hibp dataclasses                         # all data classes
echo -n 'P@ssw0rd' | hibp password       # check a password (stdin keeps it out of shell history)

export HIBP_API_KEY=...                  # required for account endpoints
hibp account info@example.com
hibp pastes info@example.com

All output is JSON, so it composes with jq.

HTTP API server & Docker

hibp serve exposes the same functionality as a JSON API — useful from PHP, scripts, or any non-Go service:

Route Description
GET /healthz Health check
GET /v1/breaches?domain= All breaches, optional domain filter
GET /v1/breaches/{name} Single breach by name
GET /v1/dataclasses All data classes
GET /v1/breachedaccount/{email} Breaches for an account (?domain=&truncate=&unverified=)
GET /v1/pasteaccount/{email} Pastes for an account
POST /v1/pwnedpassword Body {"password":"..."}{"pwned":true,"count":N}

Run it from the published image:

docker run --rm -p 8080:8080 -e HIBP_API_KEY=... ghcr.io/marco-montesines/haveibeenpwned:latest

curl -s -X POST localhost:8080/v1/pwnedpassword -d '{"password":"P@ssw0rd"}'
# {"count":6421042,"pwned":true}

Or with docker compose (starts the API and the FrankenPHP demo):

HIBP_API_KEY=... docker compose up --build
# API:       http://localhost:8080
# FrankenPHP demo: http://localhost:8081/?password=P@ssw0rd

Images are built for linux/amd64 and linux/arm64 and published to GHCR by .github/workflows/docker.yml only on version tags (v1.2.3:1.2.3, :1.2, :latest) or when the workflow is run manually — never on regular pushes or pull requests.

FrankenPHP: call the library natively from PHP

The frankenphp/ directory contains a FrankenPHP extension written in Go that compiles this library into PHP:

<?php
if (hibp_pwned_password_count($_POST['password']) > 0) {
    throw new RuntimeException('This password appears in known data breaches.');
}

$breaches = json_decode(hibp_breaches('adobe.com'), true);

Build and try it:

docker compose up frankenphp
# open http://localhost:8081/?password=P@ssw0rd&domain=adobe.com

See frankenphp/README.md for details.

Development

go build ./...        # build library, CLI, examples
go test ./... -race   # run the test suite (uses httptest mocks, no network)
go vet ./...

CI runs formatting, vet, build, and race-enabled tests on every push and pull request.

Rate limits & acceptable use

The authenticated endpoints are rate limited per API key; on 429 the client surfaces the Retry-After value via HIBPErrorResponse.RetryAfter. Please respect the HIBP acceptable use policy and set a descriptive User-Agent (WithUserAgent) identifying your project.

License

MIT. Not affiliated with or endorsed by Have I Been Pwned or Troy Hunt. Data is provided by the Have I Been Pwned service.

Documentation

Overview

Package haveibeenpwned is a client for Troy Hunt's Have I Been Pwned API v3 (https://haveibeenpwned.com/API/v3), including the Pwned Passwords k-anonymity range API (https://api.pwnedpasswords.com).

Endpoints that query data for a specific account (GetBreachedAccount, GetPastedAccount) require an API key from https://haveibeenpwned.com/API/Key. All other endpoints, including PwnedPasswordCount, work without a key.

Index

Constants

View Source
const (
	// UserAgent is the default User-Agent header sent with every request.
	// The HIBP API rejects requests without a User-Agent.
	UserAgent = "haveibeenpwned-go/1.0 (+https://github.com/marco-montesines/haveibeenpwned)"
	// Accept is the media type requested from the API.
	Accept = "application/json"
	// Endpoint is the default base URL of the HIBP v3 API.
	Endpoint = "https://haveibeenpwned.com/api/v3/"
	// PwnedPasswordsEndpoint is the default base URL of the Pwned Passwords range API.
	PwnedPasswordsEndpoint = "https://api.pwnedpasswords.com/"
	// Domain matches a syntactically valid ASCII domain name.
	Domain = `^(?:[_\-a-z0-9]+\.)*([\-a-z0-9]+\.)[\-a-z0-9]{2,63}$`
	// DomainUnicode matches a syntactically valid internationalized domain name.
	DomainUnicode = `^(?:[_\-\p{L}\d]+\.)*([\-\p{L}\d]+\.)[\-\p{L}\d]{2,63}$`
)

Variables

This section is empty.

Functions

This section is empty.

Types

type Breach

type Breach struct {
	Name               string      `json:"Name,omitempty"`
	Title              string      `json:"Title,omitempty"`
	Domain             string      `json:"Domain,omitempty"`
	BreachDate         string      `json:"BreachDate,omitempty"`
	AddedDate          time.Time   `json:"AddedDate,omitempty"`
	ModifiedDate       time.Time   `json:"ModifiedDate,omitempty"`
	PwnCount           int         `json:"PwnCount,omitempty"`
	Description        string      `json:"Description,omitempty"`
	LogoPath           string      `json:"LogoPath,omitempty"`
	DataClasses        DataClasses `json:"DataClasses,omitempty"`
	IsVerified         bool        `json:"IsVerified,omitempty"`
	IsFabricated       bool        `json:"IsFabricated,omitempty"`
	IsSensitive        bool        `json:"IsSensitive,omitempty"`
	IsRetired          bool        `json:"IsRetired,omitempty"`
	IsSpamList         bool        `json:"IsSpamList,omitempty"`
	IsMalware          bool        `json:"IsMalware,omitempty"`
	IsSubscriptionFree bool        `json:"IsSubscriptionFree,omitempty"`
	IsStealerLog       bool        `json:"IsStealerLog,omitempty"`
}

Breach describes a single breach in the HIBP corpus. See https://haveibeenpwned.com/API/v3#BreachModel.

type DataClasses

type DataClasses []string

DataClasses is the list of data attributes compromised in a breach.

type HIBPErrorResponse

type HIBPErrorResponse struct {
	Message     string `json:"message"`
	Description string `json:"-"`
	Code        int    `json:"statusCode"`
	RetryAfter  int    `json:"-"`
}

HIBPErrorResponse is the error payload returned by the HIBP API for non-2xx responses. It implements the error interface. RetryAfter carries the Retry-After header value (in seconds) on 429 responses.

func (*HIBPErrorResponse) Error

func (e *HIBPErrorResponse) Error() string

type HaveIBeenPwned

type HaveIBeenPwned struct {
	// contains filtered or unexported fields
}

HaveIBeenPwned is a client for the HIBP v3 and Pwned Passwords APIs. It is safe for concurrent use by multiple goroutines.

func New

func New(accessKey string, opts ...Option) *HaveIBeenPwned

New returns a client for the HIBP v3 API. The accessKey may be empty if only endpoints that do not require authentication are used.

func (*HaveIBeenPwned) GetBreachedAccount

func (o *HaveIBeenPwned) GetBreachedAccount(ctx context.Context, email string, domain string,
	truncateResponse bool, includeUnverified bool) ([]Breach, error)

GetBreachedAccount returns all breaches an account appears in. Requires an API key. A nil slice with a nil error means the account was not found in any breach. See https://haveibeenpwned.com/API/v3#BreachesForAccount.

func (*HaveIBeenPwned) GetBreachedSite

func (o *HaveIBeenPwned) GetBreachedSite(ctx context.Context, site string) (*Breach, error)

GetBreachedSite returns a single breach by its name (the stable "Name" attribute, e.g. "Adobe"). No API key required. See https://haveibeenpwned.com/API/v3#SingleBreach.

func (*HaveIBeenPwned) GetBreaches

func (o *HaveIBeenPwned) GetBreaches(ctx context.Context, domain string) ([]*Breach, error)

GetBreaches returns all breaches in the system, optionally filtered by the domain the breach occurred on. No API key required. See https://haveibeenpwned.com/API/v3#AllBreaches.

func (*HaveIBeenPwned) GetDataClasses

func (o *HaveIBeenPwned) GetDataClasses(ctx context.Context) (*DataClasses, error)

GetDataClasses returns all data classes in the system. No API key required. See https://haveibeenpwned.com/API/v3#AllDataClasses.

func (*HaveIBeenPwned) GetPastedAccount

func (o *HaveIBeenPwned) GetPastedAccount(ctx context.Context, email string) ([]*Paste, error)

GetPastedAccount returns all pastes an account appears in. Requires an API key. A nil slice with a nil error means the account was not found in any paste. See https://haveibeenpwned.com/API/v3#PastesForAccount.

func (*HaveIBeenPwned) PwnedPasswordCount

func (o *HaveIBeenPwned) PwnedPasswordCount(ctx context.Context, password string) (int, error)

PwnedPasswordCount reports how many times a password appears in the Pwned Passwords corpus, using the k-anonymity range API: only the first five characters of the password's SHA-1 hash ever leave the process. A count of zero means the password was not found. No API key required. See https://haveibeenpwned.com/API/v3#PwnedPasswords.

type Option

type Option func(*HaveIBeenPwned)

Option configures a HaveIBeenPwned client.

func WithBaseURL

func WithBaseURL(u *url.URL) Option

WithBaseURL replaces the HIBP v3 API base URL, e.g. for tests or proxies.

func WithHTTPClient

func WithHTTPClient(client *http.Client) Option

WithHTTPClient replaces the default *http.Client.

func WithPwnedPasswordsBaseURL

func WithPwnedPasswordsBaseURL(u *url.URL) Option

WithPwnedPasswordsBaseURL replaces the Pwned Passwords API base URL.

func WithUserAgent

func WithUserAgent(userAgent string) Option

WithUserAgent replaces the default User-Agent header.

type Paste

type Paste struct {
	Source     string    `json:"Source,omitempty"`
	Id         string    `json:"Id,omitempty"`
	Title      string    `json:"Title,omitempty"`
	Date       time.Time `json:"Date,omitempty"`
	EmailCount int       `json:"EmailCount,omitempty"`
}

Paste describes a single paste containing a searched account. See https://haveibeenpwned.com/API/v3#PasteModel.

Directories

Path Synopsis
cmd
hibp command
Command hibp is a command line interface and HTTP API server for Troy Hunt's Have I Been Pwned API v3, built on the github.com/marco-montesines/haveibeenpwned library.
Command hibp is a command line interface and HTTP API server for Troy Hunt's Have I Been Pwned API v3, built on the github.com/marco-montesines/haveibeenpwned library.
examples
quickstart command
A minimal example of the haveibeenpwned library.
A minimal example of the haveibeenpwned library.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL