# Security Policy ## What listens on your computer Two local endpoints are **always on** while Vitals runs, whether or not Remote access is enabled: - **The local API on `127.0.0.1:7330`.** Bound to loopback only, so it cannot be reached from the network. It serves the same routes as the LAN server without a token, **read-only**, for the `vitals` CLI; see "Loopback reads, never controls" below. - **A per-user named pipe, `vitals-`.** Created with an explicit security descriptor that grants access to your account, SYSTEM and administrators only, and refuses remote clients; two users on one machine get two pipes. It carries metrics out and accepts no actions. Any process on this machine can read metrics through the local API; only a process running as you can open the pipe. Neither can act on a process. ## What is exposed today: the LAN server Vitals can serve its readings over your local network so a phone, a dashboard or a script can read them — and, if you allow it, end a process. That is the one surface reachable from other devices, and it is designed around the assumption that other devices on the same Wi-Fi are not trusted. **Off by default.** Nothing listens on the network (TCP 7331) and nothing broadcasts until you turn on Remote access in Settings. Turning it off closes the listener and withdraws the mDNS advertisement (`_vitals._tcp`), which only ever exists while the server runs. The loopback API and the named pipe above are separate and stay on. **Bearer tokens, generated by the OS CSPRNG.** Every request except `/api/v1/health` needs `Authorization: Bearer `. A token is shown in full exactly once, at pairing; afterwards the UI shows an 8-character prefix. Tokens are revocable individually. On the PC only a SHA-256 hash of each token is stored (`lan-tokens.json`), never the token itself; the phone keeps its token in the browser's local storage. **Two scopes.** A token is either _read_ or _control_. A new pairing is read-only. `POST /api/v1/control` with a read token is refused with 403 and the controller is never invoked. Granting control is a separate, explicit step on the desktop, and the phone confirms every action with a second tap. **Constant-time comparison, against every token.** The presented token is compared with each stored token using a constant-time equality, so response timing reveals neither a partial match nor the position of a token in the list. A wrong token and a missing token produce byte-identical 401s. **Authorisation before validation.** The control route reads the raw body and checks the token before parsing, so an unauthorised caller cannot learn the request schema by watching 403 turn into 422. **The token travels in the URL fragment.** The pairing QR encodes `http://:/mobile.html#t=`. Browsers do not send the fragment to the server, so it never appears in a request line or an access log; the page reads it, stores it locally and strips it from the address bar. **Loopback reads, never controls.** On the machine itself, `127.0.0.1:7330` serves the same routes without a token, for the `vitals` CLI, with _read_ scope only. Loopback TCP is not per-user on Windows: another account on the same PC (fast user switching, Remote Desktop, a service) reaches it too, so a tokenless _control_ grant would let that account end your processes. The CLI acts on processes on its own machine through Windows directly, which checks the caller's own rights. A page in a browser gets nothing tokenless: any `Origin` header or a non-loopback `Host` is refused. The decision is made on the connecting address; a request from the network still needs a token. ### What it does not do - **No TLS.** Traffic is plain HTTP on a private address. A passive attacker on the same network can read the readings in transit and could capture a token. The scope model bounds what a captured _read_ token allows to reading; keep control tokens to devices you trust, and revoke a token you think has leaked. - **No rate limiting on token guesses.** Tokens are 32 random bytes, so brute force is not the realistic attack; capture is. - **No authentication for `/api/v1/health`**, which returns only that the server is up. ## Replacing Task Manager (opt-in, writes `HKLM`) Settings → General → **Replace Task Manager** uses the mechanism Process Explorer has used for twenty years: the `Debugger` value under `HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\taskmgr.exe`, pointed at `vitals-desktop.exe`. Windows then launches Vitals wherever it would have launched Task Manager. - **Off by default.** Nothing is written until you turn the switch on. - **Elevation is a separate, short-lived process.** The UI never gains privileges. Turning the switch on or off starts a second copy of Vitals under UAC with the argument `--set-taskmgr-replacement on|off`; it writes one registry value, exits, and the un-elevated instance re-reads the key to confirm the write actually landed. A declined prompt is a "no", not an error. - **It will not displace another tool.** If the value already names a different program (Process Explorer, System Informer), the switch is disabled and says so; Vitals never overwrites or deletes a hook it did not set. - **The real Task Manager stays reachable.** "Open Windows Task Manager" in the tray and in Settings starts `taskmgr.exe` as a debuggee, the documented way to bypass the hook. Task Manager then relaunches itself elevated, and that launch is redirected to Vitals too — an _elevated_ Vitals started with Task Manager's command line therefore hands straight back to the real program and exits, without showing a window or touching the sampler. - **Uninstalling removes the value** if it still names Vitals. The installer's uninstall hook reads the `Debugger` value and deletes it only when it points at Vitals, so a hook set by another tool is left alone. ## The elevated helper (planned, not shipped) ## The CPU sensors service (optional, installed on request) `vitals-sensors.exe` ([ADR-0034](docs/adr/0034-cpu-sensors-service.md)) is the one component that runs as SYSTEM. It is installed only when the user presses **Install sensors service** on Devices & sensors, after one administrator prompt, and it can be removed from the same place. - **Read only.** It reads CPU temperature and package-energy registers, and the motherboard's fan tachometers, through the signed PawnIO driver, whose sandboxed modules expose only the registers or ports they declare. It writes no sensor, fan or voltage register — only the Super-I/O protocol bytes (config entry/exit, register index, bank select) needed to read — and takes no input: the pipe is outbound only, and a client can do nothing except read one line. Super-I/O access holds the shared ISA-bus mutex other monitoring tools use. - **Pipe:** `\\.\pipe\vitals-sensors`, remote clients rejected. The DACL gives SYSTEM and Administrators full access and interactive/authenticated users read access. The first instance is created with `FILE_FLAG_FIRST_PIPE_INSTANCE`, so another process cannot pre-create the name and impersonate the service. - **Pinned code.** The PawnIO modules are embedded in the exe and checked against SHA-256 pins before loading. The PawnIO 2.2.0 installer, fetched only when the driver is missing, is checked against its pin by the app and again by the elevated helper, on its copy in `%ProgramFiles%`, so the file that runs is the file that was hashed. - **Location.** It is installed to `%ProgramFiles%\Vitals Sensors` (admin-only), so the user who asked for it cannot replace the binary the SCM starts. - **Not reachable from the LAN.** No `ControlRequest` installs or removes it. ## The elevated helper (planned, not shipped) `apps/helper` is a planned SYSTEM-level service for readings that need `SeDebugPrivilege` and for privileged operations. **It is a stub today and is not installed.** When it ships, the design is: - The UI itself never runs elevated. The webview — the largest attack surface — has no privileges beyond the user's own. - The pipe's DACL restricts access to the installing user and administrators. - The helper verifies the client's image path and signature before accepting any command. - Every command is an enum variant, never a shell string. There is no parser to escape and no injection surface. - Mutating commands are logged with the caller's SID. ## Reporting a vulnerability **Please do not open a public issue.** Use GitHub's [private vulnerability reporting](https://github.com/dragoscv/vitals/security/advisories/new), which is enabled on this repository. If you cannot use GitHub, email [dragoscv12@gmail.com](mailto:dragoscv12@gmail.com) with "Vitals security" in the subject. Please include: - What the flaw allows an attacker to do. - Steps to reproduce, or a proof of concept. - The affected version and your OS build. You will get an acknowledgement within 72 hours and an assessment within seven days. If the report is valid we will agree a disclosure timeline with you, and credit you in the advisory unless you prefer otherwise. ## Scope **In scope** - Reading or controlling through the LAN server without a valid token of the right scope, or distinguishing a wrong token from a missing one. - Any way to make the server listen or advertise while Remote access is off. - Reaching the loopback API or the named pipe from another machine or another user account. - A token appearing in a log, a request line or anything persisted. - Anything that lets an unprivileged process drive the helper service, once it exists. - Escaping the webview's sandbox or capability restrictions. - Code execution via a crafted update manifest, flight recording or report file. - The updater: signature bypass, downgrade attacks, or MITM. **Out of scope** - Requiring administrator rights to begin with. An administrator can already do everything the helper does. - Anything requiring physical access to an unlocked machine. - Reading LAN traffic in transit. Plain HTTP is a documented trade-off; see above. - A control token you gave to a device you no longer trust. Revoke it. - Denial of service caused by deliberately exhausting your own machine's resources. ## Supported versions | Version | Supported | | ---------- | --------- | | 0.9.x beta | Yes | | < 0.9 | No | During pre-1.0 development only the latest release receives fixes.