All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
- 🛠 - BREAKING CHANGE
- ✨ - Noteworthy change
- ✨ A node that comes back online after its certificate expired now gets a fresh certificate instead of staying stuck without one. Renewing an expired certificate cannot succeed: the renewal order references it through the ACME ARI
replacesfield, and the CA rejects orders that reference a certificate it no longer considers current (Let's Encrypt returns HTTP 404urn:ietf:params:acme:error:malformed), which certmagic retried forever. On startup the client now discards an expired certificate found in local storage and requests a new one from scratch, without thereplacesfield.1
- ✨ First-time certificate setup now confirms the registration broker is healthy (HTTP 204 from
/v1/health) before starting ACME issuance. While the broker keeps failing the check, the client logs a single ERROR and re-checks hourly instead of running doomed ACME flows that certmagic would retry with backoff for weeks; issuance starts automatically once the broker recovers. Nodes with a certificate already in storage are unaffected. The re-check interval respects aRetry-Afterheader sent by the broker when it is longer than the hourly default, capped at 24h. The probe is exposed asclient.CheckBrokerHealthtogether with theclient.HealthCheckPathconstant; see ipfs/kubo#11397 for an example of wiring this in a downstream node. (#91)
- ✨ Fixed AutoTLS registration failing with a
401when the forge endpoint is load-balanced (such asregistration.libp2p.direct). The DNS-01 PeerID-auth handshake makes two requests, and without a session-affinity cookie the second can reach a different backend than the first, which never issued the challenge and rejects the request with an additional401.client.SendChallengenow adds a cookie jar when the supplied*http.Clientlacks one, so the affinity cookie pins both requests to the same backend. A jar you set viaWithHTTPClientorWithChallengeHTTPClientstays in place. (#90)
- Bumped direct dependencies.
certmagicv0.21.6 → v0.25.3 hardens OCSP delegated-responder validation, andcorednsv1.14.2 → v1.14.3 builds against Go 1.26.2 to sweep in stdlib CVE fixes.fsnotifyv1.9.0 → v1.10.1 fixes the inotify sibling-path watch removal that affected thedenylistplugin. Also bumpedacmez/v3v3.0.0 → v3.1.6,pebble/v2v2.7.0 → v2.10.1,bartv0.26.0 → v0.28.0,go-datastorev0.8.2 → v0.9.1,go-multiaddrv0.16.0 → v0.16.1,go-multiaddr-dnsv0.4.1 → v0.5.0,go-multibasev0.2.0 → v0.3.0, andslok/go-http-metricsv0.12.0 → v0.13.0, plus patch bumps forgo-log/v2,prometheus/client_golang, andzap. The pebble bump required passingkeyAlg="rsa"topebbleCA.New,caaIdentities=niltopebbleWFE.New, and pointing pebble VA's DNS queries at CoreDNS's TCP listener, since pebble v2.10 forces TCP for ACME DNS lookups. - 🛠 Bumped the optional DynamoDB datastore (
database-type dynamo) togo-ds-dynamodbv0.3.0. The release switches to the current AWS SDK for Go and fixes several crashes and deadlocks. Operators should confirm their AWS credentials still work;AWS_REGION,AWS_ACCESS_KEY_ID, andAWS_SECRET_ACCESS_KEYstill apply. See the go-ds-dynamodb v0.3.0 release notes for details.
denylistplugin leaked the previous instance's feed tickers and fsnotify watcher on every Caddy reload. Cleanup now runs onOnShutdowninstead ofOnFinalShutdown, so reloads release these resources.
- ✨
client.WithHTTPClient(*http.Client)option onP2PForgeCertMgrand a matchingclient.WithChallengeHTTPClient(*http.Client)option forclient.SendChallenge. Lets callers supply a custom*http.Client(with a customTransport, resolver, or root CAs) for the DNS-01 challenge POST to the forge registration endpoint. Useful for test harnesses that run an in-process forge on a loopback address while the PeerID-auth signature must stay scoped to the production registration hostname.client.SendChallengegains a trailing variadicopts ...SendChallengeOptionparameter; existing positional-only callers compile unchanged. (#87)
- Bumped
google.golang.org/grpcto v1.79.3, clearing CVE-2026-33186 from SBOM scanners (not exploitable in p2p-forge at runtime; no gRPC listener is exposed) - Bumped
go-libp2pto v0.48.0 andcorednsto v1.14.2 to match the newquic-gov0.59.0 that ships with go-libp2p - Bumped
go-ds-dynamodbto v0.2.2 andgo-logto v2.9.1 - Pinned
coredns/caddyto the tagged v1.1.4 release in place of the pre-release master snapshot inherited from coredns - Wired the go-log slog bridge in
main.go, required by go-log v2.9 and go-libp2p v0.45+, so libp2p subsystem logs flow through go-log and respond togolog.SetLogLevel
- 🛠 IP denylist plugin (
denylist) supporting local files with fsnotify auto-reload and HTTP feeds (e.g. Spamhaus DROP, URLhaus) with periodic refresh. Integrates with theipparser(DNS) andacme(HTTP) plugins: denied IPs get NODATA on DNS and HTTP 403 on ACME requests. Allowlists are checked first and bypass denylists. Prometheus metrics expose blocked request counts, list sizes, and refresh status.
- 🛠 Bumped go.mod to Go 1.24
- Hardened
ipparser: extracted IP parsing intoparseIPFromPrefix()with query-type validation, removed deadANYquery handling (now returned as HINFO via theanyplugin per RFC 8482), and isolated tests to stop flakiness
- Metrics registry race conditions
- Datastore not closed on shutdown, leaking file handles on Windows
- Fixed addrs factory skipping logic in client/acme.go
- Added test coverage for addr skipping in client/acme_test.go
- Updated go-libp2p to v0.42
- Use autonatv2 event to begin cert management
- Filter out public /p2p-circuit addrs in client
- Updated go-libp2p to v0.41.1
Footnotes
-
RFC 9773, section 5: servers SHOULD validate the certificate referenced by
replacesand SHOULD reject the newOrder request when those checks fail. ↩