Skip to content

fix(csrf): return 403 on CSRF rejection instead of silent empty 200#2692

Open
elibosley wants to merge 1 commit into
unraid:masterfrom
elibosley:fix/api-status-csrf
Open

fix(csrf): return 403 on CSRF rejection instead of silent empty 200#2692
elibosley wants to merge 1 commit into
unraid:masterfrom
elibosley:fix/api-status-csrf

Conversation

@elibosley

@elibosley elibosley commented Jul 14, 2026

Copy link
Copy Markdown
Member

Problem

When local_prepend.php rejects a POST for a missing/invalid CSRF token, it logs the error and exits with no status code — so the client receives an empty HTTP 200. A fetch/XHR caller can't distinguish that from a genuine empty result.

Concretely: Settings → Management Access → Unraid API Status issues a fetch POST to unraid-api.php. When that POST is blocked by the CSRF gate, the empty 200 is read as "the service returned nothing" and the panel renders Not Running — even though the API is up and the request was simply blocked. (Full first-paint reproduction and the client-side fix are in unraid/api#2039.)

Change

csrf_terminate() now returns an explicit 403 with a small JSON body ({"error":"<reason> csrf_token"}) instead of a silent empty 200.

Validation logic is unchanged — the gate still rejects the same requests and still logs the same wrong csrf_token line. Only the response shape of an already-rejected request changes, so a blocked read is observable and can't masquerade as a real (empty) response. A legitimate request carrying a valid token never reaches this path.

headers_sent() is guarded so this is a no-op if output already began.

Testing

Note / possible follow-up

The token is read as $_POST['csrf_token'] ?? $_SERVER['HTTP_X_CSRF_TOKEN']. ?? only falls through on null, not on an empty string, so a caller that sends a blank csrf_token= body field overrides a valid x-csrf-token header. If we want the header to win in that case, ($_POST['csrf_token'] ?? '') ?: ($_SERVER['HTTP_X_CSRF_TOKEN'] ?? null) would do it without weakening validation. Left out of this PR to keep the change minimal — happy to add if desired.

Summary by CodeRabbit

  • Bug Fixes
    • CSRF validation failures now return a clear JSON error response with HTTP status 403.
    • Clients can reliably distinguish CSRF errors from legitimate empty responses.

When local_prepend.php rejects a POST for a bad/missing CSRF token it logged
the error and `exit`ed with no status code, so the client received an empty
HTTP 200. A fetch-based caller can't tell that apart from a genuine empty
result — e.g. the Unraid API Status panel renders "Not Running" (service
down) when the request was actually just blocked by the CSRF gate.

Return an explicit 403 with a small JSON body so the rejection is
observable and can't masquerade as a real response. Validation logic is
unchanged — the gate still rejects and still logs; only the response shape
of a rejected request changes.
@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 4b252c90-ba6c-4320-8ee2-14e2dab0542d

📥 Commits

Reviewing files that changed from the base of the PR and between 369f0b2 and e7949d4.

📒 Files selected for processing (1)
  • emhttp/plugins/dynamix/include/local_prepend.php

Walkthrough

The CSRF termination helper now responds with HTTP 403 and a JSON body containing the failure reason, while setting the JSON content type when headers are available.

Changes

CSRF response handling

Layer / File(s) Summary
Structured CSRF termination response
emhttp/plugins/dynamix/include/local_prepend.php
csrf_terminate now emits a 403 JSON response with the provided CSRF failure reason instead of an empty termination response.

Estimated code review effort: 1 (Trivial) | ~2 minutes

Poem

A rabbit finds a blocked front door,
And stamps a neat four-oh-three score.
“Here’s why,” it says, in JSON bright,
Then bounds away into the night.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: CSRF rejections now return HTTP 403 instead of a silent empty 200 response.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@github-actions

Copy link
Copy Markdown

🔧 PR Test Plugin Available

A test plugin has been generated for this PR that includes the modified files.

Version: 2026.07.14.0356
Build: View Workflow Run

📥 Installation Instructions:

Install via Unraid Web UI:

  1. Go to Plugins → Install Plugin
  2. Copy and paste this URL:
https://preview.dl.unraid.net/pr-plugins/pr-2692/webgui-pr-2692.plg
  1. Click Install

Alternative: Direct Download

⚠️ Important Notes:

  • Testing only: This plugin is for testing PR changes
  • Backup included: Original files are automatically backed up
  • Easy removal: Files are restored when plugin is removed
  • Conflicts: Remove this plugin before installing production updates
  • Post-merge behavior: This preview stays available after merge until preview storage expires or it is manually cleaned up

📝 Modified Files:

Click to expand file list
emhttp/plugins/dynamix/include/local_prepend.php

🔄 To Remove:

Navigate to Plugins → Installed Plugins and remove webgui-pr-2692, or run:

plugin remove webgui-pr-2692

🤖 This comment is automatically generated and will be updated with each new push to this PR.

@unraid-bot unraid-bot added the 7.4.0 Approved for release 7.4.0 (auto-managed by notification-worker) label Jul 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

7.4.0 Approved for release 7.4.0 (auto-managed by notification-worker)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants