Share public work and securely access local developer services from one
personal page, without deploying those services or opening ports.
Live at https://dev.site, verified with two real Google accounts: @frank
opens @dami's Agent and gets a page served from 127.0.0.1:4102 over an
Iroh relay, while Hermes stays absent from every listing and returns 404
on a direct capability request by id.
Architecture:
browser (ephemeral Iroh key)
-> POST /api/capability
control plane (axum + SQLite) authorizes, signs an Ed25519 capability
-> {capability, daemon endpoint, relay url}
browser -- Iroh, E2EE via relay --> daemon
verifies signature, audience, expiry, browser key vs the authenticated
peer, and that the resource is in local config
-> 127.0.0.1:4101
The browser never names an upstream URL; the daemon resolves the origin
from its own config, keyed by resource id, which is what keeps it from
becoming an open proxy. The control plane holds permissions but never
carries service traffic.
Authentication is Shoo (shoo.dev), an OIDC broker for Google sign-in —
not GitHub, despite the name suggesting otherwise. Browser-side PKCE, an
ES256 id_token verified server-side against JWKS with the algorithm
pinned and the audience checked exactly. Account identity derives from
the public origin, so DEVSITE_PUBLIC_ORIGIN cannot change once anyone has
signed in.
Hosted behind Cloudflare Tunnel, so the control plane has no inbound port
either. The wasm bundle is published under a content hash and cached
immutably; the manifest, HTML and every API response are not.
51 tests. crates/devsite-daemon/tests/authz.rs drives real Iroh against a
real local service and asserts that forged, expired, misaddressed,
rebound, replayed and unknown-resource capabilities are each refused, and
that a denial reveals nothing about which.- commit
- 4801ca95df7fca90181cca1b120b3b7c1f6c1fc7
- author
- Dami <dami.etoile@gmail.com>
Not shown: this page already shows 6000 lines of diff.
Not shown: this page already shows 6000 lines of diff.
Not shown: this page already shows 6000 lines of diff.
Not shown: this page already shows 6000 lines of diff.
Not shown: this page already shows 6000 lines of diff.
Not shown: this page already shows 6000 lines of diff.
Not shown: this page already shows 6000 lines of diff.
Not shown: this page already shows 6000 lines of diff.
Not shown: this page already shows 6000 lines of diff.
Not shown: this page already shows 6000 lines of diff.
Not shown: this page already shows 6000 lines of diff.
Not shown: this page already shows 6000 lines of diff.
Not shown: this page already shows 6000 lines of diff.
Not shown: this page already shows 6000 lines of diff.
Not shown: this page already shows 6000 lines of diff.
Not shown: this page already shows 6000 lines of diff.
Not shown: this page already shows 6000 lines of diff.
Not shown: this page already shows 6000 lines of diff.