Krellbot docs

Dashboard

krellbot ui binds a tiny dashboard to 127.0.0.1 on a random (or user-chosen) port and prints a one-shot URL like:

Dashboard running at http://127.0.0.1:53182/264a3861588691fc018fb24d303ce081530fc2593be9fb2b2781992930ab4d42/

The 64-hex-char path is a 32-byte token from secrets.token_hex(32). The same token becomes the krellbot_session cookie on the first GET. The server binds only to loopback; GET status does not probe Kraken, Coinbase, or the venue and shows historical local metadata. The Exchange form is different: its handler sends an authenticated HTTPS permission request to the selected venue.

krellbot ui             # random port, prints URL
krellbot ui --port 8080 # fixed port (still loopback)

Ctrl-C stops the server and returns 0.

What the server reads

The server is a ThreadingHTTPServer from the standard library, bound to 127.0.0.1 only. There is no host argument that can widen it. A request with a Host header other than 127.0.0.1:<port> or localhost:<port> is rejected with 403 Forbidden and sets no cookie.

The first page is /{token}/. That response sets two cookies:

Both cookies are issued on the first good GET. They are not Secure because the page is served over plain HTTP on the loopback address. The session cookie's value matches the URL token; that match is checked with hmac.compare_digest. A request whose path token does not match the server's token is 403 and sets no cookie.

POSTs require:

  1. The krellbot_session cookie to equal the URL token.
  2. The Origin header to be http://127.0.0.1:<port> or http://localhost:<port>.
  3. A csrf form field whose value matches the krellbot_csrf cookie.

Any of those three failing is 403 and changes no state.

What the dashboard reads from disk

For GET views — including the wizard pages and the per-venue status block — the server reads local state, including nonsecret disk metadata under $KRELLBOT_HOME and the detected keychain backend type. The historical status file is written after a credential POST when possible:

The GET status view does not contact Kraken, Coinbase, or krellbot.dev. Explicit docs exit links can redirect your browser to krellbot.dev/docs or to the public source repository; those navigation links do not carry exchange credentials. The status block is HISTORICAL — derived solely from the durable timestamp written by the last credential POST. Current key presence is not checked, current connectivity is not implied, current keyring contents are not read. CLI-side rotation cannot be detected by the page.

Credential-source precedence when the engine later loads a key is a complete KRELLBOT_<VEN>_KEY and KRELLBOT_<VEN>_SECRET environment pair, then the keyring, then an explicitly configured KRELLBOT_<VEN>_KEYFILE if no keyring pair exists. A partial environment pair is an error. The historical browser status does not prove which credential a later trade will use.

The wizard Exchange step is the single credential-bearing path

When the dashboard is opened by krellbot ui on a fresh install, the four-step wizard renders Welcome → Security → Exchange → Next. Of those, the Exchange step is the only place a credentialed form lives:

The browser form action is keys/add on the loopback dashboard — the browser POST stays on the machine. The Exchange handler sends an authenticated HTTPS request to the selected Kraken or Coinbase venue to verify trade-only permission, and on success stores the pair in the native OS keychain via keys_onboarding.probe_and_store.

The venue probe is conditional on the operator submitting the Exchange form. krellbot.dev never receives the exchange key or secret via this form. The credential is never echoed back or written to a receipt or journal; the form has autocomplete="off" on every secret field.

The four-step wizard ships with the frozen binary. On a host whose chip / OS combination is not in the manifest, the install endpoint returns 503 and the wizard is not yet reachable — use the CLI path shown in Getting started until the manifest entry for that combination is published. The dashboard as a server is reachable from the installed engine; the four-step wizard UI is the part that requires the matching manifest entry on disk.

What the GET status page does NOT do

Actions

Those actions submit to relative URLs (arm, disarm, stop_all, adopt) and the server reads the form body itself. The Exchange POST's outbound venue probe is described above. There is no third-party CDN or Google font.

Static assets

src/krellbot/ui/static/ contains only relative references: index.html loads static/style.css and static/app.js. The HTML, CSS, and JS files contain no http://, no https://, and no protocol-relative // URLs. The repository enforces this with rg -n "https?://" src/krellbot/ui/static, which must come back empty.

Tests

tests/test_ui_server.py covers the seven contract points in one shot: foreign host header rejection, CSRF enforcement, loopback bind, no external URLs in static, stop-all disarming, live-arm refusal, and adopt-while-long refusal. Every test starts a DashboardServer on a thread with port=0, reads the bound port back, talks to it over http.client, and tears the server down in finally. No sleep-to-poll.

Threat model

← docs