Standalone support for the ThermalGrizzly WireView Pro II without
depending on the external wireview_reporter exporter:
- wireview.py: serial protocol (STX/ETX + 16-bit CRC16-CCITT) with
vendor-data product identification, config version, UID, build
string, screen layout, and temperature/power/current sensor reads;
hwmon fallback with per-channel index resolution; udev-based
detection (vendor 0x2560 / product 0x0101) with fallback port
probing; serial read timeout and watchdog reconnect
- server.py: startup detection (root only), 1 Hz poller gated on
subscribed clients, WS 'wireview' channel, GET /api/wireview,
rejected-product memoization, stale-read race guard
- frontend: WireView tab (visible when a device is detected) with
live temperature/power/current cards, sparkline history, and
device info; nullable temp channels
- tests: 76 tests covering parser, CRC, fault classification,
hwmon resolution, JSON safety, and the serial transport against
a pty-based fake device
Verified live: tab appears with the device connected, disappears
when unplugged, reconnects on re-plug, no serial traffic when idle.
Adds an experimental power-cap mode using the undocumented RM ioctl
interface (based on panchovix's LACT PR #1205) to set power limits
below the VBIOS minimum (down to 30 W).
- hal/rm_power.py: RM ioctl power-cap read/write/reset + runtime probe
- limits.py: power_cap_mode (nvml/ioctl) with support detection
- config.py: persist power_cap_mode per GPU
- profiles: record/apply power_cap_mode
- server.py: POST /api/limits validates ioctl support (409 on failure)
- cli.py: profile save falls back to persisted mode
- client.py: power_cap_mode in Limits
- frontend: toggle + warning with panchovix attribution (LACT #1205)
- tests: test_rm_power.py (unit) + integration coverage
- Makefile: add test_rm_power.py to make test
Also includes automated linter reformatting (prettier, ruff, shellcheck,
isort, markdownlint) that the linter would apply anyway.
Security review findings, fixed and verified:
Critical
- Fix unauthenticated arbitrary file read: the SPA catch-all route
joined the raw URL path onto the dist dir without containment, so
encoded '..' segments (/%2e%2e/etc/passwd) leaked any file readable
by the root server. Resolve with realpath and reject paths outside
the dist dir (fail-closed 404).
High
- Daemon socket: serve_start no longer accepts caller-chosen
host/port. The socket is world-connectable (unprivileged CLI users),
so callers could previously rebind the root web server to 0.0.0.0.
The daemon now always binds the operator-configured address and
reports it in the response; the CLI warns on mismatch.
Medium
- Remove the per-request max_delta_khz override from the API: the
server-enforced safety cap is now authoritative. CLI direct paths
(write, profile apply, verify) honor the configured cap; --max-delta
still overrides for explicit root use.
- Snapshot restore: confine filepath to the snapshot directory
(realpath containment; blocks symlink escapes).
- Login lockout: honor X-Forwarded-For only for peers listed in the
new trusted_proxies config (rightmost untrusted hop), so the
per-IP lockout works behind a reverse proxy. Spoofed headers from
untrusted peers are ignored.
- /api/shutdown: new allow_api_shutdown config (default true);
shared systems can disable the API shutdown path.
TLS (opt-in, like auth)
- New ssl_certfile/ssl_keyfile config + CLI flags (serve start,
service install/configure, --no-ssl to disable). When active:
HTTPS for UI/API, wss:// for WebSockets, Secure session cookie,
CLI auto-switches to https://. Cert/key paths are validated up
front with a clear error instead of a silent uvicorn crash.
Tests & docs
- tests/test_security.py: standalone regression tests (no new deps)
covering SPA containment, snapshot containment, cap removal,
client-IP derivation, proxy normalization, TLS scheme detection,
and daemon host/port hardening.
- README + Usage-Guide: TLS section, new config keys, updated
security notes.