Skip to content

IPIP-548: Sunset x-ipfs-path header - #548

Merged
lidel merged 10 commits into
mainfrom
ipip-548-sunset-x-ipfs-path
Aug 27, 2026
Merged

IPIP-548: Sunset x-ipfs-path header#548
lidel merged 10 commits into
mainfrom
ipip-548-sunset-x-ipfs-path

Conversation

@achingbrain

Copy link
Copy Markdown
Member

The x-ipfs-path header can contain characters outside of the US-ASCII character set which some clients will refuse to process.

Adding encoding rules to this header could break existing clients, instead add an ipfs-uri header that performs the same task but is a URI instead of a path so follows standard percent-encoding rules found in RFC 3986.

Futher discussion can be found in #547.

The `x-ipfs-path` header can contain characters outside of the US-ASCII
character set which some clients will refuse to process.

Adding encoding rules to this header could break existing clients,
instead add an `ipfs-uri` header that performs the same task but is a
URI instead of a path so follows standard percent-encoding rules found
in RFC 3986.

Futher discussion can be found in #547.
@achingbrain
achingbrain requested a review from lidel as a code owner August 18, 2026 11:41
@github-actions

github-actions Bot commented Aug 18, 2026

Copy link
Copy Markdown

🚀 Build Preview on IPFS ready

  • 🔎 Commit: b446c2d
  • 🔏 CID bafybeiglnramjtn4grv6witviufzugq2xovc4ealxw4hkbi6bygddex3ey
  • 📦 Preview:

@achingbrain
achingbrain requested a review from a team as a code owner August 18, 2026 11:55
@lidel

lidel commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

(I'll be adding some extra ipip-0548-test-fixtures.zip)

lidel added 2 commits August 21, 2026 03:51
Build the Ipfs-Uri header on the ipfs-uri and ipns-uri specs instead
of restating their canonical forms, and pin down what a second
implementation needs to emit byte-identical values:

- content path: decode request segments once, %2F is a separator
- strict RFC 3986 percent-encoding, with %2E / %2E%2E for dot segments
- DNSLink authority converts to A-labels or the header is omitted
- header also sent on redirects and errors, capped at 8192 bytes,
  exposed for CORS
- X-Ipfs-Path is legacy-only and MUST be omitted for non-ASCII paths
Make the IPIP lean on the URI specs and give implementers testable
fixtures per AGENTS.md:

- cite ipfs-uri and ipns-uri as the base of the design
- fixture table covers all 15 files of the tricky-filenames dir; the
  CAR is attached to the PR and the root is pinned as
  ipip-548_test-fixtures
- add gateway-conformance dir-with-percent-encoded-filename.car as a
  second fixture that catches raw URL path passthrough
- status back to proposal, point relatedIssues at discussion #547
lidel added a commit to ipfs/boxo that referenced this pull request Aug 21, 2026
Implement the Ipfs-Uri response header from ipfs/specs#548: a
canonical ipfs:// or ipns:// URI for the requested content path,
safe in an HTTP field value for any UnixFS file name.

- authority in canonical form: base32 CIDv1, base36 IPNS name
  keeping its multicodec, lowercase A-label DNSLink; header omitted
  when the root cannot be normalized
- every path segment percent-encoded over the RFC 3986 unreserved
  set with uppercase hex; "." and ".." emitted as %2E / %2E%2E
- sent on deserialized, redirect, and error responses up to 8192
  bytes, and exposed via default Access-Control-Expose-Headers
- X-Ipfs-Path no longer sent by default; restore via
  Config.DeprecatedXIpfsPath + Headers.WithDeprecatedXIpfsPath,
  still omitted for bytes invalid in a field value
- CI runs gateway-conformance from ipfs/gateway-conformance#301
  until a release with the IPIP-548 tests ships
lidel added a commit to ipfs/gateway-conformance that referenced this pull request Aug 21, 2026
Cover the Ipfs-Uri response header from ipfs/specs#548 and the
deprecation of X-Ipfs-Path, one request per failure class:

- canonical percent-encoded segments: delimiters, platform-encoder
  gaps (!'()*~), 4-byte UTF-8; full byte-level vectors live in the
  IPIP-0548 fixtures table
- request-spelling independence over
  dir-with-percent-encoded-filename.car: raw sub-delims and
  lowercase hex must yield the same canonical value
- decode-once normalization (%2F, %2E, dot segments), nested
  trailing slashes, query exclusion on alternate formats, redirects
- authority normalization: CIDv0 and legacy base58 peer IDs
- X-Ipfs-Path must be omitted for non-ASCII paths and unchanged
  when present for ASCII-safe ones; existing expectations move to
  Ipfs-Uri, raw block tests drop the header expectation, CORS
  expects Ipfs-Uri exposed
- DNSLink hosts get an ipns:// assertion in the dnslink suite
lidel added a commit to ipfs/boxo that referenced this pull request Aug 21, 2026
Implement the Ipfs-Uri response header from ipfs/specs#548: a
canonical ipfs:// or ipns:// URI for the requested content path,
safe in an HTTP field value for any UnixFS file name.

- authority in canonical form: base32 CIDv1, base36 IPNS name
  keeping its multicodec, lowercase A-label DNSLink; header omitted
  when the root cannot be normalized
- every path segment percent-encoded over the RFC 3986 unreserved
  set with uppercase hex; "." and ".." emitted as %2E / %2E%2E
- sent on deserialized, redirect, and error responses up to 8192
  bytes, and exposed via default Access-Control-Expose-Headers
- X-Ipfs-Path no longer sent by default; restore via
  Config.DeprecatedXIpfsPath + Headers.WithDeprecatedXIpfsPath,
  still omitted for bytes invalid in a field value
- CI runs gateway-conformance from ipfs/gateway-conformance#301
  until a release with the IPIP-548 tests ships
@lidel

lidel commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

Thanks @achingbrain for kicking this off, Ipfs-Uri closes a real gap.

The header hid more edge cases than expected (./.. segments, %2F, names that already look percent-encoded, DNSLink IDNs, legacy base58 peer IDs), so I pushed clarifications here (9d187a6, 99afeee): the Ipfs-Uri section now builds on the ipfs:// / ipns:// URI specs and pins the exact serialization, X-Ipfs-Path is legacy-only and omitted when the content path is not ASCII-safe, and the fixtures table covers names across nine alphabets (CAR in the zip above).

Remaining before ratification:

Both implementations follow the spec as closely as possible; push back or relax if any of it feels too pedantic. I'm also leaning toward dropping x-ipfs-path by default and keeping it opt-in (as boxo does): the raw value is unsafe anyway. PTAL

lidel added a commit to ipfs/gateway-conformance that referenced this pull request Aug 22, 2026
Cover the Ipfs-Uri response header from ipfs/specs#548 and the
deprecation of X-Ipfs-Path, one request per failure class:

- canonical percent-encoded segments: delimiters, platform-encoder
  gaps (!'()*~), 4-byte UTF-8; full byte-level vectors live in the
  IPIP-0548 fixtures table
- request-spelling independence over
  dir-with-percent-encoded-filename.car: raw sub-delims and
  lowercase hex must yield the same canonical value
- decode-once normalization (%2F, %2E, dot segments), nested
  trailing slashes, query exclusion on alternate formats, redirects
- a hand-built fixture with a link literally named "a/b.txt" proves
  such an entry is not path-addressable: %2F always decodes to a
  separator, so the nested b.txt wins under every request spelling
- authority normalization: CIDv0 and legacy base58 peer IDs
- X-Ipfs-Path must be omitted for non-ASCII paths and unchanged
  when present for ASCII-safe ones; existing expectations move to
  Ipfs-Uri, raw block tests drop the header expectation, CORS
  expects Ipfs-Uri exposed
- DNSLink hosts get an ipns:// assertion in the dnslink suite
lidel added a commit to ipfs/boxo that referenced this pull request Aug 22, 2026
Implement the Ipfs-Uri response header from ipfs/specs#548: a
canonical ipfs:// or ipns:// URI for the requested content path,
safe in an HTTP field value for any UnixFS file name.

- authority in canonical form: base32 CIDv1, base36 IPNS name
  keeping its multicodec, lowercase A-label DNSLink; header omitted
  when the root cannot be normalized
- every path segment percent-encoded over the RFC 3986 unreserved
  set with uppercase hex; "." and ".." emitted as %2E / %2E%2E
- sent on deserialized, redirect, and error responses up to 8192
  bytes, and exposed via default Access-Control-Expose-Headers
- X-Ipfs-Path no longer sent by default; restore via
  Config.DeprecatedXIpfsPath + Headers.WithDeprecatedXIpfsPath,
  still omitted for bytes invalid in a field value
- CI runs gateway-conformance from ipfs/gateway-conformance#301
  until a release with the IPIP-548 tests ships
lidel added a commit to ipfs/gateway-conformance that referenced this pull request Aug 22, 2026
Cover the Ipfs-Uri response header from ipfs/specs#548 and the
deprecation of X-Ipfs-Path, one request per failure class:

- canonical percent-encoded segments: delimiters, platform-encoder
  gaps (!'()*~), 4-byte UTF-8; full byte-level vectors live in the
  IPIP-0548 fixtures table
- request-spelling independence over
  dir-with-percent-encoded-filename.car: raw sub-delims and
  lowercase hex must yield the same canonical value
- decode-once normalization (%2F, %2E, dot segments), nested
  trailing slashes, query exclusion on alternate formats, redirects
- a hand-built fixture with a link literally named "a/b.txt" proves
  such an entry is not path-addressable: %2F always decodes to a
  separator, so the nested b.txt wins under every request spelling
- authority normalization: CIDv0 and legacy base58 peer IDs
- X-Ipfs-Path must be omitted for non-ASCII paths and unchanged
  when present for ASCII-safe ones; existing expectations move to
  Ipfs-Uri, raw block tests drop the header expectation, CORS
  expects Ipfs-Uri exposed
- DNSLink hosts get an ipns:// assertion in the dnslink suite
lidel added a commit to ipfs/boxo that referenced this pull request Aug 22, 2026
Implement the Ipfs-Uri response header from ipfs/specs#548: a
canonical ipfs:// or ipns:// URI for the requested content path,
safe in an HTTP field value for any UnixFS file name.

- authority in canonical form: base32 CIDv1, base36 IPNS name
  keeping its multicodec, lowercase A-label DNSLink; header omitted
  when the root cannot be normalized
- every path segment percent-encoded over the RFC 3986 unreserved
  set with uppercase hex; "." and ".." emitted as %2E / %2E%2E
- sent on deserialized, redirect, and error responses up to 8192
  bytes, and exposed via default Access-Control-Expose-Headers
- X-Ipfs-Path no longer sent by default; restore via
  Config.DeprecatedXIpfsPath + Headers.WithDeprecatedXIpfsPath,
  still omitted for bytes invalid in a field value
- CI runs gateway-conformance from ipfs/gateway-conformance#301
  until a release with the IPIP-548 tests ships
A dag-pb link name may contain a slash at the byte level, but no
content path can address it. Make every relevant spec say so:

- unixfs: names containing "/" join the restricted names list, and
  the path escaping section defines the HTTP gateway and URI behavior
  while leaving other contexts unspecified
- path-gateway: spell out the consequence next to the %2F rule
- ipip-0548: record the previously-undefined escaping gap in the
  design rationale, and reference the conformance fixture that proves
  a slash-named link resolves to the nested file under every request
  spelling

Refs ipfs/helia-verified-fetch#376 (comment)
@lidel
lidel force-pushed the ipip-548-sunset-x-ipfs-path branch from 8e17877 to 01ababb Compare August 22, 2026 12:41
In the DNSLink case a name with no dot never becomes an Ipfs-Uri
authority, because on each network it can point at different content.
Give the omission clause a concrete pair: examplenodot never works,
example.local is fine. Matching positive and negative tests exist in
gateway-conformance, boxo, and helia-verified-fetch.

Refs ipfs/helia-verified-fetch#376 (comment)
lidel added a commit to ipfs/gateway-conformance that referenced this pull request Aug 26, 2026
* feat: tests for IPIP-548 Ipfs-Uri header

Cover the Ipfs-Uri response header from ipfs/specs#548 and the
deprecation of X-Ipfs-Path, one request per failure class:

- canonical percent-encoded segments: delimiters, platform-encoder
  gaps (!'()*~), 4-byte UTF-8; full byte-level vectors live in the
  IPIP-0548 fixtures table
- request-spelling independence over
  dir-with-percent-encoded-filename.car: raw sub-delims and
  lowercase hex must yield the same canonical value
- decode-once normalization (%2F, %2E, dot segments), nested
  trailing slashes, query exclusion on alternate formats, redirects
- a hand-built fixture with a link literally named "a/b.txt" proves
  such an entry is not path-addressable: %2F always decodes to a
  separator, so the nested b.txt wins under every request spelling
- authority normalization: CIDv0 and legacy base58 peer IDs
- X-Ipfs-Path must be omitted for non-ASCII paths and unchanged
  when present for ASCII-safe ones; existing expectations move to
  Ipfs-Uri, raw block tests drop the header expectation, CORS
  expects Ipfs-Uri exposed
- DNSLink hosts get an ipns:// assertion in the dnslink suite

* test: dot-less DNSLink names never in Ipfs-Uri

Per IPIP-548, only dotted DNSLink names can be an ipns:// authority: a
name with no dot can point at different content on each network. Add a
positive and a negative test over new dir_listing dnslink entries:
example.local yields ipns://example.local/, and examplenodot never
yields an Ipfs-Uri header, no matter how the gateway answers.

Refs ipfs/helia-verified-fetch#376 (comment)

* test: rename dot-less domain to examplemissingtld

* Update fixtures/path_gateway_unixfs/README.md

Co-authored-by: Alex Potsides <alex@achingbrain.net>

* fix: Has() checks every expected header value

HeaderBuilder.Has overwrote its check in a loop, so only the last
argument was ever asserted and the rest of each list in TestCors was
silently ignored. Build one And() over all values instead, drop the
per-callsite Contains workaround, and add a regression test for a
missing first value.

The kubo-specific X-Chunked-Output and X-Stream-Output entries, which
no spec mandates, are removed from the Access-Control-Expose-Headers
expectation instead of becoming newly enforced.

---------

Co-authored-by: Alex Potsides <alex@achingbrain.net>
lidel added a commit to ipfs/boxo that referenced this pull request Aug 27, 2026
* feat(gateway): Ipfs-Uri response header

Implement the Ipfs-Uri response header from ipfs/specs#548: a
canonical ipfs:// or ipns:// URI for the requested content path,
safe in an HTTP field value for any UnixFS file name.

- authority in canonical form: base32 CIDv1, base36 IPNS name
  keeping its multicodec, lowercase A-label DNSLink; header omitted
  when the root cannot be normalized
- every path segment percent-encoded over the RFC 3986 unreserved
  set with uppercase hex; "." and ".." emitted as %2E / %2E%2E
- sent on deserialized, redirect, and error responses up to 8192
  bytes, and exposed via default Access-Control-Expose-Headers
- X-Ipfs-Path no longer sent by default; restore via
  Config.DeprecatedXIpfsPath + Headers.WithDeprecatedXIpfsPath,
  still omitted for bytes invalid in a field value
- CI runs gateway-conformance from ipfs/gateway-conformance#301
  until a release with the IPIP-548 tests ships

* test: dotted vs dot-less DNSLink Ipfs-Uri

Per IPIP-548 only dotted DNSLink names become an ipns:// authority:
example.local works, examplenodot never does.

Refs ipfs/helia-verified-fetch#376 (comment)

* ci: bump gateway-conformance pin

Use the conformance tests from ipfs/gateway-conformance#301 at its
latest commit, which adds the dot-less DNSLink Ipfs-Uri tests.

* test: rename dot-less name to examplemissingtld

* ci: gateway-conformance v0.14

* docs: changelog for Ipfs-Uri and conformance v0.14
lidel added a commit to ipfs/kubo that referenced this pull request Aug 27, 2026
* 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

@lidel lidel left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Status update:

@achingbrain if no concerns we can flip this to ratified state and merge?

@github-project-automation github-project-automation Bot moved this to Needs Triage in IPIP pipeline Aug 27, 2026
@github-project-automation github-project-automation Bot moved this from Needs Triage to 🔍 Ready for Final Reviews in IPIP pipeline Aug 27, 2026
achingbrain and others added 2 commits August 27, 2026 10:35
gateway-conformance v0.14.0 contains ipfs/gateway-conformance#301;
both CARs are byte-identical to the previously pinned refs.
achingbrain added a commit to ipfs/helia-verified-fetch that referenced this pull request Aug 27, 2026
Replace the pass-through ipfs-uri value from #375 with the canonical
serialization from ipfs/specs#548, so the header no longer depends
on how the request spelled the path:

- authority normalized: base32 CIDv1 for ipfs://, base36 libp2p-key
  CIDv1 for ipns:// (legacy base58 peer ids converted), lowercase
  A-label DNSLink names; header omitted when the root cannot be
  normalized
- each path segment decoded once and strictly re-encoded over the
  RFC 3986 unreserved set with uppercase hex
- x-ipfs-path kept for legacy clients but omitted when the content
  path cannot appear in an HTTP field value, instead of mangling
  it to ASCII
- Ipfs-Uri added to Access-Control-Expose-Headers

Per IPIP-548 only dotted DNSLink names become an ipns:// authority: a
name with no dot can point at different content on each network. Use
examplenodot for the negative case instead of localhost, and add
example.local as the positive private-network case.

Refs #376 (comment)

---------

Co-authored-by: Alex Potsides <alex@achingbrain.net>
github-actions Bot pushed a commit to ipfs/helia-verified-fetch that referenced this pull request Aug 27, 2026
## [@helia/verified-fetch-v8.1.0](https://github.com/ipfs/helia-verified-fetch/compare/@helia/verified-fetch-8.0.4...@helia/verified-fetch-8.1.0) (2026-08-27)

### Features

* canonical Ipfs-Uri header (IPIP-548) ([#376](#376)) ([0b26cc1](0b26cc1)), closes [#375](#375) [ipfs/specs#548](ipfs/specs#548)

### Trivial Changes

* just return overridden fields ([#378](#378)) ([ff7c6fd](ff7c6fd))
@lidel
lidel merged commit d32dd06 into main Aug 27, 2026
4 checks passed
@lidel
lidel deleted the ipip-548-sunset-x-ipfs-path branch August 27, 2026 16:14
@github-project-automation github-project-automation Bot moved this from 🔍 Ready for Final Reviews to ✅ Ratified in IPIP pipeline Aug 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: ✅ Ratified

Development

Successfully merging this pull request may close these issues.

3 participants