Skip to content

Release v0.43.1 - #11456

Draft
lidel wants to merge 18 commits into
releasefrom
release-v0.43.1
Draft

Release v0.43.1#11456
lidel wants to merge 18 commits into
releasefrom
release-v0.43.1

Conversation

@lidel

@lidel lidel commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

capricornusx and others added 17 commits September 8, 2026 22:55
* feat(cli): add --human and --sort-size to ipfs ls

- --human (-H): SI human-readable sizes in text output (humanize.Bytes)
- --sort-size (-S): sort directory entries by size, largest first
- Validation: --sort-size + --stream and --sort-size + --size=false errors
- Unit tests for formatSize and sort helpers
- CLI integration tests for both flags
- Changelog highlight in v0.44

* test(ls): make sort tests fail when sorting breaks

The tests covering --sort-size could not detect a broken feature. The
unit tests copied the comparator into the test body and sorted with their
own copy, so they passed regardless of what ls.go did. The CLI tests named
each fixture after its size, which left alphabetical order and size order
in agreement, so every ordering subtest passed even with --sort-size
disabled outright.

- extract lsLinkByName and lsLinkBySize so the tests exercise shipped code
- point the unit tests at those two functions
- name fixtures so their alphabetical order disagrees with their sizes
- pin the real directory behaviour: UnixFS directories carry no Filesize,
  so they sort as 0, tie with empty files and break by name rather than
  landing strictly last

* fix(ls): return 400 not 500 for bad flag combos

Passing --sort-size together with --stream or --size=false made
/api/v0/ls answer 500 Internal Server Error, telling API clients the
server had broken when the caller had simply combined flags that cannot
work together. Clients that retry on 5xx would retry a request that can
never succeed.

cmds.ErrClient maps to 400, matching how the rest of core/commands
reports caller mistakes. CLI output is unchanged.

* docs: fix --human size examples and JSON claims

The help for --human advertised "1K 234M 2G", which no kubo command has
ever printed. All three use humanize.Bytes, so the real output is SI with
a space: 1.2 kB, 234 MB, 2.0 GB. The stale example was copied into
'ipfs ls' from the two commands that already carried it, so correct all
three together.

- ls, repo stat, bitswap stat: examples now match real output
- drop the claim that --enc=json reports bytes. On the CLI, 'ipfs ls'
  has a PostRun that prints the text table whatever --enc says, so no
  JSON is produced there at all. Only the /api/v0/ls response is JSON,
  and that part is true
- directories have no UnixFS Filesize, so they sort as 0 and tie with
  empty files. They do not land strictly last, so stop saying they do
- changelog: a #### highlight with a TOC entry, kept short and pointing
  at 'ipfs ls --help' for the details
- format sizes with strconv.FormatUint rather than fmt.Sprintf("%d")

---------

Co-authored-by: Marcin Rataj <lidel@lidel.org>
(cherry picked from commit d4bbbef)
Signed-off-by: weifanglab <weifanglab@outlook.com>
Co-authored-by: Andrew Gillis <11790789+gammazero@users.noreply.github.com>
(cherry picked from commit 9d5290e)
The key expired in 2018 and the pgp.mit.edu lookup it pointed at returns
503, so anyone following it hits a dead end twice. No successor key is
published for security@ipfs.io on any keyserver, so the notes point at
the repository security policy instead.

Seeding the init docs changes their directory CID, so the constants the
tests assert against move with it.

(cherry picked from commit 48012cb)
* docs: guide for running on low-memory devices

Add docs/production/low-memory.md with tuning for 8 GiB devices like
Raspberry Pi: GOMEMLIMIT, systemd MemoryHigh/MemoryMax, and
Provide.DHT.MaxWorkers, plus how to verify with cgroup memory pressure.

- README.md, docs/README.md: link the guide
- docs/config.md: link from lowpower profile and ResourceMgr notes,
  caution against setting Swarm.ResourceMgr.MaxMemory too low
- misc/systemd: commented example limits pointing at the guide

* docs: announcement sizing and dht client advice

Extend the low-memory guide with measured announcement rates, the 48h
record expiry floor, and a worked 10M-CID example (MaxWorkers=6,
Interval=32h). Advise client-only routing (autoclient), disabling the
AutoNAT and relay services, and warn against enabling the accelerated
DHT client on constrained hardware.

- docs/environment-variables.md: document GOMEMLIMIT
- docs/config.md: broaden lowpower profile pointer to the guide

(cherry picked from commit bb0ac77)
* ci: remove self-hosted runners

* test: fix zsh completion test on hosted runners

The test runs compinit in a non-interactive zsh. When a directory
in fpath is writable by group or others, compinit asks a question.
There is no terminal to answer it, so compinit aborts and the test
fails. GitHub-hosted runner images have such a directory.

compinit -i skips insecure directories without asking. The test
checks kubo's generated completion script, not the permissions of
the host's zsh directories.

---------

Co-authored-by: Marcin Rataj <lidel@lidel.org>
(cherry picked from commit c374658)
…11428)

* fix(key): restrict overwritten key exports to owner-only permissions

Signed-off-by: questfever <questfever@outlook.com>

* fix(atomicfile): temp file leak and name limit

Both problems surface through `ipfs key export`, which now writes
through this helper, but they affect every caller: config writes, repo
migrations and `ipfs update`.

- remove the temporary file when the rename fails, so one holding
  private key material is not left next to the target
- keep the ".tmp-" prefix and the random suffix within the 255 byte
  file name limit, so a target with a long name can still be written

* fix(key): route key export by target type

Choosing the write path with os.Lstat treated /dev/stdout, /dev/stderr
and /dev/fd/N as plain paths, because they are symlinks into
/proc/self/fd, so the atomic write failed on targets that worked
before. Decide by what the path resolves to, and state the whole
contract in the command help.

- regular file or nothing yet: written to a temporary file and renamed
  over the target, following symlinks, including one whose target does
  not exist yet
- character device or pipe: streamed in place, confirmed on the open
  descriptor and without O_TRUNC, so a path swapped for a regular file
  can neither receive the key nor be emptied
- anything else: refused, naming the path
- errors name the file the user asked for, and the temporary file is
  flushed before the rename

* fix(key): stop export landing on the wrong file

An export could replace a file the target symlink does not point at.

resolveSymlink joined a relative link target onto the path as typed,
so ".." collapsed lexically. Where a parent component was itself a
symlink, the join named a file outside the directory the link
resolves to: the key was renamed over that file, and the intended
target was never written. The link's parent is now resolved with
filepath.EvalSymlinks before the join.

* test(key): drop umask dependency in export test

os.WriteFile applies the umask, so under umask 077 the fixture was
created 0600 and the check that a failed export leaves the file at
0644 failed for reasons unrelated to the code under test. The CLI
test already chmods for the same reason.

---------

Signed-off-by: questfever <questfever@outlook.com>
Co-authored-by: Marcin Rataj <lidel@lidel.org>
(cherry picked from commit 842d9aa)
* fix(fuse): report a link count of 1

st_nlink was left at 0, which POSIX gives an inode with no remaining
names, so tools can read a live file as one on its way out. Neither
IPFS nor MFS has hard links. Directories report 1 as well, which keeps
GNU find from trusting a subdirectory count and skipping entries.

* fix(ipns): fill attrs in key directory lookups

The /ipns root answers lookups for its key directories and alias
symlinks itself, and the reply carried zeroed attributes. Every later
lookup refreshed the kernel's cache with the same zeroes, so the
Getattr that would have corrected them never ran and a key directory
showed up as d--------- with no link count.

* fix(fuse): give mounts stable inode numbers

go-fuse numbers any node left with a zero StableAttr.Ino itself, and
picks a new number every time. The kernel drops a mount's dentries once
EntryTimeout expires, so a file nobody touched came back from the next
lookup under a different st_ino, and programs that compare file
identity over time read that as the file being replaced. vim abandons a
save with "E949: File changed while writing", which is what made the
FUSE CI job flaky once it moved to slower runners and the save started
crossing the one second cache boundary.

- /ipfs takes the number from the multihash digest inside the CID, so
  the same content is one object whichever path reaches it; inline CIDs
  hash the whole CID instead, their digest being the content itself
- /ipns and /mfs allocate per mount from a counter keyed by parent and
  name, retired on unlink, rmdir and rename so a name that is created
  again is never handed a removed entry's number
- writable nodes carry a generation of their own, so go-fuse builds a
  fresh node per lookup instead of reusing one bound to an *mfs.File
  that boxo has since replaced
- mount points report inode 1 instead of 0, and readdir reports the
  same numbers as stat

* fix(fuse): tell two CIDs apart on /ipfs

go-fuse matches a lookup against the nodes it already holds by the
whole of StableAttr, so two entries that agree on it are served as one
object. The inode number alone is 63 bits, and two CIDs can end up
sharing one, by chance or by choice. Reading the second one then
returned the first one's bytes. A dag-pb CID and the raw CID of the
same block hit this every time, because the number ignored the codec.

The number and the generation now both come from a hash of the codec
and the multihash, so it takes a match on 128 bits to confuse two
entries. The mount also sets FirstAutomaticIno instead of relying on
go-fuse's default, so the range it keeps for itself stays where the
code says it is.

* fix(fuse): stat /ipfs entries we cannot read

A stat of a child whose block is missing, or of one in a codec this
mount does not decode, read UnixFS metadata that was never loaded and
panicked. go-fuse does not recover a panic in its serve loop, so this
took the whole daemon down. Neither case is exotic: a dag-cbor object
linked from a UnixFS directory needs no missing blocks at all.

A lookup that cannot read the block now fails instead of building an
entry from it, and a block that is not UnixFS is reported as a file of
its own size.

* fix(fuse): keep a file a rename cannot move

`mv /ipns/<key>/f /ipns/f` unlinked the source before finding out that
the /ipns root holds no files of its own, then failed with EINVAL and
left the file nowhere. The destination is now checked before anything
is written.

* fix(fuse): store a moved file where it landed

A rename across directories only wrote the source directory back, so
the file's new name lived in memory until something else flushed it,
and a daemon that stopped first lost the file. The destination is
written back first, so an interrupted rename leaves the file under
both names rather than under neither.

* fix(fuse): keep the inode number over a rename

Both names gave up their inode numbers on rename, so a moved file came
back about a second later as a different file, which is the problem
this branch fixes everywhere else. A moved directory was worse: its
entries are keyed by its number, so the whole subtree was renumbered
and the old keys were left behind until unmount.

The number now moves with the entry. Nothing else has to change to
make that safe: go-fuse cannot hand back the old node anyway, because
every node gets a generation of its own. The comments that credited
the renumbering for it were wrong.

* test(fuse): check listings and stat agree

Each mount fills in the inode number of a directory entry separately
from the one it reports to stat, and nothing compared the two. Tools
read whichever is cheaper for them.

* docs(config): warn about writing to a mounted mfs

`ipfs files` writes to the same tree as the /mfs and /ipns mounts
without the mount knowing, so the two can lose each other's writes.

(cherry picked from commit 93868b9)
* fix(fuse): keep a rename's writes

go-fuse hands the kernel's existing node to the new name once Dir.Rename
returns, and that node still held the MFS handle the rename had unlinked.
MFS treats such a handle as gone: a write through it was accepted and
then dropped, so `mv a b` followed by a write to `b` read back the old
contents a second later, once the entry cache expired. A directory was
worse. Creating a file in one that had just been renamed flushed through
the dead handle, which carried the name the rename had moved away from,
so the new file was lost and the old directory came back for good.

Each node now reaches its MFS handle through an atomic, and a rename
points the moved node at the entry that exists afterwards. Entries the
kernel had already looked up underneath a renamed directory hang off the
handle it was reached through, so the walk follows them down; it covers
what the kernel is holding, not the whole tree.

Invalidating the entry instead was tried and does not work: the kernel
processes FUSE_NOTIFY_INVAL_ENTRY while holding the parent inode lock,
so notifying from inside Rename deadlocks, and notifying asynchronously
still loses most of the writes it races.

Left unfixed: a write through a file descriptor held open across the
rename still goes to the descriptor opened from the old handle.

* fix(fuse): refuse to replace a non-empty directory

A rename may only overwrite a directory that is empty. MFS removes a
directory and everything under it without complaint, so `mv -T src dst`
took dst's contents with it and reported success. Rmdir already had the
check; Rename now makes it too, before it unlinks anything.

The check for an absent destination also goes through errors.Is now. It
compares against a sentinel that boxo returns bare today, and the cost of
that changing is the source file, which by then has been unlinked.

* fix(fuse): read /ipfs blocks we cannot decode

A UnixFS directory can link to a block of any codec. stat reports one
the mount cannot decode as a file the size of the block, but every read
of it failed, because the read path went looking for a UnixFS DAG that
is not there. A size stat promises has to be a size reads deliver, so
serve the block itself.

* fix(fuse): list a directory with a missing block

One child whose block is not held locally failed the whole listing, and
with an errno the caller could make nothing of: ipld.ErrNotFound has no
mapping, so it arrived as ENOSYS. The readable entries are worth having,
so report the one that is missing with no type and let a stat of it say
what is wrong.

* fix(fuse): report the CID the path used

The ipfs.cid xattr answered with a CID the caller had never seen. A
lookup rebuilds the node by decoding the block, which drops the version
and codec of the path it came from, so a v1 dag-pb path reported its v0
form. Keep the CID the entry resolved to and report that.

The changelog entry also covers the rename check from the commit before
it, which landed without one.

* test(fuse): make the rename tests catch their bugs

TestRenameOntoNamespaceRoot read the file back through the mount, which
answers from the entry the kernel still has cached and so succeeds
whether or not the rename took the file away. It passed against the bug
it was written for. Ask MFS instead.

The dirent helper also loops on a record length it never checks, which
would spin rather than fail if the kernel ever sent zero.

* test(ipns): settle the repo path before mounting

TestStatfs assigned Root.RepoPath once the server was already serving,
and Statfs reads it from a FUSE handler goroutine, so `go test -race
./fuse/...` reported a data race on every run. The mfs and readonly
tests already settle it before their mount; do the same here.

(cherry picked from commit a73e8c0)
* feat: Ipfs-Uri gateway header (IPIP-548)

Bump boxo to the IPIP-548 implementation (ipfs/boxo#1209): gateway
responses carry a canonical percent-encoded Ipfs-Uri header and stop
sending the deprecated X-Ipfs-Path, which cannot represent every
UnixFS file name.

- sharness: CORS expects Ipfs-Uri exposed, X-Ipfs-Path gone
- gateway-conformance CI pinned to the IPIP-548 test suite
  (ipfs/gateway-conformance#301) until a release ships
- reverse-proxy doc and v0.44 changelog updated

Refs ipfs/specs#548

* feat: opt-in Gateway.DeprecatedXIpfsPath

Expose boxo's opt-in for the legacy X-Ipfs-Path response header as a
kubo config flag, default off. Unsafe: the legacy value cannot
represent every UnixFS file name, so it must only be used to
facilitate migration to Ipfs-Uri, and even when enabled the header
is still skipped when the value would include non-ASCII byte
sequences.

Refs ipfs/specs#548

* ci: bump gateway-conformance pin

* ci: gateway-conformance v0.14

* chore: boxo with IPIP-548 from boxo/main

* docs: Ipfs-Uri changelog in v0.43.1

* docs: assemble v0.43.1 changelog

Move the v0.44 highlights and dependency lines into a new v0.43.1
section, and add the missing entry for owner-only key exports
(#11428). v0.44.md returns to an empty skeleton.

* docs: note boxo v0.42.2 fixes in v0.43.1

(cherry picked from commit a554311)
(cherry picked from commit f9aad61)
* chore: pin unreleased go-libp2p-kad-dht

Validation branch for libp2p/go-libp2p-kad-dht#1293 (batched provider
datastore writes): pins its head commit so kubo CI runs against it
before it merges. Not for merging; repoint at a tagged release once
one exists.

- go.mod (x3): go-libp2p-kad-dht v0.42.1 => 5a1c2420 pseudo-version;
  pion/stun v3.1.5 rides along as a transitive bump from kad-dht master
- core/node/provider.go: the pin pulls in
  libp2p/go-libp2p-kad-dht#1288, whose WithAddLocalRecord callback now
  receives the provide system's lifecycle context; use it instead of
  context.Background()

* chore: bump kad-dht to v0.42.2

* docs: changelog entry

---------

Co-authored-by: guillaumemichel <guillaume@michel.id>
(cherry picked from commit 329838a)
* feat: opt-in unixfs-v1-2026 profile (IPIP-550)

Opt-in Data-first PBNode field ordering via the new
unixfs-v1-2026 config profile, per IPIP-550. Defaults and the
preexisting unixfs-v0-2015 and unixfs-v1-2025 profiles are
unchanged and keep their CIDs.

- config: Import.UnixFSPBNodeFieldOrder (links-first default,
  data-first) and the unixfs-v1-2026 profile applying
  unixfs-v1-2025 settings plus data-first
- core/node: wires merkledag.DefaultPBNodeFieldOrder from config
- deps: boxo bump to the ipfs/boxo#1212 encoder commit
- test/cli: byte-exact fixtures from the IPIP-550 table; pinned
  CIDs for existing profiles unchanged

Refs ipfs/specs#550

* ci: run gateway-conformance from ipip-550 commit

Temporary pin to the ipfs/gateway-conformance#304 head so the
PBNode field ordering tests run against kubo. Switch back to a
tagged release once one ships.

* ci: gateway-conformance back to v0.14

v0.14.1 shipped the ipfs/gateway-conformance#304 tests, so the
moving v0.14 tag covers them again.

* docs: unixfs-v1-2026 scope and MFS re-encode

State what the profile actually changes: every dag-pb node with both
Data and Links gets a new CID, files larger than one chunk included,
and data already in MFS is upgraded to the new order on first read
(plain `ipfs files ls` or `stat` included), a sharded root before its
child shards; the MFS root is re-encoded by any command that starts a
node.

- config/profile.go, docs/config.md: profile description and the
  unixfs-v1-2026 section
- docs/changelogs/v0.43.md: highlight leads with the need (readers get
  the HAMT layout before links) and the upgrade-on-first-use behavior

* chore: update boxo to 04a079ec27b1

Pins the ipfs/boxo#1212 branch tip: data-first bytes derive from
dagpb.AppendEncode so link sorting is inherited, unknown field order
values return an error, and pinned end-to-end CIDs cover the profile.

* refactor: drop unixfs-v1-2026, keep opt-in knob

A dated successor profile invites unintentional adoption and a
de facto new CIDv1 default. Import.UnixFSPBNodeFieldOrder stays
as the documented low-level opt-in; unixfs-v0-2015 and
unixfs-v1-2025 now pin links-first explicitly.

- deps: boxo bump to the ipfs/boxo#1212 commit that drops
  UnixFS_v1_2026
- test/cli: field order exercised via the config knob, same
  IPIP-550 fixture bytes and CIDs

Refs ipfs/specs#550

* chore: update boxo to 02026ddcf262

Squash-merge of ipfs/boxo#1212 on main.

* docs: tighten v0.43.1 changelog

- tighter CID profile entry; MFS caveat now says re-encode
  happens on rewrite, not read
- deps: boxo pseudo-version with ipfs/boxo#1212,
  gateway-conformance v0.14.1

* docs: tighten Import.UnixFSPBNodeFieldOrder docs

(cherry picked from commit 9dbaab7)
* fix: empty Bootstrap list no longer dials stale backup peers

Rework of #11453 per maintainer review (lidel): the fix belongs in
boxo (bootstrap.bootstrapRound skips the backup list when no
bootstrap peers are configured), not behind a Routing.Type=none guard
in kubo. This PR is now the companion to ipfs/boxo#1213:

- Pin the boxo fix via a temporary replace directive pointing at the
  boxo PR branch. Once ipfs/boxo#1213 merges, repoint at boxo main
  and convert to a pseudo-version pin.
- Add a kubo-level regression test
  (TestBootstrapWithEmptyPeerListAndStaleBackupPeers) verifying
  IpfsNode.Bootstrap runs with an empty Bootstrap config and a
  populated TempBootstrapPeersKey without error.
- Document under Bootstrap in docs/config.md that an empty list
  disables all bootstrap dialing, including saved backup peers.
- Add a v0.44 changelog highlight. Drop the v0.43 entry and the
  broken emoji from the original PR.

Closes #11452

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* chore: review follow-ups for empty Bootstrap list

Replace the kubo-level test with one that fails without the boxo fix
(a mocknet backup peer must not be dialed when Bootstrap is empty),
shorten the changelog and config.md text, and point the boxo replace
at the reviewed commit.

* test: bootstrap fallback needs a configured peer

TestBackupBootstrapPeers used an empty Bootstrap list to force the
fallback to saved peers. An empty list now disables bootstrap dialing
entirely, so the test configures an unreachable bootstrap peer instead.
The changelog and config.md describe what still connects with an
empty list and when backup peers are used.

* chore: update boxo to 2edf737db3aa

Drops the temporary replace on a fork now that ipfs/boxo#1213 is
merged, and moves the changelog entry to v0.43.1.

---------

Co-authored-by: kalou <research@beleganjur.kalou.net>
Co-authored-by: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: Marcin Rataj <lidel@lidel.org>
(cherry picked from commit a13ea47)
maybeRunGC ran PeriodicGC on req.Context, which the RPC shutdown
command never cancels (it only closes the node), so gcErrc never
closed and daemonFunc blocked in merge() forever. Cancel the GC
context on node close as well, keeping req.Context so a signal still
aborts an in-flight sweep before the OnStop hooks close the datastore.

Closes #11424

(cherry picked from commit ceafeba)
* docs: spell out datastore layouts in profiles

Profile names and docs now say which store holds what: 'flatfs-levelds'
(blocks in flatfs, everything else in leveldb) is the canonical name of
the default layout, with 'flatfs' and 'flatfs-measure' kept as aliases.
'flatfs-pebbleds' and its '-measure' twin name the flatfs plus pebble
layout that otherwise needs a hand-written Datastore.Spec.

- docs: why flatfs holds only blocks, measure overhead note,
  Datastore.Spec example, profile sections
- cmd/ipfs: datastore paragraph in 'ipfs init --help'
- config: spec functions named after the profiles they back
- test/cli: layout on disk, restart, aliases, default, measure metrics
- test/sharness: profiles added to t0025

Closes #11423

* fix: safer flatfs shardFunc setup at init

The flatfs shard depth can only be set when a repo is created, by
passing a config file to 'ipfs init', and that route had traps.

- init: refuse a config without Identity.PrivKey ('ipfs config show'
  output) on both 'ipfs init <file>' and 'ipfs daemon --init', and
  validate Datastore.Spec before writing anything
- config profile apply: refuse profiles that change the on-disk layout
- fsrepo: mismatch error labels config and datastore_spec correctly
  and says the layout is fixed at init
- docs: why and when to use next-to-last/3, the config-file procedure,
  migration by moving data to a new repo

* docs: add go-ds-pebble v0.5.13 to v0.43.1 changelog

(cherry picked from commit 088e79f)
@lidel
lidel changed the base branch from master to release September 8, 2026 21:50
The collector at telemetry.ipshipyard.dev shuts down with the end of
Shipyard's IPFS work. The built-in endpoint is now empty, so a node
collects nothing and sends nothing unless its operator configures
one, and it drops a telemetry_uuid left by an earlier version.
@socket-security

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Updatedgithub.com/​ipfs/​boxo@​v0.42.1 ⏵ v0.42.3-0.20260907225511-2edf737db3aa74 +1100100100100

View full report

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants