Aleph
Gateway RPC

协议

WebSocket JSON-RPC 2.0 protocol

The Aleph Gateway uses JSON-RPC 2.0 over a single WebSocket for every RPC, event, and connection handshake.

Endpoint

ws://127.0.0.1:18790/ws          # default bind, loopback
wss://<host>:<port>/ws           # once native TLS is enabled
  • The default bind is 127.0.0.1:18790 (see GatewayServerConfig::default in src/gateway/config.rs); a loopback connection is an implicit operator — zero-config single-host use.
  • A non-loopback bind requires one of the TLS tiers or it refuses to start.
  • Browser cross-origin / DNS-rebinding attacks are blocked at the /ws upgrade by src/gateway/origin_policy.rs.

Session init (connect handshake)

The first frame on every /ws connection must be connect (see the "Session-init invariant" branch in src/gateway/server/handler.rs; any other first frame gets AUTH_REQUIRED and the socket is closed):

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "connect",
  "params": {
    "token": "aleph-<uuid>"
  }
}

A remote connection presents one of three credentials to satisfy connect (priority order is src/gateway/handlers/connect.rs::resolve_connect_auth); missing or invalid ⇒ falls into the login wall:

// 1) Loopback: omit `params` entirely — always operator.
// 2) Paired device: long-lived device token.
{
  "method": "connect",
  "params": { "device_token": "aleph-dt-<uuid>", "device_id": "panel-1" }
}

// 3) First-time pairing: trade a single-use bootstrap ticket
//    for a fresh device token (returned in the response).
{
  "method": "connect",
  "params": { "bootstrap_ticket": "aleph-bt-<uuid>", "device_id": "panel-1" }
}

// 4) Legacy shared Gateway token (forward-compat only).
{
  "method": "connect",
  "params": { "token": "aleph-<uuid>" }
}

On success handle_connect returns the session baseline and server::handler overlays the authorization verdict:

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "role": "operator",
    "state_version": 17,
    "keepalive": { "ping_interval_secs": 30, "idle_timeout_secs": 90 },
    "authorized": true,
    "needs_token": false
  }
}
  • When bootstrap_ticket is accepted, the response carries device_token — the Panel persists it and replays it on every reconnect.
  • A rejected remote connect is recorded in the security audit log (AuditEventType::AuthFailure), bounded by the Auth-scope rate limiter (≤10/60s/IP by default).
  • Full comparison of the three credentials, TTLs, and revocation paths: see Authentication.

TLS tiers

Remote connections are gated by a boot gate and a per-connect gate; an upstream trusted-proxy is layered on top via trusted_proxy, which restores the real client IP.

LayerWhereBehaviour
Boot gate (check_network_exposure)src/gateway/server/mod.rsA non-loopback bind with tls / trusted_proxy / allow_insecure_remote all unset ⇒ process refuses to start.
Per-connect gate (refuse_insecure_remote)ws_upgrade_handler in src/gateway/server/handler.rsA non-loopback client on an unencrypted leg ⇒ WS upgrade returns 426 Upgrade Required.
Trusted proxy (trusted_proxy)[gateway] trusted_proxy.{enabled, trusted_ips} (default enabled=false, trusted_ips=["127.0.0.1","::1"])Only when the connection's peer ∈ trusted_ips: take the last entry of X-Forwarded-For as the real client, and treat the leg as encrypted when X-Forwarded-Proto: https. Untrusted peers' forwarding headers are ignored (spoof-safe).

trusted_proxy only restores XFF — it can never flip is_loopback: loopback is loopback regardless of the header. The full schema and the three recommended TLS tiers (self-signed, trusted-proxy, operator-cert) live in Authentication / Remote TLS.

Session and rate limits

After the /ws upgrade the server builds a ConnectionState:

  • first_message — the first frame must be connect; anything else returns AUTH_REQUIRED and the socket is closed.
  • Login wall — an unauthorized remote connection may only call connect; every other method returns AUTH_REQUIRED. UnauthorizedFloodGuard (src/gateway/server/flood_guard.rs) counts strikes under the Auth scope; once it trips the socket is closed and a RateLimited audit entry is written.
  • Operator tier — a successful connect (loopback or token-bearing) is operator, single-tier, identical authority to local — there is no sub-tier for a Panel.
  • Per-IP cap[gateway] max_connections_per_ip (default 64, 0 disables), loopback exempt, counted against the trusted_proxy-resolved client_ip so the real client is bounded even when many share a reverse-proxy socket.
  • Rate limitersrc/gateway/rate_limiter.rs, keyed on (resolved_client_ip, method_scope). Loopback is exempt; the identity is a textual IP, not a session token.

Message types

Request (Client → Gateway)

{
  "jsonrpc": "2.0",
  "id": "unique-id",
  "method": "namespace.action",
  "params": { ... }
}
  • method follows the <namespace>.<action> convention (see "Method namespaces" below).
  • id may be a string, number, or null (null = notification; no response expected).

Response (Gateway → Client)

Success:

{
  "jsonrpc": "2.0",
  "id": "unique-id",
  "result": { ... }
}

Error:

{
  "jsonrpc": "2.0",
  "id": "unique-id",
  "error": {
    "code": -32603,
    "message": "Internal error",
    "data": { ... }
  }
}

error.data carries only structured fields (e.g. an idempotency_key hint, a receipt_id user-readable receipt) — the raw error chain is never leaked.

Event (Gateway → Client)

{
  "jsonrpc": "2.0",
  "method": "event",
  "params": {
    "topic": "stream.token",
    "data": { ... }
  }
}

A streaming RPC's lifecycle is three events broadcast through the per-connection forwarder in src/gateway/server/handler.rs:

{ "method": "event", "params": { "topic": "stream.start",  "data": { "run_id": "..." } } }
{ "method": "event", "params": { "topic": "stream.token",  "data": { "text": "..." } } }
{ "method": "event", "params": { "topic": "stream.end",    "data": { "run_id": "..." } } }

Method namespaces

Method names are registered in src/gateway/handlers/mod.rs::register. Common namespaces:

PrefixExamplesNotes
chat.*chat.send, chat.abort, chat.history, chat.clearPanel-facing chat API; chat.send wraps agent.run.
session.*session.create, session.usage, session.compact, session.truncateSession metadata / compaction / checkpoints.
agents.*agents.list, agents.bindings, agents.teamsAgent lifecycle, bindings, teams.
config.*config.get, config.patch, config.schema, config.applyHot-reload config CRUD.
exec.*exec.approvals.*Exec approval (action-level; see Security / Approvals).
cron.*cron.list, cron.create, cron.run, cron.toggleScheduled jobs.
wizard.*wizard.start, wizard.next, wizard.answer, wizard.cancelSetup wizard.
cluster.*cluster.admit_node, cluster.deregister, environments.listCluster-node admission / deregister.
gateway.*gateway.ticket.create, gateway.token.current, gateway.token.rotate, gateway.devices.list, gateway.devices.revoke, gateway.credentials, gateway.metrics.lanesGateway-internal surface — bootstrap tickets, shared-token CRUD, paired devices, connection snapshot.
secrets.*secrets.list, secrets.set, secrets.deleteVault CRUD; the shared token doubles as the vault master key.
channel.*channel.list, channel.sendChannel aggregation.
pairing.*pairing.list, pairing.approve, pairing.rejectChannel-side unknown-sender approval — unrelated to device pairing.
tools.invoke(single method)Direct tool call, bypasses the LLM loop (E2E / debug only).
events.*events.subscribe, events.unsubscribe, events.listEvent subscription.

The full directory (hundreds of gateway.* and business methods) lives in Methods Reference.

Event subscription

Use events.subscribe / events.unsubscribe for glob topics:

{ "method": "events.subscribe",   "params": { "patterns": ["stream.*", "presence.*"] } }
{ "method": "events.unsubscribe", "params": { "patterns": ["stream.*"] } }

events.list returns the active subscriptions on the current connection. A broadcast Lagged notifies the affected subscriber and recovers — it does not permanently kill the connection.

Error codes

error.code constants are defined in src/gateway/protocol.rs; a few are aliases of the aleph-protocol core set:

CodeConstantDescription
-32700PARSE_ERRORParse error
-32600INVALID_REQUESTInvalid request
-32601METHOD_NOT_FOUNDMethod not found
-32602INVALID_PARAMSInvalid params
-32603INTERNAL_ERRORInternal error
-32604RESOURCE_NOT_FOUNDResource not found
-32605TIMEOUT_ERRORTimeout (also -32002 as an alias)
-32000AUTH_REQUIREDConnect required (also used when the first frame isn't connect)
-32001AUTH_FAILEDInvalid credential
-32002PERMISSION_DENIEDPermission denied
-32003TOOL_ERRORTool execution error
-32004RATE_LIMITEDRate limit (data carries retry_after_ms / lockout_remaining_ms)
-32030IDEMPOTENCY_KEY_REQUIREDMutating RPC without idempotency_key under require_idempotency_key
-32099SERVICE_UNAVAILABLERegistered but not wired in this build/mode

See also

On this page