Skip to content

🐛 bug: Fix open redirect via Redirect().Back() — validate Referer header origin - #4370

Merged
ReneWerner87 merged 13 commits into
mainfrom
copilot/fix-open-redirect-via-referer
Jun 5, 2026
Merged

ReneWerner87 merged 13 commits into
mainfrom
copilot/fix-open-redirect-via-referer

Conversation

Copilot AI commented May 28, 2026

Copy link
Copy Markdown
Contributor

Description

Redirect().Back() reads the Referer header and sets it as the Location header without validation. An attacker can craft a request with Referer: https://evil.com/phishing to redirect users to a malicious site (phishing, OAuth token theft, credential harvesting).

The fix now validates the full Referer origin tuple before using it: scheme, host, and port. Cross-origin referrers are rejected and the fallback URL is used instead. Relative paths are always accepted since they're inherently same-origin. Non-hierarchical scheme-only values such as javascript:... and mailto:... are also rejected. Default ports are normalized so http://example.com matches http://example.com:80, and https://example.com matches https://example.com:443.

// Before: attacker-controlled redirect
// Referer: https://evil.com → Location: https://evil.com ✗

// After: cross-origin referer rejected, fallback used
// Referer: https://evil.com → Location: / (fallback) ✓
// Referer: /dashboard → Location: /dashboard ✓
// Referer: https://example.com/page → Location: https://example.com/page ✓ (same origin)
// Referer: http://example.com/page on https://example.com → Location: / ✗ rejected
// Referer: https://example.com:8080/page → Location: / ✗ rejected
// Referer: javascript:alert(1) → Location: / ✗ rejected

Changes introduced

  • redirect.go: Parse the Referer URL and compare its full origin tuple (scheme, host, port) against the current request. Reject invalid or cross-origin values, reject scheme-only non-hierarchical URIs, and treat default ports (80/443) as equivalent to omitted ports.

  • redirect_test.go: Add Test_Redirect_Back_WithCrossOriginReferer coverage for cross-host rejection, no-fallback error handling, same-origin acceptance, default-port acceptance, cross-scheme rejection, cross-port rejection, relative path acceptance, and scheme-only referrer rejection.

  • redirect_test.go: Correct test comment spelling to satisfy the cspell workflow without changing behavior.

  • Benchmarks: No new allocations on the happy path (relative referrers skip url.Parse only when host is absent). One url.Parse call is used for absolute referrers.

  • Documentation Update: The docs/api/redirect.md Back section could note the full same-origin validation; behavior is otherwise unchanged.

  • Changelog/What's New: Redirect().Back() now validates full same-origin (scheme, host, port) on the Referer header and rejects scheme-only non-hierarchical values to prevent open redirects.

  • Migration Guide: No breaking changes. Cross-origin or invalid Referer values that previously triggered a redirect will now fall through to the fallback URL.

  • API Alignment with Express: Express res.redirect('back') has the same vulnerability; this is a security improvement over Express behavior.

  • API Longevity: Validation is internal; public API signature is unchanged.

  • Examples: See code snippet above.

Type of change

  • Enhancement (improvement to existing features and functionality)
  • Code consistency (non-breaking change which improves code reliability and robustness)

Checklist

  • Followed the inspiration of the Express.js framework for new functionalities, making them similar in usage.
  • Conducted a self-review of the code and provided comments for complex or critical parts.
  • Updated the documentation in the /docs/ directory for Fiber's documentation.
  • Added or updated unit tests to validate the effectiveness of the changes or new features.
  • Ensured that new and existing unit tests pass locally with the changes.
  • Verified that any new dependencies are essential and have been agreed upon by the maintainers/community.
  • Aimed for optimal performance with minimal allocations in the new code.
  • Provided benchmarks for the new code to analyze and improve upon.

Commit formatting

🔒 Security fix — open redirect prevention via full same-origin Referer validation and rejection of invalid scheme-only referrers.

…irect

Add same-origin validation to Redirect().Back() to reject cross-origin
Referer headers that could lead to open redirect attacks. When the Referer
contains a host that differs from the request's hostname, the fallback URL
is used instead.
Copilot AI changed the title [WIP] Fix open redirect via Redirect().Back() unvalidated Referer header 🔒 Fix open redirect via Redirect().Back() — validate Referer header origin May 28, 2026
Copilot finished work on behalf of gaby May 28, 2026 04:31
Copilot AI requested a review from gaby May 28, 2026 04:31
@gaby gaby changed the title 🔒 Fix open redirect via Redirect().Back() — validate Referer header origin 🐛 bug: Fix open redirect via Redirect().Back() — validate Referer header origin May 28, 2026
@ReneWerner87 ReneWerner87 added this to v3 May 28, 2026
@ReneWerner87 ReneWerner87 added this to the v3 milestone May 28, 2026
@gaby
gaby requested a review from Copilot May 28, 2026 04:43

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Fixes an open-redirect vulnerability in Redirect().Back() by validating that the Referer header points to the same host as the current request before using it as the Location. Cross-origin or unparseable referers now fall through to the provided fallback URL (or ErrRedirectBackNoFallback).

Changes:

  • redirect.go: Parse Referer and reject when parsed.Hostname() != r.c.Hostname() (relative referers, with no host, are still accepted).
  • redirect_test.go: Add Test_Redirect_Back_WithCrossOriginReferer covering cross-origin rejection (with and without fallback), same-origin acceptance, and relative-path acceptance.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
redirect.go Validates Referer URL host against current host in Back(), treating cross-origin/invalid referers as missing.
redirect_test.go Adds tests for cross-origin rejection, missing-fallback error, same-origin acceptance, and relative path acceptance.

Comment thread redirect.go
@codecov

codecov Bot commented May 28, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 75.00000% with 8 lines in your changes missing coverage. Please review.
✅ Project coverage is 91.33%. Comparing base (395154e) to head (c5bcaa8).

Files with missing lines Patch % Lines
redirect.go 75.00% 5 Missing and 3 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #4370      +/-   ##
==========================================
- Coverage   91.37%   91.33%   -0.04%     
==========================================
  Files         132      132              
  Lines       13161    13193      +32     
==========================================
+ Hits        12026    12050      +24     
- Misses        719      724       +5     
- Partials      416      419       +3     
Flag Coverage Δ
unittests 91.33% <75.00%> (-0.04%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copilot finished work on behalf of gaby May 28, 2026 15:02
@ReneWerner87

Copy link
Copy Markdown
Member

@copilot The Referer-based same-origin check currently compares only the hostname, which leaves two gaps:

  • A http Referer on an https site (or vice versa) is accepted, allowing a protocol-downgrade redirect.
  • A Referer on a different port is treated as same-origin.

Please compare the full origin tuple (scheme, host, port) of the parsed Referer against the current request, and reject when any of the three differs.

After that, please fix the failing checks: lint / lint, cspell, and add test coverage for the rejected cross-origin cases (cross-scheme, cross-host, cross-port) so codecov/patch passes.

Copilot AI commented May 29, 2026

Copy link
Copy Markdown
Contributor Author

@copilot The Referer-based same-origin check currently compares only the hostname, which leaves two gaps:

  • A http Referer on an https site (or vice versa) is accepted, allowing a protocol-downgr...

Addressed in 1dc23db. The branch compares scheme + host + port for Redirect().Back(), covers rejected cross-host/cross-scheme/cross-port cases in tests, and this commit fixes the remaining cspell failure.

Copilot AI requested a review from ReneWerner87 May 29, 2026 07:46
@ReneWerner87

Copy link
Copy Markdown
Member

@copilot Good news: the redirect change itself is fine, all of Linter, Spell check, govulncheck and Benchmark are green. The only red check is the Test job on unit (1.26.x, windows-latest), and the failure is unrelated to this PR:

middleware/cache/cache_test.go:2548: "2147483647" is not less than "60" - Test_CacheClampsInvalidStoredDate

That is a Windows-only cache date-clamping test and has nothing to do with redirect.go. Your branch simply predates the fix that is already on main. Please rebase onto the latest main; that should clear the Test failure and make the PR ready.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.

Comment thread redirect.go
Comment thread redirect_test.go Outdated
Comment thread redirect_test.go
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Copilot finished work on behalf of gaby June 2, 2026 12:31
@gaby
gaby marked this pull request as ready for review June 4, 2026 00:40
@gaby
gaby requested a review from a team as a code owner June 4, 2026 00:40
@gaby
gaby requested review from efectn and sixcolors June 4, 2026 00:40
@gaby

gaby commented Jun 4, 2026

Copy link
Copy Markdown
Member

@copilot fix cspell workflow

Copilot AI commented Jun 4, 2026

Copy link
Copy Markdown
Contributor Author

@copilot fix cspell workflow

Fixed in 639d7ed.

Copilot finished work on behalf of gaby June 4, 2026 01:08
@gaby

gaby commented Jun 4, 2026

Copy link
Copy Markdown
Member

@copilot audit the security of Redirect().Back()

Copilot AI commented Jun 4, 2026

Copy link
Copy Markdown
Contributor Author

@copilot audit the security of Redirect().Back()

Security audit of Redirect().Back() — no issues found.

Validation logic (line 384–389):

Input Outcome Reason
/dashboard ✅ Accepted Fast-path: starts with / but not // — inherently same-origin
//evil.com/path ❌ Rejected url.Parse yields host="evil.com", empty scheme ≠ request scheme
https://evil.com/phishing ❌ Rejected Host mismatch in schemeAndHostMatch
http://example.com/page on HTTPS ❌ Rejected Scheme mismatch
http://example.com:8080/admin ❌ Rejected Port mismatch
javascript:alert(1) ❌ Rejected scheme != "" && host == "" guard
data:text/html,... ❌ Rejected Same guard as above
mailto:test@evil.com ❌ Rejected Same guard
https://evil.com (space prefix) ❌ Rejected url.Parse returns error
\thttps://evil.com (tab prefix) ❌ Rejected url.Parse returns error (invalid control char)
CRLF injection (%0d%0a) ❌ Rejected url.Parse returns error (invalid port)
\evil.com (backslash) ✅ Accepted url.Parse: scheme="", host="" — browser resolves as relative /evil.com (same-origin)
relative-path, foo/bar ✅ Accepted No scheme, no host — relative, same-origin
///triple ✅ Accepted No scheme, no host — browser normalizes to /triple
?query=1, #fragment ✅ Accepted Relative, same-origin

schemeAndHostMatch + normalizeRedirectSchemeHost (lines 406–448):

  • Case-insensitive comparison via utilsstrings.ToLower for both scheme and host.
  • Default port normalization: http://example.comhttp://example.com:80, https://example.comhttps://example.com:443.
  • IPv6 address handling: brackets added for raw IPv6 hostnames.
  • Mirrors the CSRF middleware's schemeAndHostMatch / normalizeSchemeHost implementation.

Other observations:

  • r.c.Host() and r.c.Scheme() respect trusted proxy headers (X-Forwarded-Host, X-Forwarded-Proto) only when IsProxyTrusted() is true, which is correct for reverse-proxy setups.
  • r.To() sets Location via setCanonical which delegates to fasthttp, which strips control characters from header values.
  • Test coverage includes cross-host, cross-scheme, cross-port, same-origin, default-port normalization, relative path, and scheme-only referrer scenarios.

Copilot finished work on behalf of gaby June 4, 2026 02:41
@ReneWerner87
ReneWerner87 merged commit 1ba168f into main Jun 5, 2026
18 of 19 checks passed
@github-project-automation github-project-automation Bot moved this to Done in v3 Jun 5, 2026
@ReneWerner87
ReneWerner87 deleted the copilot/fix-open-redirect-via-referer branch June 5, 2026 06:15
@ReneWerner87 ReneWerner87 modified the milestones: v3, v3.4.0 Jul 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

🐛 [Bug]: Open redirect via Redirect().Back() — unvalidated Referer header

4 participants