token_broker
token_broker
¶
Vault token broker — HTTP/WebSocket reverse proxy with secret injection.
This module has zero terok imports. It is a self-contained security
component embedded by VaultProxy
inside each per-container supervisor: an aiohttp app that listens on a
per-container Unix socket (or 127.0.0.1 TCP port), validates phantom
tokens against a SQLCipher database, injects real credentials from the
same database, and forwards requests to upstream API servers.
Task containers see only phantom API keys (worthless outside the broker). Real secrets never enter the container filesystem or environment.
Route config (JSON, generated by terok-executor from the YAML registry)::
{
"claude": {
"upstream": "https://api.anthropic.com",
"auth_header": "Authorization",
"auth_prefix": "Bearer ",
}
}
UnixBind(socket_path)
dataclass
¶
Unix-domain bind for the vault HTTP proxy.
Used by the supervisor in socket mode. The proxy binds the socket
with mode 0600 so only the same-UID rootless container peers
via the bind-mount.
socket_path
instance-attribute
¶
TcpBind(host, port)
dataclass
¶
VaultProxy(*, db_path, scope_id, bind, routes_path=None, audit_path=None, runtime_dir=None)
¶
Embeddable aiohttp vault — one per container, owned by the supervisor.
Each per-container supervisor builds a fresh
VaultProxy
bound to a per-container socket (or TCP port); it serves only its
own container and lives only as long as that container itself.
Two transport flavours, picked by bind at construction:
UnixBind— a single mode-0600 Unix socket the rootless container reaches via bind-mount. The supervisor places the socket under$XDG_RUNTIME_DIR/terok/vault/<container_id>.sock.TcpBind—127.0.0.1:<port>for the krun / SELinux-restricted path; the shield's loopback allowlist covers the container side.
The OAuth refresh task runs every _REFRESH_INTERVAL
seconds inside the same loop. Cross-supervisor coordination uses a
non-blocking flock on
$XDG_RUNTIME_DIR/terok/vault/locks/refresh-<credential_set>-<provider>.lock:
a contended lock means another supervisor is already refreshing the
same row, so we skip and pick up the fresh value on the next read.
Wire the proxy to the credential DB and a transport.
scope_id is informational at present — the broker authorises
per-token via _TokenDB.lookup_token,
not by scope. Reserved for future per-supervisor scope-filtering
(e.g. refusing tokens outside the supervisor's container scope).
routes_path defaults to $XDG_CONFIG_HOME/terok/vault/routes.json
when omitted — the canonical location terok-executor writes
the route table to.
runtime_dir — explicit runtime root for the cross-supervisor
refresh flock files. Required when the proxy runs in
crun's rootless userns (getuid() reports 0 there but
the actual host runtime dir is under the operator's uid); if
omitted, falls back to $XDG_RUNTIME_DIR / /run/user/<uid>.
Source code in src/terok_sandbox/vault/daemon/token_broker.py
bind
property
¶
Return the transport binding the proxy was constructed with.
scope_id
property
¶
Return the scope the proxy was constructed with (informational).
start()
async
¶
Build the aiohttp app and bring the listener up.
Initial-pass refresh runs at startup (same shape as
_refresh_loop)
so the first request never blocks on a slow token-endpoint
round-trip — the periodic task continues from there.
Source code in src/terok_sandbox/vault/daemon/token_broker.py
stop()
async
¶
Tear down the listener and free the aiohttp resources.
Idempotent — a stop call on a never-started proxy is a no-op.
Source code in src/terok_sandbox/vault/daemon/token_broker.py
acquire_refresh_lock(lock_dir, credential_set, provider)
¶
Try to acquire an exclusive flock on the per-credential refresh file.
Returns the held file-descriptor on success, None when another
supervisor is already refreshing the same row (the lock is
non-blocking, LOCK_NB). Callers os.close the fd to release.
Soft-fails to None on any I/O error — the periodic task will
pick the credential up again on the next tick.
The lock file lives under
$XDG_RUNTIME_DIR/terok/vault/locks/refresh-<credential_set>-<provider>.lock;
the directory is created with mode 0700 on demand. The
credential_set / provider components are sanitised (see
_safe_lock_component) so the lock file always stays inside
lock_dir.
Source code in src/terok_sandbox/vault/daemon/token_broker.py
release_refresh_lock(fd)
¶
Release the flock held by fd (see acquire_refresh_lock).