Skip to content

🐛 bug: reject malformed host authorities in hostauthorization#4293

Open
gaby wants to merge 5 commits into
mainfrom
fix-host-authorization-bypass-vulnerability
Open

🐛 bug: reject malformed host authorities in hostauthorization#4293
gaby wants to merge 5 commits into
mainfrom
fix-host-authorization-bypass-vulnerability

Conversation

@gaby
Copy link
Copy Markdown
Member

@gaby gaby commented May 18, 2026

Motivation

  • The host authorization middleware previously authorized based on c.Hostname() (a parsed hostname) without validating the original raw authority, allowing malformed authorities like allowed.com:443@attacker.example to bypass an allowlist when TrustProxy is enabled.
  • The intent of the change is to validate the raw Host/X-Forwarded-Host authority syntax and reject malformed forms before matching against AllowedHosts to prevent Host header poisoning/authority confusion.

Description

  • Added parseNormalizedAuthority(authority string) (string, bool) to validate raw authority syntax (rejecting userinfo @, malformed brackets, invalid ports) and return a normalized host for matching.
  • Added isValidPort to ensure ports are numeric and within 0-65535.
  • Updated the middleware to validate c.Host() (raw authority) using the new parser and only perform allowlist matching on the validated/normalized host.
  • Added unit tests for parseNormalizedAuthority and a regression test which verifies a malformed X-Forwarded-Host is rejected when TrustProxy is enabled; modified files: middleware/hostauthorization/hostauthorization.go, middleware/hostauthorization/hostauthorization_test.go.

Copilot AI review requested due to automatic review settings May 18, 2026 14:41
@gaby gaby requested a review from a team as a code owner May 18, 2026 14:41
@gaby gaby requested review from ReneWerner87, efectn and sixcolors May 18, 2026 14:41
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 18, 2026

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

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: fd9cb8fe-8bc7-4cb6-a24a-85b605288705

📥 Commits

Reviewing files that changed from the base of the PR and between 9539140 and 22f3a0b.

📒 Files selected for processing (1)
  • middleware/hostauthorization/hostauthorization.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • middleware/hostauthorization/hostauthorization.go

Walkthrough

The hostauthorization middleware now parses and strictly validates request authorities via parseNormalizedAuthority, normalizes the host (including Punycode), and denies malformed authorities; tests cover parsing edge cases and middleware rejection of malformed forwarded hosts.

Changes

Host Authority Validation

Layer / File(s) Summary
Authority parsing implementation
middleware/hostauthorization/hostauthorization.go
Adds parseNormalizedAuthority and isValidPort, updates normalizeHost trimming (using utils helpers). Implements strict authority parsing (reject user-info, validate bracketed IPv6 and host:port, numeric port 0–65535) and host normalization (lowercase + Punycode).
Middleware integration
middleware/hostauthorization/hostauthorization.go
Hot-path now calls parseNormalizedAuthority(c.Host()) and returns ErrForbiddenHost on parse/validation failure instead of normalizing c.Hostname() only.
Test coverage
middleware/hostauthorization/hostauthorization_test.go
Adds Test_ParseNormalizedAuthority (table-driven unit tests for valid and invalid authorities) and Test_HostAuthorization_XForwardedHost_TrustProxy_RejectsMalformedAuthority (integration test asserting 403 for malformed X-Forwarded-Host).
sequenceDiagram
  participant Client
  participant HostAuthorizationMiddleware
  participant parseNormalizedAuthority
  participant HostMatcher
  participant ErrorHandler
  Client->>HostAuthorizationMiddleware: HTTP request (Host / X-Forwarded-Host)
  HostAuthorizationMiddleware->>parseNormalizedAuthority: parse & normalize authority
  alt parse ok
    parseNormalizedAuthority-->>HostAuthorizationMiddleware: normalized host
    HostAuthorizationMiddleware->>HostMatcher: matchHost(normalized host)
    HostMatcher-->>HostAuthorizationMiddleware: allow/deny
  else parse failed
    parseNormalizedAuthority-->>HostAuthorizationMiddleware: error
    HostAuthorizationMiddleware->>ErrorHandler: ErrForbiddenHost
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

  • gofiber/fiber#4199: Related hostauthorization middleware updates; this PR extends/adjusts host parsing/normalization and tests.
  • gofiber/fiber#4012: Related adjustments to forwarded-host parsing/whitespace handling affecting authority parsing.
  • gofiber/fiber#4291: Similar change to fail-closed earlier in host/authority handling and reject empty/malformed hosts before matching.

Suggested labels

📜 RFC Compliance

Suggested reviewers

  • sixcolors
  • ReneWerner87
  • efectn

Poem

🐰 I nibble at brackets, colons, and dots,
No @ may pass these guarded spots.
IPv6 tucked in brackets tight,
Ports must be digits, proper and right.
Punycode hums — the host sleeps tight.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main change: rejecting malformed host authorities in hostauthorization middleware to fix a security vulnerability.
Description check ✅ Passed The description adequately explains the motivation, implementation details, and testing approach, though some template sections are incomplete.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix-host-authorization-bypass-vulnerability

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 golangci-lint (2.12.2)

level=error msg="[linters_context] typechecking error: pattern ./...: directory prefix . does not contain main module or its selected dependencies"


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 and usage tips.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds stricter authority validation to the hostauthorization middleware so that malformed Host / X-Forwarded-Host values (e.g. authorities containing userinfo @, mismatched IPv6 brackets, non-numeric ports) are rejected before allowlist matching. This closes a host-header confusion bypass where input like allowed.com:443@attacker.example could match an allowed entry once parsed via c.Hostname().

Changes:

  • New parseNormalizedAuthority + isValidPort helpers that validate raw authority syntax and return a normalized host.
  • Middleware now validates c.Host() (raw authority including X-Forwarded-Host when proxy-trusted) instead of relying on the parsed c.Hostname().
  • Added a unit test for the new parser and a regression test for the malformed X-Forwarded-Host rejection path.

Reviewed changes

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

File Description
middleware/hostauthorization/hostauthorization.go Adds authority parser/validator and switches handler to validate the raw host.
middleware/hostauthorization/hostauthorization_test.go Adds tests for the new parser and the malformed X-Forwarded-Host regression.

@gaby
Copy link
Copy Markdown
Member Author

gaby commented May 18, 2026

@copilot resolve the merge conflicts in this pull request

Co-authored-by: gaby <835733+gaby@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request enhances the host authorization middleware by introducing a dedicated authority parser that handles IPv6 addresses and port validation, effectively preventing bypasses using malformed authority strings. Review feedback suggests optimizing the port validation to strictly adhere to RFC 7230, reducing string scans in the parsing logic for better performance, and expanding the test suite to cover additional edge cases such as bare IPv6 and out-of-range ports.

Comment on lines +165 to +176
func isValidPort(raw string) bool {
if raw == "" {
return false
}

port, err := strconv.Atoi(raw)
if err != nil {
return false
}

return port >= 0 && port <= 65535
}
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.

medium

The isValidPort function uses strconv.Atoi, which allows leading signs (e.g., +80) and doesn't strictly enforce that the input consists only of digits. According to RFC 7230, the port should be a sequence of digits. Additionally, we can optimize this by avoiding strconv.Atoi and checking the length first.

Suggested change
func isValidPort(raw string) bool {
if raw == "" {
return false
}
port, err := strconv.Atoi(raw)
if err != nil {
return false
}
return port >= 0 && port <= 65535
}
func isValidPort(raw string) bool {
if raw == "" || len(raw) > 5 {
return false
}
var port int
for i := 0; i < len(raw); i++ {
if raw[i] < '0' || raw[i] > '9' {
return false
}
port = port*10 + int(raw[i]-'0')
}
return port <= 65535
}

Comment on lines +119 to +163
func parseNormalizedAuthority(authority string) (string, bool) {
authority = utils.TrimSpace(authority)
if authority == "" || strings.Contains(authority, "@") {
return "", false
}

host := authority
if strings.HasPrefix(authority, "[") {
idx := strings.IndexByte(authority, ']')
if idx <= 1 {
return "", false
}

host = authority[1:idx]
rest := authority[idx+1:]
if rest != "" {
if !strings.HasPrefix(rest, ":") {
return "", false
}
if !isValidPort(rest[1:]) {
return "", false
}
}
} else {
if strings.Contains(authority, "[") || strings.Contains(authority, "]") {
return "", false
}

if i := strings.LastIndexByte(authority, ':'); i != -1 {
host = authority[:i]
port := authority[i+1:]

if strings.IndexByte(host, ':') >= 0 || !isValidPort(port) {
return "", false
}
}
}

host = normalizeHost(host)
if host == "" {
return "", false
}

return host, true
}
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.

medium

The parseNormalizedAuthority function performs multiple scans of the authority string (e.g., strings.Contains, strings.HasPrefix, strings.IndexByte, strings.LastIndexByte). While correct for security, this could be optimized to reduce the number of passes over the string, especially since this middleware runs on every request. Consider a single-pass parser if performance becomes a bottleneck.

Comment on lines +146 to +153
}{
{name: "plain host", input: "example.com", expected: "example.com", expectOK: true},
{name: "host with valid port", input: "example.com:8080", expected: "example.com", expectOK: true},
{name: "ipv6 with port", input: "[::1]:443", expected: "::1", expectOK: true},
{name: "userinfo style authority", input: "allowed.com:443@attacker.example", expectOK: false},
{name: "invalid port syntax", input: "allowed.com:http", expectOK: false},
{name: "malformed bracket", input: "[::1", expectOK: false},
}
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.

medium

Consider adding more edge cases to Test_ParseNormalizedAuthority, such as bare IPv6 (e.g., ::1), empty ports (e.g., example.com:), ports out of range (e.g., example.com:65536), and authorities with extra trailing data (e.g., [::1]extra).

		{name: "plain host", input: "example.com", expected: "example.com", expectOK: true},
		{name: "host with valid port", input: "example.com:8080", expected: "example.com", expectOK: true},
		{name: "ipv6 with port", input: "[::1]:443", expected: "::1", expectOK: true},
		{name: "ipv6 without port", input: "[::1]", expected: "::1", expectOK: true},
		{name: "bare ipv6", input: "::1", expectOK: false},
		{name: "empty port", input: "example.com:", expectOK: false},
		{name: "port out of range", input: "example.com:65536", expectOK: false},
		{name: "userinfo style authority", input: "allowed.com:443@attacker.example", expectOK: false},
		{name: "invalid port syntax", input: "allowed.com:http", expectOK: false},
		{name: "malformed bracket", input: "[::1", expectOK: false},
		{name: "extra data after bracket", input: "[::1]extra", expectOK: false},

Copy link
Copy Markdown
Contributor

Copilot AI commented May 18, 2026

@copilot resolve the merge conflicts in this pull request

Resolved in 59207b1 by merging origin/main into this branch and keeping the raw-authority validation plus the merged test coverage.

@gaby
Copy link
Copy Markdown
Member Author

gaby commented May 18, 2026

@copilot address all the pull request comments. Use fiberutils when possible.

Copy link
Copy Markdown
Contributor

Copilot AI commented May 18, 2026

@copilot address all the pull request comments. Use fiberutils when possible.

Addressed in 69666f2 by switching the host normalization trimming to github.com/gofiber/utils/v2 helpers where practical, while keeping the existing authority-validation behavior intact.

Copilot finished work on behalf of gaby May 18, 2026 15:03
@codecov
Copy link
Copy Markdown

codecov Bot commented May 18, 2026

Codecov Report

❌ Patch coverage is 80.48780% with 8 lines in your changes missing coverage. Please review.
✅ Project coverage is 91.23%. Comparing base (dfc64f5) to head (69666f2).

Files with missing lines Patch % Lines
middleware/hostauthorization/hostauthorization.go 80.48% 4 Missing and 4 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #4293      +/-   ##
==========================================
- Coverage   91.26%   91.23%   -0.03%     
==========================================
  Files         132      132              
  Lines       12933    12968      +35     
==========================================
+ Hits        11803    11832      +29     
- Misses        715      717       +2     
- Partials      415      419       +4     
Flag Coverage Δ
unittests 91.23% <80.48%> (-0.03%) ⬇️

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

☔ View full report in Codecov by Sentry.
📢 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 18, 2026 15:21
@gaby
Copy link
Copy Markdown
Member Author

gaby commented May 18, 2026

@copilot Address all the code review comments

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@middleware/hostauthorization/hostauthorization.go`:
- Around line 125-149: The bracketed-authority branch currently accepts any
token inside '[' ']' and should instead only accept valid IP-literals; update
the bracketed handling (the code that sets host = authority[1:idx] and rest :=
authority[idx+1:]) to validate that host is a real IP-literal (IPv6 or
RFC-6874/IPvFuture as appropriate) and reject (return "", false) for any non-IP
reg-name like "allowed.com"; you can call net.ParseIP (or an existing
IPv6/IPvFuture validator) and keep the existing port check via isValidPort for
rest, failing closed on any validation failure.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 98f2cb87-165c-4493-a260-24d64877de1c

📥 Commits

Reviewing files that changed from the base of the PR and between 69666f2 and 9539140.

📒 Files selected for processing (2)
  • middleware/hostauthorization/hostauthorization.go
  • middleware/hostauthorization/hostauthorization_test.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • middleware/hostauthorization/hostauthorization_test.go

Comment on lines +125 to +149
if authority[0] == '[' {
idx := -1
for i := 1; i < len(authority); i++ {
switch authority[i] {
case '@', '[':
return "", false
case ']':
idx = i
i = len(authority)
}
}
if idx <= 1 {
return "", false
}

host = authority[1:idx]
rest := authority[idx+1:]
if rest != "" {
if rest[0] != ':' {
return "", false
}
if !isValidPort(rest[1:]) {
return "", false
}
}
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.

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Reject bracketed non-IP literals before normalization.

Line 140 currently accepts any non-empty token inside [ ], so malformed authorities like [allowed.com] or [allowed.com]:443 normalize to allowed.com and can still satisfy the allowlist. That leaves a host-authorization bypass in the exact path this patch is trying to harden. The bracketed branch should only accept real IP-literals and fail closed for bracketed reg-names.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@middleware/hostauthorization/hostauthorization.go` around lines 125 - 149,
The bracketed-authority branch currently accepts any token inside '[' ']' and
should instead only accept valid IP-literals; update the bracketed handling (the
code that sets host = authority[1:idx] and rest := authority[idx+1:]) to
validate that host is a real IP-literal (IPv6 or RFC-6874/IPvFuture as
appropriate) and reject (return "", false) for any non-IP reg-name like
"allowed.com"; you can call net.ParseIP (or an existing IPv6/IPvFuture
validator) and keep the existing port check via isValidPort for rest, failing
closed on any validation failure.

Copy link
Copy Markdown
Contributor

Copilot AI commented May 18, 2026

@copilot Address all the code review comments

Addressed in 9539140 and 22f3a0b. The follow-up tightens port validation to digits-only, reduces repeated scans in parseNormalizedAuthority, and adds the missing parser edge-case coverage.

Copilot finished work on behalf of gaby May 18, 2026 22:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

4 participants