Auth
RunWisp has one password and one session for network clients (Web UI, remote REST), and a local Unix socket for clients that run alongside the daemon (TUI, CLI). No secrets are stored on disk. The trust model is “if you can log in over the network — or if you can already read the data directory — you control the daemon”, and RunWisp is built for one operator on one machine.
Local clients (CLI, TUI)
Section titled “Local clients (CLI, TUI)”The CLI and TUI talk to the daemon over a Unix domain socket at
<datadir>/runwisp.sock. The socket file is created 0600 inside
the (0700) data directory, so only the user that started the daemon
can connect — exactly the same guarantee the SQLite file relies on.
On top of that, the daemon verifies the peer UID at accept time with
SO_PEERCRED (Linux) / LOCAL_PEERCRED (macOS); a connection from a
foreign UID is closed immediately.
Local CLI/TUI clients therefore need no password and no JWT — they are
identified by being on the local socket as the right user. Commands
like runwisp, runwisp tui, runwisp list, and runwisp exec
connect over the socket automatically.
If the socket is missing or unreachable, the CLI reports daemon not running at <datadir> instead of falling back to a different transport.
A running daemon you cannot reach over the socket is almost always a
data-dir ownership or path mismatch, not a “the network is down”
problem.
Network clients (Web UI, remote REST)
Section titled “Network clients (Web UI, remote REST)”The Web UI shows a single password field. Your password never travels over the network as plain text. The login uses a challenge-response handshake: the browser asks the daemon for a one-time challenge, mixes it with your password, and sends back a hash the daemon can verify. Even on plain HTTP behind a TLS-terminating proxy, the password stays out of the wire.
After a successful login the daemon issues a session in a secure
cookie. Setting a fresh RUNWISP_PASSWORD and restarting the daemon
invalidates every existing session.
The TUI opens a browser session via the “Open in browser” action —
the TUI mints a single-use launch ticket over the local socket, the
browser redeems it on 127.0.0.1, and the daemon sets a session
cookie. The password never leaves the host.
The password
Section titled “The password”RunWisp never writes the password (or the JWT signing key) to disk. You have two ways to set it:
RUNWISP_PASSWORDenv var: the daemon uses the value from the environment, in memory only. This is what you want for Docker secrets, systemd’sLoadCredential=, or sealed-secrets workflows. As long as the value is stable across restarts, existing browser sessions survive a restart.- Unset: the daemon generates a fresh random password every boot and keeps it in memory only. Restarting rotates the password, which rotates every JWT key, which logs every browser out.
The JWT signing key is derived from the password via HKDF-SHA-256
salted by the per-install fingerprint (machine ID, cwd, executable
path, hostname). The same RUNWISP_PASSWORD on the same install
yields the same key, so browser sessions persist across restarts; a
different password — or the same password on another machine — yields
a different key, so sessions don’t leak between installs.
To rotate the password, change RUNWISP_PASSWORD (or unset it for a
fresh ephemeral one) and restart the daemon. Every existing browser
session is invalidated.
Retrieving the ephemeral password
Section titled “Retrieving the ephemeral password”When the daemon mints an ephemeral password (no RUNWISP_PASSWORD
set), the value never appears in a log line, the startup banner, or a
TUI frame. Instead:
-
The TUI’s Home view shows a
Passwordfield rendered as••…. Press Enter while the field is focused to copy the real value to the clipboard via the copy modal. -
From any shell that can reach the daemon’s data directory, run
runwisp passwordto print it to stdout. The output is a single line so you can pipe it into a clipboard tool:Terminal window runwisp password | wl-copy # Waylandrunwisp password | pbcopy # macOSrunwisp password | xclip -selection clipboard
The endpoint that backs runwisp password lives on the daemon’s local
Unix socket only — it is 403 Forbidden over the network even with a
valid session cookie, and it returns 404 Not Found when the daemon
is configured with RUNWISP_PASSWORD. The operator-supplied
RUNWISP_PASSWORD is never disclosed by the daemon; retrieve it
from wherever you originally set it (Docker secret, systemd
LoadCredential=, vault, password manager, …).
runwisp password writes the value to stdout, which lands in shell
scrollback. Pipe to a clipboard tool when that matters.
Rate limiting
Section titled “Rate limiting”Failed network login attempts are rate-limited per source IP. Cross
the limit and you get 429 Too Many Requests until the window slides
past; restarting the daemon clears the counter. The local-socket
path is not subject to this limit.
Live log streams are limited too — at most a few dozen open at once,
plus a small per-IP cap — so a buggy script can’t accidentally exhaust
the daemon. If you hit the limit, the response is 503 Service Unavailable; close some open log views and try again.
Reverse proxies
Section titled “Reverse proxies”RunWisp binds to 127.0.0.1:9477 by default and serves plain HTTP.
To expose it publicly with TLS, run a reverse proxy in front (nginx,
Caddy, Traefik, Cloudflare Tunnel, …) and tell the daemon which IP
ranges that proxy lives on:
export RUNWISP_TRUST_PROXY='10.0.0.0/8,2001:db8::/32'runwisp daemon --host 127.0.0.1 --port 9477RUNWISP_TRUST_PROXY is a comma-separated list of CIDR ranges whose
X-Forwarded-Proto and X-Forwarded-For headers RunWisp will trust.
When a request from a trusted range arrives with X-Forwarded-Proto: https, the daemon treats the connection as secure and marks the
session cookie accordingly. Rate limiting always counts the real TCP
peer, not the forwarded header, so a misconfigured proxy can’t open a
hole.
The daemon rejects 0.0.0.0/0 and ::/0 — trusting the entire
internet would let any client lie about its source IP.