fix(security): block PasswordHash bypass via whole-WebUI section replace#283
Conversation
PR #271 blocked dotted-key WebUI.PasswordHash writes, but section-level WebUI object replacement in ApplyDottedConfigChanges bypassed that guard. An attacker with the API token could clear PasswordHash and re-bootstrap set-password using WebUI.Token alone. Add ValidatePasswordHashForConfigApiSave as a save-time invariant in Host SaveAndRespondConfigUpdate and WebUI config save paths. Regression test confirms section-level replace returns 403. Co-authored-by: Feramance <Feramance@users.noreply.github.com>
…fig save Co-authored-by: Feramance <Feramance@users.noreply.github.com>
There was a problem hiding this comment.
PR Validation (Cursor Automation)
Recommendation: Merge
Primary reason: Clean post-merge validation: no conflicts, release build passed, non-live .NET tests passed, and Vitest passed. GitHub currently reports this PR as MERGED.
Gates
| Gate | Status | Notes |
|---|---|---|
| Merge conflicts | Pass | cursor/bug-finding-automation-7bbb merged cleanly with current origin/master; conflicts: none. |
dotnet build |
Pass | dotnet build -c Release succeeded with 18 warnings and 0 errors after installing pinned SDK 10.0.100 into /tmp/dotnet for this runner. |
dotnet test (non-live) |
Pass | dotnet test -c Release --no-build --filter "Category!=Live": 766 passed, 0 failed, 0 skipped. Test count is above the stated baseline. |
vitest |
Pass | npx vitest run: 16 files passed, 154 tests passed. |
npm run build (webui) |
N/A | The PR did not modify the React webui/ source tree, so this conditional gate was not required. |
Validation
| Axis | Score | Notes |
|---|---|---|
| Purpose | Pass | Addresses a real remaining config-auth gap after #271: section/full-object WebUI replacement could still alter PasswordHash. |
| Correctness | Pass | Adds a centralized save-time invariant in WebUIAuthHelpers and applies it to Host/WebUI config save paths while preserving [redacted] placeholder behavior. |
| Tests | Pass | Adds host regression coverage for whole-WebUI replacement and helper unit coverage; full non-live test totals did not drop. |
| Hygiene | Pass | Narrow 5-file scope; no generated build output, SDK junk, temp docs, or unrelated noise. |
| Overlap | Pass | Not a duplicate of #271: #271 fixed dotted-key writes, while this PR covers section/full-object replacement. #276 proposed similar coverage but is closed/unmerged. |
Why
The fix is aligned with the Torrentarr-specific Auth/WebUI rule that WebUI.PasswordHash must only be set via POST /web/auth/set-password. The implementation rejects direct hash changes at save time, so bypasses that avoid dotted-key validation are also blocked. The PR is already merged according to GitHub, but the locally merged validation tree builds and tests cleanly.
Required audit note: docs/audits/pr-triage-*.md was not present in this checkout, so overlap was checked using the 2026-06-15 winner table from the automation prompt plus gh pr view/gh pr list.
Overlap
None. Related PRs checked: #271 is merged and narrower; #276 is closed/unmerged.
Commands run
git fetch origin mastergit fetch origin cursor/bug-finding-automation-7bbbgit checkout -B pr-validate origin/cursor/bug-finding-automation-7bbbgit merge origin/mastergit diff --name-status origin/master...origin/cursor/bug-finding-automation-7bbbgh pr view 283 --json ...gh pr view 271 --json ...gh pr view 276 --json ...gh pr list --state open --search "PasswordHash" --json ...dotnet restoredotnet build -c Releasedotnet test -c Release --no-build --filter "Category!=Live"cd webui && npm ci && npx vitest run
Sent by Cursor Automation: Torrentarr PR validation triage
There was a problem hiding this comment.
Bug Scan Summary (automation)
Date: 2026-06-22
Commits inspected: 3f2c16f..98fd421 (2 commits)
Result: No critical bugs found in this diff. The change correctly closes the auth bypass.
What was verified
ValidatePasswordHashForConfigApiSave is now invoked on every config-save path before disk write:
- Host
SaveAndRespondConfigUpdate(coversPOST /web/configandPOST /api/config) - WebUI
POST /web/config(afterFlatToConfig) - WebUI
POST /web/config/save
The save-time guard catches section-level WebUI replacement (and any other merge path) that bypasses the per-key RejectPasswordHashConfigChange check. [redacted] is restored from the current config; any other hash change returns 403.
Regression coverage
PostConfig_WithWholeWebUISectionEmptyPasswordHash_Returns403_AndPreservesLoginexercises the reported bypass vector.WebUIAuthHelpersTests.ValidatePasswordHashForConfigApiSave_*cover reject / restore / unchanged cases.- All 6
ConfigRedactionTestspass; full non-live suite passes onmaster(596 .NET tests).
Deduplication
- Open PRs: searched
PasswordHash,auth bypass,section replace— no competing fix PRs. - Recently merged:
#271,#258, and this PR (e3845a4onmaster) — this is the intended fix for the whole-WebUI bypass (#271only blocked dotted keys). - Open issues: no duplicate tracking issues found.
No additional changes recommended from this scan.
Sent by Cursor Automation: Torrentarr - Find critical bugs


Summary
PR #271 blocked dotted-key
WebUI.PasswordHashwrites, but section-levelWebUIobject replacement inApplyDottedConfigChangesbypassed that guard. An attacker with the API token could clearPasswordHashand re-bootstrapPOST /web/auth/set-passwordusingWebUI.Tokenalone — enabling account takeover.Root cause:
RejectPasswordHashConfigChangeonly inspected dotted keys named exactlyWebUI.PasswordHash. Top-level section replace ({"changes": {"WebUI": {..., "PasswordHash": ""}}}) never hit that check before save.Fix: Added
WebUIAuthHelpers.ValidatePasswordHashForConfigApiSave()as a centralized save-time guard. Restores[redacted]placeholder from current config; rejects any otherPasswordHashchange. Applied in HostSaveAndRespondConfigUpdate(covers/web/configand/api/config) and WebUI/web/config+/web/config/save.Closed PR #276 proposed the same fix but was never merged.
Testing
PostConfig_WithWholeWebUISectionEmptyPasswordHash_Returns403_AndPreservesLogin— regression for section-level bypassValidatePasswordHashForConfigApiSave_*unit testsConfigRedactionTestspassdotnet test tests/Torrentarr.Host.Tests/ --filter ConfigRedactionTests|WebUIAuthHelpersTestsdotnet test tests/Torrentarr.Infrastructure.Tests/ --filter WebUIAuthHelpersTestsChecklist