Skip to content

feat: hardware sensor probe temp source + stable USB hwmon path resolution#3

Open
mrjacobarussell wants to merge 19 commits into
unraid:masterfrom
mrjacobarussell:corsair-commander-pro
Open

feat: hardware sensor probe temp source + stable USB hwmon path resolution#3
mrjacobarussell wants to merge 19 commits into
unraid:masterfrom
mrjacobarussell:corsair-commander-pro

Conversation

@mrjacobarussell

@mrjacobarussell mrjacobarussell commented Jun 9, 2026

Copy link
Copy Markdown

Summary

Two improvements for fan control with USB hwmon controllers (e.g. Corsair Commander Pro):

1. Hardware sensor probe temperature source

  • New -s <sensor_path> flag on autofan script reads temp from any hwmon temp*_input file instead of scanning drives via smartctl
  • Useful for case/ambient temp probes connected to controllers like the Corsair Commander Pro
  • Falls back to HDD scanning if sensor file not found

2. Stable hwmon path resolution across reboots

  • USB hwmon devices (Commander Pro, etc.) get a different hwmonN number each reboot depending on init order
  • rc.autofan now resolves the current hwmon directory by kernel device name at start/stop time
  • New hwmon_name cfg field (e.g. corsaircpro) — set it and paths are always correct regardless of reboot order

UI changes (FanSettings.page)

  • Temperature source dropdown: Hard drives (smartctl) / Hardware sensor probe
  • Temperature sensor picker: AJAX-populated dropdown of all available hwmon temp sensors with current readings
  • Stable device name field: maps to hwmon_name in cfg

Files changed

  • source/system-autofan/scripts/autofan — v1.7→v1.8, add -s flag
  • source/system-autofan/scripts/rc.autofan — add resolve_hwmon_in_string()
  • source/system-autofan/include/SystemFan.php — add list_temp() + op=temps endpoint
  • source/system-autofan/include/update.autofan.php — skip temp_source/hwmon_name from options
  • source/system-autofan/FanSettings.page — new UI fields
  • source/system-autofan/default.cfg — new field defaults

Test plan

  • With temp_source=sensor and valid sensor path, fan adjusts to probe temp not disk temp
  • With hwmon_name=corsaircpro, reboot — rc.autofan start resolves correct hwmon dir
  • With temp_source=hdd (default), existing behavior unchanged
  • Sensor picker populates in UI when switching to sensor mode
  • Detect/PWM detect still works

Summary by CodeRabbit

  • New Features

    • Hardware sensor probe as an alternate temperature source with selectable sensor dropdown
    • Stable hwmon device name support for consistent USB controller/fan resolution
    • PWM Auto Pairing to auto-detect and apply PWM↔fan matches; filtered fan picker and drive include/exclude mode
    • Startup/logs now report chosen temperature source
  • Bug Fixes

    • Fan detection limited to the selected controller chip to avoid cross-chip false matches
  • Documentation

    • README replaced with detailed “Enhanced Fork” guide including install, Corsair Commander Pro setup, and usage tips

Squidly271 and others added 3 commits November 18, 2025 00:20
- autofan: add -s <sensor_path> flag to read temp from hwmon probe
  instead of scanning drives via smartctl
- rc.autofan: resolve_hwmon_in_string() finds current hwmonN dir by
  kernel device name at start/stop time, fixing USB controllers that
  get a different hwmon number each reboot
- FanSettings.page: add Temperature source dropdown (HDD / sensor),
  sensor picker populated via AJAX, and Stable device name field
- SystemFan.php: add list_temp() + op=temps endpoint for sensor picker
- update.autofan.php: skip temp_source/hwmon_name from options string;
  temp_sensor maps to -s flag via prefix
- default.cfg: add temp_source/temp_sensor/hwmon_name defaults

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jun 9, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds hwmon temperature-sensor support and stable hwmon-name resolution across UI, PHP API, runtime scripts, and rc wiring; introduces autopair PWM↔fan pairing, drive include/exclude mode, new config keys, updated option assembly, and plugin/README metadata for the Enhanced Fork.

Changes

Hardware Sensor Support and Stable Device Naming

Layer / File(s) Summary
Config defaults & SystemFan sensor/fan APIs
source/system-autofan/default.cfg, source/system-autofan/include/SystemFan.php
Add temp_source, temp_sensor, hwmon_name; implement list_temp() and structured list_fan(); tighten detect to selected hwmon chip; add ?op=temps and ?op=autopair.
Frontend UI: FanSettings page
source/system-autofan/FanSettings.page
Server-side list_fan_page() and client JS add loadTempSensors(), toggleTempSource(), autoPair()/applyPair(), switch to select[name=fan], add filter_mode label updates, temp_sensor persistence, and hwmon_name input.
Update option assembly
source/system-autofan/include/update.autofan.php
Exclude UI-only POST keys from generic options building; append drive-filter CLI flag using filter_mode (-e vs -I) and drive list.
Autofan runtime script
source/system-autofan/scripts/autofan
Bump version, add -s and -I CLI flags and parsing, support INCLUDE, derive DRIVES from INCLUDE/EXCLUDE/ALL, implement sensor reading dispatcher function_get_temp(), and log temperature source.
RC script hwmon resolution
source/system-autofan/scripts/rc.autofan
Add resolve_hwmon() and resolve_hwmon_in_string(); resolve saved controller/fan paths at start/stop/speed to current hwmon sysfs paths before invoking autofan actions.
Plugin metadata and READMEs
unRAIDv6/dynamix.system.autofan.plg, README.md, source/system-autofan/README.md
Update plugin XML entities (author, version, pluginURL, MD5), pin release URL, add post-install message, and replace/expand README(s) documenting the Enhanced Fork and Corsair Commander Pro setup.

Sequence Diagram(s)

sequenceDiagram
  participant FanUI as FanSettings.page
  participant API as SystemFan.php
  participant RC as rc.autofan
  participant Autofan as autofan
  participant Sysfs as /sys/devices
  FanUI->>API: POST op=autopair
  API->>RC: stop rc.autofan
  API->>Sysfs: scan pwm* and fan*_input per hwmon chip
  loop per pwm
    API->>Autofan: set pwm value
    Autofan->>Sysfs: sample fan*_input rpm
    Sysfs-->>Autofan: rpm sample
    Autofan-->>API: rpm delta
  end
  API->>RC: start rc.autofan
  API-->>FanUI: return JSON pairs
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 I hopped through sysfs, found each name,
I counted millidegrees and paired each fan,
USB hwmons now settle in place,
Autopair clicked true — no more chase,
Cheers from the rabbit: hum, hop, and scan.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title follows conventional commits format with 'feat:' prefix and accurately summarizes the two main features added: hardware sensor probe temperature source and stable USB hwmon path resolution.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
✨ Simplify code
  • Create PR with simplified code

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

mrjacobarussell and others added 2 commits June 9, 2026 14:09
Cross-chip RPM fluctuation caused detect to return a motherboard fan
when a USB controller (Corsair Commander Pro) PWM was selected.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🧹 Nitpick comments (3)
unRAIDv6/dynamix.system.autofan.plg (2)

8-8: ⚡ Quick win

Consider using a tagged release for the pluginURL.

The pluginURL points to a specific branch (corsair-commander-pro) rather than a tagged release or master. Branch-based URLs are mutable and can change or be deleted, which reduces installation stability and reproducibility.

For production deployments, consider:

  • Creating a tagged release (e.g., v2025.06.09) and updating the URL to point to that tag
  • Or, if this is intentional for a development fork, document this in the plugin metadata or comments
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@unRAIDv6/dynamix.system.autofan.plg` at line 8, The pluginURL ENTITY
currently points to a branch URL ("corsair-commander-pro") which is mutable;
update the <!ENTITY pluginURL ...> value to reference a specific git tag/release
(e.g., replace "corsair-commander-pro" with a release tag like "vYYYY.MM.DD" or
a commit SHA) to ensure reproducible installs, or if you intend to track the
branch, add a comment or metadata near the pluginURL explaining this is a
development branch and may change.

16-23: ⚡ Quick win

Clarify the version timeline in the changelog.

The changelog lists 2025.06.09 before 2025.11.14 (upstream), which creates confusion:

  • Date-wise, June comes before November, suggesting 2025.06.09 is older
  • But the placement suggests 2025.06.09 is the "newer" enhanced fork entry

Consider adding clarifying text such as:

  • If this fork diverged from 2025.11.14 upstream: "Forked from upstream 2025.11.14"
  • If these are parallel timelines: Explain the relationship between the fork and upstream versions
  • Or adjust the versioning to avoid date-based comparison issues (see earlier comment about version numbers)

This will help users understand the lineage and which version to expect.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@unRAIDv6/dynamix.system.autofan.plg` around lines 16 - 23, Update the
changelog header block under "##Dynamix System Autofan (Enhanced Fork)" to
clarify lineage between the two entries: explicitly state whether "2025.06.09"
is the enhanced fork (e.g., "2025.06.09 — Enhanced fork, changes since
upstream") and whether "2025.11.14 (upstream)" is the upstream baseline (e.g.,
"Forked from upstream 2025.11.14"), or reorder the entries so chronological
order is clear; modify the text lines containing "2025.06.09" and "2025.11.14
(upstream)" to include a short clarifying phrase such as "Forked from upstream"
or "Enhanced fork" to remove ambiguity.
source/system-autofan/include/SystemFan.php (1)

36-56: ⚡ Quick win

Consider refactoring end(explode(...)) for PHP 7.4+ compatibility.

Lines 44 and 48 use end(explode('/', ...)) which passes a temporary value to end(). While functional, this generates strict standards warnings in PHP 7.4+ since end() expects a reference.

♻️ Proposed refactor
-      $label = is_file($label_file) ? trim(file_get_contents($label_file)) : end(explode('/',$temp));
+      $label = is_file($label_file) ? trim(file_get_contents($label_file)) : basename($temp);
-        'name'   => end(explode('/',$temp)),
+        'name'   => basename($temp),

This also applies to line 32 in list_fan():

-    if ($name) foreach (preg_grep("/fan\d+_input/", scan_dir($chip)) as $fan) $out[] = ['chip'=>trim($name), 'name'=>end(explode('/',$fan)), 'sensor'=>$fan , 'rpm'=>intval(file_get_contents($fan))];
+    if ($name) foreach (preg_grep("/fan\d+_input/", scan_dir($chip)) as $fan) $out[] = ['chip'=>trim($name), 'name'=>basename($fan), 'sensor'=>$fan , 'rpm'=>intval(file_get_contents($fan))];
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@source/system-autofan/include/SystemFan.php` around lines 36 - 56, Refactor
occurrences of end(explode('/', ...)) in list_temp (and list_fan) to avoid
passing a temporary to end(): split the string into a variable first (e.g.
$parts = explode('/', $temp) or $parts = explode('/', $somePath)) then use
end($parts) or array_pop($parts) to get the last element, or simply use
basename($temp) where appropriate; update the 'name' and 'label' assignments in
function list_temp (and the analogous place in list_fan) to use the temporary
array or basename instead of end(explode(...)).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@README.md`:
- Line 1: Update README.md to remove the "archived" message and instead document
this repo as an enhanced active fork of the upstream Dynamix project: mention
it's an enhanced fork of https://github.com/unraid/dynamix, summarize the
enhanced features (hardware sensor probe temperature sources, stable hwmon
naming for USB controllers, UI improvements), add installation instructions for
this fork, and include an "Upstream" link to https://github.com/unraid/dynamix;
also ensure the README's installation/link text matches the .plg file reference
to mrjacobarussell/dynamix so users aren't confused by inconsistent install
sources.

In `@source/system-autofan/FanSettings.page`:
- Around line 122-139: The dropdown construction in loadTempSensors concatenates
item.chip, item.label, item.temp and item.sensor directly into HTML; to fix,
build options using safe DOM APIs instead of string concatenation (e.g., create
option elements with jQuery: $('<option>') and set .val(...) and .text(...) and
.prop('selected', ...) or otherwise HTML-escape values) for the selector
'`#temp_sensor_select`' so that item.chip, item.label, item.temp and item.sensor
are not injected raw; keep the existing logic that sets
$('input[name=temp_sensor]').val(sel.val() || '') and the change handler intact.

In `@source/system-autofan/scripts/autofan`:
- Around line 221-231: In function_get_sensor_temp, validate TEMP_SENSOR before
reading: ensure the path is absolute and resides under /sys (e.g., starts with
/sys/), confirm it is a regular file and readable (and optionally not a symlink
or resolves to a path under /sys via realpath), and only then cat it; if
validation fails, set HIGHEST_TEMP=0 and log the same warning via program_name.
Reference TEMP_SENSOR, HIGHEST_TEMP, program_name and function_get_sensor_temp
when adding these checks.

In `@source/system-autofan/scripts/rc.autofan`:
- Around line 31-39: In resolve_hwmon(), grep is currently invoked with a regex
match (grep -rlx "$name") which can misinterpret device names containing regex
metacharacters; change the grep invocation to use fixed-string matching (add the
-F flag) so it treats "$name" literally (e.g., use grep with -F -r -l -x) to
reliably locate the matching /sys/class/hwmon/*/name file.
- Around line 87-95: In autofan.speed(), guard reads of $fan_path by checking
the file exists (and/or is readable) before using cat; update the conditional
that currently uses [[ -f $rc && $(cat $fan_path) -gt 0 ]] to first test [[ -f
$rc && -f $fan_path ]] (or -r for readability), then only perform the numeric
cat on $fan_path when that check passes, otherwise fall back to echoing "0";
ensure you reference the autofan.speed function and the variables rc and
fan_path so the change is applied to the correct conditional.

In `@unRAIDv6/dynamix.system.autofan.plg`:
- Line 6: The version entity value was lowered from "2025.11.14" to
"2025.06.09", which will cause downgrade/rejection issues; update the <!ENTITY
version ...> value in dynamix.system.autofan.plg to preserve monotonic ordering
by either bumping from the upstream base (e.g., change to "2025.11.14.1") or
append a fork suffix to the actual date (e.g., "2025.06.09-fork.1"), so the XML
entity named version reflects a higher/ordered version string that plugin
managers will accept.

---

Nitpick comments:
In `@source/system-autofan/include/SystemFan.php`:
- Around line 36-56: Refactor occurrences of end(explode('/', ...)) in list_temp
(and list_fan) to avoid passing a temporary to end(): split the string into a
variable first (e.g. $parts = explode('/', $temp) or $parts = explode('/',
$somePath)) then use end($parts) or array_pop($parts) to get the last element,
or simply use basename($temp) where appropriate; update the 'name' and 'label'
assignments in function list_temp (and the analogous place in list_fan) to use
the temporary array or basename instead of end(explode(...)).

In `@unRAIDv6/dynamix.system.autofan.plg`:
- Line 8: The pluginURL ENTITY currently points to a branch URL
("corsair-commander-pro") which is mutable; update the <!ENTITY pluginURL ...>
value to reference a specific git tag/release (e.g., replace
"corsair-commander-pro" with a release tag like "vYYYY.MM.DD" or a commit SHA)
to ensure reproducible installs, or if you intend to track the branch, add a
comment or metadata near the pluginURL explaining this is a development branch
and may change.
- Around line 16-23: Update the changelog header block under "##Dynamix System
Autofan (Enhanced Fork)" to clarify lineage between the two entries: explicitly
state whether "2025.06.09" is the enhanced fork (e.g., "2025.06.09 — Enhanced
fork, changes since upstream") and whether "2025.11.14 (upstream)" is the
upstream baseline (e.g., "Forked from upstream 2025.11.14"), or reorder the
entries so chronological order is clear; modify the text lines containing
"2025.06.09" and "2025.11.14 (upstream)" to include a short clarifying phrase
such as "Forked from upstream" or "Enhanced fork" to remove ambiguity.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: fa820481-4b98-4cd2-b6cd-fa471f6f1159

📥 Commits

Reviewing files that changed from the base of the PR and between 717fb24 and 62e00fa.

⛔ Files ignored due to path filters (1)
  • source/system-autofan/icons/dynamix.system.autofan.png is excluded by !**/*.png
📒 Files selected for processing (9)
  • README.md
  • archive/dynamix.system.autofan.txz
  • source/system-autofan/FanSettings.page
  • source/system-autofan/default.cfg
  • source/system-autofan/include/SystemFan.php
  • source/system-autofan/include/update.autofan.php
  • source/system-autofan/scripts/autofan
  • source/system-autofan/scripts/rc.autofan
  • unRAIDv6/dynamix.system.autofan.plg

Comment thread README.md Outdated
Comment thread source/system-autofan/FanSettings.page
Comment on lines +221 to +231
function_get_sensor_temp() {
# Read temperature from hwmon sensor file (values are in millidegrees Celsius)
local raw
raw=$(cat "$TEMP_SENSOR" 2>/dev/null)
if [[ -n $raw ]]; then
HIGHEST_TEMP=$((raw/1000))
else
HIGHEST_TEMP=0
echo "$program_name: warning: could not read temp sensor $TEMP_SENSOR" | logger -t$program_name
fi
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add path validation for TEMP_SENSOR to prevent potential path traversal.

The function reads from $TEMP_SENSOR without validating that the path is under /sys. While the configuration file is root-controlled, defense-in-depth principles suggest validating that sensor paths are legitimate hwmon sensor files.

🛡️ Proposed validation
 function_get_sensor_temp() {
   # Read temperature from hwmon sensor file (values are in millidegrees Celsius)
+  # Validate that sensor path is under /sys/devices
+  case "$TEMP_SENSOR" in
+    /sys/devices/*) ;;
+    *) echo "$program_name: error: sensor path must be under /sys/devices" | logger -t$program_name; HIGHEST_TEMP=0; return ;;
+  esac
   local raw
   raw=$(cat "$TEMP_SENSOR" 2>/dev/null)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function_get_sensor_temp() {
# Read temperature from hwmon sensor file (values are in millidegrees Celsius)
local raw
raw=$(cat "$TEMP_SENSOR" 2>/dev/null)
if [[ -n $raw ]]; then
HIGHEST_TEMP=$((raw/1000))
else
HIGHEST_TEMP=0
echo "$program_name: warning: could not read temp sensor $TEMP_SENSOR" | logger -t$program_name
fi
}
function_get_sensor_temp() {
# Read temperature from hwmon sensor file (values are in millidegrees Celsius)
# Validate that sensor path is under /sys/devices
case "$TEMP_SENSOR" in
/sys/devices/*) ;;
*) echo "$program_name: error: sensor path must be under /sys/devices" | logger -t$program_name; HIGHEST_TEMP=0; return ;;
esac
local raw
raw=$(cat "$TEMP_SENSOR" 2>/dev/null)
if [[ -n $raw ]]; then
HIGHEST_TEMP=$((raw/1000))
else
HIGHEST_TEMP=0
echo "$program_name: warning: could not read temp sensor $TEMP_SENSOR" | logger -t$program_name
fi
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@source/system-autofan/scripts/autofan` around lines 221 - 231, In
function_get_sensor_temp, validate TEMP_SENSOR before reading: ensure the path
is absolute and resides under /sys (e.g., starts with /sys/), confirm it is a
regular file and readable (and optionally not a symlink or resolves to a path
under /sys via realpath), and only then cat it; if validation fails, set
HIGHEST_TEMP=0 and log the same warning via program_name. Reference TEMP_SENSOR,
HIGHEST_TEMP, program_name and function_get_sensor_temp when adding these
checks.

Comment on lines +31 to +39
# Find the current hwmon directory for a device by its kernel name.
# hwmon numbers (hwmon0, hwmon1, ...) can change each reboot for USB devices.
# If hwmon_name is set in cfg, this resolves the correct current path.
resolve_hwmon() {
local name="$1"
local match
match=$(grep -rlx "$name" /sys/class/hwmon/*/name 2>/dev/null | head -1)
[[ -n $match ]] && dirname "$match"
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Use grep -F for literal string matching to prevent regex interpretation.

Line 37 uses grep -rlx "$name" which interprets $name as a regex pattern. If the hwmon device name contains regex metacharacters (e.g., corsair.cpro with a dot), it could match unintended files.

🔧 Proposed fix
 resolve_hwmon() {
   local name="$1"
   local match
-  match=$(grep -rlx "$name" /sys/class/hwmon/*/name 2>/dev/null | head -1)
+  match=$(grep -rFx "$name" /sys/class/hwmon/*/name 2>/dev/null | head -1)
   [[ -n $match ]] && dirname "$match"
 }

The -F flag treats the pattern as a fixed string rather than a regex.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@source/system-autofan/scripts/rc.autofan` around lines 31 - 39, In
resolve_hwmon(), grep is currently invoked with a regex match (grep -rlx
"$name") which can misinterpret device names containing regex metacharacters;
change the grep invocation to use fixed-string matching (add the -F flag) so it
treats "$name" literally (e.g., use grep with -F -r -l -x) to reliably locate
the matching /sys/class/hwmon/*/name file.

Comment on lines 87 to 95
autofan.speed() {
for cfg in $cfg_path/$plugin*.cfg; do
source $cfg
[[ -f $controller && $(cat $fan) -gt 0 ]] && echo -n "$(($(($(cat $controller)*100))/255)) " || echo -n "0 "
local rc fan_path
rc=$(resolve_hwmon_in_string "$controller" "$hwmon_name" "$controller")
fan_path=$(resolve_hwmon_in_string "$fan" "$hwmon_name" "$controller")
[[ -f $rc && $(cat $fan_path) -gt 0 ]] && echo -n "$(($(($(cat $rc)*100))/255)) " || echo -n "0 "
done
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Add file existence check for $fan_path before reading.

Line 93 reads from $fan_path without checking if the file exists. If the resolved fan path is invalid or the device has been unplugged, cat $fan_path will fail and produce an error.

🔧 Proposed fix
 autofan.speed() {
   for cfg in $cfg_path/$plugin*.cfg; do
     source $cfg
     local rc fan_path
     rc=$(resolve_hwmon_in_string "$controller" "$hwmon_name" "$controller")
     fan_path=$(resolve_hwmon_in_string "$fan" "$hwmon_name" "$controller")
-    [[ -f $rc && $(cat $fan_path) -gt 0 ]] && echo -n "$(($(($(cat $rc)*100))/255)) " || echo -n "0 "
+    [[ -f $rc && -f $fan_path && $(cat $fan_path) -gt 0 ]] && echo -n "$(($(($(cat $rc)*100))/255)) " || echo -n "0 "
   done
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
autofan.speed() {
for cfg in $cfg_path/$plugin*.cfg; do
source $cfg
[[ -f $controller && $(cat $fan) -gt 0 ]] && echo -n "$(($(($(cat $controller)*100))/255)) " || echo -n "0 "
local rc fan_path
rc=$(resolve_hwmon_in_string "$controller" "$hwmon_name" "$controller")
fan_path=$(resolve_hwmon_in_string "$fan" "$hwmon_name" "$controller")
[[ -f $rc && $(cat $fan_path) -gt 0 ]] && echo -n "$(($(($(cat $rc)*100))/255)) " || echo -n "0 "
done
}
autofan.speed() {
for cfg in $cfg_path/$plugin*.cfg; do
source $cfg
local rc fan_path
rc=$(resolve_hwmon_in_string "$controller" "$hwmon_name" "$controller")
fan_path=$(resolve_hwmon_in_string "$fan" "$hwmon_name" "$controller")
[[ -f $rc && -f $fan_path && $(cat $fan_path) -gt 0 ]] && echo -n "$(($(($(cat $rc)*100))/255)) " || echo -n "0 "
done
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@source/system-autofan/scripts/rc.autofan` around lines 87 - 95, In
autofan.speed(), guard reads of $fan_path by checking the file exists (and/or is
readable) before using cat; update the conditional that currently uses [[ -f $rc
&& $(cat $fan_path) -gt 0 ]] to first test [[ -f $rc && -f $fan_path ]] (or -r
for readability), then only perform the numeric cat on $fan_path when that check
passes, otherwise fall back to echoing "0"; ensure you reference the
autofan.speed function and the variables rc and fan_path so the change is
applied to the correct conditional.

Comment thread unRAIDv6/dynamix.system.autofan.plg Outdated
<!ENTITY author "Bergware/gfjardim">
<!ENTITY version "2025.11.14">
<!ENTITY author "Bergware/gfjardim/mrjacobarussell">
<!ENTITY version "2025.06.09">

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Critical: Version number appears to downgrade.

The version changes from 2025.11.14 to 2025.06.09, which represents a backward movement in date-based versioning. Plugin management systems typically compare version numbers lexicographically or numerically to determine update eligibility. A lower version number will:

  • Prevent automatic updates on systems running 2025.11.14 (if it exists)
  • Potentially be rejected by plugin installers expecting monotonically increasing versions
  • Confuse users about which version is newer

If 2025.06.09 represents the actual release date of this enhanced fork and 2025.11.14 was the upstream version, consider using a different versioning scheme that maintains ordering, such as 2025.06.09-fork.1 or 2025.11.14.1 (incrementing from the upstream base).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@unRAIDv6/dynamix.system.autofan.plg` at line 6, The version entity value was
lowered from "2025.11.14" to "2025.06.09", which will cause downgrade/rejection
issues; update the <!ENTITY version ...> value in dynamix.system.autofan.plg to
preserve monotonic ordering by either bumping from the upstream base (e.g.,
change to "2025.11.14.1") or append a fork suffix to the actual date (e.g.,
"2025.06.09-fork.1"), so the XML entity named version reflects a higher/ordered
version string that plugin managers will accept.

mrjacobarussell and others added 2 commits June 9, 2026 15:25
- autofan: add -I <drives> flag for include-only mode
- FanSettings.page: Drive filter mode dropdown (Exclude/Include)
  with dynamic label update
- update.autofan.php: build -e or -I flag based on filter_mode
- default.cfg: add filter_mode="exclude" default

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
source/system-autofan/scripts/autofan (1)

227-245: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Sensor-read failure currently collapses to 0°C instead of falling back to disk temperature.

If the sensor file exists but returns unreadable/non-numeric content, HIGHEST_TEMP becomes 0, which can incorrectly reduce fan speed. Also, log labels use TEMP_SENSOR presence instead of actual source used. Make sensor read return a status and fallback to function_get_highest_hd_temp on failure, while tracking the real source for logs.

💡 Suggested patch
 function_get_sensor_temp() {
   # Read temperature from hwmon sensor file (values are in millidegrees Celsius)
   local raw
   raw=$(cat "$TEMP_SENSOR" 2>/dev/null)
-  if [[ -n $raw ]]; then
-    HIGHEST_TEMP=$((raw/1000))
-  else
-    HIGHEST_TEMP=0
-    echo "$program_name: warning: could not read temp sensor $TEMP_SENSOR" | logger -t$program_name
-  fi
+  if [[ $raw =~ ^[0-9]+$ ]]; then
+    HIGHEST_TEMP=$((raw/1000))
+    return 0
+  fi
+  echo "$program_name: warning: could not read temp sensor $TEMP_SENSOR; falling back to disk temps" | logger -t$program_name
+  return 1
 }
 
 function_get_temp() {
+  TEMP_SOURCE_LABEL="disk"
   if [[ -n $TEMP_SENSOR && -f $TEMP_SENSOR ]]; then
-    function_get_sensor_temp
-  else
-    function_get_highest_hd_temp
+    if function_get_sensor_temp; then
+      TEMP_SOURCE_LABEL="sensor (${TEMP_SENSOR##*/})"
+      return
+    fi
   fi
+  function_get_highest_hd_temp
 }
@@
-    if [[ -n $TEMP_SENSOR ]]; then
-      TEMP_SOURCE_LABEL="sensor (${TEMP_SENSOR##*/})"
-    else
-      TEMP_SOURCE_LABEL="disk"
-    fi
     echo "$program_name: Highest $TEMP_SOURCE_LABEL temp is ${REPORTED_TEMP}${TEMP_UNIT}, adjusting fan speed from: $CURRENT_OUTPUT to: $ADJUSTED_OUTPUT"
     echo "Highest $TEMP_SOURCE_LABEL temp is ${REPORTED_TEMP}${TEMP_UNIT}, adjusting fan speed from: $CURRENT_OUTPUT to: $ADJUSTED_OUTPUT" | logger -t$program_name
@@
-if [[ -n $TEMP_SENSOR ]]; then
-echo "$program_name using sensor probe $TEMP_SENSOR for temperature source" | logger -t$program_name
+if [[ -n $TEMP_SENSOR ]]; then
+echo "$program_name sensor probe configured: $TEMP_SENSOR" | logger -t$program_name
 fi

Also applies to: 314-318, 377-379

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@source/system-autofan/scripts/autofan` around lines 227 - 245,
function_get_sensor_temp should not silently set HIGHEST_TEMP=0 on
unreadable/non-numeric sensor contents; instead make it return a failure status
and avoid modifying HIGHEST_TEMP so caller can fall back to disk temp. Update
function_get_sensor_temp to validate that raw is numeric (after trimming), set
HIGHEST_TEMP only when valid, and return 0 on success / non-zero on failure;
update function_get_temp to call function_get_sensor_temp and on non-zero return
call function_get_highest_hd_temp to populate HIGHEST_TEMP. Also change log
messages to reference the actual source used (TEMP_SENSOR vs disk) and include
program_name in the log. Reference symbols: TEMP_SENSOR, HIGHEST_TEMP,
function_get_sensor_temp, function_get_temp, function_get_highest_hd_temp,
program_name.
source/system-autofan/include/SystemFan.php (1)

29-29: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Fix find patterns to match multi-digit sensor numbers.

Both list_fan() (line 29) and list_temp() (line 38) use find patterns that only match single-digit sensor numbers: fan[0-9]_input and temp[0-9]_input. These patterns will miss fan10_input, temp10_input, and higher-numbered sensors. However, the subsequent preg_grep filters on lines 32 and 42 use \d+, which does match multi-digit numbers.

The result: if a hwmon chip has only temp10_input (or fan10_input) with no lower-numbered sensors, the find command will not include that chip's directory in $chips, and those sensors will never appear in the UI dropdown.

🔧 Proposed fix
-  exec("find /sys/devices -type f -iname 'fan[0-9]_input' -exec dirname \"{}\" +|uniq", $chips);
+  exec("find /sys/devices -type f -iname 'fan[0-9]*_input' -exec dirname \"{}\" +|uniq", $chips);
-  exec("find /sys/devices -type f -name 'temp[0-9]_input' -exec dirname \"{}\" +|uniq", $chips);
+  exec("find /sys/devices -type f -name 'temp[0-9]*_input' -exec dirname \"{}\" +|uniq", $chips);

Also applies to: 38-38

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@source/system-autofan/include/SystemFan.php` at line 29, The find patterns in
list_fan() and list_temp() only match single-digit sensors; update the exec()
calls that search for "fan[0-9]_input" and "temp[0-9]_input" so they match
multi-digit sensor names (e.g. use glob patterns like "fan[0-9][0-9]*_input" and
"temp[0-9][0-9]*_input"); change the pattern passed to exec() in the list_fan
and list_temp functions so chips containing only fan10_input/temp10_input are
included and the existing preg_grep(\d+) filtering continues to work as before.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@source/system-autofan/include/SystemFan.php`:
- Line 29: The find patterns in list_fan() and list_temp() only match
single-digit sensors; update the exec() calls that search for "fan[0-9]_input"
and "temp[0-9]_input" so they match multi-digit sensor names (e.g. use glob
patterns like "fan[0-9][0-9]*_input" and "temp[0-9][0-9]*_input"); change the
pattern passed to exec() in the list_fan and list_temp functions so chips
containing only fan10_input/temp10_input are included and the existing
preg_grep(\d+) filtering continues to work as before.

In `@source/system-autofan/scripts/autofan`:
- Around line 227-245: function_get_sensor_temp should not silently set
HIGHEST_TEMP=0 on unreadable/non-numeric sensor contents; instead make it return
a failure status and avoid modifying HIGHEST_TEMP so caller can fall back to
disk temp. Update function_get_sensor_temp to validate that raw is numeric
(after trimming), set HIGHEST_TEMP only when valid, and return 0 on success /
non-zero on failure; update function_get_temp to call function_get_sensor_temp
and on non-zero return call function_get_highest_hd_temp to populate
HIGHEST_TEMP. Also change log messages to reference the actual source used
(TEMP_SENSOR vs disk) and include program_name in the log. Reference symbols:
TEMP_SENSOR, HIGHEST_TEMP, function_get_sensor_temp, function_get_temp,
function_get_highest_hd_temp, program_name.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 4d01909e-cf46-4001-8ab8-0937c847c582

📥 Commits

Reviewing files that changed from the base of the PR and between 62e00fa and d716869.

📒 Files selected for processing (7)
  • archive/dynamix.system.autofan.txz
  • source/system-autofan/FanSettings.page
  • source/system-autofan/default.cfg
  • source/system-autofan/include/SystemFan.php
  • source/system-autofan/include/update.autofan.php
  • source/system-autofan/scripts/autofan
  • unRAIDv6/dynamix.system.autofan.plg
🚧 Files skipped from review as they are similar to previous changes (3)
  • source/system-autofan/default.cfg
  • source/system-autofan/include/update.autofan.php
  • unRAIDv6/dynamix.system.autofan.plg

mrjacobarussell and others added 12 commits June 9, 2026 15:39
Fan picker now shows only fans from the same hwmon chip as the
selected PWM controller. Detect button auto-selects the matched
fan in the dropdown instead of filling a text field.
Falls back to all fans if chip has no fan sensors.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… setup

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ixes

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- autofan: rename program_name to "MRJAR autofan"
- SystemFan.php: add op=autopair — cycles all PWMs on selected chip,
  finds matching fan by RPM response, returns JSON array of pairs
- FanSettings.page: Auto Pair button next to PWM controller dropdown,
  shows results table with Apply button per pair

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Set icon attribute to fan icon (was showing generic plugin manager icon)
- Package now includes images/dynamix.system.autofan.png and README.md
  (Plugins page renders README.md as the description)
- Author field now credits mrjacobarussell as fork maintainer
- Added support link to GitHub issues

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Avoid creating a new GitHub release; reuse the existing release's
asset slot for the rebuilt txz.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
source/system-autofan/scripts/autofan (1)

180-187: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Critical: drive filtering breaks with multiple drives.

Lines 182 and 184 use command substitution $(echo $INCLUDE | sed ...) to convert comma-separated drive lists into patterns for grep. However, command substitution collapses newlines into spaces, so a multi-drive list like "sda,sdb" becomes the literal pattern "/dev/sda sdb" instead of matching individual drives.

Example failure:

  • INCLUDE="sda,sdb"
  • After substitution: grep -F "/dev/sda /dev/sdb" (single literal pattern)
  • Input: "/dev/sda\n/dev/sdb"
  • Result: no match (expects literal "/dev/sda /dev/sdb" on one line)

Both include (line 182) and exclude (line 184) logic exhibit this bug. Single-drive filters work, but multi-drive filters silently fail, causing incorrect temperature monitoring.

🐛 Proposed fix using proper multi-pattern grep
 # Get list of drives to monitor
 ALL_DRIVES=$(ls /dev/{[hs]d*[a-z],nvme[0-9]} 2>/dev/null | grep -v "$flash")
 if [[ -n $INCLUDE ]]; then
-  DRIVES=$(echo "$ALL_DRIVES" | grep -F "$(echo $INCLUDE | sed -e $'s/,/\\\n/g' | sed -e 's/[np][0-9]//g' | sed 's|^|/dev/|')")
+  # Build grep -e patterns for each included drive
+  include_pattern=""
+  for drive in $(echo $INCLUDE | tr ',' ' '); do
+    drive_clean=$(echo $drive | sed -e 's/[np][0-9]//g')
+    include_pattern="$include_pattern -e /dev/$drive_clean"
+  done
+  DRIVES=$(echo "$ALL_DRIVES" | grep -F $include_pattern)
 elif [[ -n $EXCLUDE ]]; then
-  DRIVES=$(echo "$ALL_DRIVES" | grep -v "/dev/$(echo $EXCLUDE | sed -e $'s/,/\\\n/g' | sed -e 's/[np][0-9]//g')")
+  # Build grep -v -e patterns for each excluded drive
+  exclude_pattern=""
+  for drive in $(echo $EXCLUDE | tr ',' ' '); do
+    drive_clean=$(echo $drive | sed -e 's/[np][0-9]//g')
+    exclude_pattern="$exclude_pattern -e /dev/$drive_clean"
+  done
+  DRIVES=$(echo "$ALL_DRIVES" | grep -v $exclude_pattern)
 else
   DRIVES=$ALL_DRIVES
 fi

Alternatively, use extended regex with grep -E:

 if [[ -n $INCLUDE ]]; then
-  DRIVES=$(echo "$ALL_DRIVES" | grep -F "$(echo $INCLUDE | sed -e $'s/,/\\\n/g' | sed -e 's/[np][0-9]//g' | sed 's|^|/dev/|')")
+  include_regex=$(echo $INCLUDE | sed -e 's/[np][0-9]//g' -e 's/,/|/g')
+  DRIVES=$(echo "$ALL_DRIVES" | grep -E "/dev/($include_regex)")
 elif [[ -n $EXCLUDE ]]; then
-  DRIVES=$(echo "$ALL_DRIVES" | grep -v "/dev/$(echo $EXCLUDE | sed -e $'s/,/\\\n/g' | sed -e 's/[np][0-9]//g')")
+  exclude_regex=$(echo $EXCLUDE | sed -e 's/[np][0-9]//g' -e 's/,/|/g')
+  DRIVES=$(echo "$ALL_DRIVES" | grep -v -E "/dev/($exclude_regex)")
 fi
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@source/system-autofan/scripts/autofan` around lines 180 - 187, The drive
filtering collapses comma-separated INCLUDE/EXCLUDE lists into a single-space
string due to command substitution, so update the logic that sets DRIVES to
treat each comma-separated entry as a separate grep pattern: for INCLUDE and
EXCLUDE convert the variable into newline-separated entries (e.g. via tr ','
'\n' or printf '%s\n') and feed that to grep using -F -f /dev/stdin or construct
an alternation for grep -E (replace commas with | and prefix /dev/), ensuring
ALL_DRIVES is filtered correctly; modify the blocks that reference ALL_DRIVES,
INCLUDE, EXCLUDE and set DRIVES accordingly.
♻️ Duplicate comments (3)
unRAIDv6/dynamix.system.autofan.plg (1)

6-6: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Critical: version still downgrades from upstream (duplicate of past review).

The version entity is set to 2025.06.10, which is lexicographically and chronologically lower than the upstream 2025.11.14. Plugin management systems will reject this as a downgrade, preventing users on the upstream version from installing or updating to this enhanced fork.

This issue was previously flagged suggesting a version scheme like 2025.11.14.1 (incrementing from upstream) or 2025.06.10-fork.1 (fork suffix) to maintain monotonic ordering.

📦 Recommended version fix
-<!ENTITY version   "2025.06.10">
+<!ENTITY version   "2025.11.14.1">

Or with fork suffix:

-<!ENTITY version   "2025.06.10">
+<!ENTITY version   "2025.11.14-fork.1">

Then update the download URL and CHANGES section to match.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@unRAIDv6/dynamix.system.autofan.plg` at line 6, The VERSION entity in
dynamix.system.autofan.plg is set to 2025.06.10 which is lower than upstream
2025.11.14 and will be treated as a downgrade; change the <!ENTITY version
"2025.06.10"> to a monotonic, higher value (e.g., "2025.11.14.1" or
"2025.06.10-fork.1") so plugin managers accept updates, and then update the
plugin download URL and the CHANGES/metadata to match the new version string
(ensure the new value is used wherever ENTITY version is referenced).
source/system-autofan/scripts/autofan (1)

227-237: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Path validation for TEMP_SENSOR (duplicate of past review).

The function reads from $TEMP_SENSOR without validating that the path is under /sys/devices. While the configuration file is root-controlled, defense-in-depth principles suggest validating sensor paths to prevent reading arbitrary files.

This issue was flagged in a previous review (lines 227-237) suggesting validation that the path starts with /sys/devices and is a regular file.

🛡️ Proposed validation
 function_get_sensor_temp() {
   # Read temperature from hwmon sensor file (values are in millidegrees Celsius)
+  # Validate that sensor path is under /sys/devices
+  case "$TEMP_SENSOR" in
+    /sys/devices/*) ;;
+    *) echo "$program_name: error: sensor path must be under /sys/devices" | logger -t$program_name; HIGHEST_TEMP=0; return ;;
+  esac
   local raw
   raw=$(cat "$TEMP_SENSOR" 2>/dev/null)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@source/system-autofan/scripts/autofan` around lines 227 - 237, In
function_get_sensor_temp, validate TEMP_SENSOR before reading: ensure it is
non-empty, begins with the prefix "/sys/devices" and is a regular readable file
(e.g., test with a prefix check and [[ -f "$TEMP_SENSOR" ]] or similar) and only
then read it; if the check fails set HIGHEST_TEMP=0 and log a warning
referencing $TEMP_SENSOR and $program_name; replace the direct cat
"$TEMP_SENSOR" call with the guarded read so arbitrary files cannot be read.
source/system-autofan/FanSettings.page (1)

172-189: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

XSS risk in sensor dropdown construction (duplicate of past review).

The dropdown construction in loadTempSensors (lines 179-181) still concatenates item.chip, item.label, item.temp, and item.sensor directly into HTML without escaping. While these values originate from kernel sysfs (generally safe), defense-in-depth principles recommend using safe DOM construction methods.

This issue was flagged in a previous review (lines 172-189) and marked as addressed in commits 1a132d6 to 5b85225, but the current code still exhibits the same pattern. Please verify whether the fix was reverted or if the addressing note was premature.

🛡️ Safer construction using jQuery
     $.each(data, function(i, item) {
       var label = item.chip + ' - ' + item.label + ' (' + item.temp + '°C)';
       var selected = (item.sensor === current) ? ' selected' : '';
-      sel.append('<option value="' + item.sensor + '"' + selected + '>' + label + '</option>');
+      var opt = $('<option>').val(item.sensor).text(label);
+      if (selected) opt.prop('selected', true);
+      sel.append(opt);
     });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@source/system-autofan/FanSettings.page` around lines 172 - 189, The
loadTempSensors function builds option HTML by concatenating item.chip,
item.label, item.temp and item.sensor directly which creates an XSS risk;
replace the string-concatenation with safe DOM construction (e.g., create option
elements via jQuery or document.createElement, use .val()/.prop('selected',
true) and .text() to set the visible label) when populating `#temp_sensor_select`
and when setting input[name=temp_sensor], ensuring item.sensor is used only as
the option value and all displayed text is set via text nodes rather than HTML
concatenation.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@source/system-autofan/FanSettings.page`:
- Around line 132-165: The autoPair() function builds HTML by concatenating
server values (p.chip, p.pwm_name, p.fan_name, p.rpm_delta) and injects
p.pwm/p.fan into an inline onclick, which risks HTML/attribute injection; update
autoPair() to build the table using safe DOM APIs/jQuery element creation (e.g.,
$('<tr>'), $('<td>') .text(...)) so values are inserted via .text() or text
nodes (not string concatenation), remove the inline onclick and instead attach a
click handler (or store pwm/fan in data-* attributes and bind a click listener
that calls applyPair(pwm, fan)), and ensure applyPair() is still used but
receives values from the bound handler rather than from HTML-string
interpolation.

In `@unRAIDv6/dynamix.system.autofan.plg`:
- Line 128: The <URL> element currently hardcodes the GitHub release tag
"2025.06.10" which must match the package version entity (&version;); update the
<URL> value to use the matching release tag for &version; (e.g., change
"2025.06.10" to "2025.11.14.1") so the download link and the &version; entity
are consistent; ensure the new tag exists in the GitHub releases and update any
other hardcoded occurrences of the old tag in the file (search for the <URL>
element and any references to the old tag).

---

Outside diff comments:
In `@source/system-autofan/scripts/autofan`:
- Around line 180-187: The drive filtering collapses comma-separated
INCLUDE/EXCLUDE lists into a single-space string due to command substitution, so
update the logic that sets DRIVES to treat each comma-separated entry as a
separate grep pattern: for INCLUDE and EXCLUDE convert the variable into
newline-separated entries (e.g. via tr ',' '\n' or printf '%s\n') and feed that
to grep using -F -f /dev/stdin or construct an alternation for grep -E (replace
commas with | and prefix /dev/), ensuring ALL_DRIVES is filtered correctly;
modify the blocks that reference ALL_DRIVES, INCLUDE, EXCLUDE and set DRIVES
accordingly.

---

Duplicate comments:
In `@source/system-autofan/FanSettings.page`:
- Around line 172-189: The loadTempSensors function builds option HTML by
concatenating item.chip, item.label, item.temp and item.sensor directly which
creates an XSS risk; replace the string-concatenation with safe DOM construction
(e.g., create option elements via jQuery or document.createElement, use
.val()/.prop('selected', true) and .text() to set the visible label) when
populating `#temp_sensor_select` and when setting input[name=temp_sensor],
ensuring item.sensor is used only as the option value and all displayed text is
set via text nodes rather than HTML concatenation.

In `@source/system-autofan/scripts/autofan`:
- Around line 227-237: In function_get_sensor_temp, validate TEMP_SENSOR before
reading: ensure it is non-empty, begins with the prefix "/sys/devices" and is a
regular readable file (e.g., test with a prefix check and [[ -f "$TEMP_SENSOR"
]] or similar) and only then read it; if the check fails set HIGHEST_TEMP=0 and
log a warning referencing $TEMP_SENSOR and $program_name; replace the direct cat
"$TEMP_SENSOR" call with the guarded read so arbitrary files cannot be read.

In `@unRAIDv6/dynamix.system.autofan.plg`:
- Line 6: The VERSION entity in dynamix.system.autofan.plg is set to 2025.06.10
which is lower than upstream 2025.11.14 and will be treated as a downgrade;
change the <!ENTITY version "2025.06.10"> to a monotonic, higher value (e.g.,
"2025.11.14.1" or "2025.06.10-fork.1") so plugin managers accept updates, and
then update the plugin download URL and the CHANGES/metadata to match the new
version string (ensure the new value is used wherever ENTITY version is
referenced).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 07058621-3ca0-45e2-ae49-c8bb02884a1f

📥 Commits

Reviewing files that changed from the base of the PR and between d716869 and fbfc33b.

📒 Files selected for processing (7)
  • README.md
  • archive/dynamix.system.autofan.txz
  • source/system-autofan/FanSettings.page
  • source/system-autofan/README.md
  • source/system-autofan/include/SystemFan.php
  • source/system-autofan/scripts/autofan
  • unRAIDv6/dynamix.system.autofan.plg

Comment on lines +132 to +165
function autoPair() {
var pwm = $('select[name=controller]').val();
$('#autopair_btn').prop('disabled',true).val('_(Scanning...)_');
$('#autopair_results').html('<em>_(Please wait — cycling each PWM channel to detect fans...)_</em>');
$.getJSON('/plugins/<?=$plugin?>/include/SystemFan.php', {op:'autopair', pwm:pwm}, function(data) {
$('#autopair_btn').prop('disabled',false).val('_(Auto Pair)_');
if (!data || data.length === 0) {
$('#autopair_results').html('<span style="color:red">_(No fan/PWM pairs detected.)_</span>');
return;
}
var html = '<table style="margin-top:6px;border-collapse:collapse">';
html += '<tr><th style="padding:3px 8px;text-align:left">PWM</th><th style="padding:3px 8px;text-align:left">Fan</th><th style="padding:3px 8px;text-align:left">RPM change</th><th></th></tr>';
$.each(data, function(i, p) {
html += '<tr>';
html += '<td style="padding:3px 8px">' + p.chip + ' - ' + p.pwm_name + '</td>';
html += '<td style="padding:3px 8px">' + p.fan_name + '</td>';
html += '<td style="padding:3px 8px">+' + p.rpm_delta + ' rpm</td>';
html += '<td style="padding:3px 8px"><input type="button" value="_(Apply)_" onclick="applyPair(\'' + p.pwm + '\',\'' + p.fan + '\')"></td>';
html += '</tr>';
});
html += '</table>';
$('#autopair_results').html(html);
}).fail(function() {
$('#autopair_btn').prop('disabled',false).val('_(Auto Pair)_');
$('#autopair_results').html('<span style="color:red">_(Auto pair failed.)_</span>');
});
}
function applyPair(pwm, fan) {
$('select[name=controller]').val(pwm).trigger('change');
$('select[name=fan]').val(fan).trigger('change');
$('input[name=pwm]').prop('placeholder','_(Click DETECT)_');
$('#pwm').prop('disabled',false);
$('#autopair_results').html('');
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

HTML/attribute injection risk in autoPair results table.

Lines 146-149 concatenate server-provided values (p.chip, p.pwm_name, p.fan_name, p.rpm_delta) directly into HTML, and line 149 embeds p.pwm and p.fan paths into an onclick attribute using single-quote delimiters. While these values originate from kernel sysfs (generally safe), defense-in-depth principles recommend avoiding string concatenation for HTML construction.

If a path were to contain a single quote (e.g., via a compromised or unusual filesystem), the onclick handler would break out of its attribute boundary.

🛡️ Safer DOM construction using jQuery
     var html = '<table style="margin-top:6px;border-collapse:collapse">';
     html += '<tr><th style="padding:3px 8px;text-align:left">PWM</th><th style="padding:3px 8px;text-align:left">Fan</th><th style="padding:3px 8px;text-align:left">RPM change</th><th></th></tr>';
+    var tbody = $('<tbody>');
     $.each(data, function(i, p) {
-      html += '<tr>';
-      html += '<td style="padding:3px 8px">' + p.chip + ' - ' + p.pwm_name + '</td>';
-      html += '<td style="padding:3px 8px">' + p.fan_name + '</td>';
-      html += '<td style="padding:3px 8px">+' + p.rpm_delta + ' rpm</td>';
-      html += '<td style="padding:3px 8px"><input type="button" value="_(Apply)_" onclick="applyPair(\'' + p.pwm + '\',\'' + p.fan + '\')"></td>';
-      html += '</tr>';
+      var row = $('<tr>');
+      row.append($('<td style="padding:3px 8px">').text(p.chip + ' - ' + p.pwm_name));
+      row.append($('<td style="padding:3px 8px">').text(p.fan_name));
+      row.append($('<td style="padding:3px 8px">').text('+' + p.rpm_delta + ' rpm'));
+      var btn = $('<input type="button" value="_(Apply)_">').data({pwm: p.pwm, fan: p.fan}).on('click', function() {
+        applyPair($(this).data('pwm'), $(this).data('fan'));
+      });
+      row.append($('<td style="padding:3px 8px">').append(btn));
+      tbody.append(row);
     });
     html += '</table>';
-    $('`#autopair_results`').html(html);
+    $('`#autopair_results`').html(html.replace('</table>', '')).find('table').append(tbody).end().find('table').append('</table>');

Alternatively, construct the entire table via jQuery without string templates.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@source/system-autofan/FanSettings.page` around lines 132 - 165, The
autoPair() function builds HTML by concatenating server values (p.chip,
p.pwm_name, p.fan_name, p.rpm_delta) and injects p.pwm/p.fan into an inline
onclick, which risks HTML/attribute injection; update autoPair() to build the
table using safe DOM APIs/jQuery element creation (e.g., $('<tr>'), $('<td>')
.text(...)) so values are inserted via .text() or text nodes (not string
concatenation), remove the inline onclick and instead attach a click handler (or
store pwm/fan in data-* attributes and bind a click listener that calls
applyPair(pwm, fan)), and ensure applyPair() is still used but receives values
from the bound handler rather than from HTML-string interpolation.

Comment thread unRAIDv6/dynamix.system.autofan.plg Outdated
<!-- SOURCE PACKAGE -->
<FILE Name="&source;.txz" Run="upgradepkg --install-new --reinstall">
<URL>https://raw.githubusercontent.com/unraid/dynamix/master/archive/&name;.txz</URL>
<URL>https://github.com/mrjacobarussell/dynamix/releases/download/2025.06.10/dynamix.system.autofan.txz</URL>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Download URL tag should match version entity.

Line 128 pins the download to GitHub tag 2025.06.10, which should match the &version; entity. If the version is updated to 2025.11.14.1 (as recommended to avoid downgrade issues), ensure the release tag and download URL are updated accordingly.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@unRAIDv6/dynamix.system.autofan.plg` at line 128, The <URL> element currently
hardcodes the GitHub release tag "2025.06.10" which must match the package
version entity (&version;); update the <URL> value to use the matching release
tag for &version; (e.g., change "2025.06.10" to "2025.11.14.1") so the download
link and the &version; entity are consistent; ensure the new tag exists in the
GitHub releases and update any other hardcoded occurrences of the old tag in the
file (search for the <URL> element and any references to the old tag).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants