Skip to content

Sharing redesign: share_gateway service + per-origin cutover, remove cloudflared - #358

Draft
joshalbrecht wants to merge 5 commits into
mainfrom
mngr/finish-forwarding
Draft

Sharing redesign: share_gateway service + per-origin cutover, remove cloudflared#358
joshalbrecht wants to merge 5 commits into
mainfrom
mngr/finish-forwarding

Conversation

@joshalbrecht

Copy link
Copy Markdown
Contributor

Companion PR to imbue-ai/mngr-internal#207 (self-hosted, end-to-end-encrypted workspace sharing; spec in the mngr repo's blueprint/sharing-redesign/).

  • Phase 1c (already on the base branch): /service/ proxy stack deleted, one-rule origin derivation, /api/browsers passthrough, DNS-safe service-name validation.
  • New system/services/share_gateway/: in-workspace TLS termination (Caddy), grants-enforcing forward_auth gateway, and the frpc relay tunnel, all gated on injected share materials. Pinned caddy + frpc binaries; supervisord program; OOM band.
  • Removes the cloudflared tunnel stack (replaced by share_gateway).

🤖 Generated with Claude Code

joshalbrecht and others added 5 commits July 29, 2026 14:51
…end and scripts

Every registered service now owns a real browser origin (locally
http://<name>.host-<32hex>.localhost:8421/; future share hostnames follow
the same prefix rule on a longer base), routed by the external forwarder.

- Remove proxy.py / service_dispatcher.py (SW bootstrap, fetch-prefixing
  service worker, HTML rewriting, WS shim, cookie-Path scoping) and their
  tests; drop the register_service_routes wiring and the ServiceName
  primitive.
- Add a same-origin GET/POST /api/browsers passthrough that forwards the
  browser fleet API to the registered browser service's local backend
  (503 JSON error when unregistered or unreachable).
- layout_ops.py / layout.py now parse service names from service-origin
  hostnames (first label when a host-<32hex> label sits deeper in), with a
  drift test pinning the two parsers together; replace-url expectations
  stay service-relative and are compared by service coordinates.
- forward_port.py validates names as origin hostname labels (lowercase
  alnum/underscore runs joined by single hyphens; host-/agent- prefixes
  and localhost reserved) on both upsert and --remove, with a new
  subprocess-driven forward_port_test.py including a scaffold drift guard.
- supervisord.conf drops the dead ROOT_PATH=/service/browser env; the
  browser runner drops its informational ROOT_PATH read.
Add src/origin.ts with deriveServiceOrigin(name) = protocol//name.host/ --
one prefix rule that works locally and on future share hostnames because
workspace hostnames nest. All panel-URL builders (serviceRefUrl,
getTerminalUrl, buildAgentTerminalUrl, buildSessionTerminalUrl,
resolveReplaceUrl, the + menu) now emit absolute service origins.

On layout restore, persisted URLs are stale absolute origins from whichever
host saved them, so service panel URLs are re-derived from serviceName
(preserving any ?query) and agent-terminal URLs are rebuilt from their ttyd
dispatch args, keeping saved layouts portable across hosts.

The browser fleet listing and the New-browser create POST go through the
shell backend's same-origin /api/browsers passthrough (sibling service
origins are same-site but not same-origin, and the daemon sends no CORS
headers); CreateBrowserModal drops its browserServiceUrl attr. Service
panels are cross-origin iframes now, so IframePanel's src-reassignment
refresh is the normal path (comments updated).
…ng origins

Rewrite build-app (SKILL.md, cross-flow-gotchas, public-url, verify),
update-app, service-processes, type-app, web-frontend-testing, and
docs/system/workspace-internals.md for the service-per-origin scheme: an
app's local origin is http://<name>.<host-id>.localhost:8421/ (share
hostnames follow the same prefix rule), nothing rewrites anything,
verification curls the registered backend port directly, and the old
proxy-era symptoms/sections (duplicated dockview tab bar, Location-header
rewriting -- which never actually existed in code -- trailing-slash
redirects) are replaced or deleted. Service names are documented as
DNS-safe hostname labels with host-/agent- prefixes reserved.

preview_wrapper_server.py derives the inner service's sibling origin from
location.host by swapping its own leading label (no static src, no
path-prefix); serve_isolated_instance.py prints the preview's service
name on stdout instead of a /service/... path. The scaffolder reserves
localhost and the host-/agent- prefixes so every name it mints registers
cleanly.

Changelog entries for the agents, dev, system_interface, and browser
projects.
New system/services/share_gateway service. While share materials
(data/.secrets/share.env, written by the minds app at share-enable) are
present, the runner keeps the share stack up:

- caddy terminates the workspace's real TLS locally (cert/key under
  data/.secrets/share_tls/; the relay only ever splices ciphertext) and
  routes by Host from data/.state/apps.toml -- bare domain to the shell,
  <service>.<domain> to that service's local backend, unknown-but-plausible
  service origins to an auto-retrying loading page. The Caddyfile re-renders
  and hot-reloads (admin API) when the service registry changes.
- frpc claims exactly this workspace's domain + wildcard on the region's
  relay, authenticated by the per-share relay token in its client metadata.
- a forward_auth gateway enforces the owner's grants on every request:
  imbue_machine_session cookie (24h, HS256, workspace-generated secret),
  grants re-read per request (instant revocation; malformed grants fail
  closed), per-service grants scoped to their origin, WebSocket/non-GET
  Origin policy, session cookie stripped before services see the request.
  Visitors without a session round-trip through the accounts broker; the
  callback verifies the 60-second audience-bound handoff token (JWKS cache,
  state nonce, single-use jti) before setting the cookie.

The TLS key is generated in-container and never leaves it (CSR-only
issuance via the connector); a daily check renews certs within 30 days of
expiry. Key, cert, and signing secret persist across unshare for fast
re-share.

Wiring: supervisord program (share-gateway), pinned sha256-verified caddy
2.11.4 + frpc 0.70.1 in setup_system.sh, share-gateway OOM band, Dockerfile
manifest layer, workspace-member registration, docs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The share-gateway service (workspace-terminated TLS + grants enforcement +
frp relay tunnel) replaces Cloudflare tunnels for sharing. Deletes
system/services/cloudflare_tunnel, its supervisord program, the pinned
cloudflared binary install, the Dockerfile manifest layer, its OOM band, and
every doc/skill mention; ShareModal now describes machine sharing. Cloudflare
references that are unrelated to tunnels (R2 backup storage, the apt-mirror
Worker, CAPTCHA notes) stay.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant