npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@claxedo/workspace-relay

v0.10.2

Published

Relay/tunnel that routes authenticated traffic to workspace runtimes on Cloudflare: JWT workspace tokens and a header-stripping trust boundary

Readme

@claxedo/workspace-relay

The Claxedo workspace relay: the tunnel between browsers and workspace-runtime hosts, whether the host is a cloud VM or a user laptop.

It deploys as a Cloudflare Worker. src/worker.ts is a stateless gateway that routes each workspace to its own Durable Object room (src/cloudflare.ts), so the room that owns a workspace's host tunnel also serves every request for that workspace. scripts/deploy-cloudflare.ts deploys it.

Deployment Security

The relay is a public edge service for workspace traffic, but it is not the identity provider or policy database. It verifies short-lived runtime access tokens, calls the configured target resolver/revocation callback, and forwards accepted traffic to the selected host with a freshly minted relay-host token.

| Token | Default TTL | Issuer | Audience | Purpose | | --- | ---: | --- | --- | --- | | Runtime Access Token (RAT) | 30 minutes | claxedo-control-plane | workspace-relay | User/browser authorization to reach one workspace and host through the relay. | | Host Tunnel Token (HTT) | 5 minutes | claxedo-control-plane | workspace-relay-host-tunnel | Workspace runtime authorization to register a host tunnel. | | Relay Host Token (RHT) | 60 seconds | workspace-relay | workspace-host-service | Per-request relay-to-host authorization minted after RAT validation. |

Runtime access tokens and host tunnel tokens bind issuer, audience, subject, workspace id, host id, expiry, issue time, and JTI. Runtime access tokens also bind role. Relay-host tokens additionally bind the placement: cloud-vm, local-worktree or durable-object.

A cloud runtime access token carries routing_id, a UUID owned by the lease store's address transition. It changes when the address or resource changes and on a new acquisition, including after lease deletion. The resolver requires the signed identity to match the current ready lease; otherwise it returns 401 runtime_access_token_invalid. The app renews through connection POST once.

Target lookups are keyed by workspace, host and routing identity. A resolved cloud or host-tunnel target is kept for CLAXEDO_RELAY_TARGET_CACHE_TTL_MS (10 s), the same lag the revocation cache allows; a workspace still starting is asked about on every request. A durable-object target is kept for 30 minutes, the longest life of a Runtime Access Token, because the host id names the session's object and the object never moves; revocation is still asked about on its own cadence. The revocation check and the target lookup go out together, since each needs only the token's claims. A request already resolving or forwarding when the lease changes can finish with its previous snapshot; established streams are not disconnected by this fence. The runtime receives a relay-host token, so it does not independently fence the original routing identity. The relay does not retry upstream failures.

Revocation And Active Checks

isRuntimeAccessTokenActive is the revocation/target freshness hook. A deployed relay implements it by calling the control plane or an equivalent authority on every new HTTP request and WebSocket upgrade. A false result rejects before forwarding to the host.

Long-lived relayed sockets are authorized at establishment time. Already-open SSE, PTY, and WebSocket streams may live until normal close, reconnect, relay drain, host disconnect, or process restart. If immediate stream revocation is a requirement, the control plane must also close the session/runtime channel.

Host Tunnel Serving-Generation Fence

Every Host Tunnel Token carries enrollment_id and generation. A relay requires a host-generation resolver and asks GET <CLAXEDO_RELAY_RESOLVER_URL>/host-generation?enrollmentId= on every admission and re-checks established tunnels every 30 s. The verdicts, in order:

| Control plane answer | Admission | Established tunnel | | --- | --- | --- | | Same generation, not revoked | admitted | kept | | Higher generation | 403 host_generation_superseded | closed 1008 | | Lower generation | 403 host_generation_unknown | closed 1008 | | Enrollment revoked or paused | 403 host_enrollment_revoked | closed 1008 | | 404 relay_resolver_enrollment_not_found | 403 host_enrollment_unknown | closed 1008 | | Any other status, a bare 404 (route missing), malformed body, or the 5 s deadline | 503 host_generation_lookup_unavailable (retry) | survives two consecutive failures, closed 1012 on the third |

A token missing either fence claim is refused. An incumbent in a workspace room yields only to an equal or higher generation.

A host.registration.update frame must carry a complete, verified token. A missing fence closes the socket with 1008 Host tunnel registration update denied; a lower generation closes it with 1008 Host tunnel registration update superseded. The relay then checks the control plane and closes with the table's established-tunnel code on refusal, without outage grace for an update. An accepted update replaces the socket's claims and identities and keeps the periodic check or hibernation alarm active.

A relay composed without resolveHostGeneration refuses host tunnels with 403 host_generation_unverifiable. This refusal is not retryable because the resolver belongs to the composition.

Forwarding Boundary

The relay strips client-supplied x-forwarded-for, x-forwarded-host, x-forwarded-proto, x-real-ip, x-claxedo-internal-*, and x-supervisor-* headers. It replaces Authorization with an RHT, sets x-workspace-id, and adds x-forwarded-by: workspace-relay.

For local-worktree targets, Cookie is stripped before forwarding. A host tunnel ends on a machine someone uses, whose browser cookie jar the host service may share, so browser cookies must not be passed through to it. Cloud VM targets may receive cookies when the caller intentionally sends them.

A durable-object target is a session served by its own Durable Object, host session-do:<root>. The control plane resolves it with an empty baseUrl, and the room forwards HTTP to the SESSION_HOST binding's object named by the root, cookies stripped as for a host tunnel and response streams passed through. A WebSocket upgrade to it is refused with 400 durable_object_websocket_unsupported: a session host serves its events over HTTP streams.

CORS

The relay owns CORS responses for browser-facing workspace requests. Do not forward upstream CORS headers as the source of truth. Configure browser origins with CLAXEDO_RELAY_ALLOWED_ORIGINS or CLAXEDO_APP_ORIGINS (see Configuration) and keep wildcard origins out of credentialed deployments. A development relay that serves a local app names its loopback origins itself, for example CLAXEDO_APP_ORIGINS=http://localhost:*,http://127.0.0.1:*; a deployed relay admits none by default.

Host-Tunnel Topology

A Durable Object room is scoped to one workspace, so a host tunnel registering through the Worker presents exactly one workspaceId per connection.

Public surface

Re-exported from src/index.ts:

| Concern | Module | Notable exports | | --- | --- | --- | | Token issuance / verification | src/auth.ts | mintRuntimeAccessToken, verifyRuntimeAccessToken, mintRelayHostToken, verifyRelayHostToken, mintHostTunnelToken, verifyHostTunnelToken, types RelayRole, RelayBacking, RelayJwtAlgorithm, RuntimeAccessTokenClaims, RelayKey, RelayKeyResolver, error class WorkspaceRelayAuthError | | Request authorization and forwarding | src/server.ts | authorizeWorkspaceRelayRequest, workspaceRelayForwardRequestInit, workspaceRelayTargetUrl, the cached resolver clients, types WorkspaceRelayOptions, WorkspaceRelayTarget, RuntimeAccessTokenActiveResult, WorkspaceRelayAuditEvent | | CORS policy | src/cors-origins.ts | DEFAULT_RELAY_APP_ORIGINS, RELAY_ALLOWED_REQUEST_HEADERS, createOriginMatcher, parseAllowedOrigins | | Active-host directory | src/directory.ts | createWorkspaceRelayDirectory, types WorkspaceRelayDirectory, HostTunnelPresence | | Cloudflare Worker gateway and room | src/cloudflare.ts | createWorkspaceRelayDurableObjectGateway, createWorkspaceRelayDurableObjectRoom |

Wire types live in the sibling package @claxedo/workspace-relay-protocol (TUNNEL_PROTOCOL_VERSION, TunnelMessage, isTunnelMessage, makeTunnelPong). Keep that split — it lets non-Node consumers implement the tunnel protocol without pulling Jose.

Configuration

All knobs are environment variables: Worker vars and secrets in wrangler.toml and scripts/deploy-cloudflare.ts.

| Env var | Purpose | | --- | --- | | CLAXEDO_RELAY_RESOLVER_URL | Control-plane resolver base (https://<control-plane>/internal/relay). The relay derives /target, /revocation, and /host-generation from it. The Worker also accepts CLAXEDO_CENTRAL_URL and appends /internal/relay. | | CLAXEDO_RELAY_RESOLVER_TOKEN | Bearer token the relay sends to the resolver. The Worker refuses to start without it. | | CLAXEDO_RELAY_HOST_GENERATION_URL | Optional absolute URL of the host-generation lookup. Unset (the normal case) derives <CLAXEDO_RELAY_RESOLVER_URL>/host-generation; set it only when the lookup lives at a different origin than the rest of the resolver. There is no way to turn the fence off on a resolver-backed relay. | | CLAXEDO_RELAY_HOST_GENERATION_CACHE_TTL_MS | Cache TTL for host-generation answers. Defaults to 10000. A superseded tunnel closes within the re-check interval (30 s) plus this TTL. | | CLAXEDO_RELAY_REVOCATION_CACHE_TTL_MS | Cache TTL for /revocation answers (default 10000 ms). Target answers are not retained. | | CLAXEDO_CONTROL_PLANE_JWKS_URL | Remote JWKS the relay uses to verify runtime-access and host-tunnel tokens. | | CLAXEDO_RUNTIME_ACCESS_TOKEN_PUBLIC_KEY_PEM | Inline public key (alternative to JWKS). | | CLAXEDO_RELAY_HOST_SIGNING_KEY_PEM | Private PEM the relay uses to mint relay-host tokens. The Worker publishes its public half at /.well-known/jwks.json. | | CLAXEDO_RELAY_ALLOWED_ORIGINS | Comma-separated browser-origin allowlist for CORS. Replaces the built-in default list (https://claxedo.com and https://*.claxedo.com; no loopback origin). Grammar: exact origin, https://*.example.com, http://localhost:*. |

Cloudflare Worker tracing knobs (Durable Object deployment):

| Env var | Purpose | | --- | --- | | CLAXEDO_RELAY_TRACE_SAMPLE_RATE | 0–1 sampling rate for request traces. Only sampled requests emit Server-Timing phase headers and structured trace logs; unsampled responses carry just the x-claxedo-trace-id correlation header. Defaults to 0 (off). | | CLAXEDO_RELAY_TRACE_FORCE_SECRET | Optional operator secret. When set, a request with x-claxedo-relay-trace: <secret> forces sampling for that request (targeted debugging). Unset (default) means the force header is ignored — clients can never force tracing or timing emission. | | CLAXEDO_APP_ORIGINS | Cloudflare-only, additive origin entries layered on top of the base allowlist (kept for existing deploys; prefer CLAXEDO_RELAY_ALLOWED_ORIGINS for full control). |

Token verification is pluggable today via the RelayKey | RelayKeyResolver pair on verifyRuntimeAccessToken and friends, and via the unified TokenVerifier interface in @claxedo/workspace-relay-protocol. A custom verifier is only the crypto/introspection authority; the relay still validates its output into RuntimeAccessTokenClaims, binds the URL workspace id to the claims, applies revocation, and allowlists roles. Missing, unknown, or malformed roles deny.

To swap the relay's auth backend:

import { createStaticTokenVerifier } from "@claxedo/workspace-relay-protocol"

const verifier = createStaticTokenVerifier({
  tokens: {
    "tok-tenant-1": { subject: "u1", scopes: ["workspace:write"], claims: {} },
  },
})
// Pass `verifier` as `WorkspaceRelayOptions.tokenVerifier`.

The TokenVerifier interface intentionally contains only verify(token). JWKS fetching, audience binding, and replay caches belong to the implementation. Two reference implementations ship in the protocol package: createHttpTokenVerifier for remote verifiers (token introspection, custom OIDC), and createStaticTokenVerifier for tests and single-tenant setups.

createHttpTokenVerifier is a reference implementation. Its HTTPS endpoint is the crypto authority and must enforce issuer, audience, expiry, key selection, and replay policy before returning claims. Treat the endpoint as trusted operator configuration, not as tenant/user input.

Long-lived relayed sockets are authorized at establishment. Revocation is checked for new HTTP requests and WebSocket upgrades; already-established WebSocket/SSE/PTY streams may live until their normal close, reconnect, relay drain, or room restart.

Tunnel Lifecycle

Host-tunnel reconnects replace the old socket deterministically. Replacement cleans the old socket's pending HTTP responses, child WebSocket channels, heartbeat timer, and buffered work before installing the new socket. Stale close events identity-check the current owner before deleting presence, so an old socket cannot mark a replacement offline.

External Directory Design

The current WorkspaceRelayDirectory is an in-memory implementation of the directory contract:

type WorkspaceRelayDirectory = {
  registerHostTunnel(input: { hostId: string; workspaceIds: string[] }): HostTunnelPresence
  recordPong(hostId: string): HostTunnelPresence | undefined
  disconnectHost(hostId: string): void
  activeHost(input: { hostId: string; workspaceId: string }): HostTunnelPresence | undefined
}

The Durable Object room keeps presence with its live socket. Hibernation restores presence from socket attachments. Cloudflare routes every request for one workspace to that room, which owns its tunnel.

Routing

Browsers call /workspaces/:workspaceId/* on the relay with a Runtime Access Token. src/worker.ts selects the workspace room; src/cloudflare.ts authorizes the request and forwards it to the resolved cloud target or machine tunnel.

Seam: internal-relay vs workspace-relay

| | claxedo-server/src/deployments/shared-routes/internal-relay.ts | @claxedo/workspace-relay | | --- | --- | --- | | Layer | Control-plane auth | Tunnel transport | | Trust | Authenticates the relay process itself (loopback or bearer) | Authenticates the user's runtime-access token | | Routes | GET /internal/relay/target, GET /internal/relay/revocation | WS upgrade + tunnel framing | | Owners | claxedo-server (depends on the workspace authority, the identity provider, audit log) | workspace-relay (no authority or identity-provider dependency) | | Lives in | packages/claxedo-server/ (server-side only) | packages/workspace-relay/ (Worker and Durable Object) |

This split is deliberate: the relay never reads the workspace authority or the identity provider directly. It calls back to claxedo-server over HTTP for resolver decisions. The decision data crosses the seam as RuntimeAccessTokenActiveResult (defined in server.ts).

Development

bun --cwd packages/workspace-relay test        # unit suites, including the workerd ones
bun --cwd packages/workspace-relay typecheck

The TS error baseline for this package is 0 — keep it that way.

Boundary rule

Consumers outside this package import @claxedo/workspace-relay only — not @claxedo/workspace-relay/src/.... Direct deep-source imports break the package boundary. The check is

grep -rn "workspace-relay/src/" packages/claxedo-{server,app}/src

— required to return zero hits.