Skip to content

Commit 6f8078b

Browse files
committed
fix: bound ipns cache-control to record eol
A fixed stale-while-revalidate window let caches reuse an IPNS record past its Validity (EOL). After EOL the signature is expired, so validating clients reject the record, which surfaced as sporadic 500s in the service-worker gateway. Mirror the bound applied in ipfs/boxo#1166: max-age plus the stale window must end before the EOL, and an already-expired record must not be cached. - path-gateway: keep a fixed stale window for generated /ipfs/ HTML (content-addressed, no EOL), but cap the mutable /ipns/ window to the record's remaining validity; no-store past EOL - http-routing-v1: size the ipns record stale window as remaining validity minus max-age so it ends at the EOL; no-store when the record is expired or its ValidityType is unrecognized
1 parent 2091beb commit 6f8078b

2 files changed

Lines changed: 39 additions & 19 deletions

File tree

src/http-gateways/path-gateway.md

Lines changed: 33 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ description: >
44
The comprehensive low-level HTTP Gateway enables the integration of IPFS
55
resources into the HTTP stack through /ipfs and /ipns namespaces, supporting
66
both deserialized and verifiable response types.
7-
date: 2026-03-05
7+
date: 2026-06-15
88
maturity: reliable
99
editors:
1010
- name: Marcin Rataj
@@ -496,22 +496,40 @@ in caches.
496496
Returned directive depends on requested content path and format:
497497

498498
- `Cache-Control: public, max-age=29030400, immutable` MUST be returned for
499-
every immutable resource under `/ipfs/` namespace.
500-
- Generated `/ipfs/` responses such as UnixFS directory listings are not
501-
byte-for-byte immutable and SHOULD have a meaningful expiration that allows
502-
for generated responses to be updated over time. Suggested value is at least one week:
499+
every immutable resource under the `/ipfs/` namespace.
500+
- Generated `/ipfs/` responses, such as UnixFS directory listings, are not
501+
byte-for-byte stable: their markup can change between implementations and
502+
versions, so they MUST NOT be marked `immutable`. Give them an expiration
503+
that lets caches pick up regenerated markup. The underlying data is
504+
content-addressed and never expires, so a stale response can only show
505+
outdated markup, never invalid data, and a fixed stale window is safe. A
506+
floor of one week works well:
503507
`public, max-age=604800, stale-while-revalidate=2678400`.
504508

505-
- `Cache-Control: public, max-age=<ttl>, stale-while-revalidate=2678400` SHOULD be returned for mutable
506-
resources under `/ipns/{id-with-ttl}/` namespace; `max-age=<ttl>` SHOULD
507-
indicate remaining TTL of the mutable pointer such as :cite[ipns-record] or DNSLink
508-
TXT record.
509-
- Implementations MAY place an upper bound on any TTL received, as
510-
noted in Section 8 of :cite[rfc2181].
511-
- If TTL value is unknown, implementations MAY send a best-effort `Cache-Control`
512-
informing caches and CDNs how long a stale response is acceptable.
513-
- No matter if TTL value is known or not, implementations SHOULD always
514-
send a [`Last-Modified`](#last-modified-response-header) header with the timestamp of the record resolution.
509+
- `Cache-Control: public, max-age=<ttl>` SHOULD be returned for mutable
510+
resources under the `/ipns/{id-with-ttl}/` namespace, where `<ttl>` is the
511+
remaining TTL of the mutable pointer: the :cite[ipns-record] `TTL` field or
512+
the DNSLink TXT record TTL.
513+
- Implementations MAY place an upper bound on any TTL received, as noted in
514+
Section 8 of :cite[rfc2181].
515+
- A `stale-while-revalidate` or `stale-if-error` window MAY be added to
516+
improve CDN and offline behavior, but it MUST respect the pointer's
517+
expiration. An :cite[ipns-record] with `ValidityType=0` is cryptographically
518+
valid only until its `Validity` (EOL) timestamp, and a validating client
519+
rejects it once that timestamp passes. Cap `max-age` to the remaining
520+
validity, and size the stale window so that `max-age` plus the stale window
521+
still ends before the EOL; a fixed stale window is unsafe because it can
522+
push reuse past the EOL. Return `Cache-Control: no-store` for a response
523+
backed by an already-expired record. This bound is mandatory for the raw
524+
record response (`format=ipns-record`), whose body is the signed record
525+
itself. A DNSLink pointer carries no signature EOL and MAY use a bounded
526+
best-effort stale window.
527+
- If the TTL is unknown, implementations MAY send a best-effort
528+
`Cache-Control` telling caches and CDNs how long a stale response is
529+
acceptable.
530+
- Whether or not the TTL is known, implementations SHOULD always send a
531+
[`Last-Modified`](#last-modified-response-header) header with the timestamp
532+
of the record resolution.
515533

516534
### `Last-Modified` (response header)
517535

src/routing/http-routing-v1.md

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ description: >
44
Delegated routing is a mechanism for IPFS implementations to use for offloading
55
content routing, peer routing and naming to another process/server. This specification describes
66
an HTTP API for delegated routing of content, peers, and IPNS.
7-
date: 2025-12-17
7+
date: 2026-06-15
88
maturity: reliable
99
editors:
1010
- name: Marcin Rataj
@@ -232,9 +232,11 @@ Each object in the `Peers` list is a record conforming to the [Peer Schema](#pee
232232
#### Response Headers
233233

234234
- `Etag`: a globally unique opaque string used for HTTP caching. MUST be derived from the protobuf record returned in the body.
235-
- `Cache-Control: public, max-age={ttl}, public, stale-while-revalidate={sig-ttl}, stale-if-error={sig-ttl}`: meaningful cache TTL returned with :ref[IPNS Record]
236-
- The `max-age` value in seconds SHOULD match duration from `IpnsEntry.data[TTL]`, if present and bigger than `0`. Otherwise, implementation SHOULD default to `max-age=60`.
237-
- Implementations SHOULD include `sig-ttl`, set to the remaining number of seconds the returned IPNS Record is valid.
235+
- `Cache-Control: public, max-age={ttl}, stale-while-revalidate={stale}, stale-if-error={stale}`: cache lifetime for the returned :ref[IPNS Record].
236+
- An IPNS Record with `ValidityType=0` is valid only until its EOL, the `Validity` timestamp. A cache that reuses the record past the EOL serves a record that fails validation, so the entire cache lifetime MUST stay within the remaining validity.
237+
- `{ttl}` (the `max-age`) SHOULD match `IpnsEntry.data[TTL]` in seconds, or default to `60` when `TTL` is absent or `0`. Either way, cap it to the remaining validity.
238+
- `{stale}` SHOULD be the remaining validity minus `max-age`, so `max-age` plus the stale window ends at the EOL and never crosses it.
239+
- Return `Cache-Control: no-store` instead when the record is already expired, or when its `ValidityType` is unrecognized and the EOL is therefore unknown.
238240
- `Expires:`: an HTTP-date timestamp ([RFC9110, Section 5.6.7](https://www.rfc-editor.org/rfc/rfc9110#section-5.6.7)) when the validity of IPNS Record expires (if `ValidityType=0`, when signature expires)
239241
- `Last-Modified`: an HTTP-date timestamp of when cacheable resolution occurred: allows HTTP proxies and CDNs to support inexpensive update checks via `If-Modified-Since`
240242
- `Vary: Accept`: allows intermediate caches to play nicely with the different possible content types.

0 commit comments

Comments
 (0)