Remote Web Access / Owner only / Self-hosted

Four access methods, one owner-authentication scheme

Supports same-machine access, LAN or VPN, SSH tunnels, and owner domains behind HTTPS reverse proxies. Every Web startup generates an instance token; HTTP, SSE and WebSocket share authentication rules. OpenProgram operates no public relay and adds no accounts, RBAC or project permissions.

HOSTResolve authority
CLASSIFYroute / method / source
ORIGINBrowser and CSRF
TOKENCookie or Bearer
AUTHORITYMap owner tier
ACTIONHTTP / SSE / WS
01

Methods and product boundaries

Web UI is a complete owner-administration authority boundary. Multi-person collaboration continues through channel-speaker attribution and restricted capabilities, not Web accounts.

Same machine

Same-machine access

127.0.0.1

CLI automatically opens a loopback URL with a fragment token.

Token authentication is always enabled; Host and Origin are still validated.
LAN / VPN

Direct network access

web.host = 0.0.0.0

Requires explicit configuration of complete exact allowed_origins.

HTTP is allowed only for explicit local/overlay address ranges with a warning; otherwise use HTTPS.
SSH tunnel

SSH port forwarding

127.0.0.1

The browser connects to a local forwarded port; SSH encrypts transport.

Do not change web.host; the instance token still authenticates.
Public domain

HTTPS reverse proxy

proxy → 127.0.0.1

nginx or Caddy terminates HTTPS on the same machine, preserving public Host.

Trust only loopback proxies; OpenProgram does not manage certificates.
FOUR SUPPORTED DEPLOYMENTS · ONE APPLICATION CREDENTIAL LOCAL Browser Open- Program 127.0.0.1 TOKEN + HOST + ORIGIN Local access is not exempt from authentication LAN / VPN Device Open- Program EXPLICIT web.host NONEMPTY EXACT ORIGINS Reject startup without required configuration SSH TUNNEL Browser SSH Open- Program BACKEND LOOPBACK SSH ENCRYPTION + TOKEN Do not change web.host PUBLIC HTTPS Browser nginx / Caddy Open- Program HTTPS → LOOPBACK OWNER DOMAIN + TOKEN No OpenProgram relay
Local machineDirect network SSHPublic HTTPS
Boundary:No Web accounts, RBAC, project permissions, OAuth/SSO, built-in TLS, certificate lifecycle, public relays, hosted tunnels, public sharing URLs or authentication-off mode.
02

Current implementation and remaining controls

Owner process tokens, canonical Origin, fragment bootstrap, shared HTTP/WS authentication, auth-url, minimal health and non-retrievable secrets are in production paths; complete deployment acceptance remains unfinished.

Current behavior

OwnerAuthMiddleware authenticates first

OwnerAuthState Generate a 32-byte token, hold a per-state lock, write an owner-only token file and token-free access.json, and derive a profile-specific cookie.OwnerAuthMiddleware Validate Host/request origin and cookie or Bearer before route dispatch.

Attach current-profile owner authority only after authentication; validate WebSocket before accept() . Uvicorn uses proxy_headers=False.

Remaining work

Constrain secret handling and acceptance scope

Both plaintext-reveal paths are removed: legacy account routes return 410, config-key reveal queries return 404, and credential responses retain masks only; replace/preserve/delete schemas are implemented.

Still missing direct-HTTP startup warnings and independent acceptance for SSE, real-browser bootstrap/WS, restart, bind failure, multiple profiles and nginx/Caddy smoke deployments.

Current implementation and missing controls
Current objectImplementedMissing or risky
OwnerAuthStateToken, lock, owner-only files, access snapshot, cookie and constant-time comparisonReal bind-failure, restart and multi-profile acceptance missing
Canonical OriginEffective Origin, non-loopback fail-closed behavior and local HTTP networksStartup output still lacks complete fields and direct-HTTP warnings
OwnerAuthMiddlewareHTTP/WS, cookie/Bearer, Host/Origin/CSRF, owner authorityIndependent SSE and real-browser WS acceptance missing
Fragment bootstrapBackend, frontend gate, synchronous clearing, no Web Storage and auth-urlBrowser-level end-to-end acceptance missing
Reverse proxyproxy_headers=False and loopback-only forwarded schemenginx/Caddy HTTPS, WS and SSE smoke deployments untested
Provider secretSome responses masked by defaultTwo reveal paths return plaintext
/healthzReturns only status=ok; details are under protected /api/diagnosticsComplete response-class cache audit remains unfinished
Loopback is not authentication.Any website can send requests to localhost; WebSocket is not protected by HTTP same-origin read rules; local processes can omit Origin; DNS rebinding can supply an external Host. Tokens are therefore mandatory locally too.
03

Open-source implementation survey

Covers existing reference material and additional systems with remote Web, authentication, reverse-proxy or secret-handling capabilities; explicitly marks absent target capabilities as not applicable.

Open-source remote-access comparison
SystemDefaults and authenticationRemote methodAdoptRejected or not applicable
OpenClawLoopback; external access requires authentication and explicit OriginSSH, reverse proxies and other external-network methodsfragment, Host/Origin, proxy trustTokenless identity replacement
Jupyter ServerNotebook 4.3 defaults to tokens, exchanged for cookies on first accessSSH, HTTPS public serverAutomatic token → cookie experienceQuery tokens and password mode
Hermes AgentLoopback; external bind requires an auth provider and fails closedSession cookie; one-time WS ticketExternal fail-closed and secret redactionAuth-provider accounts, a second ticket system and reveal
Agent ZeroLocal; optional single login; cookie + CSRFReverse proxy, VPS and built-in tunnelcookie/CSRF/Origin/WS, secret maskOptional authentication and built-in public tunnel
OpenHandssession API key; HTTP/WSSSH, nginx/HTTPSInstance key and loopback backendTokens exposed through public HTML/browser storage and plaintext secret retrieval
opencodeLoopback; no authentication without a passwordExternal host and CORS origins; separate WS ticketloopback defaultOptional authentication and second WS ticket
Open WebUIAccounts, roles and JWT/cookienginx/Caddy, WS, SSEProxy, Upgrade and SSE-buffering configurationAccounts, groups and RBAC
Difyaccount/workspace/roleReverse proxyCredential obfuscation and replacement semanticsTenant and role layers
LibreChatRegistration, JWT, roles and ACLnginx HTTPS/WSProxy detailsUser-identity data model
AnythingLLMSingle or multiple users; optional single-user passwordDocker portSingle owner-credential conceptOptional authentication and role system
AutoGen StudioLoopback; no authentication by default; optional OAuth/JWTresearch prototypeloopback defaultquery/localStorage token, post-accept auth
SWE-agentCurrent repository provides a command-line agentNo owner Web-control UIRecord capability absenceRemote-security design not applicable
pi-mono / pi-aiTUI, SDK, RPC/provider transportNo packaged owner Web serverRecord capability absenceRemote Web ownership not applicable
WeClawWeChat gateway + HTTP APIConfigurable listen addressDistinguish external HTTP from Web UINo browser owner UI
Codex CLIApp-server and experimental WSservice-mediated pairing/remote controlRecord protocol differencesDo not adopt relay-based remote control
Three remote-identity design classes
TypeFramework or capabilityOpenProgram treatment
Instance credentialJupyter, OpenClaw, OpenHandsAdopt: one owner, one root key, two transport credential forms
Application accountOpen WebUI, Dify, LibreChat, AnythingLLMReject: no registration, roles or resource ACL needed
Hosted remote controlpairing, hosted tunnel, relay serviceReject: OpenProgram operates no external service
04

Adopt, adapt and reject

The final design keeps only one root key (instance token), two transport credential forms (Bearer header and profile-scoped HttpOnly cookie), one browser bootstrap and one HTTP/WS/SSE authentication rule set.

Adopt
OpenClaw 's exact Origin, Host/DNS-rebinding controls, SSH and proxy-trust restrictions.
Adapt
Jupyter 's automatic launch-token experience: replace query with fragment, then exchange for an HttpOnly cookie.
Adopt
Generate 32 random bytes with the standard library and compare decoded credentials in constant time; this is OpenProgram's own instance-token construction.
Adopt
Agent Zero 's combined cookie, CSRF, Origin and WebSocket validation, and masked-only secret updates.
Adapt
External proxies or VPNs may add transport and access controls but cannot replace the OpenProgram token.
Reject
Disabling authentication, query/localStorage tokens, plaintext secret reveal, multi-user accounts, RBAC, built-in tunnels and public relays.
05

Final authentication and deployment design

Run one Web process per profile/state instance. Rotate tokens on every startup. Browsers bootstrap once with fragments, then use derived HttpOnly cookies; native clients use Bearer headers.

Generate32 random bytes encoded as 43-character unpadded base64url.
Lock and saveFirst hold <state>/web.lock, then atomically write the owner-only token and token-free access.json.
Derive and compareopenprogram_owner_<owner16> Use a fixed HMAC domain; after decoding, compare only with compare_digest.
InvalidateProcess restart rotates the token, invalidating old Bearer and cookie credentials together.

Unified request-processing order

COMMON ASGI ORDER · NO ACTION BEFORE AUTHENTICATION Request HTTP · SSE · WS Host + scheme canonical parse request_origin Classify route · method credential source Origin / CSRF Sec-Fetch-Site safe / unsafe rules Authentication cookie · Bearer bootstrap body Owner scope owner_authority( principal_id) Route / stream WS accept occurs only here 403Host / scheme 403Origin / CSRF 401authentication Public static shells, ownership challenges, bootstrap and minimal health still validate Host; only credential checks are omitted or built in according to route policy.

Fragment to HttpOnly cookie

FRAGMENT BOOTSTRAP · TOKEN DOES NOT ENTER THE INITIAL HTTP REQUEST CLI Browser Web server 01 open /#token=<token> 02 GET / · fragment absent public static shell 03 read token into memory history.replaceState() 04 POST /api/auth/bootstrap · exact JSON ≤ 256 B 204 · Set-Cookie derived HMAC; HttpOnly; SameSite=Strict Subsequent HTTP / SSE / WebSocket uses the cookie; server restart invalidates it
Public allowlist

Only four public categories

Static shells/assets (including the prebuilt-file-only GET/HEAD /docs documentation site), ownership challenge, bootstrap and minimal health. Source checkouts read docs/_site; installed packages read the same prebuilt site placed under openprogram_server/_webui/_frontend/docs during release. Staleness checks run only when source workers register routes, not on HTTP requests. The shell uses CSP frame-ancestors 'none' and X-Frame-Options: DENY.

Browser cookie

CSRF validation remains

GET/HEAD/OPTIONS must be side-effect-free. Unsafe methods and WS require Origin equal to request_origin; only safe requests may omit Origin.

Native client

Header-only Bearer

HTTP, SSE and native WS use Bearer. Reject query tokens. Except for bootstrap, any Authorization header prevents invalid Bearer credentials from falling back to cookies. Bootstrap with Authorization returns the same 401 directly.

Listener ownership: <state>/web/access.json Freeze bind, port, effective Origin and token fingerprint, but not the token. After verifying worker PID/port, CLI sends a fresh 32-byte nonce only to GET /api/auth/challenge and locally verifies HMAC-SHA256(raw_token, "openprogram-web-challenge-v1\0" || nonce || "\0" || revision); the token is never sent to the probed port.

Authentication matrix

HTTP, SSE and WebSocket authentication matrix
Request formCredentialOriginFailure result
Cookie + unsafe HTTPRequiredEqual to request_origin; reject missing401 or 403
Cookie + WebSocketRequired before acceptEqual to request_origin; reject missingUpgrade HTTP 401/403
Cookie + safe HTTP/SSERequiredMust match when present; same-origin navigation may omit401 or 403
Bearer HTTP/SSERequiredMay omit; must match when present401 or 403
Bearer WebSocketRequired before acceptNative clients may omit; must match when presentUpgrade HTTP 401/403
Listener ownership challengeNo credential; nonce + optional revisionMay omit; must match when present400 or 403
Fragment bootstrapBody token only; Authorization rejectedEqual to request_origin401 or 403
SSH

Keep backend on loopback

ssh -N -L 18100:127.0.0.1:18100 owner@remote-host

openprogram web auth-url \
  --base-url http://127.0.0.1:18100
Reverse proxy

Public domains use HTTPS

{
  "web": {
    "host": "127.0.0.1",
    "allowed_origins": ["https://agent.example.com"]
  }
}

# proxy preserves Host and Upgrade,
# overwrites X-Forwarded-Proto, clears unused forwarded headers,
# and disables SSE buffering
Origin and proxy trust:Default loopback automatically includes the exact current-port localhost and actual listen literal; other Origins require explicit config.json configuration. HTTP permits only that localhost, loopback, RFC 1918, IPv4/IPv6 link-local, IPv6 ULA and RFC 6598 range; other addresses and DNS names require HTTPS. Each request accepts one valid Host;http/ws and https/wss map to their corresponding browser Origins. Uvicorn uses proxy_headers=False; OpenProgram does not trust X-Forwarded-For and accepts only one X-Forwarded-Proto from the original loopback peer. The request_origin constructed from scheme + Host must be valid.

Non-retrievable secrets

Remove

Remove both reveal paths

Account reveal routes return 410; config-key status retains masks but returns 404 on a reveal query.

Return

Masked responses only

Return has_value and masked/masked_key. Values at least 12 characters use sk-…abc4, hiding at least five characters; shorter values use eight fixed dots.

Replace / delete

Define the two action classes separately

Config keys use POST /api/config for replacement andDELETE /api/config/key/{env} for deletion; API-key accounts use dedicated update for replacement andaccounts/remove to delete the entire account. Empty values and masks are invalid.

06

Implementation contract and acceptance

Accept connections only after configuration validation, secure token persistence and fingerprint calculation. Executable behavior tests establish completion, not documentation.

  1. Loopback HTTP, SSE and WS fail without credentials.
  2. Correct Bearer succeeds without Origin; wrong tokens perform no action and return the same 401.
  3. Clear fragments before other fetches; bootstrap accepts only exact bounded JSON, rejects mixed Authorization and derives the specified HMAC cookie.
  4. Unauthenticated GET/HEAD /docs and /docs/** can read prebuilt static docs in both source checkouts and installed packages without scanning source or triggering builds; the wheel must include openprogram_server/_webui/_frontend/docs/index.html. Host validation still applies; unsafe methods and private APIs are excluded.
  5. Cookie-authenticated unsafe HTTP and WS reject missing, opaque and cross-site Origins.
  6. Loopback, direct bind and proxies reject external, duplicate, unspecified and multicast Hosts and validate HTTP/WS scheme mapping.
  7. Default loopback accepts only implicit Origins; other SSH local ports require explicit configuration.
  8. Non-loopback bind without a valid Origin list rejects startup; explicit local/overlay HTTP ranges are accepted with warnings, other addresses rejected.
  9. Disable Uvicorn proxy-header rewriting; scheme trusts only the original loopback peer, X-Forwarded-For never changes trust, and direct WS/Caddy WSS validate scheme mapping.
  10. A second process using the same state directory cannot alter the live token; failure cleanup verifies ownership.
  11. Restart rotates tokens and invalidates old Bearer and cookies.
  12. Malformed Authorization cannot fall back to a valid cookie.
  13. Static assets and minimal health contain no session, filesystem or credential metadata.
  14. CSP/X-Frame-Options prevent framing; authentication and sensitive responses use no-store.
  15. Legacy account reveal returns 410 and config-key reveal query returns 404. Config keys and accounts enforce exact replace/preserve/delete schemas; 8–11-character values use fixed masks; frontend types contain no plaintext field.
  16. HTTP, SSE and WS authenticate behind nginx/Caddy HTTPS while backend stays on loopback.
  17. Channel turns retain paired authority tiers rather than gaining Web-owner authority.
  18. Profile servers sharing a hostname but using different ports have distinct cookie names and independent authentication.
  19. Listener ownership probes verify worker PID/port and access snapshot, send only random nonces, validate versioned token-HMAC proofs and reject foreign listeners, stale snapshots and port/fingerprint/proof/revision mismatches.
07

Implementation progress

Keep the three levels strictly separate. Design prose is not implementation evidence; classify using current production code and tests only.

IMPLEMENTED process token / cookie / middleware fragment bootstrap / auth-url / health Non-retrievable secrets / BackendEndpoint Real-listener HTTP / SSE / WS acceptance Evidence in current code and tests PARTIAL Browser-driven asset / navigation audit nginx and Caddy smoke deployments Production paths exist; acceptance scope incomplete EXPLICITLY OUT OF SCOPE Accounts / RBAC / project ACL OAuth replacing tokens Built-in TLS / certificates relay / tunnel / insecure Explicitly excluded from the product
Implemented
  • Default loopback, lifespan and per-profile owner
  • OwnerAuthState token, lock, cookie
  • Canonical Origin and shared middleware
  • Fragment bootstrap, auth-url and minimal health
  • proxy_headers=False and loopback-only forwarded scheme
  • Both plaintext-reveal paths removed, strict secret schemas and unified masks
  • MCP server credentials: separate disk persistence from response serialization;env/headers/bearer/client secrets all masked, preserve/replace/delete editing, configuration file 0600 atomic writing
  • BackendEndpoint drives Bearer authentication for TUI and MCP CLI
  • Startup reports bind, Origin, fingerprint and plaintext-HTTP warnings
  • Real Uvicorn listener acceptance for HTTP/SSE/WS, bind failure, rotation, dual profiles and leakage scans
Partially implemented
  • Browser-driven asset/navigation audit
  • nginx and Caddy smoke deployments
Explicitly excluded
  • Accounts, RBAC and project permissions
  • OAuth/SSO replacing tokens
  • Built-in TLS and certificate management
  • Public relays, hosted tunnels and pairing
  • Authentication-off and plaintext reveal