Skip to content

🐛 bug: Fix CORS subdomain wildcard boundary#3690

Merged
gaby merged 10 commits intomainfrom
2025-08-17-21-42-32
Aug 19, 2025
Merged

🐛 bug: Fix CORS subdomain wildcard boundary#3690
gaby merged 10 commits intomainfrom
2025-08-17-21-42-32

Conversation

@gaby
Copy link
Member

@gaby gaby commented Aug 17, 2025

Summary

  • harden CORS subdomain wildcard handling by verifying normalized host boundaries
  • add regression tests to prevent malformed subdomain matches

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Aug 17, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

Refactors CORS middleware wildcard-origin handling to centralize origin trimming and improve wildcard/subdomain processing; tightens subdomain.match validation with explicit early-return checks; updates tests to accept whitespace-padded wildcard entries and add malformed/empty subdomain cases.

Changes

Cohort / File(s) Summary of Changes
CORS wildcard handling refactor
middleware/cors/cors.go
Centralizes trimming to trimmedOrigin; detects wildcard using "://*."; removes "*." after the scheme to build withoutWildcard; normalizes and validates origins; constructs subdomain descriptors using the scheme boundary; reuses trimmedOrigin for normalization and error messages; no public API changes.
Subdomain match logic
middleware/cors/utils.go
Replaces a compound boolean with ordered early-return checks: minimum-length guard, prefix check, suffix check, explicit dot-before-suffix validation, and per-label non-empty checks for the subdomain portion.
CORS tests and benchmarks
middleware/cors/cors_test.go, middleware/cors/utils_test.go
Tests updated to accept whitespace-padded wildcard entries; added malformed subdomain tests (e.g., http://evil.comexample.com), empty or malformed host-label cases (https://.example.com, https://..example.com); switched suffix test data from .example.com to example.com; inserted context resets between subtests; benchmark inputs adjusted accordingly.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant CORS_Middleware
  participant SubdomainMatcher

  Client->>CORS_Middleware: Request with Origin
  CORS_Middleware->>CORS_Middleware: Trim origin -> trimmedOrigin
  alt Exact allowlist entry
    CORS_Middleware-->>Client: Set Access-Control-Allow-Origin = Origin
  else Wildcard pattern found ("://*.")
    CORS_Middleware->>CORS_Middleware: Build withoutWildcard (remove "*.")
    CORS_Middleware->>CORS_Middleware: normalizeOrigin(withoutWildcard)
    CORS_Middleware->>SubdomainMatcher: match(prefix, suffix, origin)
    alt match == true
      CORS_Middleware-->>Client: Set Access-Control-Allow-Origin = Origin
    else
      CORS_Middleware-->>Client: Do not set Access-Control-Allow-Origin
    end
  else No match
    CORS_Middleware-->>Client: Do not set Access-Control-Allow-Origin
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested reviewers

  • sixcolors
  • ReneWerner87
  • efectn

Poem

I nibble on code like clover so green,
I trim stray spaces and tidy the scheme.
I watch for the dot before suffix I know,
I stop sly hosts that try to grow.
CORS gates now neat — a rabbit's small show. 🐇✨

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between b5bb601 and da72650.

📒 Files selected for processing (1)
  • middleware/cors/utils.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • middleware/cors/utils.go
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: unit (1.25.x, windows-latest)
  • GitHub Check: repeated
  • GitHub Check: Compare
✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch 2025-08-17-21-42-32

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
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.

Summary of Changes

Hello @gaby, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request addresses a potential vulnerability or incorrect behavior in the CORS subdomain wildcard handling. The primary goal is to harden the existing logic to ensure that only truly valid subdomains are matched, thereby preventing unintended access or security bypasses. The changes involve refining the subdomain matching algorithm and introducing new regression tests to validate the robustness of the fix against malformed subdomain patterns.

Highlights

  • Enhanced CORS Subdomain Validation: The logic for matching subdomains in CORS has been strengthened to correctly identify and reject malformed or invalid subdomain patterns, improving security and reliability.
  • Improved subdomain.match Function: The subdomain.match method in middleware/cors/utils.go now includes explicit checks for origin length, prefix/suffix matching, and proper domain normalization to ensure accurate boundary verification.
  • New Regression Tests for Malformed Subdomains: Comprehensive test cases have been added in cors_test.go and utils_test.go to specifically cover scenarios with malformed subdomains, ensuring the fix prevents future regressions.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point in your pull request via creating an issue comment (i.e. comment on the pull request page) using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in issue comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@gaby gaby changed the title fix cors subdomain wildcard boundary 🐛 bug: Fix CORS subdomain wildcard boundary Aug 17, 2025
@gaby gaby added the v3 label Aug 17, 2025
@gaby gaby added this to v3 Aug 17, 2025
@gaby gaby added this to the v3 milestone Aug 17, 2025
@gaby gaby moved this to In Progress in v3 Aug 17, 2025
@codecov
Copy link

codecov bot commented Aug 17, 2025

Codecov Report

❌ Patch coverage is 88.00000% with 3 lines in your changes missing coverage. Please review.
✅ Project coverage is 92.00%. Comparing base (ff8597d) to head (da72650).
⚠️ Report is 244 commits behind head on main.

Files with missing lines Patch % Lines
middleware/cors/utils.go 84.21% 2 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #3690      +/-   ##
==========================================
+ Coverage   91.98%   92.00%   +0.01%     
==========================================
  Files         114      114              
  Lines       11485    11504      +19     
==========================================
+ Hits        10565    10584      +19     
  Misses        661      661              
  Partials      259      259              
Flag Coverage Δ
unittests 92.00% <88.00%> (+0.01%) ⬆️

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.

Copy link
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 aims to fix a CORS subdomain wildcard vulnerability. While the intent is correct and regression tests have been added, the implementation of the fix in subdomain.match is still flawed and does not correctly validate the subdomain boundary. A malicious origin like http://evil.comexample.com would still be allowed for a pattern http://*.example.com. I've provided a corrected implementation for subdomain.match that properly checks for the domain separator.

@gaby
Copy link
Member Author

gaby commented Aug 18, 2025

/gemini review

Copy link
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 aims to fix a CORS subdomain wildcard vulnerability. The changes are a good step, correctly adjusting index calculations and improving the matching logic in utils.go. However, I've identified a critical bug in the same code block where an index from the raw origin string is used on a normalized string of a potentially different length. This can break subdomain matching when origins are configured with extra whitespace. I've left a detailed comment on this issue. Addressing this bug is crucial for the feature to work reliably.

@gaby
Copy link
Member Author

gaby commented Aug 18, 2025

/gemini review

Copy link
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 effectively hardens the CORS subdomain wildcard handling, addressing a security vulnerability where a malformed origin could bypass the policy. The changes in subdomain.match to verify host boundaries are correct, and the logic for parsing wildcard origins has been improved. The addition of regression tests is also a great practice. My only suggestion is to replace magic numbers with len() calls to improve code readability and maintainability.

@gaby
Copy link
Member Author

gaby commented Aug 18, 2025

/gemini review

Copy link
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 effectively hardens the CORS subdomain wildcard handling, fixing a potential vulnerability where a malformed origin could be incorrectly allowed. The changes in subdomain.match correctly enforce that a dot separator must exist for a valid subdomain match. The addition of regression tests for malformed subdomains is also a great improvement. I have one suggestion to slightly improve performance and readability in the match function by reordering the checks.

Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
@gaby
Copy link
Member Author

gaby commented Aug 19, 2025

/gemini review

Copy link
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 provides a solid fix for a potential security issue in CORS subdomain wildcard handling. The logic for parsing and matching wildcard origins has been correctly hardened, and the new regression tests ensure this behavior is maintained. The changes are well-implemented. I have one minor suggestion to improve code readability.

@gaby gaby marked this pull request as ready for review August 19, 2025 00:14
Copilot AI review requested due to automatic review settings August 19, 2025 00:14
@gaby gaby requested a review from a team as a code owner August 19, 2025 00:14
Copy link
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

This PR fixes a security vulnerability in CORS subdomain wildcard handling by implementing proper host boundary validation. The fix prevents malicious domains from bypassing CORS restrictions by exploiting loose subdomain matching.

Key changes:

  • Enhanced the subdomain.match() method to require a dot separator before the domain suffix
  • Updated test cases to remove leading dots from suffix patterns for consistency
  • Added regression tests to verify that malformed subdomains are properly rejected

Reviewed Changes

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

File Description
middleware/cors/utils.go Implements stricter subdomain matching with dot boundary validation
middleware/cors/cors.go Updates subdomain parsing logic to handle wildcard patterns correctly
middleware/cors/utils_test.go Updates test cases and adds regression test for malformed subdomain attacks
middleware/cors/cors_test.go Adds integration test to verify malformed subdomain rejection

Copy link
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: 0

♻️ Duplicate comments (2)
middleware/cors/utils.go (1)

64-77: Dot-boundary enforcement is correct; length-first check prevents off-by-one.

This fixes the previously exploitable suffix match by requiring a '.' immediately before the suffix and short-circuiting on insufficient length. Good separation of prefix/suffix checks and clear early returns.

middleware/cors/cors.go (1)

49-58: Whitespace-trim + scheme-based split fixes prior index misuse.

Computing the wildcard removal on trimmedOrigin, then deriving the scheme boundary from normalizedOrigin addresses the earlier bug where indices from a raw origin (with spaces) were used against normalized strings.

🧹 Nitpick comments (3)
middleware/cors/utils.go (1)

15-28: Docstring is misleading vs behavior (IPv6 ports, path/query/fragment preservation).

The comment says “removes the scheme and port,” but the implementation:

  • Preserves IPv6 ports (by design with the leading '[' check).
  • Preserves path/query/fragment (as tests also assert).

Suggest updating the comment to reflect actual behavior for accuracy.

-// normalizeDomain removes the scheme and port from the input domain
+// normalizeDomain removes the scheme and (for hostnames/IPv4) the port from the input.
+// For IPv6 literals (e.g. [::1]:8080), the port is preserved to avoid ambiguity with
+// the address colons. Path, query, and fragment (if present) are preserved.
middleware/cors/utils_test.go (1)

138-161: Duplicate test names; make them unique for clearer failures.

Two cases are both named “match with different scheme”. Rename to distinguish which side has the https/http mismatch.

-        {
-            name:     "match with different scheme",
+        {
+            name:     "scheme mismatch (pattern http://api. vs origin https://...)",
             sub:      subdomain{prefix: "http://api.", suffix: "example.com"},
             origin:   "https://api.service.example.com",
             expected: false,
         },
         {
-            name:     "match with different scheme",
+            name:     "scheme mismatch (pattern https:// vs origin http://...)",
             sub:      subdomain{prefix: "https://", suffix: "example.com"},
             origin:   "http://api.service.example.com",
             expected: false,
         },
middleware/cors/cors.go (1)

49-58: Minor readability improvement: use Replace and name the separators.

This reduces mental parsing of slice indices and avoids repeating magic strings.

-        trimmedOrigin := utils.Trim(origin, ' ')
-        if i := strings.Index(trimmedOrigin, "://*."); i != -1 {
-            withoutWildcard := trimmedOrigin[:i+len("://")] + trimmedOrigin[i+len("://*."):]
+        trimmedOrigin := utils.Trim(origin, ' ')
+        const schemeSep = "://"
+        const wildcardToken = "://*."
+        if strings.Contains(trimmedOrigin, wildcardToken) {
+            withoutWildcard := strings.Replace(trimmedOrigin, wildcardToken, schemeSep, 1)
             isValid, normalizedOrigin := normalizeOrigin(withoutWildcard)
             if !isValid {
                 panic("[CORS] Invalid origin format in configuration: " + trimmedOrigin)
             }
-            schemeSep := strings.Index(normalizedOrigin, "://") + len("://")
-            sd := subdomain{prefix: normalizedOrigin[:schemeSep], suffix: normalizedOrigin[schemeSep:]}
+            schemeIdx := strings.Index(normalizedOrigin, schemeSep) + len(schemeSep)
+            sd := subdomain{prefix: normalizedOrigin[:schemeIdx], suffix: normalizedOrigin[schemeIdx:]}
             allowSOrigins = append(allowSOrigins, sd)
         } else {
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between ff8597d and 43482d9.

📒 Files selected for processing (4)
  • middleware/cors/cors.go (1 hunks)
  • middleware/cors/cors_test.go (2 hunks)
  • middleware/cors/utils.go (1 hunks)
  • middleware/cors/utils_test.go (2 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
middleware/cors/cors_test.go (2)
client/request.go (2)
  • Request (46-71)
  • Header (675-677)
constants.go (5)
  • MethodOptions (12-12)
  • HeaderAccessControlRequestMethod (205-205)
  • MethodGet (5-5)
  • HeaderOrigin (206-206)
  • HeaderAccessControlAllowOrigin (201-201)
🔇 Additional comments (4)
middleware/cors/utils_test.go (2)

119-119: Benchmark updated to dotless suffix matches the new matcher contract.

Using suffix: "example.com" aligns with the new dot-before-suffix rule in subdomain.match. Looks good.


181-186: Nice regression: malformed subdomain is correctly rejected.

This guards the evil.comexample.com case that previously slipped through. Good addition.

middleware/cors/cors_test.go (2)

329-329: Good: config trims surrounding whitespace in AllowOrigins.

This ensures wildcard parsing is resilient to common config formatting. Test reads well.


364-376: Solid negative test for malformed subdomain preflight.

Confirms http://evil.comexample.com is disallowed for http://*.example.com. This directly covers the boundary bug.

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Copy link
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

🧹 Nitpick comments (2)
middleware/cors/utils.go (2)

65-68: Tighten the length guard to reject empty-label cases like http://.example.com

As written, http://.example.com will pass: it satisfies prefix/suffix and the character before suffix is '.', but there is no label before that dot. Require at least one label character and a dot before the suffix by bumping the threshold by +1.

Apply this diff:

-	// Not a subdomain if not long enough for a dot separator.
-	if len(o) <= len(s.prefix)+len(s.suffix) {
+	// Not a subdomain if not long enough for "label + dot" before the suffix.
+	// Need at least one character label and a '.' before the suffix.
+	if len(o) <= len(s.prefix)+len(s.suffix)+1 {
 		return false
 	}

74-76: Optional: Assert a non-empty label before the dot for extra hardening

If you want an explicit check in addition to the stricter length guard, verify that there’s at least one valid label character before the '.':

  • This blocks inputs like http://..example.com or http://.example.com even if they slip past other guards.
  • Keep it lightweight to avoid over-validating DNS rules.

Apply this diff:

  // Check for the dot separator.
  suffixStartIndex := len(o) - len(s.suffix)
- return suffixStartIndex > 0 && o[suffixStartIndex-1] == '.'
+ if suffixStartIndex <= 0 || o[suffixStartIndex-1] != '.' {
+ 	return false
+ }
+ // Ensure at least one non-separator character before the dot.
+ if suffixStartIndex-2 < 0 {
+ 	return false
+ }
+ c := o[suffixStartIndex-2]
+ // Accept a-z, 0-9, or '-' as a minimal label-char set.
+ if !((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '-') {
+ 	return false
+ }
+ return true

Alternatively, keep the code as-is and cover this via tests (see next comment).

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 43482d9 and de2b14d.

📒 Files selected for processing (1)
  • middleware/cors/utils.go (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: unit (1.25.x, macos-latest)
  • GitHub Check: unit (1.25.x, windows-latest)
  • GitHub Check: Compare
  • GitHub Check: unit (1.25.x, macos-13)
  • GitHub Check: repeated
🔇 Additional comments (1)
middleware/cors/utils.go (1)

65-76: Dot-boundary subdomain match fix looks solid

  • Early length guard, separate prefix/suffix checks, and the explicit “dot before suffix” test close the prior vulnerability where evil.comexample.com could match *.example.com.
  • The extra bounds check on suffixStartIndex prevents panics.

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Copy link
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: 0

🧹 Nitpick comments (1)
middleware/cors/utils.go (1)

74-76: Disallow empty labels like .example.com or a..example.com (optional hardening)

As written, an origin such as http://.example.com will pass: it has the required dot before the suffix but with an empty label. Browsers won’t typically emit such Origins, but hardening here avoids edge-case mis-matches and aligns with the intent to only allow well-formed subdomains.

Apply this diff to ensure there’s at least one non-dot character before the separator dot:

-	// Check for the dot separator.
-	suffixStartIndex := len(o) - len(s.suffix)
-	return suffixStartIndex > 0 && o[suffixStartIndex-1] == '.'
+	// Check for the dot separator and ensure there's a non-empty label before it.
+	suffixStartIndex := len(o) - len(s.suffix)
+	dotIndex := suffixStartIndex - 1
+	if dotIndex <= len(s.prefix) || o[dotIndex] != '.' {
+		return false
+	}
+	// Disallow empty label directly before the suffix, e.g., http://.example.com or http://a..example.com
+	if o[dotIndex-1] == '.' {
+		return false
+	}
+	return true
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between de2b14d and 4796a52.

📒 Files selected for processing (1)
  • middleware/cors/utils.go (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: repeated
  • GitHub Check: unit (1.25.x, macos-latest)
  • GitHub Check: unit (1.25.x, windows-latest)
  • GitHub Check: Compare
🔇 Additional comments (2)
middleware/cors/utils.go (2)

65-76: Subdomain boundary hardened correctly (dot separator + early length check) — LGTM

The added length guard and explicit dot-before-suffix check address the prior “comexample.com” bypass and make the matching semantics clear and robust.


70-72: Normalization invariants verified in CORS and CSRF code

  • The subdomain.prefix and suffix values are constructed exclusively from normalizeOrigin(…), which returns a fully lower-cased scheme://host (including any port).
  • All runtime calls to subdomain.match(o) receive o as strings.ToLower(c.Get(fiber.HeaderOrigin)) (for CORS) or similarly lower-cased in CSRF, and HTTP Origin headers never include path/query/fragment.
  • Port semantics are preserved by normalizeOrigin (it includes parsedOrigin.Host, which contains the port if specified), and HasPrefix/HasSuffix matching therefore enforces exact port presence/absence.

No further changes required.

Copy link
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

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 73df613 and b5bb601.

📒 Files selected for processing (1)
  • middleware/cors/utils.go (1 hunks)
🧰 Additional context used
🪛 GitHub Check: modernize
middleware/cors/utils.go

[failure] 88-88:
Ranging over SplitSeq is more efficient


[failure] 88-88:
Loop can be simplified using slices.Contains

🪛 GitHub Check: lint
middleware/cors/utils.go

[failure] 78-78:
File is not properly formatted (gofmt)

🪛 GitHub Actions: golangci-lint
middleware/cors/utils.go

[error] 78-78: golangci-lint: File is not properly formatted (gofmt). Run 'gofmt -w' to fix formatting.

🪛 GitHub Actions: Modernize Lint
middleware/cors/utils.go

[error] 88-88: go run golang.org/x/tools/gopls/internal/analysis/modernize/cmd/modernize@latest -test=false ./...: Loop can be simplified using slices.Contains

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: repeated
  • GitHub Check: unit (1.25.x, macos-13)
  • GitHub Check: unit (1.25.x, macos-latest)
  • GitHub Check: unit (1.25.x, windows-latest)
  • GitHub Check: Compare
🔇 Additional comments (3)
middleware/cors/utils.go (3)

65-72: Good early guards; prefix/suffix checks look correct

The min-length guard and subsequent HasPrefix/HasSuffix checks are in the right order. This sets up the boundary verification reliably and addresses the prior “evil.comexample.com” suffix-only match pitfall when combined with the dot check below.


82-87: Explicit empty subdomain guard is correct

The early return on empty subdomain (e.g., origin like “https://.example.com”) is necessary and aligns with the new negative test cases. No changes needed.


88-92: Use slices.Contains to simplify empty-label check

Replace the manual loop over strings.Split(sub, ".") with a single call to slices.Contains:

-   for _, label := range strings.Split(sub, ".") {
-       if label == "" {
-           return false
-       }
-   }
+   labels := strings.Split(sub, ".")
+   if slices.Contains(labels, "") {
+       return false
+   }

And add the "slices" import alongside "strings":

import (
    "net/url"
    "slices"
    "strings"
)

Before merging, please manually verify:

  • Your build/CI environment is running Go 1.21 or later (the slices package was added in Go 1.21).
  • The file is correctly formatted (e.g. run gofmt -l middleware/cors/utils.go or go fmt).

Copy link
Member

@sixcolors sixcolors left a comment

Choose a reason for hiding this comment

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

Analysis of Wildcard Behavior: Current vs. Proposed

This PR addresses a potential security "foot-gun" in how wildcard subdomains are handled, making the behavior safer and more predictable for the CORS middleware.

1. Current Behavior in CORS and CSRF Middleware: The Lenient, Suffix-Based Match

Currently, both the CORS and CSRF middlewares use a simple prefix and suffix check for wildcards. This was the original design.

  • Behavior: An origin like https://*.example.com is treated as a pattern that must start with https:// and end with .example.com.
  • What it Matches:
    • https://api.example.com (Correct)
    • https://v1.api.example.com (Correct)
  • The "Foot-Gun":
    • It also matches https://example.com. This is because https://example.com starts with the prefix and ends with the suffix, with an empty string in between where the wildcard * is.
    • It could also match https://some-ext.example.com if the pattern was *ext.example.com.

While this was the original intention, it violates the principle of least privilege. A developer specifying *.example.com almost always intends to allow subdomains only, not the apex/root domain itself. This unexpected match could lead to security vulnerabilities.

2. This PR's Change (New CORS Behavior): The Stricter, Boundary-Enforced Match

This PR updates the CORS middleware to fix this potential "foot-gun." It now enforces that the wildcard * must represent one or more complete and valid DNS labels (the parts of a domain between dots).

  • Behavior: An origin like https://*.example.com requires the incoming origin to have the same scheme and suffix, but critically, it also requires a . dot separator immediately before the suffix part that is matched.
  • What it Matches:
    • https://api.example.com (Correct)
    • https://v1.api.example.com (Correct)
  • What it Correctly Rejects:
    • It does not match https://example.com because there is no subdomain label and no dot before .example.com.
    • It does not match https://.example.com because the subdomain label is empty.

This new behavior is more intuitive, secure, and aligns with what developers typically expect from a subdomain wildcard.

Is the New Behavior the Best? Does it Match Other Frameworks?

Yes, the new, stricter behavior introduced by this PR is not only better but is also the standard convention adopted by most major web frameworks and security libraries.

  • Express.js (cors package): Enforces subdomain boundaries and correctly handles multiple levels.
  • Django (django-cors-headers): Encourages explicit regexes that enforce subdomain boundaries.
  • Ruby on Rails (rack-cors): Uses regex that correctly matches single and multiple-level subdomains but not the apex domain.
  • OWASP CSRFGuard: Recommends treating * as a wildcard for a single, valid subdomain label.

Final Recommendation

Approved. This change is a clear security and usability improvement for the CORS middleware.

I strongly recommend opening a follow-up PR for the CSRF middleware to adopt this same stricter matching logic. This will ensure consistent, secure behavior across the entire framework.

@gaby gaby merged commit 9ed35c9 into main Aug 19, 2025
14 of 15 checks passed
@gaby gaby deleted the 2025-08-17-21-42-32 branch August 19, 2025 23:59
@github-project-automation github-project-automation bot moved this from In Progress to Done in v3 Aug 19, 2025
@gaby gaby added the 📜 RFC Compliance Feature, implementation, or contribution adheres to relevant RFC standards. label Nov 27, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

☢️ Bug codex 📜 RFC Compliance Feature, implementation, or contribution adheres to relevant RFC standards. v3

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

4 participants