From 1d490ca4059bbc2e5e1285521fd6d4f08532dd0c Mon Sep 17 00:00:00 2001 From: ehsan shariati Date: Sat, 12 Sep 2026 08:12:00 -0400 Subject: [PATCH] feat(gateway): guard the resolver properties that published sites now rely on Companion to pinning-service#94, which makes generated sites gateway-agnostic by referencing assets DOCUMENT-RELATIVELY (`../`) instead of naming a gateway. That turns two properties of this Worker into load-bearing infrastructure for every site's images, so they are pinned by tests rather than left to chance: 1. The redirect target must keep its TRAILING SLASH. From `/ipfs//` the reference resolves to `/ipfs/`; from `/ipfs/` it resolves to `/` and 404s. The site still renders -- an injected fallback chain recovers the image from an absolute gateway -- but it is no longer served by the gateway the visitor actually chose. 2. It must REDIRECT, never proxy. If this Worker ever served the page itself, the document URL would be `fxfiles.top/w/` and `../` would resolve back into the Worker as a bogus IPNS name instead of reaching a gateway. Neither would fail loudly. A change that dropped the slash would look fine in every existing test and quietly stop every site from following its gateway. ALSO: fx.land is removed from the gateway picker. It serves a "content withheld" interstitial before HTML in a browser -- measured 2026-09-12; curl bypasses it, which is exactly why an earlier check waved it through. That is a poor thing to put in front of someone opening a shared website, and is almost certainly why it stopped being the default originally. It is NOT removed entirely: raw assets are served normally (correct image/jpeg, no interstitial), so it stays as the second entry in the published fallback chain and as an accepted `?gw=fx` key. A site is then never at the mercy of a single third party. Filebase is the only preset offered. Tests: 35 worker, 28 gateway-helper (one new asserting fx is absent from the picker while still resolving as a key), 1079 Flutter, analyzer clean. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01AwMWmCivEpYTmmzzmjSTAf --- cloudflare/ipns-resolver-worker.test.mjs | Bin 9583 -> 10977 bytes lib/core/services/ipfs_gateway_helper.dart | 17 +++++++++++------ .../services/ipfs_gateway_helper_test.dart | 12 ++++++++++-- 3 files changed, 21 insertions(+), 8 deletions(-) diff --git a/cloudflare/ipns-resolver-worker.test.mjs b/cloudflare/ipns-resolver-worker.test.mjs index 88e044587fff0e1bedf30a88c055e53b0b0c46db..5998d7a42e0bb3d56992115ffff86de933f489eb 100644 GIT binary patch delta 1250 zcmZXU!EVz)5QYy3Ayq{j5Qs`h3{s>dRcZt3iB^?Lo0gzZP?}O9aq!w6$4eZq*-}Wcn~C>gE!zpuN-&)F3hg&l!{Z5-I;%8zkepbU;RA!dIJh~eSHPi;Sf`lOk)U& zYg8aHLWyaBpkw3`m{O<}?7!JNdEGr4ZVbBp-C^%txBnhmp5wUN0S|Y)HYj1#C}&O* zX^0X;3bUA$r}TY9pEEWGg>p(oD6E(`Fr;gku(3Ir3CN_#P-;#;MO3PB1*bxeNg)v| z-0+b}o{k_A5{xbhQqxTZe8NVko>P*^gQrFtsgaON3^ZsaM@Xe|rD2Si0oR3s;b6Dd z?;Rb&aew#tr2_|2Ou%z_7Abc-Bf%ZlvvL5Y&5}yTQ2x7+5T9ckC2#Up9 z5-Gc(Oy*PhoOEj=E(?4-S{NPufaYgY*V+`hlgH7ur_+9Vf(SSWOC z{4)o772`p7zc=Xa4Lgup`^nj}IbBDjQ(0P2EqhnhFrtQeU7Bl!Nz}20LlNW?Of{Sg z`Y;oDLMFHo+aw!n_*HTCU8hTmWKMj0vX}R=*lCrhtJjM!ACC z+oNMJf)u1Qvap&+nglY11+kezwxmg8OpX`~?g}lDZZ*x6l}2dEcw)j;!c=Unl$fxG znoqIZE;CpPLdr10I=o zy~uNDoKxV1XOmsmGPyp%)cbPr4R#2alxFklK{brVjM}LfRpwtcgP(Giv{IZISL|Ow z<9x5Tf8lj#l!CSs)2ljg@S;VB9cVV&j>Ifs0k+%^Zo{=l?X88kdjD~?p5@HbZqJwu qOL!{xGp6I3$KuE8-Nl`IYY$3Im-!;suIb;2^(1Mwzx{cnZ~g_W+@aL~ delta 12 TcmaDD`rc~;v%=<5)f`p;CnE&` diff --git a/lib/core/services/ipfs_gateway_helper.dart b/lib/core/services/ipfs_gateway_helper.dart index 60cd2d9..0bd0d1c 100644 --- a/lib/core/services/ipfs_gateway_helper.dart +++ b/lib/core/services/ipfs_gateway_helper.dart @@ -24,11 +24,17 @@ class IpfsGatewayHelper { /// returned 200 from Filebase for the same CID at the same moment. static const String filebaseTemplate = 'https://ipfs.filebase.io/ipfs/'; - /// fx's own gateway. This was the pre-v0.4 default, and [init] used to - /// migrate people AWAY from it and onto dweb — that migration is gone, - /// because the destination is now the thing that is dying. It is offered as - /// a first-class preset again: it serves these CIDs with correct content - /// types (verified 2026-09-12) and, unlike any third party, it is ours. + /// fx's own gateway. + /// + /// NOT offered in the picker: it serves an interstitial "content withheld" + /// page before HTML (measured 2026-09-12 in a browser — curl bypasses it), + /// which is a poor thing to put in front of someone opening a shared + /// website, and is almost certainly why it stopped being the default. + /// + /// It remains useful, and is kept, because raw ASSETS are served normally + /// (correct `image/jpeg`, no interstitial): it is the second entry in the + /// published fallback chain, so a site is never at the mercy of one third + /// party. Also still accepted as a `?gw=fx` key on the resolver. static const String fxTemplate = 'https://ipfs.cloud.fx.land/gateway/'; static const String defaultTemplate = filebaseTemplate; @@ -46,7 +52,6 @@ class IpfsGatewayHelper { /// a gateway that [init] would migrate away from on next launch is a trap. static const Map presets = { 'Filebase': filebaseTemplate, - 'fx.land': fxTemplate, }; /// Preset label for [template], or null when it is a custom value. diff --git a/test/unit/core/services/ipfs_gateway_helper_test.dart b/test/unit/core/services/ipfs_gateway_helper_test.dart index d7fc0f8..11c5a4c 100644 --- a/test/unit/core/services/ipfs_gateway_helper_test.dart +++ b/test/unit/core/services/ipfs_gateway_helper_test.dart @@ -120,13 +120,21 @@ void main() { test('names the presets and nothing else', () { expect(IpfsGatewayHelper.presetLabelFor(IpfsGatewayHelper.filebaseTemplate), 'Filebase'); - expect(IpfsGatewayHelper.presetLabelFor(IpfsGatewayHelper.fxTemplate), - 'fx.land'); expect(IpfsGatewayHelper.presetLabelFor('https://my-host/ipfs/'), isNull); expect(IpfsGatewayHelper.presetLabelFor(IpfsGatewayHelper.dwebTemplate), isNull); }); + // fx serves an interstitial before HTML, so it is a poor thing to put in + // front of a shared website — but it serves raw assets fine and stays in + // the published fallback chain and as a resolver key. + test('fx is NOT offered in the picker but is still a resolver key', () { + expect(IpfsGatewayHelper.presets.values, + isNot(contains(IpfsGatewayHelper.fxTemplate))); + expect(IpfsGatewayHelper.frontDoorGatewayKey(IpfsGatewayHelper.fxTemplate), + 'fx'); + }); + test('tolerates surrounding whitespace', () { expect( IpfsGatewayHelper.presetLabelFor(