Skip to content

Remote daemon

A daemon can serve clients on other hosts over a private network. This is an opt-in mode for trusted environments. It is not the same thing as a public multi-tenant server.

Server setup

Start the daemon on a literal non-public IP address:

KATA_AUTH_TOKEN=change-me KATA_TRUST_PRIVATE_NETWORK=1 \
  kata daemon start --foreground --listen 100.64.0.5:7777

Or configure it persistently in <KATA_HOME>/config.toml:

listen = "100.64.0.5:7777"

[auth]
token = "change-me"
trust_private_network = true

The CLI flag wins over config. Auto-started daemons also read the config-file listener, so a host that should always use one TCP address only needs the config set once.

Run the daemon under a process manager such as launchd, systemd, or a container runtime on the host that owns the SQLite database.

Runtime profiling

The daemon exposes Go's standard net/http/pprof handlers under /debug/pprof/ on its existing listener. The same listener and authentication policy protect profiling requests; a token-protected TCP daemon requires its normal bearer token.

Capture and inspect CPU and heap profiles with standard Go tools:

curl -H "Authorization: Bearer $KATA_AUTH_TOKEN" \
  'http://100.64.0.5:7777/debug/pprof/profile?seconds=30' \
  -o kata.cpu.pprof
go tool pprof kata.cpu.pprof

curl -H "Authorization: Bearer $KATA_AUTH_TOKEN" \
  'http://100.64.0.5:7777/debug/pprof/heap' \
  -o kata.heap.pprof
go tool pprof kata.heap.pprof

The index also links the goroutine, allocation, mutex-contention, blocking, thread-creation, command-line, symbol, and execution-trace endpoints. Capture /debug/pprof/trace?seconds=5 and open it with go tool trace.

Client setup

Point clients at the daemon:

export KATA_SERVER=http://100.64.0.5:7777
export KATA_AUTH_TOKEN=change-me
kata list

For a per-workspace, gitignored setting:

version = 1

[server]
url = "http://100.64.0.5:7777"

KATA_SERVER wins over .kata.local.toml unless a command passes --daemon <name>. If none of those are set, clients next honor active_daemon in <KATA_HOME>/config.toml; otherwise they use local daemon discovery or auto-start.

For project-scoped CLI commands such as list, search, and create, the client resolves .kata.toml and Git aliases on its own host. It never asks a remote daemon to search client directories:

  • Without either binding, the command fails with project_not_initialized (exit code 4) and suggests kata init or --project.
  • A --workspace path that does not exist on the client fails with a validation error (exit code 3) that names the path.

Bind a client directory or select an existing project for one command:

kata init --project example-project
kata list --project example-project

Ordinary remote CLI commands send their API requests without a preliminary ping. Refused connections still report daemon_unavailable (exit code 7). Explicit health and discovery commands continue to probe the selected daemon.

With API 0.17.0 or later, simple create, edit, comment, and label add commands also resolve the project within the mutation, so each needs only one HTTP request. Relationship flags retain their separate project resolution and any necessary target checks. Path-only workspaces still use the explicit resolve endpoint; --comment adds its own comment request after the mutation. Upgrade the remote daemon with the CLI: older daemons reject named project selectors rather than silently performing a different operation.

When a mutation resolves a renamed workspace through its alias, the CLI repairs .kata.toml from the successful response. If that local repair fails, the CLI reports workspace_repair_failed and states that the mutation succeeded. Fix the binding without repeating the mutation.

Bare issue references and ULIDs use the workspace alias, including after a project rename or merge. An explicit --project or qualified issue reference selects that project name instead and does not repair the workspace binding. Relationship-bearing edits resolve the alias and repair the binding before submitting the mutation, because their link checks need canonical identity.

To inspect the endpoint selected by those rules, including its transport and canonical request URL, run:

kata daemon locate --json

The command probes configured remotes without printing or resolving their tokens. External clients should use this supported output instead of reimplementing the selection order. See Daemon discovery for the schema and Unix socket address form.

For an existing home on the same machine, use a pinned local daemon profile instead of guessing its loopback URL. Explicit URL overrides, including tunnels, stay pinned and are never automatically converted or removed.

Browser access

Plain kata ui opens a selected local profile directly; otherwise it starts or discovers the local browser gateway and does not use KATA_SERVER. The gateway initially selects active_daemon when one is configured.

To open a remote daemon directly, add it as a named [[daemon]] entry and run kata ui --daemon <name>. If the remote is configured only through KATA_SERVER, open its canonical /kata route in a browser instead, for example https://daemon.example/kata. A remote browser route shows an in-document token login; the token is exchanged for a browser session in memory and is never put in the URL or browser storage. Identity-token mode keeps the token actor authoritative. Static-token deployments use the daemon's normal request-actor policy.

For an HTTPS reverse proxy, set [web].public_origin to the exact browser origin and route the UI, assets, /api/v1/ui/*, ordinary /api/v1/*, and the event stream to the same daemon. Do not rewrite the browser application to a different API origin or infer the public origin from forwarded headers. When that browser listener is also a configured trusted-proxy listener, Kata exchanges the proxy-asserted actor for a browser session automatically; it does not ask the user for a daemon token.

When the shared TCP listener binds a wildcard address but clients use an intentional backend hostname (for example, a container-network alias), add that exact authority to [web].allowed_hosts. This is separate from public_origin: it admits a backend request Host without declaring a new browser origin. Keep the list narrow; bearer authentication does not bypass Host validation.

An unauthenticated --insecure-readonly listener can display the browser UI, but mutations are disabled and updates use polling. A user who authenticates under a writable configuration receives the capabilities of that session.

Plain HTTP guardrails

Mutable non-loopback HTTP requires one of:

  • a daemon API token plus explicit trusted private-network opt-in;
  • explicit unauthenticated private-network write opt-in on a literal private IP.

Plain HTTP bearer-token targets must be literal non-public IPs: loopback, RFC1918, CGNAT, link-local, or ULA. Public IPs and DNS hostnames are rejected for plaintext bearer auth. Use HTTPS through a reverse proxy or an SSH tunnel for those shapes.

For private overlay hostnames where HTTPS is intentionally not used, clients can opt out per target with KATA_ALLOW_INSECURE=1 or [server].allow_insecure = true. Federation hub enrollment tokens use their own credential store, so spokes opt in with kata federation join --allow-insecure.

version = 1

[server]
url = "http://hub.internal:7777"
allow_insecure = true

Unix sockets, loopback HTTP, and HTTPS do not require the same private-network trust opt-in.

Tokenless private-network writes

For a single-user private network where the network itself is the access boundary:

listen = "100.64.0.5:7777"

[auth]
allow_unauthenticated_private_network_writes = true

or:

KATA_ALLOW_UNAUTHENTICATED_PRIVATE_NETWORK_WRITES=1 \
  kata daemon start --listen 100.64.0.5:7777

This moves kata's local no-token trust model onto the configured private IP: mutations and the event stream are accepted without a bearer token, and write actors come from the client request body just as they do on loopback and Unix sockets. Any device that can reach the bound address can assert any actor string, so use this only when tailnet or private-network membership is the intended trust boundary.

The bind must be a literal private IP address: RFC1918, CGNAT, link-local, or ULA. Hostnames, public IPs, and wildcard binds such as 0.0.0.0:7777 or :7777 are rejected for this mode. It cannot be combined with token, require_token_identity = true, or --insecure-readonly.

Token administration remains authenticated-only in this mode. /api/v1/tokens requests are rejected rather than letting unauthenticated private-network clients mint, list, or revoke API tokens.

Identity tokens

For stable per-user attribution, configure identity mode first:

[auth]
token = "bootstrap-admin-token"
trust_private_network = true
require_token_identity = true

Restart the daemon, then use the bootstrap token to mint DB-backed user tokens:

export KATA_AUTH_TOKEN=bootstrap-admin-token
kata tokens create --actor wesm --name laptop
kata tokens list
kata tokens revoke 1

For a temporary agent worker, issue a narrower credential instead of copying a personal or daemon-wide token into the machine:

install -d -m 700 /run/user/1000/kata-worker
kata tokens create --actor worker-a --issue example-project#abc4 \
  --expires-in 4h \
  --token-file /run/user/1000/kata-worker/auth-token

The grant follows the live issue subtree rooted at abc4; see the scope guide for allowed work and membership changes. Scoped creation requires identity mode even when no shared token is configured, and a federated project's token must be minted on its hub.

The launcher reads /run/user/1000/kata-worker/auth-token and injects its value as KATA_AUTH_TOKEN only into the worker CLI or MCP process, together with KATA_SERVER for the issuing daemon's exact origin. The file path is issuance output, not an authentication input. A missing or unreadable file must stop startup without falling back to a coordinator credential. Supply a local workspace binding so the worker does not need project initialization on the hub, and revoke the grant during teardown.

When a shared token is configured without require_token_identity = true, tokens create refuses before minting. A token created in that legacy mode would not be accepted as a bearer credential.

In identity mode:

  • the bootstrap token can create, list, and revoke user tokens;
  • the bootstrap token can perform reads;
  • attributed writes require a DB-backed token;
  • the daemon derives the actor from the token and ignores body-provided actor strings for mutations.

Administrators can inspect retained credentials with kata tokens list, press C in the TUI, or open Credentials in the Web UI. See credential auditing for access requirements, lifecycle state, and last-use limitations.

Connector administration is denied to database-backed tokens by default. Enable it only when every active identity token should administer connectors and external-root bridges across the daemon. Add this setting to the existing [auth] table:

allow_identity_connector_administration = true

The environment equivalent is KATA_ALLOW_IDENTITY_CONNECTOR_ADMINISTRATION=1. The identity-mode bootstrap token, browser sessions, and trusted-proxy principals still cannot administer connectors.

The kata federation enroll CLI workflow authenticates to the hub with --hub-token-env or a daemon-catalog credential whose URL has the same origin; the local daemon's global token is never sent to the hub. On an identity-mode hub, use a DB-backed personal token for the actor doing the setup. The generated enrollment token printed by that command is separate and is only for spoke transport. The enrollment is bound to the token actor in identity mode, and the hub rejects pushed federation events whose actor differs from that bound actor. See Federation token boundaries.

Token lifecycle events are stored in the event log and preserved by backup, restore, and JSONL cutover. Hidden system-project token events are excluded from ordinary project lists, stats, event feeds, and federation.

Web or OAuth frontends

A web app can sit in front of kata without daemon-side OAuth. The web app should authenticate the browser user, map that identity to a kata actor handle, and call the daemon with a server-side kata bearer token. The browser should hold only the web app session, never the daemon token.

Because the daemon stores only token hashes, a web app cannot retrieve an existing plaintext kata token later. It must vault plaintext tokens server-side or mint and revoke tokens as part of its own session lifecycle.

Trusted-proxy actor header

When a reverse proxy already authenticates users (through SSO, OAuth, or mutual TLS), it can assert the kata actor on each request through a configured header, and the daemon credits that actor instead of any client-supplied value. This is a third attribution pattern alongside identity tokens (where the daemon is the auth boundary, for direct clients holding personal tokens) and the web-frontend pattern above: here the proxy is the auth boundary and the daemon owns only audit attribution.

The mode is off by default and changes nothing until configured. Enable it by naming the header and the listeners on which it is honored:

[auth.proxy]
trusted_actor_header = "X-Kata-Actor"
trusted_proxy_listeners = ["unix:///run/kata/proxy.sock"]

The environment overrides are KATA_TRUSTED_ACTOR_HEADER and KATA_TRUSTED_PROXY_LISTENERS (comma-separated). An empty or unset env var means "no override," so a mode enabled in config.toml cannot be silently turned off by an empty environment variable; disabling it requires editing the config.

After merging environment and config:

  • Header empty or absent: mode is off; any header is ignored everywhere.
  • Header set but trusted_proxy_listeners empty: rejected at config load. A silent no-op here would let an operator believe proxy attribution is on while body-supplied actors keep flowing.
  • Header and at least one listener set: on for those listeners; every other listener passes through unchanged.

Listener addresses

Listener entries must be literal bind addresses that match the daemon's listen value: a Unix socket path (unix:///run/kata/proxy.sock) or a specific host:port (100.64.0.5:7777). Wildcard binds (0.0.0.0:7777, :7777) are never valid entries: an accepted connection reports the specific interface it arrived on, so a wildcard would never match. A trusted listener should be a Unix socket or a private IP that only the proxy can reach.

Security model

The header is trustworthy only because of where it is honored, so the deployment must hold up its end:

  • Trust is bound to the listed listeners. A client on any other path cannot set the header to spoof an actor. The header is meaningful exactly because nothing but the proxy can reach that listener.
  • The proxy must strip any client-supplied copy of the header before forwarding, and must send a single value. The daemon uses the first value of the configured header and does not police duplicates; sanitizing inbound headers at the proxy boundary is the operator's responsibility.
  • Terminate each attribution mode on its own listener. On a trusted listener the proxy-asserted actor overwrites a token identity, so running direct token-holding clients and the proxy on the same listener would discard their token identity. Give the proxy a Unix socket and direct clients a separate TCP port.
  • A proxy front-end cannot mint or revoke tokens. Token-admin endpoints stay restricted to the bootstrap or loopback path regardless of the header; the proxy asserts identity, never token administration.

Behavior

On a trusted listener the header value becomes the actor and any body or query actor is ignored. A mutation that arrives on a trusted listener with the header missing or empty is rejected with 400 actor_header_required rather than falling back to a client-supplied actor; otherwise a non-proxied client could omit the header and claim any identity. Reads carry no actor, never reach actor resolution, and are never blocked by this mode. The header asserts identity, not permission: it does not add per-operation or per-actor authorization.

Read-only private-network experiments

For unauthenticated experiments:

kata daemon start --foreground --listen 100.64.0.5:7777 --insecure-readonly

This permits GET requests only. Mutations and the event stream still require authentication. Network ACLs, VPNs, or tailnet policy are the access boundary in this development mode.

Clients of a plaintext private-network daemon must opt into the same trust the server does, even when no token is configured:

export KATA_SERVER=http://100.64.0.5:7777
export KATA_TRUST_PRIVATE_NETWORK=1

kata validates the bearer target whenever it builds a client, so a plaintext non-loopback endpoint is refused up front rather than failing later as an opaque error. The opt-in states that the private network is the access boundary; without it, use HTTPS, a loopback address, or an SSH tunnel.

require_token_identity = true cannot be combined with --insecure-readonly.

What this mode does not provide

Kata provides identity tokens and fixed issue-subtree grants. It has no per-actor roles, project ACLs, impersonation scope, or OAuth provider. A reverse proxy can own user authentication and assert the actor (see Trusted-proxy actor header). Repository, machine, and network isolation remain the operator's responsibility.