Skip to content

Repository files navigation

Xanh Docker

CI

Multi-tenant orchestration and an OCI session runtime for Xanh remote browsing.

xanh-docker authenticates a tenant, creates an isolated browser session, relays WebRTC signaling, and destroys the session on burn. A session owns one container and may contain one to four tabs; browser state is never shared across tenants.

Important

This repository is an engineering candidate, not a production release. Load, latency, memory-pressure, capacity, public-network, and operator-host soak gates still need current release evidence.

What is here

Area Implementation
Control plane Node.js 24, strict TypeScript, Fastify 5, OIDC, tenant quotas, and lifecycle reconciliation
Signaling Authenticated WebSocket gateway with exact-origin checks and one-use credentials
Session runtime Native C++20/GStreamer runtime using WPE WebKit, H.264/Opus WebRTC, and control DataChannel navigation
Isolation One container, network, tmpfs namespace, quota, and credential set per tenant session
Deployment Hardened Docker profile, Nginx TLS/WebSocket adapter, optional TURN secret mount, and immutable-image promotion tooling
Contracts Versioned Xanh Tab OpenAPI snapshot and manifest

The session image installs Debian's gstreamer1.0-wpe and libwpewebkit-2.0-1 packages. It does not compile or bundle the xanh-webkit desktop application. XANH_WEBKIT.lock records the shared browser-behavior lineage; session/image-manifest.json marks it as reference-only-not-built-into-image.

Architecture

flowchart LR
    C[Controller] -->|HTTPS / WebSocket| N[Nginx]
    N --> O[Fastify orchestrator]
    O -->|OIDC + tenant policy| A[Session service]
    A --> D[Docker Engine]
    D --> S[Isolated session container]
    S --> W[WPE WebKit: 1-4 tabs]
    W --> G[GStreamer H.264 / Opus]
    G -->|WebRTC| C
Loading

Each container exposes one selected audio/video stream. Switching the active tab changes the selected GStreamer inputs without replacing the container or its tenant-scoped WebKit context.

Security model

  • Tenant identity comes only from verified authentication context; request bodies cannot select a tenant.
  • Production JWT verification requires an exact issuer, audience, JWKS URL, asymmetric algorithm allowlist, subject, and validated tenant claim. The current verifier validates time claims when present but does not require an exp claim, so operators must enforce bounded token lifetime at the identity provider until that release gate is added.
  • Development bearer authentication is restricted to loopback and cannot start in production.
  • Pairing tickets and signaling tokens are short-lived, one-use credentials whose stored material is SHA-256 hashed. The separately generated runtime bootstrap secret is scoped to one session container and may be reused for authenticated internal reconnects during that container's lifetime. It becomes unavailable only after successful container destruction; a failed cleanup remains observable and retryable.
  • The public API omits tenant identity, container details, internal endpoints, and bootstrap secrets.
  • The signaling gateway checks an exact allowed Origin, authenticates the first frame, bounds payloads and buffering, and closes credentials during burn.
  • Container creation is transactional. Startup health failure removes both the partial container and its network.
  • Restart reconciliation fails closed: only resources carrying the managed label and exact instance label are reclaimed. Legacy dev.fireball.* labels are recognized only for in-place migration; new resources use dev.xanh.*.
  • Containers run non-root with a read-only root filesystem, dropped capabilities, no-new-privileges, private PID/network state, bounded tmpfs, and reviewed seccomp/AppArmor policy.
  • TURN credentials are supplied through a read-only, root-owned host file. They are not copied into the image or Docker environment.
  • Burn revokes pending and active credentials before cleanup. A cleanup failure remains visible as failed and can be retried.

Container isolation is defense in depth, not a guarantee against every browser zero-day, kernel flaw, or container escape. Docker socket access is effectively host-control authority; use a dedicated host and restrict the socket to the orchestrator service identity.

API

Method Route Purpose
GET /healthz Process health; no authentication
POST /orchestrator/v1/sessions Create a tenant-bound session and pairing ticket
GET /orchestrator/v1/sessions/:id Read an owned session
DELETE /orchestrator/v1/sessions/:id Revoke credentials and burn a session
POST /orchestrator/v1/sessions/:id/signaling/tickets Rotate pending signaling credentials
POST /orchestrator/v1/signaling/tickets/exchange Exchange a pairing ticket for a 30-second signaling token
GET /orchestrator/v1/signaling Upgrade to the authenticated signaling WebSocket
GET /orchestrator/v1/sessions/:id/tabs List the session's tabs
POST /orchestrator/v1/sessions/:id/tabs Create and activate a tab
PUT /orchestrator/v1/sessions/:id/tabs/:tabId/active Select the streamed tab
PUT /orchestrator/v1/sessions/:id/tabs/:tabId/navigation Navigate to a validated HTTP(S) URL
DELETE /orchestrator/v1/sessions/:id/tabs/:tabId Delete a tab while retaining at least one

Authenticated routes require an OIDC bearer token in production. Development accepts Authorization: Bearer dev:<tenant>:<subject> on loopback only. Tickets and signaling tokens are never accepted in query strings.

After a WebSocket upgrade, the first text frame must be:

{"type":"authenticate","token":"<one-use-token>"}

The gateway sends {"type":"ready","sessionId":"<uuid>"} only after the container runtime accepts its separate bootstrap secret.

Requirements

  • Node.js >=24 <27
  • npm with lockfile support
  • Docker Engine API 1.47 or compatible
  • Linux for the runtime image; Docker Desktop can build the Linux image on macOS

Build and verify

npm ci
npm ci --prefix session --ignore-scripts
npm run check
docker build -f deploy/Dockerfile -t xanh/orchestrator:dev .

npm run check validates the contracts, deployment adapter, candidate evidence, seccomp provenance, session image metadata, TypeScript build, and automated tests.

For an Intel Mac reproducibility smoke:

docker build --platform linux/amd64 \
  -f deploy/Dockerfile \
  -t xanh/orchestrator:macos-smoke .

The production container command fails closed until the required OIDC and immutable-image settings are present. For an internal health smoke, explicitly use development mode, retain the loopback listener, and query /healthz from inside the container.

There is no user-facing dashboard in this repository, so Docker Desktop or API screenshots are not presented as product UI.

Production configuration

The minimal production shape is:

NODE_ENV=production
XANH_HOST=127.0.0.1
XANH_PORT=8787
XANH_INSTANCE_ID=primary
XANH_OIDC_ISSUER=https://identity.example.com/
XANH_OIDC_AUDIENCE=xanh-docker
XANH_OIDC_JWKS_URL=https://identity.example.com/.well-known/jwks.json
XANH_OIDC_TENANT_CLAIM=tenant_id
XANH_PUBLIC_ORIGINS=https://browser.example.com
XANH_SESSION_IMAGE=ghcr.io/lamppkk/xanh-session@sha256:<64-hex-digest>
XANH_SESSION_SECCOMP_PROFILE=/etc/xanh/xanh-session-seccomp.json
DOCKER_SOCKET=/var/run/docker.sock
DOCKER_API_VERSION=1.47

Production requires an immutable session-image digest and the exact reviewed seccomp profile. On AppArmor hosts, load deploy/apparmor/xanh-session and set XANH_SESSION_APPARMOR_PROFILE=xanh-session. Optional TURN configuration uses XANH_ICE_SERVERS_FILE with an absolute, root-owned, read-only host path.

Do not disable WebKit sandboxing, use seccomp=unconfined, add CAP_SYS_ADMIN, remove Docker system-path masks, or run privileged containers. Two live orchestrators must not share an XANH_INSTANCE_ID.

Read the operational details before deployment:

Session image promotion

session/Dockerfile targets linux/amd64 and linux/arm64 from a digest-pinned base image and exact source revisions. Debian security packages are resolved at build time, so release identity comes from the tested output digest, SBOM, attestations, and signature rather than a claim that every package input is immutable. The runtime is UID/GID 10001, uses a read-only root filesystem plus /run/xanh-session tmpfs, and records component versions in the artifact.

The manual session-candidate workflow is the release gate. It must rebuild and test each native platform, publish a two-platform OCI index by exact digest, emit an SPDX SBOM and attestations, and sign the index with GitHub OIDC/Cosign. Historical binaries or a green source-only CI run are not promotion evidence.

Inspect bounded runtime metadata with:

docker run --rm --entrypoint /usr/bin/node <xanh-session-image> \
  /opt/xanh-session/healthcheck.mjs --metadata

Repository map

Path Purpose
src/ Fastify API, authentication, lifecycle, Docker adapter, and signaling gateway
session/ WPE/GStreamer runtime, supervisor, tab control, healthcheck, and image definition
deploy/ Orchestrator image, Nginx, TURN example, seccomp, and AppArmor assets
contracts/xanhtab/ Frozen Xanh Tab API snapshot consumed by this repository
docs/session-image.md Image architecture, evidence, and promotion runbook
docs/deployment-adapters.md TLS, WebSocket, and TURN deployment guidance
test/ Unit, contract, authentication, isolation, and lifecycle coverage

Current release gaps

  • Re-run the manual session candidate lane after material image changes and promote only its exact signed digest.
  • Complete load, memory-pressure, bitrate, latency, and capacity benchmarks before publishing sizing claims.
  • Treat public DNS, certificate issuance, firewall policy, and operator-host soak testing as deployment gates.
  • Regenerate the frozen Xanh Tab contract only from an explicitly promoted upstream artifact.

Xanh suite

Repository Role
xanh-webkit Multi-platform reference hosts, shared engine policies, and release gates
xanh-android Android browser application
xanh-ios Native iPhone and iPad browser
xanh-webview Cross-platform embedding contract
xanh-tab Raspberry Pi remote-browser appliance and controller contract
xanh-docker This orchestrator and OCI session runtime

License

This repository does not currently include a project-wide license file. Until one is added, no general permission to copy, modify, or redistribute the Xanh Docker source is granted. Third-party inputs retain their own terms; the Moby-derived seccomp material includes its Apache-2.0 notice under deploy/seccomp/.

About

Isolated multi-tenant WPE WebKit remote-browser runtime for the Xanh suite

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages