Skip to content

fix(security): block PasswordHash changes via config API#271

Merged
Feramance merged 1 commit into
masterfrom
cursor/critical-bug-investigation-7357
Jun 15, 2026
Merged

fix(security): block PasswordHash changes via config API#271
Feramance merged 1 commit into
masterfrom
cursor/critical-bug-investigation-7357

Conversation

@cursor

@cursor cursor Bot commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes a critical auth bypass where anyone with WebUI.Token could take over a configured admin account.

Bug and impact

An attacker with only the API token (from config.toml, logs, or GET /api/token) could:

  1. POST /web/config or POST /api/config with {"changes": {"WebUI.PasswordHash": ""}} to clear the stored password hash
  2. Call POST /web/auth/set-password using WebUI.Token as the bootstrap setup token
  3. Set a new admin password and gain full WebUI access

This bypassed the hardening in #225/#228 that restricted set-password when a password was already configured.

Root cause

ApplyDottedConfigChanges / POST /web/config only skipped [redacted] placeholder values for sensitive keys. An empty string was accepted and persisted, re-opening bootstrap mode without requiring TORRENTARR_SETUP_TOKEN.

Fix

  • Reject all direct WebUI.PasswordHash writes via config endpoints (except the [redacted] placeholder)
  • Password changes must go through POST /web/auth/set-password
  • Added unit and integration tests covering empty-string rejection and the full takeover chain

Validation

  • dotnet test tests/Torrentarr.Infrastructure.Tests/ --filter FullyQualifiedName~WebUIAuthHelpersTests
  • dotnet test tests/Torrentarr.Host.Tests/ --filter FullyQualifiedName~ConfigRedactionTests
Open in Web View Automation 

POST /web/config and /api/config accepted WebUI.PasswordHash="", which
cleared the stored hash while leaving WebUI.Token valid. An attacker with
only the API token could then call set-password in bootstrap mode and
take over the admin account.

Reject all direct PasswordHash writes through config endpoints (except the
[redacted] placeholder). Password changes must use POST /web/auth/set-password.

Co-authored-by: Feramance <Feramance@users.noreply.github.com>
@cursor cursor Bot marked this pull request as ready for review June 15, 2026 12:09
cursor Bot pushed a commit that referenced this pull request Jun 15, 2026
- Prompt: push conflict fixes, gh pr ready on Merge, Maintainer commands section
- New docs/audits/pr-triage-gh-actions.md with phased gh commands
- Marked #229 #271 #258 #243 ready for review via gh

Co-authored-by: Feramance <Feramance@users.noreply.github.com>
@Feramance Feramance merged commit c3a63f2 into master Jun 15, 2026
14 checks passed
@Feramance Feramance deleted the cursor/critical-bug-investigation-7357 branch June 15, 2026 12:12

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Critical bypass found in PR #271

The dotted-key guard (RejectPasswordHashConfigChange) is correct for the paths it covers, but two config API shapes still bypass it and can clear PasswordHash, reopening bootstrap POST /web/auth/set-password with only the API token.

Bypass 1: Full /api/config replace

POST /api/config without a changes wrapper deserializes a full TorrentarrConfig and never calls RejectPasswordHashConfigChange:

else
{
    updatedConfig = JsonConvert.DeserializeObject<TorrentarrConfig>(payload.GetRawText());
}

Trigger: Authenticated caller sends POST /api/config with WebUI.PasswordHash: "" in a full config body → 200 OK → set-password with setupToken: <api-token> succeeds.

Bypass 2: Section-level WebUI replace

ApplyDottedConfigChanges only checks dotted keys. A top-level WebUI object in changes replaces the entire section:

{"changes": {"WebUI": {"PasswordHash": "", ...}}}

Trigger: Same account-takeover chain as above; confirmed with integration test returning 200 before fix.

Recommended fix

Add a save-time guard (ValidatePasswordHashForConfigApiSave) that rejects any PasswordHash change on config API persistence, regardless of payload shape. Implemented on branch cursor/critical-bug-investigation-82da.

What looks good

  • Dotted-key rejection for "" and arbitrary hash values
  • [redacted] placeholder handling for normal frontend round-trips
  • Regression tests for the originally reported dotted-key attack
Open in Web View Automation 

Sent by Cursor Automation: Torrentarr - Find critical bugs

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

PR Validation (Cursor Automation)

Recommendation: Close (incorrect fix)
Primary reason: The PR blocks WebUI.PasswordHash only for dotted changes payloads, but POST /api/config still accepts a full JSON replacement payload that can set or clear WebUI.PasswordHash without going through POST /web/auth/set-password.

Gates

Gate Status Notes
Merge conflicts Pass origin/cursor/critical-bug-investigation-7357 merged cleanly with origin/master; no conflicts.
dotnet build Pass dotnet build -c Release passed with 0 errors and 6 existing NU1510 warnings.
dotnet test (non-live) Pass dotnet test -c Release --no-build --filter "Category!=Live" passed: 745 passed, 0 failed.
vitest Pass npm ci && npx vitest run passed: 16 files, 154 tests.

Validation

Axis Score Notes
Purpose Pass Direct config changes to WebUI.PasswordHash are a real auth/config issue and the PR is aimed at the correct surface.
Correctness Fail ApplyDottedConfigChanges rejects WebUI.PasswordHash, but the no-changes branch in POST /api/config still deserializes and saves a full TorrentarrConfig, leaving a direct PasswordHash write path. This also fails the Torrentarr-specific check that POST /api/config should use dotted changes merge rather than full JSON replace.
Tests Fail Added tests cover /web/config and /api/config dotted changes, but not the remaining full-payload /api/config bypass.
Hygiene Pass Focused 5-file change with no generated SDK junk or unrelated noise.
Overlap Pass Not treating as a duplicate: the prompt’s 2026-06-15 winner table names #271 for Config PasswordHash security. #258 overlaps broader /api/config dotted-only work but is not the preferred focused PasswordHash winner.

Why

The merge/build/test gates are green, but the security invariant is still not enforced globally: WebUI.PasswordHash can still be supplied through the full-replacement POST /api/config path because that branch bypasses the new RejectPasswordHashConfigChange helper. The fix should either remove/disable full replacement for /api/config in favor of dotted changes, or explicitly reject WebUI.PasswordHash changes before saving any full config payload.

Required repo context was checked. Note: docs/audits/pr-triage-2026-06-15.md was not present in the checkout; the latest available audit file was docs/audits/pr-triage-2026-06-11.md, and the trigger prompt’s 2026-06-15 winner table was used for overlap guidance.

Overlap

None as a duplicate recommendation. Related broader config PR: #258.

Commands run

git fetch origin master; git fetch origin cursor/critical-bug-investigation-7357; git checkout -B pr-validate origin/cursor/critical-bug-investigation-7357; git merge origin/master; git diff --stat origin/master...HEAD; gh pr list --state open --search "PasswordHash repo:Feramance/Torrentarr"; gh pr view 258 --json ...; temporary /tmp/dotnet SDK install because dotnet was initially missing; dotnet restore; dotnet build -c Release; dotnet test -c Release --no-build --filter "Category!=Live"; npm ci; npx vitest run; git status --short --branch; git diff --exit-code.

Open in Web View Automation 

Sent by Cursor Automation: Torrentarr PR validation triage

cursor Bot pushed a commit that referenced this pull request Jun 15, 2026
- Keep #258 dotted-only POST /api/config (require changes field)
- Use #271 RejectPasswordHashConfigChange (403) for PasswordHash writes
- Align SetPasswordEndpointTests with 403 on empty PasswordHash clear
- Bump NewtonsoftJson to 10.0.9

Co-authored-by: Feramance <Feramance@users.noreply.github.com>
Feramance added a commit that referenced this pull request Jun 15, 2026
* docs: Cursor Automation spec for PR triage on open/push

Co-authored-by: Feramance <Feramance@users.noreply.github.com>

* docs: one-shot PR triage audit report for open pull requests

Co-authored-by: Feramance <Feramance@users.noreply.github.com>

* feat: add repo-managed Cursor Automation definition for PR triage

- .cursor/automations/torrentarr-pr-triage/ (automation.yaml + prompt.md)
- Setup helper script and updated automation docs
- Include pr-triage-2026-06-15.md ground-truth audit on branch

Co-authored-by: Feramance <Feramance@users.noreply.github.com>

* [pre-commit] auto fixes from pre-commit hooks

for more information, see https://pre-commit.ci

* docs: add one-click Cursor Automation editor launcher

Bootstrap from find-vulnerabilities template (PR opened/pushed + PR comment).
HTML launcher copies Torrentarr prompt for paste into editor.

Co-authored-by: Feramance <Feramance@users.noreply.github.com>

* Rewrite PR automation as validation (merge/close), not security scan

- Prompt: merge master, resolve conflicts, run build/tests, post Merge or Close
- Remove find-vulnerabilities bootstrap; use blank automations editor
- Rename display to Torrentarr PR Validation in docs and scripts

Co-authored-by: Feramance <Feramance@users.noreply.github.com>

* Add automated PR actions + gh one-liners for maintainer follow-up

- Prompt: push conflict fixes, gh pr ready on Merge, Maintainer commands section
- New docs/audits/pr-triage-gh-actions.md with phased gh commands
- Marked #229 #271 #258 #243 ready for review via gh

Co-authored-by: Feramance <Feramance@users.noreply.github.com>

* Simplify maintainer workflow to merge/close only

- Automation auto-closes duplicates and does all prep (rebase, push, ready)
- pr-triage-gh-actions.md: merge/close queue only
- Add scripts/pr-queue.sh for live merge/close command list

Co-authored-by: Feramance <Feramance@users.noreply.github.com>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Feramance <Feramance@users.noreply.github.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
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