126. Volající mimo loopback čte a nemění nic, co nedokáže autentizovat
Kontext
HTTP server nemá účty ani tokeny. Jeho jediná ochrana požadavku měnícího stav byla kontrola Origin (http/router.rs), která odmítá stránku prohlížeče z jiného originu a nic víc: program Origin neposílá a prošel. Dokud každý server naslouchal na loopbacku, bylo to neškodné. Přestalo to být neškodné, když mesh potřeboval servery dosažitelné z jiných strojů (docs/MESH.md, „Mesh tohoto repozitáře“) a když MAJORDOMUS_HTTP_HOST umožnil vývojáři navázat se na všechna rozhraní.
Dne 2026-10-09 naslouchaly na stroji vlastníka tři servery na všech rozhraních: hub mesh na 8791 a dva servery primárního checkoutu. Čtení kódu (audit základního stavu balíčku runtime, M01) zjistilo, že jakýkoli hostitel na LAN nebo tailnetu mohl:
- převést sledovaný záznam plánu (
plan.transition), publikovat nebo obnovit kontinuitu a uklidit osiřelé: schopnosti, jejichž efekt jerepository_mutation; - spustit jakoukoli schopnost, včetně zapisovačů, přes
executions.start; - zveřejnit relaci meshe, nárok, uvolnění, publikaci nebo převzetí předávky, nebo review, což tento uzel pak podepsal svým vlastním důvěryhodným klíčem a replikoval na každý runtime repozitáře. Peer nedokáže takovou událost odlišit od té, kterou napsal její vlastník: uzel se stal podpisovou věštírnou a záruka z
project.mesh-cooperation-is-authenticatedplatila jen pro síťový hop, ne pro autora.
Varování při bindu říkalo, že vzdálený hostitel může „číst“. Dokumentace uváděla, že neexistuje autentizace a že zapisovatelé jsou vystaveni; nikdo podle té věty nejednal, protože bezpečnostní model v dokumentaci mesh o tom nemluvil.
Rozhodnutí
Požadavek se posuzuje podle adresy, odkud přišel, protože to je jediný fakt, který server má. Požadavek z adresy, která není loopback — v obou rodinách, včetně IPv4-mapped tvaru, který hlásí dual-stack socket — spustí schopnost jen tehdy, když:
- schopnost čte (
effect: read), nebo - vstupem schopnosti je podepsaná zpráva, kterou její handler ověří proti politice důvěry, než se cokoli změní. To se deklaruje na schopnosti jako třetí zdokumentovaný dodatek k její politice provádění (
ExecutionPolicy::authenticates_its_input, serializováno jakosigned_input) a smí ho deklarovat jen příkaz, jehož efekt jeprocess_state: podpis dokazuje, kdo zprávu poslal, ne že její odesílatel smí zde měnit sledované soubory. Dnes ho deklarují přesně tři schopnosti:mesh.link.hello,mesh.link.syncamesh.register.
Vše ostatní je odmítnuto dřív, než se spustí jeho handler, s jednou novou kategorií chyby, forbidden (HTTP 403), která na každém transportu nese jednu větu (CapabilityError::remote). Otázka se klade na jednom místě, ExecutionPolicy::admits_remote, a ptá se každý transport, který zná adresu volajícího:
- HTTP router pro každou routu schopnosti;
- koncový bod MCP přes HTTP, po zprávě a ne po relaci: id relace je jméno, ne důkaz, a další zpráva relace otevřené z loopbacku může přijít odjinud.
Požadavek sestavený uvnitř procesu (Cockpit skládající čtení, test) nenese žádnou adresu a je tohoto stroje. Most stdio dosahuje sdílený server přes loopback a je tohoto stroje. executions.start je příkaz, takže vzdálený volající jej nemůže použít k dosažení zapisovatele. Lokální by mohl, pod nástrojem oznámeným jako měnícím jen paměť tohoto procesu; executions.start proto deklaruje nejsilnější efekt toho, co může spustit, repository_mutation, a klient, který se před spuštěním zapisovatele ptá, se ptá i před ním.
Důsledky
- Mesh dál funguje: ostatní runtime se k tomuto uzlu dostanou jen třemi podepsanými trasami, které se nemění.
- Vzdálený klient MCP (telefon, jiný stroj) stále čte každý nástroj a zdroj, ale už se nemůže přihlásit na nástěnce peerů ani otevřít relaci meshe přes tento uzel. Zápis z jiného stroje jde přes SSH tunel, který končí na loopbacku, tedy cestou, kterou už doporučoval
docs/MCP.md. - Čtení jsou stále servírována celé síti, nešifrovaně. Toto rozhodnutí neudělá z navázání mimo loopback soukromé; udělá z něj navázání, které nic nemůže změnit.
- Schopnost přidaná později je vzdáleným volajícím ve výchozím stavu odepřena, pokud nečte. Nikdo si nemusí pamatovat, že ji má přidat do seznamu, a
tests/remote_callers.rsprochází registr. - Schéma OpenAPI
ExecutionPolicyzískává polesigned_input; je-li nepravdivé, vynechává se, takže serializace každé další schopnosti je beze změny.
Zde nerozhodnuto a zaznamenáno jako další kroky balíku místo tvrzení: autorizace pomocí tokenu nebo klíče, která by ověřenému vzdálenému klientovi umožnila zapisovat; podepisování autora události meshe odděleně od uzlu, který ji předává; šifrování transportu.
Zamítnuté alternativy
- Odmítnout každý požadavek jiný než GET ze vzdálené adresy. Jednodušší a dvakrát špatně: podepsané routy meshe jsou POST a stejně tak každá zpráva MCP, včetně čtení.
- Navázat huby mesh na loopback. Odstraní spolupráci mezi stroji, což je účel hubů, a každé jiné navázání mimo loopback ponechá odkryté.
- Seznam chráněných tras. Seznam zastará den po napsání; efekt už nese každá schopnost a nový zapisovatel musí být odmítnut, aniž by si to kdo pamatoval.
- Rozhodnout jednou za relaci MCP, při
initialize. Id relace lze uhodnout (nese pid a čítač) a není vázáno na adresu; vzdálený host by mohl převzít relaci otevřenou z loopbacku.