| title | Console server |
|---|
The console server (oac-web) serves Web, signs the administrator in with the Core key and forwards the browser's /core/v1 requests to Core. This reference is for operators who run Web without the installer or review its security boundary.
It is the services/web process. The browser never holds the Core key or any API key. Applications, nodes and self-hosted executors reach Core through the console, which forwards /v1, /api/v1 and /docs unchanged.
Configuration reference owns its process settings and defaults.
flowchart LR
browser["Administrator browser"]
console["Console server"]
core["Core"]
database[("PostgreSQL")]
application["Application / official SDK"]
machine["Nodes and Runtime daemons"]
browser -->|"same origin: /console/*, /core/v1/*; session cookie"| console
console -->|"/core/v1/* with the Core key"| core
application -->|"/v1 with a Project API key"| console
machine -->|"/api/v1 with machine credentials"| console
console -->|"/v1, /api/v1 and /docs unchanged"| core
core <--> database
The deployment's reverse proxy sends every path to the console. The console forwards /v1, /api/v1 and /docs to Core and serves everything else itself; Configure the public address gives the proxy requirements. The console handles each path as follows:
| Path | Sign-in | Handling |
|---|---|---|
/healthz |
No | GET or HEAD answers 200 ok |
/v1, /api/v1 and below |
— | Forwarded to Core unchanged, with the caller's credential, streaming and WebSocket upgrades |
/docs, /docs/* |
No | The API reference and its OpenAPI documents, forwarded to Core unchanged |
/node-install/* |
No | The node installation payload (see Node installation payload) |
/console/auth, /console/auth/login, /console/auth/logout |
No | Sign-in |
/, /index.html, /favicon.svg, /oac-mark.svg, /assets/* |
No | Static console assets |
/console/config |
Yes | Console configuration |
/core/v1/* |
Yes | Forwarded to Core |
/core and other paths under /core/ |
Yes | 404 |
| Any other path | Yes | Static assets; a path without a file extension falls back to index.html |
Every request except /healthz, /v1, /api/v1 and /docs must pass these checks first:
- Host and origin. The
Hostheader must equal the host ofOAC_PUBLIC_URL. AnOriginheader, when present, must equal that origin, andSec-Fetch-Sitemust besame-originornone. A write that carries neitherOriginnorSec-Fetch-Site: same-originneeds a same-originReferer. Otherwise the console answers 403./node-install/*checks only the host and the path. - Safe request. The path must start with
/and contain no%, backslash, NUL, dot segment or empty segment. Absolute-form request targets,CONNECTandTRACEget 400. AnUpgradeheader gets 400 except on/v1,/api/v1and/docs, which are forwarded before these checks. A/core/v1request can therefore never leave that prefix. - Sign-in. Paths that need sign-in answer 401 without a valid session cookie.
Under /core, these failures use the Core error envelope with the codes in console-generated failures; elsewhere they return {"error": "…"}, or plain text for an unsafe request. Every response carries Cache-Control: no-store, X-Content-Type-Options: nosniff, Referrer-Policy: no-referrer and Content-Security-Policy: frame-ancestors 'none'.
The console forwards each signed-in /core/v1/* request by prefix to OAC_WEB_UPSTREAM, with its path and query unchanged. Core alone decides whether the route exists, and its responses and errors pass through unchanged. The console therefore needs no change when Core adds a /core/v1 route.
On the way to Core, the console:
- removes the browser's
Authorization,Proxy-Authorization,Cookie,OriginandRefererheaders; - sends
Authorization: Bearer <Core key>; - sets
X-Core-Console-Actor: console, replacing any value the browser sent. Core records it as a display-only audit label (Core administration API); - ignores ambient HTTP proxy settings, so the Core key reaches only the configured Core;
- streams responses without buffering.
On the way back, it removes Set-Cookie, WWW-Authenticate, Location, Refresh and every Access-Control-* header. A redirect from Core, or a failed connection to Core, becomes 502 core_unreachable.
The console server never retries a request. Browser code calls /core/v1 through the typed clients in packages/agents-client; Console API usage lists what each page reads and writes.
| Method and route | Request | Result |
|---|---|---|
GET /console/auth |
No body | 200 {"mode":"login"} or 200 {"mode":"authenticated"} |
POST /console/auth/login |
Content-Type: application/json; body {"core_key":"…"} with no other member, at most 4 KiB |
200 {"mode":"authenticated"} and the session cookie |
POST /console/auth/logout |
No body | 200 {"mode":"login"}; ends the session and clears the cookie |
The administrator signs in with the deployment's Core key. There are no console accounts, usernames or setup step, and signing in grants the whole console.
- The console compares SHA-256 digests of the submitted and configured keys in constant time. It never logs or returns the key.
- The session cookie
core_console_sessionis HttpOnly,SameSite=Strict, andSecurewhenOAC_PUBLIC_URLis HTTPS. It lasts 12 hours. - Sessions live only in the console's memory, at most 64 at a time; the oldest is dropped first. A console restart or a Core key rotation signs everyone out.
- At most two sign-in checks run at once; another attempt gets 429 with
Retry-After: 1. - Failed attempts share a budget of 10 per minute; beyond it, a wrong key gets 429 with
Retry-After: 60. The correct key always signs in, which is why the console refuses to start with a Core key that breaks the key format.
Sign-in errors: 400 for a malformed body, 401 Invalid Core key, 405 for a method other than POST, 415 for a body that is not JSON, 429 as above, and 503 when the console cannot create a session.
GET /console/config returns what the signed-in browser needs to add nodes:
| Field | Meaning |
|---|---|
node_installer |
Whether the console serves a node installation payload |
node_installer_sha256 |
SHA-256 of that payload's node-install.pyz; Add node commands verify it before running the installer |
node_artifacts |
The providers (docker, microsandbox) whose node artifacts the payload holds, locally or as a release download. Read on every request, so artifacts added by rerunning the installer appear without a restart |
With OAC_WEB_NODE_PAYLOAD_DIR set, the console serves the matched distribution's node payload at /node-install/ without sign-in: node-install.pyz, manifest.json, SHA256SUMS, runtime/seccomp.json, and the node artifacts the manifest declares under artifacts/. An artifact missing locally redirects (307) to its release download. Node install and uninstall commands download from <public_url>/node-install/, so the reverse proxy must send that path to the console. Nodes verify every checksum themselves.
The operator's reverse proxy or hosting platform terminates HTTPS and routes to the console (Configure the public address), and OAC_PUBLIC_URL records the origin that browsers, applications, nodes and executors use. The console accepts only its host, so DNS rebinding cannot reach it.
After installing or changing the console, check:
GET /healthzon the console and on Core. Each proves only that the process answers.- Sign in, then read
GET /core/v1/projectsin the browser. This proves the browser-to-console and console-to-Core path and the console's Core key. - A Project API key works on
/v1and fails on/core/v1. The Core key fails on/v1. - A cross-origin write to the console is rejected, and a forged
X-Core-Console-Actorheader does not change the audit label. - Neither sign-in nor the sandbox deployment read (
GET /core/v1/sandbox/deployment) proves that a model or a sandbox is ready. Runtime observations and history report execution separately.
A sign-in failure belongs to the console. A 401 from Core on a signed-in request means the console's Core key does not match Core's digest, or the console reaches the wrong Core. Troubleshooting covers the common symptoms.