Skip to content

Refuse cross-site state changes on the admin API - #15

Merged
GlobalTechInfo merged 1 commit into
mainfrom
fix/csrf
Oct 18, 2026
Merged

GlobalTechInfo merged 1 commit into
mainfrom
fix/csrf

Conversation

@GlobalTechInfo

@GlobalTechInfo GlobalTechInfo commented Oct 18, 2026 •

Copy link
Copy Markdown
Owner

The gap

The admin API authenticates with HTTP Basic, and browsers attach Basic credentials to any request for that origin regardless of which page started it. SameSite does not apply to Basic auth. So every mutating route was reachable from a page on any website.

The worst case needed no cleverness at all: POST /api/users/:id/reset takes no request body and uses a simple method, so a plain cross-site <form> reaches it with the admin's credentials attached. Same for revoking a VLESS UUID or a Telegram secret.

The fix

A middleware on the admin handler refuses state-changing requests carrying a foreign Origin, or Sec-Fetch-Site: cross-site. A request with no Origin at all did not come from a browser page and is left alone, so curl and scripts keep working exactly as before — this is a gate on browsers, not a redesign of the API.

Origin is the precise check and Sec-Fetch-Site only rules requests out. That split matters: Sec-Fetch-Site names a site, not an origin, so my first version — refusing anything that was not same-origin — would have broken a dashboard served from a second subdomain. Origin settles that case correctly on its own. A test covers same-site explicitly.

Reads are untouched: a cross-origin GET changes nothing here, and there is no reason for it to start failing.

Tests

Four, run against the real handler chain:

  • A cross-site DELETE of a VLESS UUID is refused, and the client still exists afterwards — the request was refused, not partially honoured.
  • Sec-Fetch-Site: cross-site is refused even with Origin stripped.
  • The attack as it actually arrives — Sec-Fetch-Site: cross-site, Sec-Fetch-Mode: cors, form Content-Type, a foreign Origin — never reaches the route.
  • Same-origin, same-site, none, and no-Origin-at-all all still reach the route.

gofmt/go vet clean, full suite green.

Summary by CodeRabbit

  • Security
    • State-changing admin API requests are now rejected with a 403 response when they come from a different origin or are marked as cross-site. Requests without an Origin remain allowed.
    • Read-only requests, including cross-origin GET requests, continue to be allowed.

The admin API authenticates with HTTP Basic, and browsers attach Basic
credentials to any request for that origin regardless of which page started
it. That made every mutating route a target for a page on another website:
POST /api/users/:id/reset needs no request body and no unusual method, so a
plain cross-site form reaches it with the admin's credentials attached.

Requests arriving with a foreign Origin, or with Sec-Fetch-Site:
cross-site, are refused. A request with no Origin at all did not come from a
browser page and is left alone, so curl and scripts keep working as they
did -- this is a gate on browsers, not on the API.

Origin is the precise check and Sec-Fetch-Site only rules requests out,
because the latter names a site rather than an origin: refusing everything
that was not same-origin would have broken a dashboard served from another
subdomain, which Origin settles correctly on its own.

Reads are untouched. A cross-origin GET changes nothing here, and there is
no reason for it to start failing.
@coderabbitai

coderabbitai Bot commented Oct 18, 2026

Copy link
Copy Markdown

Review in Change Stack →

Note

Currently processing new changes in this PR. This may take a few minutes, please wait...

⚙️ Run configuration
  • Configuration used: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 154f96a4-f858-4461-ad06-dfdc37a79ddb
📥 Commits

Reviewing files that changed from the base of the PR and between cba25f0 and 6cda034.

📒 Files selected for processing: 3
  • README.md
  • csrf_test.go
  • main.go
+----------------------------------------------------------------------------------------------------------------------------------------------+
| Eliminate effects between unrelated things. Design components that are self-contained, independent, and have a single, well-defined purpose. |
+----------------------------------------------------------------------------------------------------------------------------------------------+
  \
   \     /)/)
        ( '')
        /  |
      o(___)
✨ Finishing Touches
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

@GlobalTechInfo
GlobalTechInfo merged commit c11e27e into main Oct 18, 2026
2 of 3 checks passed
@GlobalTechInfo
GlobalTechInfo deleted the fix/csrf branch October 18, 2026 09:28
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.

1 participant