Please do not open a public issue for security problems.
Report them privately through GitHub instead: Report a vulnerability (tab Security → Report a vulnerability in this repository).
Please include:
- what is affected (hub, display, hook setup, Codex forwarder, firewall rule,
stellwerk update,autostart, hardware monitor setup; version or commit), - steps to reproduce,
- the impact you expect.
We aim to acknowledge reports within 7 days. This is a small project, so a fix may take longer; we will keep you informed and credit you in the advisory unless you prefer to stay anonymous.
| Version | Security fixes |
|---|---|
| Newest release | yes |
| Older releases | no |
main |
development state, not a supported installation |
Stellwerk is in its 0.x line: only the newest release is supported. Security fixes go into a new patch or minor release; older releases get no backports.
To stay on a supported version, update to the newest release (see "Updating"
in the README): download its stellwerk-X.Y.Z.zip and
SHA256SUMS into one folder, then
gh attestation verify stellwerk-X.Y.Z.zip --repo Azentrius/stellwerk # optional, needs gh
stellwerk update stellwerk-X.Y.Z.zip # checks the zip against SHA256SUMS firstA fixed vulnerability is announced with:
- a GitHub security advisory in this repository (tab Security), with a CVE ID where one is assigned,
- an entry in
CHANGELOG.mdmarked as a security fix, - the release notes of the fixed release, which come from
CHANGELOG.md.
To be notified, watch this repository's releases on GitHub (Watch › Custom › Releases). With a GitHub token set up, the display also says when a newer release is out.
The hub can allow tool calls of AI agents: a decision on the display lets Claude Code run a command or change a file. That is why it is built to be reachable only from the machine it runs on and only by the display it serves:
- it listens on
127.0.0.1only (fixed in the code), with a firewall rule that blocks the port for other machines as a second layer; - hook requests need a bearer token and
Content-Type: application/json; the token file is readable by the current Windows user only and the token is never logged; - the display signs in before it gets anything: a start file readable by
the current Windows user only posts a key derived from the hook token, and
only the hub's answer to it carries the page token the WebSocket needs;
the display keeps it in storage of the hub's origin (address and port),
which other sites and ports cannot read, and sends it only in the
WebSocket handshake; there is no cookie, since cookies of
127.0.0.1go to every port and so to other local services; the address alone gives no token; the key is in no address and no log;tools/check-browser.tschecks this in a real Edge with a second local service; - decisions travel only over the display's WebSocket, which needs the right
Host, the rightOriginand the page token from the sign-in; a broken or oversized message (over 4 KB) or an unreadable request target closes only its own connection; - every request is checked against
Host(DNS rebinding); - nothing is allowed automatically;
- the hub runs and ends one task: "Stellwerk hardware", with
schtasks /Runand/Endand fixed arguments, no shell, so that LibreHardwareMonitor runs while the hub runs; that task was registered by an administrator and starts only the program in its own folder in Program Files; - the hub starts no programs but one: on a tap on the display it hands a link
to the Windows shell (
explorer.exewith the link as its only argument, no shell), which opens it with the program registered for it. The display only names a session or a pull request; the hub builds the link itself from what it shows and opens only four forms, checked whole:claude://claude.ai/epitaxy/local_…(the desktop app's own id of a session, which its hooks send in a header),codex://threads/<UUID>(a chat of the Codex app) andhttps://github.com/<owner>/<repo>/pull/<n>(a pull request of the watched owner that it lists) and<uptimeKumaUrl>/dashboard/<id>(a failing monitor it shows, at the address set up), at most one a second. Only the setup commands runicacls,powershell,netshandschtasks, with fixed arguments and without a shell;serverestricts the token file and the display's start file withicaclswhen it creates or changes them; - the hub connects to two other machines at most. One is
api.github.com(fixed in the code), for the list of open pull requests and the newest release ofAzentrius/stellwerk(to say that there is one; nothing is downloaded), and only once a GitHub token is set up: fine-grained only, meant to read pull requests and Actions and nothing else, kept like the hook token, never logged, never sent to the display; GitHub's answers are checked before use, redirects are not followed, and of the "next" links of long lists only those onapi.github.comare followed, at most 10 pages per list; - the other is Uptime Kuma at the
address set up with
stellwerk kuma key(http or https, host and port, no path), only for/metrics, only once an API key is set up (which in Uptime Kuma 2 opens/metricsand nothing else), kept like the hook token, never logged, never sent to the display; redirects are not followed, and of the answer the hub keeps only each monitor's name and status; its fileuptime-kuma.jsonholds only the ids of failing monitors and since when they fail; - besides that the hub asks only LibreHardwareMonitor on
127.0.0.1:8085fordata.json, when the hardware monitor is set up, with the password from LibreHardwareMonitor's settings file, and never sends that password to the display; - for the context meters the hub reads session transcripts named in hook
events, only under
%USERPROFILE%\.claude\projectsand%USERPROFILE%\.codex\sessions(after following links), only from their end, and keeps only the token counts, the model, the context window and, for Codex, who approves and the usage of the plan's limits, and from the first line only who wrote it (originator), to tell Codex' command line from its app; nothing else of a transcript reaches its state, the display or its fileplans.json; - of what tools return, the hub looks only at the desktop app's answer to
get_usagein aPostToolUsehook and keeps only the plan's limits from it; - for the names of Codex sessions the hub reads Codex' session index
(
%USERPROFILE%\.codex\session_index.jsonl, a fixed path, never one from hook data) from its end and keeps only the name of a session it shows; for their projects it reads the Codex app's state file (%USERPROFILE%\.codex\.codex-global-state.json, a fixed path) after it changed and keeps only project names and which chat belongs to which; the title of a Claude Code session comes from its transcript like its token counts; - for the display after a restart, the hub keeps the sessions it shows in
its file
sessions.json: id, folder, title, project, how full the context is and the last activity; never a prompt, a question, a tool's input or a request, and a request never comes back after a restart; - Codex events reach the hub through Stellwerk's forwarder, which Codex runs
as a command hook: it reads the hook token from the token file, posts to
127.0.0.1only, prints only the hub's answer and ends at once without a hub; the hub checks these events like all others; stellwerk updatetakes a release you downloaded only if it matches theSHA256SUMSnext to it, unpacks it into its own folder (refusing any file name that would leave it), runsnpm ci --omit=devthere and the new version'sautostart install(andcodex installwhen Codex' hooks run Stellwerk's forwarder), with fixed arguments and no shell; it ends and runs only the hub's own task, and the display's when the display moves to another screen, and removes only folders namedstellwerk-X.Y.Zin its own releases folder that neither Codex' hooks nor the command itself still use (none while Codex' hooks cannot be read); it downloads nothing itself;stellwerk hardware installdownloads exactly one LibreHardwareMonitor archive, pinned in the code by URL, size and SHA-256, unpacks it only into its own folder in Program Files (refusing any file name that would leave it), turns on LibreHardwareMonitor's password and blocks its port for other machines; that program runs with administrator rights, from a folder only administrators can change;- the start of the hub and the display with Windows runs as the current user without elevation;
- the display loads only the hub's own files under a strict Content Security Policy and shows hook data and pull request titles as text only.
Reports are particularly welcome for:
- ways to allow or deny a request without the display, or from another machine, another user account or a web page,
- ways around the token, the
HostorOrigincheck, the display's sign-in or the page token, - requests that end the hub instead of only their own connection,
- hook data that the display renders as HTML or script,
- the hook token, the GitHub token or the API key of Uptime Kuma appearing in a log, a URL, a page or any file other than its own,
- the hub connecting anywhere but
api.github.com, Uptime Kuma's/metricsat the address set up and LibreHardwareMonitor on127.0.0.1:8085, or to them without being set up, - a hook event that makes the hub read a file outside the transcripts folders, or anything of a transcript beyond its token counts and limits reaching the display,
- the forwarder printing anything but the hub's answer, sending the token
elsewhere, or making Codex wait beyond its hook timeout;
stellwerk codex installoruninstallchanging more of Codex' hooks than Stellwerk's own, - the hub opening anything else than those four forms of links, a link the display wrote, or a session or pull request it does not show; or the desktop app's id of a session reaching the display,
stellwerk updatesetting up a zip that does not match theSHA256SUMSnext to it, unpacking anything outside its own folder in%LOCALAPPDATA%\stellwerk\releases, removing folders other than its own previous releases or one that Codex' hooks may still run, ending tasks other than Stellwerk's own two, or closing Edge windows other than the display's,- the hub running or ending any other task than "Stellwerk hardware", or that task starting anything but LibreHardwareMonitor in its own folder,
stellwerk hardware installaccepting another file than the pinned one, writing outside its folder, or leaving LibreHardwareMonitor's web server reachable without the password or from other machines,- an unanswered or slow hub blocking Claude Code beyond the hook timeouts,
stellwerk hooks installoruninstallchanging more of Claude Code's settings than Stellwerk's own entries,stellwerk autostart installregistering tasks that run with more rights or other programs than the ones it shows.