# Kubo changelog v0.43
> [!IMPORTANT]
> v0.43 is the last Kubo release with new features from the [Shipyard](https://ipshipyard.com/) team. Our IPFS work ends on September 30, 2026. Until then we will ship security and bug fix releases if any are needed. After that date, no one at Shipyard maintains Kubo. If you depend on Kubo, read [the announcement](https://ipshipyard.com/blog/2026-the-end-of-ipfs-at-shipyard/) and send us your transition questions before the end of September.
- [v0.43.0](#v0430)
- [v0.43.1](#v0431)
## v0.43.0
- [Overview](#overview)
- [๐ฆ Highlights](#-highlights)
- [๐ Native `ipfs://` and `ipns://` URIs work as input](#-native-ipfs-and-ipns-uris-work-as-input)
- [๐ One-time notice when behind CGNAT](#-one-time-notice-when-behind-cgnat)
- [๐ฉบ AutoTLS checks broker health before registration](#-autotls-checks-broker-health-before-registration)
- [๐ Revamped TTL and expiration handling for IPNS and DNSLink](#-revamped-ttl-and-expiration-handling-for-ipns-and-dnslink)
- [๐ Clearer errors for invalid config at startup](#-clearer-errors-for-invalid-config-at-startup)
- [๐๏ธ `ipfs files` no longer hangs when garbage collection runs](#-ipfs-files-no-longer-hangs-when-garbage-collection-runs)
- [๐ `ipfs config replace` keeps PeerID and private key in sync](#-ipfs-config-replace-keeps-peerid-and-private-key-in-sync)
- [๐ secp256k1 key generation, export, and import](#-secp256k1-key-generation-export-and-import)
- [๐ Sturdier DHT reprovides on large nodes](#-sturdier-dht-reprovides-on-large-nodes)
- [๐ก Future-proofing browser retrieval: `webrtc-direct` v2](#-future-proofing-browser-retrieval-webrtc-direct-v2)
- [๐ Future-proofing browser retrieval: WebTransport draft-15](#-future-proofing-browser-retrieval-webtransport-draft-15)
- [๐งญ Delegated routers now hand browsers an address they can dial](#-delegated-routers-now-hand-browsers-an-address-they-can-dial)
- [๐บ๏ธ Fewer stale addresses in the peerstore](#-fewer-stale-addresses-in-the-peerstore)
- [๐ณ๏ธ Behind NAT: faster relay recovery, dependable shutdown](#-behind-nat-faster-relay-recovery-dependable-shutdown)
- [๐ฎ `ipfs init` no longer creates an IPNS record](#-ipfs-init-no-longer-creates-an-ipns-record)
- [๐ Unified IPNS record storage](#-unified-ipns-record-storage)
- [๐งช Tests use new go-test and rand v2](#-tests-use-new-go-test-and-rand-v2)
- [๐ฅ๏ธ WebUI Improvements](#-webui-improvements)
- [๐งต A truncated CAR response now says so](#-a-truncated-car-response-now-says-so)
- [๐ Security fixes: update recommended](#-security-fixes-update-recommended)
- [๐ฆ๏ธ Dependency updates](#-dependency-updates)
- [๐ Changelog](#-changelog)
- [๐จโ๐ฉโ๐งโ๐ฆ Contributors](#-contributors)
### Overview
### ๐ฆ Highlights
#### ๐ Native `ipfs://` and `ipns://` URIs work as input
Commands that take a path or CID now also accept native IPFS URIs: `ipfs://`, `ipns://`, and the shorter `ipfs:` and `ipns:` forms. `ipfs cat ipfs://` now behaves the same as `ipfs cat /ipfs/` or `ipfs cat `.
`ipfs://` and `ipns://` are how web browsers, browser extensions, and many non-IPFS apps link to and share IPFS content. Before, you had to rewrite such an address into an `/ipfs/` path before Kubo would take it. Now you can copy a URI from a browser address bar and paste it straight into the CLI or the RPC API.
This works wherever a path or CID is accepted, including `cat`, `get`, `ls`, `refs`, `dag`, `block`, `pin`, `files`, and `name resolve`. The scheme is case-insensitive, and the CID or name after it is left untouched.
#### ๐ One-time notice when behind CGNAT
Kubo now logs a one-time notice to stderr at startup when it detects it is behind carrier-grade NAT (CGNAT) or double NAT. CGNAT is common on IPv4-scarce ISPs that share one public address across many subscribers: other peers cannot reach the node directly, and a busy node can fill the shared NAT session table and disrupt internet access for every device on the local network. The notice gives that otherwise hard-to-diagnose "my whole home network drops" symptom a clear cause.
Detection is best-effort and conservative: it fires only when a private or shared-range (`100.64.0.0/10`, RFC 6598) address appears as a NAT-mapped WAN address (via UPnP/NAT-PMP/PCP) that is not one of the node's own interfaces. Kubo ignores addresses on a local interface, so VPN and overlay tools that use `100.64.0.0/10` (such as Tailscale) do not trigger it; when the upstream address is hidden, the node looks like any ordinary NAT and Kubo stays quiet. `ipfs swarm addrs autonat` reports the current classification in its `nat` field (`--enc=json`).
Silence the notice with [`Internal.CGNATCheck`](https://github.com/ipfs/kubo/blob/master/docs/config.md#internalcgnatcheck)`=false`. The dead-listener diagnostic added in v0.42 can now be toggled too, with [`Internal.DeadListenerCheck`](https://github.com/ipfs/kubo/blob/master/docs/config.md#internaldeadlistenercheck).
#### ๐ฉบ AutoTLS checks broker health before registration
AutoTLS certificate issuance depends on the ACME DNS-01 broker at [`AutoTLS.RegistrationEndpoint`](https://github.com/ipfs/kubo/blob/master/docs/config.md#autotlsregistrationendpoint) (`registration.libp2p.direct` by default). Before, a publicly reachable node without a certificate would attempt ACME issuance even when that broker was unreachable (offline network, firewall, service outage) and keep retrying in the background for days, filling logs with errors that could not resolve themselves.
Now the broker's health endpoint is checked right before the first registration attempt, after the registration delay (1h by default, none when `AutoTLS.Enabled=true` is set explicitly) and once the node is publicly reachable. While the broker keeps failing the check, certificate setup is postponed with a single ERROR in the log and one cheap re-check per hour, and issuance starts automatically once the broker recovers. Nodes that already have a certificate are unaffected, and short-lived nodes (such as CI runners) produce no broker traffic at all.
#### ๐ Revamped TTL and expiration handling for IPNS and DNSLink
A record's own lifetime and TTL now govern both how Kubo creates it and how long clients may cache it, and a cache never outlives the record's validity. This applies when `ipfs name publish` creates an IPNS record, and when the gateway serves IPNS and DNSLink responses.
At publish time, `ipfs name publish` sanitizes its duration flags before creating an [IPNS record](https://specs.ipfs.tech/ipns/ipns-record/), instead of emitting one that fails verification later:
- `--lifetime` must be greater than zero; a non-positive value would expire the record immediately.
- `--ttl` must be non-negative. An explicit `--ttl` greater than `--lifetime` is rejected; an omitted `--ttl` is capped to `--lifetime`, since a record is not cached past its validity.
> [!IMPORTANT]
> The daemon now refuses to start when [`Ipns.RecordLifetime`](https://github.com/ipfs/kubo/blob/master/docs/config.md#ipnsrecordlifetime) is shorter than [`Ipns.RepublishPeriod`](https://github.com/ipfs/kubo/blob/master/docs/config.md#ipnsrepublishperiod): records would expire before the republisher refreshes them, leaving the name unresolvable. Raise `Ipns.RecordLifetime` or lower `Ipns.RepublishPeriod` so the lifetime is at least the period.
On the serving side, the gateway derives a client's cache lifetime from the record itself:
- **DNSLink websites** (`/ipns/`) set `Cache-Control: max-age` from the DNS TXT record's own TTL, so a browser or CDN caches the site for as long as its DNS record allows and re-fetches once that expires, instead of following a fixed default that cached short-lived records too long and long-lived ones too briefly. This needs a resolver that reports TTLs: Go's built-in OS resolver does not, so point [`DNS.Resolvers`](https://github.com/ipfs/kubo/blob/master/docs/config.md#dnsresolvers) at a DNS-over-HTTPS endpoint (a `.` entry covers every domain) to turn it on. Without one, DNSLink caching is unchanged. When a name resolves through several hops (a DNSLink pointing at an IPNS name, or a chain of them), the shortest TTL along the way wins.
- **IPNS records** cap their `Cache-Control: max-age` to the time left before the record expires (its EOL), so a cache cannot serve a record past the point it stops being valid. An already-expired record, or one whose expiration is unknown, returns `no-store`, and a malformed record's negative TTL is floored at zero instead of surfacing as a negative `max-age`. This covers both gateway IPNS responses and `/routing/v1/ipns` when the delegated routing server is enabled ([boxo#1166](https://github.com/ipfs/boxo/pull/1166)).
- **Revalidation** with `If-None-Match` or `If-Modified-Since` now returns `Etag` and `Cache-Control` on the `304 Not Modified` response, matching what the `200` would send. A bare 304 used to leave the client's stored copy expired, forcing a fresh revalidation on every later request even when the content had not changed ([boxo#1188](https://github.com/ipfs/boxo/pull/1188)).
For resolved names, a cache hit reports the time remaining on the cache entry rather than the record's original TTL, so a late hit near expiry no longer restarts the full caching window on the client. [`Ipns.MaxCacheTTL`](https://github.com/ipfs/kubo/blob/master/docs/config.md#ipnsmaxcachettl) still caps the reported value.
#### ๐ Clearer errors for invalid config at startup
When startup config is invalid, Kubo now stops with an error that names the problem instead of exiting abruptly. One case is an unsupported hole-punching setup: [`Swarm.EnableHolePunching`](https://github.com/ipfs/kubo/blob/master/docs/config.md#swarmenableholepunching) set to `true` while [`Swarm.RelayClient.Enabled`](https://github.com/ipfs/kubo/blob/master/docs/config.md#swarmrelayclientenabled) is `false`. Hole punching needs the relay client to coordinate the upgrade from a relayed to a direct connection, so the error now names both settings.
The same path also covers deprecated `Provider` and `Reprovider` settings, removed providing options, delegated routing with providing enabled, private-network `auto` routing, and the removed `IPFS_REUSEPORT` environment variable.
#### ๐๏ธ `ipfs files` no longer hangs when garbage collection runs
Running `ipfs repo gc` at the same time as MFS writes could get the whole MFS (Mutable File System) stuck: every later `ipfs files` command would hang, the damage could persist across restarts, and the daemon had to be force-killed to recover. It mostly hit busy nodes that write to MFS from several processes at once and run GC often.
The cause was garbage collection deleting blocks that an in-progress write had just added but had not yet linked into the saved MFS root, leaving the tree pointing at data that no longer existed. Kubo now takes the same lock `ipfs add` uses around every MFS change (from `ipfs files`, `ipfs add --to-files`, and the FUSE `/mfs` and `/ipns` mounts) and reads the MFS root while that lock is held, so GC can no longer collect data a live write still needs. As with `ipfs add`, a garbage collection run and in-flight MFS writes now briefly hold each other off; under GC-heavy load a single write can pause for the length of a GC and, with a short client timeout, look like it timed out and then succeed on retry.
MFS also stops hanging when a block it needs is missing or unreachable, whether from a repo damaged by an older Kubo, a manual `ipfs block rm`, a crash, or lazily-referenced content whose providers have gone away. Before, the first operation to reach that block would wait forever, freezing the whole MFS and blocking a clean shutdown. Now such an operation ends with an error instead: a lookup that stalls gives up on its own after a timeout, and `ipfs files read` and `ipfs files write` respect `--timeout`, so a stuck read or write returns and the daemon stays responsive. Content lazily referenced with `ipfs files cp /ipfs/` still loads from the network as before.
Thanks to [Rinse12](https://github.com/Rinse12) from the [bitsocial.net](https://bitsocial.net) community for stress-testing MFS and surfacing the mechanics with clear reproduction steps.
#### ๐ `ipfs config replace` keeps PeerID and private key in sync
[`ipfs config replace`](https://docs.ipfs.tech/reference/kubo/cli/#ipfs-config-replace) now re-derives [`Identity.PeerID`](https://github.com/ipfs/kubo/blob/master/docs/config.md#identitypeerid) from the node's existing private key, which cannot be set over the Kubo RPC API. You can now roll one shared config out across a fleet: replace it onto every node, and each keeps its own identity even when the file carries another node's PeerID. To change a node's identity deliberately, stop the daemon and run [`ipfs key rotate`](https://docs.ipfs.tech/reference/kubo/cli/#ipfs-key-rotate).
#### ๐ secp256k1 key generation, export, and import
Kubo can now generate secp256k1 keys and move them in and out as PEM files. Until now `ipfs key gen --type=secp256k1` failed with `unrecognized key type`, and secp256k1 keys could not be exported to PEM: the PEM PKCS #8 support went through Go's `crypto/x509`, which does not know the secp256k1 curve.
`ipfs key gen`, `ipfs key rotate`, and `ipfs init` all accept `--type=secp256k1` (`-a secp256k1` for `ipfs init`). `ipfs key export --format=pem-pkcs8-cleartext` and `ipfs key import` handle secp256k1 like the other key types, so keys move between Kubo and tools such as OpenSSL in both directions:
```console
openssl ecparam -name secp256k1 -genkey -noout | openssl pkcs8 -topk8 -nocrypt > secp.pem
ipfs key import mykey --format=pem-pkcs8-cleartext secp.pem
```
ed25519 and secp256k1 keys are always 256 bits, so `--size` (`--bits` for `ipfs init`) is accepted only when it is 256 and rejected otherwise.
#### ๐ Sturdier DHT reprovides on large nodes
[go-libp2p-kad-dht v0.41.0](https://github.com/libp2p/go-libp2p-kad-dht/releases/tag/v0.41.0) lowers peak memory during reprovides on nodes that announce many CIDs, so low-memory consumer devices are less likely to be out-of-memory killed. More in [kad-dht#1259](https://github.com/libp2p/go-libp2p-kad-dht/pull/1259).
#### ๐ก Future-proofing browser retrieval: `webrtc-direct` v2
`/webrtc-direct` and `/quic-v1/webtransport`, both on by default, are the two transports that let a web browser fetch content straight from your node: no gateway in the middle, no signalling server, no CA-issued certificate. Browsers are moving underneath both. Chrome and Firefox have breaking changes in flight, and Safari requires a setting servers were not sending. The [go-libp2p v0.49.0](https://github.com/libp2p/go-libp2p/releases/tag/v0.49.0) in this release keeps your node ready for browsers on both sides of those changes, with nothing to configure.
For `webrtc-direct`, Chrome has [already merged](https://webrtc-review.googlesource.com/c/src/+/385721) the removal of the SDP-rewriting behavior the original (v1) handshake relies on, gated for now behind the `WebRTC-NoSdpMangleUfrag` field trial. Once that reaches stable, Chrome can no longer dial a v1-only server. Kubo now also accepts the replacement [(v2) handshake](https://github.com/libp2p/specs/pull/715) on the same port, so your node is ready before browsers switch, and old clients keep working. More in [go-libp2p#3520](https://github.com/libp2p/go-libp2p/pull/3520), and [libp2p/specs#672](https://github.com/libp2p/specs/issues/672#issuecomment-4297060067) tracks progress across the other libp2p implementations.
Two more `webrtc-direct` fixes ship in the same bump:
- Your node's `/certhash` address now survives restarts. It used to change on every start (the certificate behind it was minted at random each time), so every cached copy of your address in other peers' address books and in DHT records kept going stale. The certificate is now derived from your node's identity key: the certhash changes one final time when you upgrade, then stays put for as long as you keep the same key. More in [go-libp2p#3512](https://github.com/libp2p/go-libp2p/pull/3512).
- On a node AutoNAT had confirmed publicly reachable, `/webrtc-direct` silently went missing from the confirmed-address list, a side effect of sharing UDP port 4001 with `/quic-v1` in the default config. `ipfs swarm addrs autonat` did not report it. On nodes configured to publish provider records to a delegated HTTP router, the records went out without the `/webrtc-direct` address, so a browser that discovered your node that way could not dial it over this transport. Regular DHT announcements were unaffected. Fixed in [go-libp2p#3526](https://github.com/libp2p/go-libp2p/pull/3526), and confirmed addresses also stop periodically flapping back to `unknown` ([go-libp2p#3528](https://github.com/libp2p/go-libp2p/pull/3528)).
#### ๐ Future-proofing browser retrieval: WebTransport draft-15
WebTransport is the other transport a browser can use to dial your node directly. It is still an IETF draft, and browsers implement different draft versions. The `/quic-v1/webtransport` listener now answers them all:
- [draft-15](https://www.ietf.org/archive/id/draft-ietf-webtrans-http3-15.html) renamed the two values that identify a WebTransport session on the wire. Every shipping browser still sends the old names, but Firefox is [already implementing](https://github.com/mozilla/neqo/pull/3646) the new ones, and a server that only knows the old pair would stop answering Firefox the day that ships. Your node now answers to both.
- Safari (26.4 and later) does not complete the WebTransport handshake unless the server sends the `WT_MAX_SESSIONS` session limit from an [earlier draft](https://www.ietf.org/archive/id/draft-ietf-webtrans-http3-14.html). Your node now sends it, so Safari can open sessions it previously could not.
Nothing to do today. More in [go-libp2p#3532](https://github.com/libp2p/go-libp2p/pull/3532). The same update closes a memory-exhaustion hole in the WebTransport server, covered in [Security fixes](#-security-fixes-update-recommended) below.
One regression, and it is go-to-go only: when dialing out over WebTransport, Kubo now speaks only draft-15, so it can no longer dial peers running older go-libp2p over this transport. Those peers can still dial your node, and go-to-go connections prefer plain `/quic-v1` on the same UDP port anyway. WebTransport is there for browsers, and browsers are unaffected.
#### ๐งญ Delegated routers now hand browsers an address they can dial
This one is for nodes that send provider records to an HTTP router, which today means [`Routing.Type=custom`](https://github.com/ipfs/kubo/blob/master/docs/config.md#routingtype) with a `provide` entry in [`Routing.Methods`](https://github.com/ipfs/kubo/blob/master/docs/config.md#routingmethods). The default `auto` provides over the DHT alone and is unaffected.
Those records now carry every address the node announces, including the AutoTLS `/tls/ws` and `webrtc-direct` ones. The DHT already published those two; the HTTP path dropped them. Loopback and LAN addresses stay out of the record whenever the node has a public one.
Before, a publicly reachable node with [AutoTLS](https://github.com/ipfs/kubo/blob/master/docs/config.md#autotls) or `webrtc-direct` enabled published records listing only `tcp`, `quic-v1`, and `webtransport`. No browser can dial any of those. `ipfs id` still showed the missing addresses and the node was still listening on them, so everything looked healthy while browsers that found the node through a delegated router had nothing to connect with.
That gap broke browser-first apps. [Bitsocial](https://bitsocial.net/) runs libp2p in the browser, uses delegated routers only to find peers, and fetches posts straight from those peers. A record without `/tls/ws` or `webrtc-direct` is a record its readers cannot use, no matter how reachable the node behind it is. See [#11369](https://github.com/ipfs/kubo/issues/11369).
#### ๐บ๏ธ Fewer stale addresses in the peerstore
Your node remembers addresses for peers it hears about, and dead ones pile up and waste dial attempts. Two fixes trim them:
- When a peer announces a newer signed address list, it now replaces the stored one instead of merging into it. Addresses in use by a live connection are kept. More in [go-libp2p#3487](https://github.com/libp2p/go-libp2p/pull/3487).
- Kubo no longer puts empty addresses into the signed records it announces about itself, which other implementations error out on. More in [go-libp2p#3494](https://github.com/libp2p/go-libp2p/pull/3494).
The new [`Internal.NonPublicAddrPublishing`](https://github.com/ipfs/kubo/blob/master/docs/config.md#internalnonpublicaddrpublishing) flag controls whether your node publishes addresses the wider internet cannot reach, such as private, CGNAT, and loopback ranges. Set it to `false` to keep them out of the signed peer record and the DHT, or `true` to publish them, which is what a LAN-only node wants. Leave it unset to follow go-libp2p's defaults, which [are known to change](https://github.com/libp2p/go-libp2p/issues/3460).
#### ๐ณ๏ธ Behind NAT: faster relay recovery, dependable shutdown
A node behind NAT depends on circuit relays to stay reachable and to hole punch direct connections. When such a node lost its relay (a relay restart, a dropped connection), it put that same known-good relay on a penalty list for up to an hour before trying it again, and could sit without a relayed address, invisible to inbound peers, for that long. Only failed attempts to reserve a slot count against a relay now, so your node reconnects to a lost relay right away. More in [go-libp2p#3482](https://github.com/libp2p/go-libp2p/pull/3482).
Two rare shutdown races in the same machinery are fixed as well: hole punching discovering the node's first public address at the moment of shutdown could leave `ipfs daemon` hanging on exit until force-killed ([go-libp2p#3504](https://github.com/libp2p/go-libp2p/pull/3504)), and stopping the daemon mid-reachability-check could crash it ([go-libp2p#3528](https://github.com/libp2p/go-libp2p/pull/3528)).
#### ๐ฎ `ipfs init` no longer creates an IPNS record
Since the earliest days, `ipfs init` published an IPNS record for the node's own key pointing at an empty directory. The record was effectively invisible (the DHT discarded records stored without a receive timestamp), so resolving an unpublished name failed as expected. With this release's unified record storage the record would have become visible to the network, making every fresh node's name resolve to an empty directory instead of failing until the first real `ipfs name publish`. The publish is removed; `ipfs init` still pins the empty directory. Resolving a never-published name now consistently returns an error.
#### ๐ Unified IPNS record storage
Offline resolution (offline nodes, `--offline` commands, and `Gateway.NoFetch` gateways) and the DHT now share one value store, under a dedicated `/dht` prefix in the repo datastore (`/dht/pk/...`, `/dht/ipns/...`). So a name resolves the same whether your node is reachable or not: publish online and it still resolves offline; publish offline and peers get it once you reconnect.
How long a record lives depends on whether a DHT is running. A running DHT drops value records not refreshed within 48h (`amino.DefaultMaxRecordAge`); your own survive because the republisher re-announces them every `Ipns.RepublishPeriod` (4h default). An offline node runs no DHT, so nothing sweeps the store and records last until their IPNS EOL, as offline resolution always has.
On first daemon start after upgrading, Kubo clears the old root-level records (pre-v0.42 layout) once in the background; they return in the new layout as they are republished. Provider records keep their `/providers/...` prefix, untouched.
The cleanup runs in the background and does not block startup, but it has to scan every key in the datastore once, because the stale records sit at the datastore root with no shared prefix to narrow the search. On a large repo the first start after upgrading can spend extra time on this. A marker is written once the scan finishes, so later starts skip it entirely.
#### ๐งช Tests use new go-test and rand v2
The new `go-test` is upgraded to use `math/rand/v2` in all of its packages. The `/go-test/random` package now allows reuse of the random number generator for more efficiently generating sets of random values.
Additionally, the `/go-test/random` package removes support for a global seed for the random number generator. This led to the possibility of multiple tests setting the global seed to generate deterministic values, and breaking other tests by causing the generator to generate unexpected values. This breakage could be intermittent and difficult to debug, depending on the how/which tests ran at the same time.
Since the underlying pseudo-random number generator was changed in the go-test module, the data generated for a given seed also changed. This required updating tests that relied on seeding the generator and getting expected values.
#### ๐ฅ๏ธ WebUI Improvements
IPFS Web UI has been updated to [v4.13.0](https://github.com/ipfs/ipfs-webui/releases/tag/v4.13.0).
Share Link only ever produced a public gateway URL, so everything you shared depended on a server someone else runs. A new "Sharing IPFS Links" section in Settings lets you pick native `ipfs://` and `ipns://` addresses instead. This is opt-in: the default still points at `dweb.link`.
Through Docker or a reverse proxy, the Web UI handed out dead links built from [`Addresses.Gateway`](https://github.com/ipfs/kubo/blob/master/docs/config.md#addressesgateway), usually `/ip4/0.0.0.0/tcp/8080`. The new "Local HTTP Gateway" field takes a URL your browser can reach.
Removing files through the selection toolbar left their pins behind. It now offers the "Also remove local pin (recommended)" checkbox.
#### ๐งต A truncated CAR response now says so
A CAR response that stopped partway through used to look exactly like a complete one, so a client could accept a short DAG as the whole thing. Such a response now ends with `[Gateway Error: CAR stream truncated, response is incomplete]`, which makes the trailing bytes invalid CAR: a reader stops with an error instead of trusting what it got. If you run a gateway behind a reverse proxy or a CDN, a short response now identifies itself instead of leaving you to guess which hop dropped it.
The gateway also caps how deep a CAR response descends into a DAG at 1024 levels, far beyond anything UnixFS produces: traversal cost grows with depth, and the cap stops an adversarially nested DAG from eating memory. A response cut at the limit ends with the same marker ([boxo#1197](https://github.com/ipfs/boxo/pull/1197)).
#### ๐ Security fixes: update recommended
This release closes several memory-exhaustion and crash issues, some of them already public. Update when you can.
- **Pubsub memory exhaustion** ([CVE-2026-46679](https://github.com/advisories/GHSA-4f8r-922h-2vgv)): a remote peer could subscribe to an endless stream of unique topic names, disconnect, and leave your node holding every one of them, with memory growing each round until a restart. Kubo now frees a topic's state once the last peer leaves it and limits how much a peer can pack into a single control message. Only nodes that turn pubsub on are affected, through [`Pubsub.Enabled`](https://github.com/ipfs/kubo/blob/master/docs/config.md#pubsubenabled) or [`Ipns.UsePubsub`](https://github.com/ipfs/kubo/blob/master/docs/config.md#ipnsusepubsub); if you set either, update as soon as you can. The Go and Python libp2p ports track the same pattern in [go-libp2p-pubsub#705](https://github.com/libp2p/go-libp2p-pubsub/issues/705) and [py-libp2p#1349](https://github.com/libp2p/py-libp2p/issues/1349).
- **WebTransport memory exhaustion** ([CVE-2026-57497](https://github.com/advisories/GHSA-g35j-m5xg-vh3q)): a peer could send a WebTransport message of a type your node did not recognise, and your node would hold the whole body in memory while skipping past it. A big enough message, or enough of them, exhausts memory. Affects any node listening on `/quic-v1/webtransport`, which is the default.
- **HTTP/3 trailer decompression memory exhaustion** ([CVE-2026-40898](https://github.com/advisories/GHSA-vvgj-x9jq-8cj9)): quic-go limited the compressed size of incoming HTTP/3 trailers but not their decoded size, so a crafted message could expand to about fifty times its wire size in memory. Kubo runs quic-go's HTTP/3 server as part of the default WebTransport listener; the quic-go v0.60.0 in this release includes the fix.
- **libp2p resource caps**: a hostile peer could flood your peerstore with unconnected addresses ([go-libp2p#3486](https://github.com/libp2p/go-libp2p/pull/3486)), plant more than 1,800 protocol entries through chunked identify messages ([go-libp2p#3501](https://github.com/libp2p/go-libp2p/pull/3501)), or attach an unbounded number of addresses to one `webrtc-direct` connection ([go-libp2p#3500](https://github.com/libp2p/go-libp2p/pull/3500)). Each is now bounded.
- **Daemon crash on routing queries** ([go-libp2p#3490](https://github.com/libp2p/go-libp2p/pull/3490)): a data race could corrupt the results streamed by `ipfs routing findprovs`, `ipfs routing findpeer`, and `ipfs dht query`, taking the whole daemon down mid-response.
- **Tracing exporter memory** ([CVE-2026-39882](https://github.com/open-telemetry/opentelemetry-go/security/advisories/GHSA-w8rr-5gcm-pp58)): a hostile or man-in-the-middle collector could exhaust memory through the OpenTelemetry OTLP HTTP exporter. Affects nodes that export traces over OTLP HTTP.
#### ๐ฆ๏ธ Dependency updates
- update `ipfs-webui` to [v4.13.0](https://github.com/ipfs/ipfs-webui/releases/tag/v4.13.0)
- update `go-libp2p` to [v0.49.0](https://github.com/libp2p/go-libp2p/releases/tag/v0.49.0)
- update `go-libp2p-pubsub` to [v0.17.0](https://github.com/libp2p/go-libp2p-pubsub/releases/tag/v0.17.0)
- update `go-libp2p-kad-dht` to [v0.42.1](https://github.com/libp2p/go-libp2p-kad-dht/releases/tag/v0.42.1) (incl. [v0.42.0](https://github.com/libp2p/go-libp2p-kad-dht/releases/tag/v0.42.0), [v0.41.0](https://github.com/libp2p/go-libp2p-kad-dht/releases/tag/v0.41.0) and the [local record validation fix](https://github.com/libp2p/go-libp2p-kad-dht/pull/1285)); see [Unified IPNS record storage](#-unified-ipns-record-storage) above
- update `boxo` to [v0.42.1](https://github.com/ipfs/boxo/releases/tag/v0.42.1) (incl. [v0.42.0](https://github.com/ipfs/boxo/releases/tag/v0.42.0), [v0.41.0](https://github.com/ipfs/boxo/releases/tag/v0.41.0)); see [Revamped TTL and expiration handling for IPNS and DNSLink](#-revamped-ttl-and-expiration-handling-for-ipns-and-dnslink) and [Unified IPNS record storage](#-unified-ipns-record-storage) above, plus a bitswap fix so fetches from a just-reconnected peer no longer stall ([boxo#1164](https://github.com/ipfs/boxo/pull/1164)), a fix so HTTP providers on IPv6 addresses are dialed correctly ([boxo#1196](https://github.com/ipfs/boxo/pull/1196)), and shorter stale windows on cached `/routing/v1` responses so clients stop getting long-dead peer addresses ([boxo#1195](https://github.com/ipfs/boxo/pull/1195)); also pulls in `go-doh-resolver` [v0.6.0](https://github.com/libp2p/go-doh-resolver/releases/tag/v0.6.0) and `go-multiaddr-dns` [v0.6.0](https://github.com/multiformats/go-multiaddr-dns/releases/tag/v0.6.0)
- update `p2p-forge/client` to [v0.10.1](https://github.com/ipshipyard/p2p-forge/releases/tag/v0.10.1) (incl. [v0.10.0](https://github.com/ipshipyard/p2p-forge/releases/tag/v0.10.0), [v0.9.1](https://github.com/ipshipyard/p2p-forge/releases/tag/v0.9.1), [v0.9.0](https://github.com/ipshipyard/p2p-forge/releases/tag/v0.9.0), [v0.8.1](https://github.com/ipshipyard/p2p-forge/releases/tag/v0.8.1)); a node that comes back online after its AutoTLS certificate expired now discards it and requests a fresh one, instead of retrying a renewal the CA always rejects
- update `go-ds-pebble` to [v0.5.12](https://github.com/ipfs/go-ds-pebble/releases/tag/v0.5.12)
- updates `github.com/cockroachdb/pebble` to [v2.1.6](https://github.com/cockroachdb/pebble/releases/tag/v2.1.6)
### ๐ Changelog
Full Changelog
- github.com/ipfs/kubo:
- chore: set version to v0.43.0-rc2
- test: fix flakes that force CI re-runs (#11413) ([ipfs/kubo#11413](https://github.com/ipfs/kubo/pull/11413))
- chore: restore default telemetry for now (#11415) ([ipfs/kubo#11415](https://github.com/ipfs/kubo/pull/11415))
- fix(http-routing): keep browser transports in provider records (#11394) ([ipfs/kubo#11394](https://github.com/ipfs/kubo/pull/11394))
- chore: update p2p-forge to v0.10.1 (#11414) ([ipfs/kubo#11414](https://github.com/ipfs/kubo/pull/11414))
- docs: go-libp2p v0.49.0 + boxo v0.42.1 (#11410) ([ipfs/kubo#11410](https://github.com/ipfs/kubo/pull/11410))
- chore: upgrade to boxo v0.42.1 + go-libp2p to v0.49 (#11412) ([ipfs/kubo#11412](https://github.com/ipfs/kubo/pull/11412))
- fix: harden CAR streaming and truncation (#11409) ([ipfs/kubo#11409](https://github.com/ipfs/kubo/pull/11409))
- chore: set version to v0.43.0-rc1
- chore: go 1.26.5 and deps (#11400) ([ipfs/kubo#11400](https://github.com/ipfs/kubo/pull/11400))
- docs: add contribution and stability guardrails
- chore: upgrade to boxo v0.42.0 (#11399) ([ipfs/kubo#11399](https://github.com/ipfs/kubo/pull/11399))
- chore: bump go-libp2p-kad-dht to v0.42 (#11398) ([ipfs/kubo#11398](https://github.com/ipfs/kubo/pull/11398))
- feat(autotls): skip issuance when broker is down (#11397) ([ipfs/kubo#11397](https://github.com/ipfs/kubo/pull/11397))
- fix: update go-test module (#11390) ([ipfs/kubo#11390](https://github.com/ipfs/kubo/pull/11390))
- chore: update webui to v4.13.0 (#11396) ([ipfs/kubo#11396](https://github.com/ipfs/kubo/pull/11396))
- upgrade go-libp2p-pubsub to v0.17.0 (#11391) ([ipfs/kubo#11391](https://github.com/ipfs/kubo/pull/11391))
- chore(deps): bump go-libp2p and go-libp2p-pubsub (#11389) ([ipfs/kubo#11389](https://github.com/ipfs/kubo/pull/11389))
- docs: clarify open vs NoFetch gateway recipes (#11382) ([ipfs/kubo#11382](https://github.com/ipfs/kubo/pull/11382))
- fix(key): restore secp256k1 keygen and add PEM PKCS8 import/export (#11387) ([ipfs/kubo#11387](https://github.com/ipfs/kubo/pull/11387))
- fix(mfs): stop repo gc from freezing files ops (#11386) ([ipfs/kubo#11386](https://github.com/ipfs/kubo/pull/11386))
- chore(deps): bump go-libp2p-kad-dht to v0.41.0 (#11377) ([ipfs/kubo#11377](https://github.com/ipfs/kubo/pull/11377))
- feat(cli): accept native ipfs:// and ipns:// URIs (#11375) ([ipfs/kubo#11375](https://github.com/ipfs/kubo/pull/11375))
- Fix check for opt-in and opt-out messages (#11383) ([ipfs/kubo#11383](https://github.com/ipfs/kubo/pull/11383))
- feat: make telemetry opt-in (#11374) ([ipfs/kubo#11374](https://github.com/ipfs/kubo/pull/11374))
- docs(changelog): merge startup config entries (#11376) ([ipfs/kubo#11376](https://github.com/ipfs/kubo/pull/11376))
- fix(daemon): return config errors instead of calling log.Fatal (#11373) ([ipfs/kubo#11373](https://github.com/ipfs/kubo/pull/11373))
- test: cover ipfs get paths containing closing bracket (#11359) ([ipfs/kubo#11359](https://github.com/ipfs/kubo/pull/11359))
- fix(config): protect and derive PeerID during JSON replace (#11344) ([ipfs/kubo#11344](https://github.com/ipfs/kubo/pull/11344))
- chore: bump p2p-forge to v0.9.1 (#11368) ([ipfs/kubo#11368](https://github.com/ipfs/kubo/pull/11368))
- fix: return error instead of log.Fatal in HolePunching (#11365) ([ipfs/kubo#11365](https://github.com/ipfs/kubo/pull/11365))
- docs: clarify optimistic provide is default-on (#11363) ([ipfs/kubo#11363](https://github.com/ipfs/kubo/pull/11363))
- upgrade to go-ds-pebble v0.5.12 (#11361) ([ipfs/kubo#11361](https://github.com/ipfs/kubo/pull/11361))
- chore: upgrade to boxo v0.41.0 (#11356) ([ipfs/kubo#11356](https://github.com/ipfs/kubo/pull/11356))
- Merge release v0.42.0 ([ipfs/kubo#11357](https://github.com/ipfs/kubo/pull/11357))
- feat(libp2p): warn when behind CGNAT (#11352) ([ipfs/kubo#11352](https://github.com/ipfs/kubo/pull/11352))
- fix: avoid redundant cidset in dag stat (#11353) ([ipfs/kubo#11353](https://github.com/ipfs/kubo/pull/11353))
- fix: bound ipns caching and validate lifetimes (#11349) ([ipfs/kubo#11349](https://github.com/ipfs/kubo/pull/11349))
- chore: go 1.26.4 (#11350) ([ipfs/kubo#11350](https://github.com/ipfs/kubo/pull/11350))
- fix(libp2p): quieter dead-listener check (#11342) ([ipfs/kubo#11342](https://github.com/ipfs/kubo/pull/11342))
- feat: derive AgentSuffix from build origin (#11341) ([ipfs/kubo#11341](https://github.com/ipfs/kubo/pull/11341))
- chore(deps): bump p2p-forge to 0.9.0 (#11336) ([ipfs/kubo#11336](https://github.com/ipfs/kubo/pull/11336))
- chore: start v0.43.0 dev cycle
- github.com/ipfs/boxo (v0.40.0 -> v0.42.1):
- Release v0.42.1 ([ipfs/boxo#1200](https://github.com/ipfs/boxo/pull/1200))
- chore: upgrade to go-libp2p v0.49.0 (#1198) ([ipfs/boxo#1198](https://github.com/ipfs/boxo/pull/1198))
- fix: shorten /routing/v1 stale windows (#1195) ([ipfs/boxo#1195](https://github.com/ipfs/boxo/pull/1195))
- fix(bitswap): bracket IPv6 hosts in provider URLs (#1196) ([ipfs/boxo#1196](https://github.com/ipfs/boxo/pull/1196))
- fix: harden dag traversal and CAR responses (#1197) ([ipfs/boxo#1197](https://github.com/ipfs/boxo/pull/1197))
- Release v0.42.0 ([ipfs/boxo#1193](https://github.com/ipfs/boxo/pull/1193))
- refactor: use one same base32 package (#1192) ([ipfs/boxo#1192](https://github.com/ipfs/boxo/pull/1192))
- chore: update dependencies (#1191) ([ipfs/boxo#1191](https://github.com/ipfs/boxo/pull/1191))
- chore: bump go-libp2p-kad-dht to v0.42 (#1189) ([ipfs/boxo#1189](https://github.com/ipfs/boxo/pull/1189))
- fix(gateway): renew cache freshness on 304 (#1188) ([ipfs/boxo#1188](https://github.com/ipfs/boxo/pull/1188))
- feat(namesys): propagate DNSLink TXT TTL (#1167) ([ipfs/boxo#1167](https://github.com/ipfs/boxo/pull/1167))
- fix: update go-test (#1187) ([ipfs/boxo#1187](https://github.com/ipfs/boxo/pull/1187))
- update dependencies (#1186) ([ipfs/boxo#1186](https://github.com/ipfs/boxo/pull/1186))
- feat(mfs): bound under-lock DAG reads (#1185) ([ipfs/boxo#1185](https://github.com/ipfs/boxo/pull/1185))
- feat(blockstore): expose BloomCacheStatus and add bloom filter Rebuild (#1184) ([ipfs/boxo#1184](https://github.com/ipfs/boxo/pull/1184))
- fix(blockstore): don't activate bloom cache if build incomplete (#1183) ([ipfs/boxo#1183](https://github.com/ipfs/boxo/pull/1183))
- feat(path): add NewPathFromURI for ipfs:// URIs (#1182) ([ipfs/boxo#1182](https://github.com/ipfs/boxo/pull/1182))
- do not use dedup cache for reprovide queue (#1181) ([ipfs/boxo#1181](https://github.com/ipfs/boxo/pull/1181))
- feat(gateway): always answer empty identity probe ([ipfs/boxo#1179](https://github.com/ipfs/boxo/pull/1179))
- chore: improve reprovider with batch queue reads ([ipfs/boxo#1173](https://github.com/ipfs/boxo/pull/1173))
- Release v0.41.0 (#1175) ([ipfs/boxo#1175](https://github.com/ipfs/boxo/pull/1175))
- feat(dag): injectable visited-set in dag traverse (#1168) ([ipfs/boxo#1168](https://github.com/ipfs/boxo/pull/1168))
- chore: remove defunct old queue package (#1172) ([ipfs/boxo#1172](https://github.com/ipfs/boxo/pull/1172))
- chore: upgrade go-car/v2 to v2.17.0 (#1170) ([ipfs/boxo#1170](https://github.com/ipfs/boxo/pull/1170))
- chore: upgrade to go-ipld-prime v0.24.0 (#1169) ([ipfs/boxo#1169](https://github.com/ipfs/boxo/pull/1169))
- fix: bound ipns cache-control to record eol (#1166) ([ipfs/boxo#1166](https://github.com/ipfs/boxo/pull/1166))
- fix(bitswap): don't mark peer unresponsive on a single send failure (#1164) ([ipfs/boxo#1164](https://github.com/ipfs/boxo/pull/1164))
- change kubo PR name in release procedure ([ipfs/boxo#1165](https://github.com/ipfs/boxo/pull/1165))
- chore: bump OTLP exporters to v1.43.0 (#1162) ([ipfs/boxo#1162](https://github.com/ipfs/boxo/pull/1162))
- github.com/ipfs/go-block-format (v0.2.3 -> v0.2.4):
- new version (#71) ([ipfs/go-block-format#71](https://github.com/ipfs/go-block-format/pull/71))
- feat: NewBlockWithPrefix constructor (#70) ([ipfs/go-block-format#70](https://github.com/ipfs/go-block-format/pull/70))
- update dependencies and README (#69) ([ipfs/go-block-format#69](https://github.com/ipfs/go-block-format/pull/69))
- github.com/ipfs/go-cid (v0.6.1 -> v0.6.2):
- update release version (#186) ([ipfs/go-cid#186](https://github.com/ipfs/go-cid/pull/186))
- use errors.New for error strings without formatting (#187) ([ipfs/go-cid#187](https://github.com/ipfs/go-cid/pull/187))
- revise README badges and remove maintainer info (#185) ([ipfs/go-cid#185](https://github.com/ipfs/go-cid/pull/185))
- chore: modernize code and update dependencies (#184) ([ipfs/go-cid#184](https://github.com/ipfs/go-cid/pull/184))
- github.com/ipfs/go-cidutil (v0.1.1 -> v0.1.2):
- new version (#61) ([ipfs/go-cidutil#61](https://github.com/ipfs/go-cidutil/pull/61))
- update dependencies and README (#60) ([ipfs/go-cidutil#60](https://github.com/ipfs/go-cidutil/pull/60))
- github.com/ipfs/go-datastore (v0.9.1 -> v0.9.2):
- bump version (#273) ([ipfs/go-datastore#273](https://github.com/ipfs/go-datastore/pull/273))
- Update README.md by removing badge links
- chore: update error checks (#272) ([ipfs/go-datastore#272](https://github.com/ipfs/go-datastore/pull/272))
- github.com/ipfs/go-ds-flatfs (v0.6.0 -> v0.6.1):
- new version (#152) ([ipfs/go-ds-flatfs#152](https://github.com/ipfs/go-ds-flatfs/pull/152))
- modernize tests (#151) ([ipfs/go-ds-flatfs#151](https://github.com/ipfs/go-ds-flatfs/pull/151))
- fix: make last batch operation take effect (#150) ([ipfs/go-ds-flatfs#150](https://github.com/ipfs/go-ds-flatfs/pull/150))
- github.com/ipfs/go-ds-leveldb (v0.5.2 -> v0.5.3):
- chore: release v0.5.3 (#85) ([ipfs/go-ds-leveldb#85](https://github.com/ipfs/go-ds-leveldb/pull/85))
- modernize tests (#84) ([ipfs/go-ds-leveldb#84](https://github.com/ipfs/go-ds-leveldb/pull/84))
- update dependencies (#83) ([ipfs/go-ds-leveldb#83](https://github.com/ipfs/go-ds-leveldb/pull/83))
- Update go datastore and README links (#77) ([ipfs/go-ds-leveldb#77](https://github.com/ipfs/go-ds-leveldb/pull/77))
- github.com/ipfs/go-ds-measure (v0.2.2 -> v0.2.3):
- chore: release v0.2.3 ([ipfs/go-ds-measure#63](https://github.com/ipfs/go-ds-measure/pull/63))
- github.com/ipfs/go-ds-pebble (v0.5.11 -> v0.5.12):
- update version (#89) ([ipfs/go-ds-pebble#89](https://github.com/ipfs/go-ds-pebble/pull/89))
- github.com/ipfs/go-ipld-cbor (v0.2.1 -> v0.3.0):
- chore: v0.3.0 bump (#125) ([ipfs/go-ipld-cbor#125](https://github.com/ipfs/go-ipld-cbor/pull/125))
- feat: increased strictness in decoding (#124) ([ipfs/go-ipld-cbor#124](https://github.com/ipfs/go-ipld-cbor/pull/124))
- chore: simplify hashing test (#123) ([ipfs/go-ipld-cbor#123](https://github.com/ipfs/go-ipld-cbor/pull/123))
- github.com/ipfs/go-ipld-format (v0.6.3 -> v0.6.4):
- bump version (#107) ([ipfs/go-ipld-format#107](https://github.com/ipfs/go-ipld-format/pull/107))
- update dependencies (#106) ([ipfs/go-ipld-format#106](https://github.com/ipfs/go-ipld-format/pull/106))
- refactor: modernize code (#103) ([ipfs/go-ipld-format#103](https://github.com/ipfs/go-ipld-format/pull/103))
- Revise README badges and sections (#101) ([ipfs/go-ipld-format#101](https://github.com/ipfs/go-ipld-format/pull/101))
- github.com/ipfs/go-ipld-git (v0.1.1 -> v0.1.3):
- fix: validate object fields before use (#77) ([ipfs/go-ipld-git#77](https://github.com/ipfs/go-ipld-git/pull/77))
- bump version (#76) ([ipfs/go-ipld-git#76](https://github.com/ipfs/go-ipld-git/pull/76))
- chore: update dependencies (#74) ([ipfs/go-ipld-git#74](https://github.com/ipfs/go-ipld-git/pull/74))
- sync: update CI config files (#56) ([ipfs/go-ipld-git#56](https://github.com/ipfs/go-ipld-git/pull/56))
- sync: update CI config files (#54) ([ipfs/go-ipld-git#54](https://github.com/ipfs/go-ipld-git/pull/54))
- github.com/ipfs/go-test (v0.3.0 -> v0.4.1):
- bump version to v0.4.1 (#41) ([ipfs/go-test#41](https://github.com/ipfs/go-test/pull/41))
- release v0.4.0 (#39) ([ipfs/go-test#39](https://github.com/ipfs/go-test/pull/39))
- feat: allow reuse of random source plus upgrades and fixes, (#37) ([ipfs/go-test#37](https://github.com/ipfs/go-test/pull/37))
- github.com/ipfs/go-unixfsnode (v1.10.4 -> v1.10.6):
- fix: use bitfield.FromBytes for HAMT bitmaps (#100) ([ipfs/go-unixfsnode#100](https://github.com/ipfs/go-unixfsnode/pull/100))
- new version (#99) ([ipfs/go-unixfsnode#99](https://github.com/ipfs/go-unixfsnode/pull/99))
- update random name and data generation in tests (#98) ([ipfs/go-unixfsnode#98](https://github.com/ipfs/go-unixfsnode/pull/98))
- github.com/ipld/go-car/v2 (v2.16.1-0.20260428045700-c4b9f366f20c -> v2.17.0):
- chore: v2.17.0 bump (#672) ([ipld/go-car#672](https://github.com/ipld/go-car/pull/672))
- github.com/ipld/go-ipld-prime (v0.23.0 -> v0.24.0):
failed to fetch repo
- github.com/ipshipyard/p2p-forge (v0.9.0 -> v0.10.1):
- fix(client): discard expired cert on startup (#95) ([ipshipyard/p2p-forge#95](https://github.com/ipshipyard/p2p-forge/pull/95))
- dns: repoint registration NS to new cloudflare account (#93) ([ipshipyard/p2p-forge#93](https://github.com/ipshipyard/p2p-forge/pull/93))
- feat(client): check broker health before issuance (#91) ([ipshipyard/p2p-forge#91](https://github.com/ipshipyard/p2p-forge/pull/91))
- fix(client): keep registration on one LB backend (#90) ([ipshipyard/p2p-forge#90](https://github.com/ipshipyard/p2p-forge/pull/90))
- github.com/libp2p/go-doh-resolver (v0.5.0 -> v0.6.0):
- feat: add LookupTXTWithTTL to expose TXT record TTL (#33) ([libp2p/go-doh-resolver#33](https://github.com/libp2p/go-doh-resolver/pull/33))
- chore!: bump go.mod to Go 1.25 and run go fix (#36) ([libp2p/go-doh-resolver#36](https://github.com/libp2p/go-doh-resolver/pull/36))
- github.com/libp2p/go-libp2p (v0.48.0 -> v0.49.0):
- Release v0.49.0
- chore: update deps
- test(basichost): cancel in-flight refresh probes
- refactor(autonatv2): unlock pickServer via defer
- docs(basichost): fix stale const name in comments
- fix(basichost): probe secondary addrs on the same 1h cadence as primaries
- fix(autonatv2): don't panic in GetReachability after Close
- test(basichost): actually spawn workers in refreshReachability cancellation test
- fix(basichost): score webrtc-direct addrs with P_WEBRTC_DIRECT
- fix(basichost): log updated host addrs, not local addrs
- fix(basichost): handle unsorted confirmed addrs from reachability tracker (#3526) ([libp2p/go-libp2p#3526](https://github.com/libp2p/go-libp2p/pull/3526))
- fix(eventbus): fix concurrency bug for slow logs (#3518) ([libp2p/go-libp2p#3518](https://github.com/libp2p/go-libp2p/pull/3518))
- feat(webrtc): support webrtc-direct v2 (#3520) ([libp2p/go-libp2p#3520](https://github.com/libp2p/go-libp2p/pull/3520))
- feat(webrtc): stable /certhash across restarts (#3512) ([libp2p/go-libp2p#3512](https://github.com/libp2p/go-libp2p/pull/3512))
- http/auth: close intermediate peer ID auth handshake responses (#3510) ([libp2p/go-libp2p#3510](https://github.com/libp2p/go-libp2p/pull/3510))
- feat(websocket): share /ws and /wss port with HTTP Server (#3509) ([libp2p/go-libp2p#3509](https://github.com/libp2p/go-libp2p/pull/3509))
- feat(autonatv2): expose AllowPrivateAddrs via an Option (#3513) ([libp2p/go-libp2p#3513](https://github.com/libp2p/go-libp2p/pull/3513))
- refactor(swarm): move the connected / disconnected callbacks into the events emitter (#3503) ([libp2p/go-libp2p#3503](https://github.com/libp2p/go-libp2p/pull/3503))
- fix(holepunch): defer mutex unlock to avoid deadlock on shutdown (#3504) ([libp2p/go-libp2p#3504](https://github.com/libp2p/go-libp2p/pull/3504))
- p2p/protocol/identify: bound the number of protocols accepted from peers (#3501) ([libp2p/go-libp2p#3501](https://github.com/libp2p/go-libp2p/pull/3501))
- Add ability to pass through TLS options to the QUIC transport. (#3481) ([libp2p/go-libp2p#3481](https://github.com/libp2p/go-libp2p/pull/3481))
- fix(webrtc): cap remote addresses per ufrag (#3500) ([libp2p/go-libp2p#3500](https://github.com/libp2p/go-libp2p/pull/3500))
- Interrupt websocket reads during close (#3496) ([libp2p/go-libp2p#3496](https://github.com/libp2p/go-libp2p/pull/3496))
- fix(config): emit fx DI registration events at debug level (#3498) ([libp2p/go-libp2p#3498](https://github.com/libp2p/go-libp2p/pull/3498))
- feat(pstore): cap unconnected addrs per peer (#3486) ([libp2p/go-libp2p#3486](https://github.com/libp2p/go-libp2p/pull/3486))
- fix(peerstore): replace stale addrs on newer signed peer record (#3487) ([libp2p/go-libp2p#3487](https://github.com/libp2p/go-libp2p/pull/3487))
- fix(peer): drop empty addrs in peer records (#3494) ([libp2p/go-libp2p#3494](https://github.com/libp2p/go-libp2p/pull/3494))
- fix(routing): prevent QueryEvent publish races (#3490) ([libp2p/go-libp2p#3490](https://github.com/libp2p/go-libp2p/pull/3490))
- feat: NonPublicAddrPublishing option (#3489) ([libp2p/go-libp2p#3489](https://github.com/libp2p/go-libp2p/pull/3489))
- fix: relay candidate added into backoff list even if reservation on that relay candidate success (#3482) ([libp2p/go-libp2p#3482](https://github.com/libp2p/go-libp2p/pull/3482))
- github.com/libp2p/go-libp2p-kad-dht (v0.40.0 -> v0.42.1):
- chore: release v0.42.1 (#1286) ([libp2p/go-libp2p-kad-dht#1286](https://github.com/libp2p/go-libp2p-kad-dht/pull/1286))
- fix: validate local records in SearchValue (#1285) ([libp2p/go-libp2p-kad-dht#1285](https://github.com/libp2p/go-libp2p-kad-dht/pull/1285))
- chore: release v0.42.0 (#1284) ([libp2p/go-libp2p-kad-dht#1284](https://github.com/libp2p/go-libp2p-kad-dht/pull/1284))
- refactor: remove lifecycle context from constructors (#1282) ([libp2p/go-libp2p-kad-dht#1282](https://github.com/libp2p/go-libp2p-kad-dht/pull/1282))
- refactor(records): consolidate records persistence (#1277) ([libp2p/go-libp2p-kad-dht#1277](https://github.com/libp2p/go-libp2p-kad-dht/pull/1277))
- refactor(pb): bound the serialized size of a peer record (#1281) ([libp2p/go-libp2p-kad-dht#1281](https://github.com/libp2p/go-libp2p-kad-dht/pull/1281))
- chore: migrate from math/rand to math/rand/v2 (#1280) ([libp2p/go-libp2p-kad-dht#1280](https://github.com/libp2p/go-libp2p-kad-dht/pull/1280))
- test: give TestProvidesMany's lookups a deadline that fits a loaded machine (#1278) ([libp2p/go-libp2p-kad-dht#1278](https://github.com/libp2p/go-libp2p-kad-dht/pull/1278))
- upgrade go-test to v0.4.0 (#1275) ([libp2p/go-libp2p-kad-dht#1275](https://github.com/libp2p/go-libp2p-kad-dht/pull/1275))
- test: fix flaky TestOptimisticProvide timeout and peerIDs seeding (#1276) ([libp2p/go-libp2p-kad-dht#1276](https://github.com/libp2p/go-libp2p-kad-dht/pull/1276))
- Update dependencies (#1273) ([libp2p/go-libp2p-kad-dht#1273](https://github.com/libp2p/go-libp2p-kad-dht/pull/1273))
- fix: correct delete-error check in provider gc (#1271) ([libp2p/go-libp2p-kad-dht#1271](https://github.com/libp2p/go-libp2p-kad-dht/pull/1271))
- fix: cap closer peers accepted per response (#1270) ([libp2p/go-libp2p-kad-dht#1270](https://github.com/libp2p/go-libp2p-kad-dht/pull/1270))
- chore: release v0.41.0 (#1269) ([libp2p/go-libp2p-kad-dht#1269](https://github.com/libp2p/go-libp2p-kad-dht/pull/1269))
- fix(provider): cover full keyspace when peers cluster under one prefix (#1265) ([libp2p/go-libp2p-kad-dht#1265](https://github.com/libp2p/go-libp2p-kad-dht/pull/1265))
- fix(provider): reprovide all regions when resume is disabled (#1267) ([libp2p/go-libp2p-kad-dht#1267](https://github.com/libp2p/go-libp2p-kad-dht/pull/1267))
- fix: cut reprovide peak memory with key count (#1259) ([libp2p/go-libp2p-kad-dht#1259](https://github.com/libp2p/go-libp2p-kad-dht/pull/1259))
- tests: fix flaky TestBootStrapWhenRTIsEmpty (#1264) ([libp2p/go-libp2p-kad-dht#1264](https://github.com/libp2p/go-libp2p-kad-dht/pull/1264))
- fix(keystore): non-colliding reset slot prefixes (#1262) ([libp2p/go-libp2p-kad-dht#1262](https://github.com/libp2p/go-libp2p-kad-dht/pull/1262))
- github.com/libp2p/go-libp2p-kbucket (v0.8.0 -> v0.9.0):
- chore: release v0.9.0 (#155) ([libp2p/go-libp2p-kbucket#155](https://github.com/libp2p/go-libp2p-kbucket/pull/155))
- refactor: modernize benchmarks and slices usage (#154) ([libp2p/go-libp2p-kbucket#154](https://github.com/libp2p/go-libp2p-kbucket/pull/154))
- refactor: replace Sort interface with slices.SortFunc (#153) ([libp2p/go-libp2p-kbucket#153](https://github.com/libp2p/go-libp2p-kbucket/pull/153))
- refactor: modernize code ([libp2p/go-libp2p-kbucket#152](https://github.com/libp2p/go-libp2p-kbucket/pull/152))
- github.com/libp2p/go-libp2p-pubsub (v0.16.0 -> v0.17.0):
- Release v0.17.0 (#720) ([libp2p/go-libp2p-pubsub#720](https://github.com/libp2p/go-libp2p-pubsub/pull/720))
- docfix: contribute.md in Readme.md (#682) ([libp2p/go-libp2p-pubsub#682](https://github.com/libp2p/go-libp2p-pubsub/pull/682))
- rerun validation if node publishes a previously seen message (#719) ([libp2p/go-libp2p-pubsub#719](https://github.com/libp2p/go-libp2p-pubsub/pull/719))
- refactor: apply go fix modernizers (#716) ([libp2p/go-libp2p-pubsub#716](https://github.com/libp2p/go-libp2p-pubsub/pull/716))
- Limit control message size of RPCs (#707) ([libp2p/go-libp2p-pubsub#707](https://github.com/libp2p/go-libp2p-pubsub/pull/707))
- test: use channel-based mutex in replayActor for synctest compatibility (#718) ([libp2p/go-libp2p-pubsub#718](https://github.com/libp2p/go-libp2p-pubsub/pull/718))
- Migrate to Google's protobuf library (#706) ([libp2p/go-libp2p-pubsub#706](https://github.com/libp2p/go-libp2p-pubsub/pull/706))
- Remove debug logs for partial messages (#714) ([libp2p/go-libp2p-pubsub#714](https://github.com/libp2p/go-libp2p-pubsub/pull/714))
- PeerFeedback is now synchronous (#711) ([libp2p/go-libp2p-pubsub#711](https://github.com/libp2p/go-libp2p-pubsub/pull/711))
- test: Add tests for syncEval
- Fix flaky test assertion (#709) ([libp2p/go-libp2p-pubsub#709](https://github.com/libp2p/go-libp2p-pubsub/pull/709))
- Clean up empty topic state maps (#708) ([libp2p/go-libp2p-pubsub#708](https://github.com/libp2p/go-libp2p-pubsub/pull/708))
- fix gossip when topic has only partial message peers (#700) ([libp2p/go-libp2p-pubsub#700](https://github.com/libp2p/go-libp2p-pubsub/pull/700))
- gossipsub: clean peer gater stats on inbound close
- gossipsub: clean up unwanted message state
- cleanup unused and redundant code (#695) ([libp2p/go-libp2p-pubsub#695](https://github.com/libp2p/go-libp2p-pubsub/pull/695))
- fix: attempt to initialize fanout when empty (#696) ([libp2p/go-libp2p-pubsub#696](https://github.com/libp2p/go-libp2p-pubsub/pull/696))
- Migrate tests to simlibp2p + synctest (#686) ([libp2p/go-libp2p-pubsub#686](https://github.com/libp2p/go-libp2p-pubsub/pull/686))
- github.com/libp2p/go-yamux/v5 (v5.0.1 -> v5.1.0):
- Release v5.1.0
- feat: Expose RTT information
- fix: deadlock on close (#130) ([libp2p/go-yamux#130](https://github.com/libp2p/go-yamux/pull/130))
- github.com/multiformats/go-multiaddr-dns (v0.5.0 -> v0.6.0):
- feat: add LookupTXTWithTTL to Resolver (#75) ([multiformats/go-multiaddr-dns#75](https://github.com/multiformats/go-multiaddr-dns/pull/75))
### ๐จโ๐ฉโ๐งโ๐ฆ Contributors
| Contributor | Commits | Lines ยฑ | Files Changed |
|-------------|---------|---------|---------------|
| [@MarcoPolo](https://github.com/MarcoPolo) | 16 | +10530/-9015 | 77 |
| [@lidel](https://github.com/lidel) | 66 | +10349/-1367 | 272 |
| [@guillaumemichel](https://github.com/guillaumemichel) | 19 | +6293/-1098 | 99 |
| [@gammazero](https://github.com/gammazero) | 63 | +2882/-2124 | 213 |
| [@sukunrt](https://github.com/sukunrt) | 11 | +1145/-287 | 27 |
| [@Sahil-4555](https://github.com/Sahil-4555) | 4 | +703/-101 | 12 |
| [@Adel-Ayoub](https://github.com/Adel-Ayoub) | 1 | +679/-15 | 11 |
| [@D4ryl00](https://github.com/D4ryl00) | 1 | +346/-2 | 4 |
| [@blackflytech](https://github.com/blackflytech) | 1 | +185/-40 | 3 |
| [@morning-verlu](https://github.com/morning-verlu) | 1 | +143/-8 | 5 |
| [@dennis-tra](https://github.com/dennis-tra) | 1 | +134/-14 | 4 |
| [@rvagg](https://github.com/rvagg) | 5 | +107/-14 | 7 |
| [@purusachdeva](https://github.com/purusachdeva) | 1 | +114/-0 | 2 |
| [@chiragsoni81245](https://github.com/chiragsoni81245) | 1 | +55/-3 | 3 |
| [@reflecttypefor](https://github.com/reflecttypefor) | 1 | +42/-3 | 3 |
| [@gmelodie](https://github.com/gmelodie) | 1 | +21/-21 | 3 |
| [@web3-bot](https://github.com/web3-bot) | 2 | +5/-4 | 3 |
| [@laciferin2024](https://github.com/laciferin2024) | 1 | +1/-1 | 1 |
| [@aarshkshah1992](https://github.com/aarshkshah1992) | 1 | +0/-2 | 1 |
## v0.43.1
- [๐ฆ Highlights](#-highlights-1)
- [๐ Empty `Bootstrap` list disables all bootstrap dialing](#-empty-bootstrap-list-disables-all-bootstrap-dialing)
- [๐๏ธ Improved datastore profiles and docs](#-improved-datastore-profiles-and-docs)
- [๐๏ธ Setting the flatfs shard depth is less error-prone](#-setting-the-flatfs-shard-depth-is-less-error-prone)
- [๐ `ipfs ls` can print readable sizes and sort by size](#-ipfs-ls-can-print-readable-sizes-and-sort-by-size)
- [๐ Files on FUSE mounts keep their inode number](#-files-on-fuse-mounts-keep-their-inode-number)
- [๐งน FUSE mounts keep what you write after a rename](#-fuse-mounts-keep-what-you-write-after-a-rename)
- [๐ `ipfs key export` writes are owner-only](#-ipfs-key-export-writes-are-owner-only)
- [๐ `ipfs shutdown` exits a daemon started with `--enable-gc`](#-ipfs-shutdown-exits-a-daemon-started-with---enable-gc)
- [๐ Gateway `Ipfs-Uri` header replaces `X-Ipfs-Path` (IPIP-548)](#-gateway-ipfs-uri-header-replaces-x-ipfs-path-ipip-548)
- [๐ Bitswap sees peers connected before it starts](#-bitswap-sees-peers-connected-before-it-starts)
- [๐คซ No more background pings to HTTP providers](#-no-more-background-pings-to-http-providers)
- [๐ Telemetry is off by default](#-telemetry-is-off-by-default)
- [๐ Future-proofing browser retrieval: WebTransport fix for Safari 26](#-future-proofing-browser-retrieval-webtransport-fix-for-safari-26)
- [๐พ Provider record writes are batched again](#-provider-record-writes-are-batched-again)
- [๐ Hardened CID profiles, new low-level knob](#-hardened-cid-profiles-new-low-level-knob)
- [๐ฆ๏ธ Dependency updates](#-dependency-updates-1)
- [๐ Changelog](#-changelog-1)
- [๐จโ๐ฉโ๐งโ๐ฆ Contributors](#-contributors-1)
This is mostly a bugfix release. It also carries a few small, opt-in configuration options and profiles that landed since v0.43.0.
### ๐ฆ Highlights
#### ๐ Empty `Bootstrap` list disables all bootstrap dialing
Setting `Bootstrap` to `[]` now stops the node from dialing bootstrap peers of any kind, including the backup peers it saved from earlier runs. Until now, a node that had once run with the default bootstrappers kept dialing those saved peers every 30 seconds, even with an empty list and `Routing.Type=none`.
The result is a node that connects only to peers you chose: [`Peering.Peers`](https://github.com/ipfs/kubo/blob/master/docs/config.md#peeringpeers), `ipfs swarm connect`, and peers found on the local network through [mDNS](https://github.com/ipfs/kubo/blob/master/docs/config.md#discoverymdnsenabled). For no outbound traffic at all, also set [`Routing.Type`](https://github.com/ipfs/kubo/blob/master/docs/config.md#routingtype) to `none` and disable [`AutoConf`](https://github.com/ipfs/kubo/blob/master/docs/config.md#autoconfenabled).
Backup peers still work when `Bootstrap` has at least one entry: the node dials the configured peers first and falls back to saved peers only while it stays below the minimum peer count.
#### ๐๏ธ Improved datastore profiles and docs
Kubo keeps blocks in flatfs and everything else (pins, MFS root, IPNS and DHT provider records) in leveldb. leveldb is slow to reclaim space after bulk deletes, so on nodes that churn that metadata all day, `datastore/` grows far past the live data. `ipfs init --profile=flatfs-pebbleds` keeps flatfs for blocks and puts the rest in pebble, which compacts deleted keys promptly. This works for new repos only. The profile is experimental, and pebble has less production use in Kubo than leveldb, so report problems in [kubo issues](https://github.com/ipfs/kubo/issues). Details in the [profile docs](https://github.com/ipfs/kubo/blob/master/docs/config.md#flatfs-pebbleds-profile).
The default layout is unchanged. Its profile is now named `flatfs-levelds`, and `flatfs` and `flatfs-measure` stay as aliases. The [datastore docs](https://github.com/ipfs/kubo/blob/master/docs/datastores.md#flatfs) say which data lives in which store and why flatfs holds only blocks.
#### ๐๏ธ Setting the flatfs shard depth is less error-prone
The flatfs shard depth (`shardFunc`) can only be set when a repo is created, by passing a config file to `ipfs init`. That route had traps. Fixed:
- `ipfs init ` and `ipfs daemon --init --init-config` refuse a config without a private key, such as `ipfs config show` output, which used to produce a repo that crashed on start.
- `ipfs init` checks `Datastore.Spec` before writing anything, so a typo in `shardFunc` no longer leaves a half-made repo.
- `ipfs config profile apply` refuses a profile that would change the datastore layout of an existing repo instead of writing a config the repo can no longer open.
- The `Datastore.Spec` vs `datastore_spec` mismatch error labels the two values correctly and says what to do.
[`docs/datastores.md`](https://github.com/ipfs/kubo/blob/master/docs/datastores.md#choosing-a-shardfunc-for-large-blockstores) has the steps for setting `next-to-last/3` on a new repo, when it is worth it, and how to move an existing repo. Defaults are unchanged.
#### ๐ `ipfs ls` can print readable sizes and sort by size
Two new flags on `ipfs ls`, both off by default. `--human` (`-H`) prints sizes in SI units such as `3.0 kB` and `2.0 MB`, matching `ipfs repo stat -H`. `--sort-size` (`-S`) lists the biggest entries first, so you can find what fills a directory without piping through `sort`. Run `ipfs ls --help` for how they combine with `--stream`, `--size`, and the JSON response.
#### ๐ Files on FUSE mounts keep their inode number
A file on a mount made by `ipfs mount` used to get a new inode number roughly every second, whenever the kernel dropped and re-read its directory entry. Programs that check whether a file is still the same file read that as the file being replaced under them. Saving a file on `/mfs` with vim could fail with `E949: File changed while writing`, and `pwd`, `find` and backup tools could misbehave for the same reason.
All three mounts now hand out stable numbers. On `/ipfs` the number comes from the CID, so the same content is the same object whichever path reaches it, and the same number comes back after a remount. On `/ipns` and `/mfs` an entry keeps its number for as long as it exists, including across a rename, while a name that is deleted and created again is numbered afresh; those numbers are assigned per mount and start over on the next one. Deleting through `ipfs files` rather than through the mount is the exception: the mount does not see it, so the name keeps its old number if it comes back.
Smaller fixes come with it: mount points report an inode number instead of `0`, `ls -i` agrees with `stat`, the link count is `1` rather than the `0` POSIX gives to a deleted file, and `/ipns/` directories show their real permissions instead of `d---------`.
#### ๐งน FUSE mounts keep what you write after a rename
Renaming on a mounted `/mfs` or `/ipns` left the kernel writing into the entry the rename had just taken away. `mv a b` followed by a write to `b` reported success and then quietly went back to the old contents a second later. A file created in a directory that had just been renamed vanished the same way, and the directory reappeared under the name it had been moved away from. Renaming over a directory that was not empty deleted everything in it, where POSIX asks for `ENOTEMPTY`.
On `/ipfs`, a listing no longer fails in its entirety when one child's block is missing, an entry in a codec the mount cannot decode reads back the block rather than refusing, and the `ipfs.cid` xattr answers with the CID from the path instead of a re-encoded form of it.
#### ๐ `ipfs key export` writes are owner-only
`ipfs key export` now writes the key file with owner-only permissions (`0600`), including when it overwrites an existing file with looser permissions. The export goes to a temporary file renamed over the target, so a failed export leaves the previous contents intact; character devices and pipes such as `/dev/null` are streamed to directly.
#### ๐ `ipfs shutdown` exits a daemon started with `--enable-gc`
A daemon started with `--enable-gc` closed its repo on `ipfs shutdown` (`POST /api/v0/shutdown`) but the process never exited, because the periodic garbage collection loop only stopped on a signal. Supervisors that stop Kubo over the RPC API waited forever. The loop now also stops when the node closes; `SIGINT` and `SIGTERM` behave as before.
#### ๐ Gateway `Ipfs-Uri` header replaces `X-Ipfs-Path` (IPIP-548)
Gateway responses now include an [`Ipfs-Uri` header](https://specs.ipfs.tech/http-gateways/path-gateway/#ipfs-uri-response-header) with a canonical [`ipfs://`](https://specs.ipfs.tech/ipfs-uri/) or [`ipns://`](https://specs.ipfs.tech/ipns-uri/) address of the requested content path ([IPIP-548](https://github.com/ipfs/specs/pull/548)). Every path segment is percent-encoded, so the address survives any file name, including names with spaces, `%`, `#`, and non-ASCII characters.
> [!IMPORTANT]
> Kubo no longer sends the deprecated `X-Ipfs-Path` header. HTTP header values cannot carry non-ASCII bytes, so a path with such a file name arrives garbled. Clients that read `X-Ipfs-Path` must migrate to `Ipfs-Uri`; decoding it back into a content path takes a single percent-decode of each path segment. Operators who need time can temporarily restore the legacy header with [`Gateway.DeprecatedXIpfsPath`](https://github.com/ipfs/kubo/blob/master/docs/config.md#gatewaydeprecatedxipfspath); even then Kubo omits it when the value contains bytes that cannot appear in an HTTP header.
#### ๐ Bitswap sees peers connected before it starts
A peer that was already connected when the node started stayed invisible to Bitswap: libp2p reports only connections opened after a notifier registers, so no want request was ever sent over such a connection. Nodes with other ways to find content usually masked it; a node relying on an already-connected peer could wait forever. Fixed in [boxo v0.42.2](https://github.com/ipfs/boxo/releases/tag/v0.42.2).
#### ๐คซ No more background pings to HTTP providers
Bitswap over HTTP no longer probes every connected HTTP provider with `GET/HEAD /ipfs/bafkqaaa` every 5 seconds for the lifetime of the process. Idle HTTP peers now generate no background traffic; latency is measured from the connection probe and from real retrieval responses instead ([boxo v0.42.2](https://github.com/ipfs/boxo/releases/tag/v0.42.2)).
#### ๐ Telemetry is off by default
Kubo no longer ships with a telemetry endpoint. The collector at `telemetry.ipshipyard.dev` shuts down with [the end of Shipyard's IPFS work](https://ipshipyard.com/blog/2026-the-end-of-ipfs-at-shipyard/), so a node now collects nothing and sends nothing unless you point it at your own collector with `Plugins.Plugins.telemetry.Config.Endpoint`. A node that reported under an earlier version removes its stored `telemetry_uuid` on the next daemon start. The opt-outs (`IPFS_TELEMETRY=off`, `DO_NOT_TRACK=1`, the config `Mode`) keep working for builds that set an endpoint. See [docs/telemetry.md](https://github.com/ipfs/kubo/blob/master/docs/telemetry.md).
#### ๐ Future-proofing browser retrieval: WebTransport fix for Safari 26
Kubo's WebTransport listener now supports session flow control from [WebTransport over HTTP/3 draft-16](https://datatracker.ietf.org/doc/draft-ietf-webtrans-http3/16/). Safari 26 opens no stream on a session unless the server advertises session limits, so Kubo advertises them. The limits are set so high they never apply; the QUIC per-connection limits and the [libp2p resource manager](https://github.com/ipfs/kubo/blob/master/docs/libp2p-resource-management.md) remain the effective caps.
#### ๐พ Provider record writes are batched again
`go-libp2p-kad-dht` v0.42.0, first shipped in Kubo v0.43.0, turned every DHT provider record write, and every delete made by the provider's garbage collection, into an individual, fsynced datastore operation. On nodes announcing many CIDs this showed up as constant disk activity. This release batches these writes and deletes again, 256 records at a time, restoring the disk write rate from before v0.43.0. Nothing changes on the wire or in the datastore layout; upgrading is all that is needed.
#### ๐ Hardened CID profiles, new low-level knob
The `unixfs-v0-2015` and `unixfs-v1-2025` profiles now pin the `PBNode` field order (`links-first`) explicitly, and regression tests lock in the exact bytes and CIDs they produce. Reading blocks in either field order is formalized in [IPIP-550](https://github.com/ipfs/specs/pull/550) and tested by [gateway-conformance v0.14.1](https://github.com/ipfs/gateway-conformance/releases/tag/v0.14.1). Nothing changes by default.
For writers that need `Data`-first output (streaming readers can then parse HAMT shards without buffering all links first), there is a new opt-in [`Import.UnixFSPBNodeFieldOrder`](https://github.com/ipfs/kubo/blob/master/docs/config.md#importunixfspbnodefieldorder) option. No profile enables it. Opting in changes the CIDs of newly added directories, HAMT shards, and multi-chunk files, and MFS re-encodes the directories that `ipfs files` operations rewrite; enable it only when consumers of your CIDs expect the new order.
#### ๐ฆ๏ธ Dependency updates
- update `boxo` to [v0.43.0](https://github.com/ipfs/boxo/releases/tag/v0.43.0) (includes [ipfs/boxo#1212](https://github.com/ipfs/boxo/pull/1212) and [ipfs/boxo#1213](https://github.com/ipfs/boxo/pull/1213)); also pulls in `go.opentelemetry.io/otel` [v1.46.0](https://github.com/open-telemetry/opentelemetry-go/releases/tag/v1.46.0)
- update `go-libp2p-kad-dht` to [v0.42.2](https://github.com/libp2p/go-libp2p-kad-dht/releases/tag/v0.42.2)
- update `go-ds-pebble` to [v0.5.13](https://github.com/ipfs/go-ds-pebble/releases/tag/v0.5.13)
- update `gateway-conformance` to [v0.14.1](https://github.com/ipfs/gateway-conformance/releases/tag/v0.14.1)
- update `quic-go` to [v0.62.0](https://github.com/quic-go/quic-go/releases/tag/v0.62.0) and `webtransport-go` to [v0.13.0](https://github.com/quic-go/webtransport-go/releases/tag/v0.13.0)
### ๐ Changelog
Full Changelog
- github.com/ipfs/kubo:
- fix(webtransport): keep Safari 26 sessions working
- chore: upgrade to boxo v0.43.0 (#11467) ([ipfs/kubo#11467](https://github.com/ipfs/kubo/pull/11467))
- chore: ship without a telemetry endpoint
- docs: clearer datastore layouts and init errors (#11449) ([ipfs/kubo#11449](https://github.com/ipfs/kubo/pull/11449))
- fix(daemon): exit on RPC shutdown with --enable-gc (#11447) ([ipfs/kubo#11447](https://github.com/ipfs/kubo/pull/11447))
- fix: empty Bootstrap list no longer dials stale backup peers (#11453) ([ipfs/kubo#11453](https://github.com/ipfs/kubo/pull/11453))
- test(unixfs): lock in CID profile behavior (IPIP-550) (#11439) ([ipfs/kubo#11439](https://github.com/ipfs/kubo/pull/11439))
- chore: bump go-libp2p-kad-dht to v0.42.2 (#11436) ([ipfs/kubo#11436](https://github.com/ipfs/kubo/pull/11436))
- chore: maintenance disclaimer
- fix: Ipfs-Uri gateway header (IPIP-548) (#11437) ([ipfs/kubo#11437](https://github.com/ipfs/kubo/pull/11437))
- fix(fuse): keep what you write after a rename (#11430) ([ipfs/kubo#11430](https://github.com/ipfs/kubo/pull/11430))
- fix(fuse): give mounts stable inode numbers (#11429) ([ipfs/kubo#11429](https://github.com/ipfs/kubo/pull/11429))
- fix(key): restrict overwritten key exports to owner-only permissions (#11428) ([ipfs/kubo#11428](https://github.com/ipfs/kubo/pull/11428))
- chore(ci): remove self-hosted runners [skip changelog] (#11426) ([ipfs/kubo#11426](https://github.com/ipfs/kubo/pull/11426))
- docs: guide for running on low-memory devices (#11427) ([ipfs/kubo#11427](https://github.com/ipfs/kubo/pull/11427))
- docs: drop expired pgp key from security notes (#11422) ([ipfs/kubo#11422](https://github.com/ipfs/kubo/pull/11422))
- docs: fix private networks setup wording (#11388) ([ipfs/kubo#11388](https://github.com/ipfs/kubo/pull/11388))
- feat(cli): add --human and --sort-size to ipfs ls (#11408) ([ipfs/kubo#11408](https://github.com/ipfs/kubo/pull/11408))
- github.com/ipfs/boxo (v0.42.1 -> v0.43.0):
- Release v0.43.0 ([ipfs/boxo#1218](https://github.com/ipfs/boxo/pull/1218))
- upgrade opentelemetry to v1.44 (#1216) ([ipfs/boxo#1216](https://github.com/ipfs/boxo/pull/1216))
- chore: upgrade go-libp2p-kad-dht to v0.42.2 ([ipfs/boxo#1214](https://github.com/ipfs/boxo/pull/1214))
- refactor: update go version to 1.26 and apply modernizers ([ipfs/boxo#1211](https://github.com/ipfs/boxo/pull/1211))
- fix(bootstrap): skip backup peer list when no bootstrap peers configured (#1213) ([ipfs/boxo#1213](https://github.com/ipfs/boxo/pull/1213))
- test(unixfs): lock in PBNode field order behavior (IPIP-550) (#1212) ([ipfs/boxo#1212](https://github.com/ipfs/boxo/pull/1212))
- fix(gateway): IPIP-548 Ipfs-Uri response header (#1209) ([ipfs/boxo#1209](https://github.com/ipfs/boxo/pull/1209))
- chore: remove @lidel from CODEOWNERS
- Release v0.42.2 ([ipfs/boxo#1207](https://github.com/ipfs/boxo/pull/1207))
- fix(bitswap/httpnet): bound probe traffic (#1205) ([ipfs/boxo#1205](https://github.com/ipfs/boxo/pull/1205))
- docs: describe bitswap broadcast control for broadcast reduction ([ipfs/boxo#1203](https://github.com/ipfs/boxo/pull/1203))
- fix(bitswap): see peers connected before startup (#1201) ([ipfs/boxo#1201](https://github.com/ipfs/boxo/pull/1201))
- docs: spec guardrails (#1204) ([ipfs/boxo#1204](https://github.com/ipfs/boxo/pull/1204))
- chore: refresh issue templates and docs links
- chore: small correction to changelog ([ipfs/boxo#1202](https://github.com/ipfs/boxo/pull/1202))
- github.com/ipfs/go-ds-pebble (v0.5.12 -> v0.5.13):
- update version for release v0.5.13 (#92) ([ipfs/go-ds-pebble#92](https://github.com/ipfs/go-ds-pebble/pull/92))
- refactor: update go version to 1.26 and apply modernizers (#91) ([ipfs/go-ds-pebble#91](https://github.com/ipfs/go-ds-pebble/pull/91))
- github.com/libp2p/go-libp2p-kad-dht (v0.42.1 -> v0.42.2):
- chore: release v0.42.2 (#1294) ([libp2p/go-libp2p-kad-dht#1294](https://github.com/libp2p/go-libp2p-kad-dht/pull/1294))
- fix(records): batch provider datastore writes and GC deletes (#1293) ([libp2p/go-libp2p-kad-dht#1293](https://github.com/libp2p/go-libp2p-kad-dht/pull/1293))
- docs: spec guardrails (#1292) ([libp2p/go-libp2p-kad-dht#1292](https://github.com/libp2p/go-libp2p-kad-dht/pull/1292))
- fix(provider): pass lifecycle context to local record callback (#1288) ([libp2p/go-libp2p-kad-dht#1288](https://github.com/libp2p/go-libp2p-kad-dht/pull/1288))
### ๐จโ๐ฉโ๐งโ๐ฆ Contributors
| Contributor | Commits | Lines ยฑ | Files Changed |
|-------------|---------|---------|---------------|
| [@lidel](https://github.com/lidel) | 24 | +5265/-515 | 106 |
| [@guillaumemichel](https://github.com/guillaumemichel) | 3 | +1058/-148 | 10 |
| [@questfever](https://github.com/questfever) | 1 | +450/-41 | 5 |
| [@capricornusx](https://github.com/capricornusx) | 1 | +444/-12 | 5 |
| [@gammazero](https://github.com/gammazero) | 10 | +138/-100 | 29 |
| [@karawitan](https://github.com/karawitan) | 2 | +150/-7 | 7 |
| [@galargh](https://github.com/galargh) | 1 | +5/-1 | 1 |
| [@weifanglab](https://github.com/weifanglab) | 1 | +2/-2 | 1 |