Fan Control runs with elevated privileges and writes to an embedded controller, so security and safe failure behavior are treated as core requirements.
Do not disclose a suspected vulnerability in a public issue. Use GitHub's private vulnerability reporting to provide:
- the affected version or commit;
- reproduction steps or a proof of concept;
- expected impact;
- any known mitigation;
- whether hardware access is required.
You should receive an acknowledgement after the report is reviewed. Please allow time for a fix before publishing technical details.
Security-relevant areas include:
- the WebKitGTK JSON-RPC bridge (
script-message-with-reply-received) on Linux and the loopback-only HTTP bridge infan_windows_gui.pyon Windows, where a per-run token authorises dashboard RPC posts (cross-origin fetch defence) and is required for the dashboard document itself, since that document carries the token inline; bundle assets (JS/CSS/bridge.js) are served without it and hold no secret, so a local process cannot read the token out of the page and the response'sAccess-Control-Allow-Origin: *cannot leak it to another origin; - the custom
fancontrol://origin and Content-Security-Policy on Linux; - privileged process boundaries (the background daemon runs as root on Linux
and as SYSTEM/Administrator on Windows to access the EC; the desktop
dashboard runs unprivileged). Linux clients use
/run/fan-control/control.sockrestricted to thefan-controlgroup; Windows clients use the named pipe\\.\pipe\fan-control-rpc, whose DACL (D:P(A;;GA;;;SY)(A;;GA;;;BA)(A;;GRGW;;;IU)) grants SYSTEM and Administrators full control and Interactive Users read/write. That grant is what lets the unprivileged dashboard and tray drive the elevated daemon, so on Windows any interactive local user can issue RPC requests; every request is validated by the daemon, and EC writes are gated by the same checks as the CLI; the daemon accepts at most 32 concurrent RPC connections and closes further ones, so a local client cannot exhaust its threads by opening connections and idling; - configuration handling;
- EC read/write validation;
- firmware handoff;
- exclusive lock files under
/run/fan-control(Linux) or%PROGRAMDATA%\fan-control\run(Windows); - Unix domain socket framing and permissions (0660 root:fan-control) on Linux, and the Windows named pipe's DACL plus the unprivileged GUI's loopback-only HTTP listener.
On Linux there is no HTTP API and no TCP listener; the previous localhost
dashboard on port 4444 has been removed. On Windows the unprivileged GUI
serves the bundled dashboard over a loopback-only HTTP bridge, and the
daemon's control channel is the named pipe \\.\pipe\fan-control-rpc, with the
run directory naming the pipe endpoint (the literal pipe for the default name)
instead of a port. A loopback TCP
socket is used only by a Python build that has neither AF_UNIX nor pipes; it
is loopback-only and carries no additional authentication.
This is an unofficial community project without a guaranteed response SLA. Manufacturer support channels cannot provide support for this software.
The daemon owns /etc/fan-control.json on Linux and
%PROGRAMDATA%\fan-control\fan-control.json on Windows. Before its first write
after loading v1 configuration, it preserves fan-control.v1.backup.json
beside the original. It writes the replacement through a temporary file,
fsync, and atomic rename. A failed persistent mutation returns an error and
restores the in-memory configuration. GUI and CLI mutations share daemon
validation and optional revision checks.
Telemetry defaults to /var/lib/fan-control/history.db on Linux and
%PROGRAMDATA%\fan-control\data\history.db on Windows. Linux packaging creates
/var/lib/fan-control as 0750 root:fan-control. The standard Python SQLite
module is required. Persistence failures degrade analytics to memory history;
fan control continues. Retention is configurable in Settings. The daemon's
--data-dir or FAN_CONTROL_DATA_DIR can change the database directory. A
dashboard (or --headless-smoke) that runs against an explicit --runtime-dir
is a private instance and keeps its history inside that runtime directory.
A dashboard started as root refuses --config, --runtime-dir and --device:
pkexec and sudo pass the caller's arguments and strip the environment, and a
cached authorization is enough for any of that user's processes to run it as
root again, so those flags would let an unprivileged caller choose where root
reads and writes. The environment variables above remain the deliberate
channel. Demo state - the lock, the socket, the history database and the demo
config - lives in the demo runtime directory, which is per-user and, for root,
a fresh 0700 directory instead of a predictable name under /tmp.
This is the remedy the pkexec manual
prescribes for exactly this shape of
program: "pkexec does no validation of the ARGUMENTS passed to PROGRAM ...
However, if an action is used for which the user can retain authorization (or
if the user is implicitly authorized) this could be a security hole. Therefore,
as a rule of thumb, programs for which the default required authorization is
changed, should never implicitly trust user input (e.g. like any other
well-written suid program)." The shipped action has a custom exec.path,
retains authorization (auth_admin_keep) and carries the
allow_gui annotation that the same manual calls discouraged, so it matches
every condition that sentence names. The manual also documents what reaches the
process: the environment is "a minimal known and safe environment" plus
PKEXEC_UID - with one exception this action takes, since allow_gui is
exactly what retains the caller's $DISPLAY and $XAUTHORITY. The arguments
are therefore the only caller-controlled path input, which is what the refusal
above is about.
Automation changes effective runtime policy without overwriting saved mode or profile. Critical protection takes precedence over automatic overlays, fan tests, and duty limits. Explicit EC Auto returns control to firmware.
Command execution is not shipped. RPC rejects run_command rules with
UNSUPPORTED, and the daemon never launches their command references. Future
support requires an administrator-owned allowlist, absolute executables, argv
arrays, a sanitized environment, time/output/concurrency limits, and auditing.
Diagnostics copy/export omits configuration and database paths and raw sensor records. Review remaining warnings before sharing, since backend error text may contain system-specific details.
The optional scripts/ui-demo.py acceptance harness binds only to loopback
and uses an isolated simulated controller. It is a development tool and is
not installed as an application service.