Skip to content

Fix cors allowed origin pattern matching - #4008

Draft
joemahady-comm wants to merge 4 commits into
cloudfoundry:developfrom
joemahady-comm:TNZGOV-15556
Draft

Fix cors allowed origin pattern matching#4008
joemahady-comm wants to merge 4 commits into
cloudfoundry:developfrom
joemahady-comm:TNZGOV-15556

Conversation

@joemahady-comm

@joemahady-comm joemahady-comm commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Ticket: cors-allowed-origin-patterns-matched-with-find-instead-of-matches

Fix: Updated the CorsFilter to anchor (^...$) operator-supplied origin patterns during compilation and transitioned from .find() to .matches() for CORS Origin evaluations, ensuring unanchored spoofed domains are rejected while retaining intended .find() URI substring behavior.

…ith-find-instead-of-matches / uaa ai-assisted=yes

Co-authored-by: Cursor <cursoragent@cursor.com>
@joemahady-comm joemahady-comm changed the title Implement AI Scan -- MEDIUM -- cors-allowed-origin-patterns-matched-w… Fix cors allowed origin pattern matching Jul 29, 2026
@duanemay
duanemay requested a review from Copilot July 29, 2026 20:13

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

This PR tightens CORS origin validation in UAA by switching origin regex evaluation from substring matching to full-string matching, and by implicitly anchoring configured origin patterns during compilation to prevent spoofed domains from being accepted.

Changes:

  • Updated CorsFilter to validate origins with Pattern.matcher(...).matches() and to compile allowed-origin patterns with implicit ^...$ anchoring.
  • Updated login CORS MockMvc tests to use valid .* subdomain patterns for *.localhost.
  • (Tests) Continued exercising zone/path variants of logout CORS preflight behavior.

Reviewed changes

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

File Description
server/src/main/java/org/cloudfoundry/identity/uaa/security/web/CorsFilter.java Makes origin evaluation full-match and anchors configured origin regexes at compile time.
uaa/src/test/java/org/cloudfoundry/identity/uaa/login/LoginMockMvcTests.java Updates CORS allowed-origin test patterns to a valid .*\\.localhost form.
uaa/src/test/java/org/cloudfoundry/identity/uaa/login/LoginMockMvcZonePathTests.java Updates zone/path CORS allowed-origin test patterns to a valid .*\\.localhost form.
Comments suppressed due to low confidence (2)

uaa/src/test/java/org/cloudfoundry/identity/uaa/login/LoginMockMvcTests.java:2270

  • The allowed URI regex uses an unescaped '.' ("^/logout.do$") which also matches unintended paths like "/logoutXdo". Escaping the dot makes the test (and example configuration) match the literal ".do" suffix only.
        corsFilter.getFilter().setCorsXhrAllowedOrigins(asList("^localhost$", "^.*\\.localhost$"));
        corsFilter.getFilter().setCorsXhrAllowedUris(singletonList("^/logout.do$"));

uaa/src/test/java/org/cloudfoundry/identity/uaa/login/LoginMockMvcZonePathTests.java:2741

  • The allowed URI regex patterns use an unescaped '.' ("^/logout.do$", "^/z/[^/]+/logout.do$") which can match unintended URIs. Escaping the dot keeps the allowed-URI whitelist exact.
        corsFilter.getFilter().setCorsXhrAllowedOrigins(asList("^localhost$", "^.*\\.localhost$"));
        // For ZONE_PATH mode, the request path is /z/{subdomain}/logout.do, so we need to allow that pattern
        List<String> allowedUris = mode == ZoneResolutionMode.ZONE_PATH
                ? asList("^/logout.do$", "^/z/[^/]+/logout.do$")
                : singletonList("^/logout.do$");

Comment thread uaa/src/test/java/org/cloudfoundry/identity/uaa/login/LoginMockMvcTests.java Outdated
Comment thread server/src/main/java/org/cloudfoundry/identity/uaa/security/web/CorsFilter.java Outdated
Comment on lines +369 to +377
private String anchorPattern(String pattern) {
if (!pattern.startsWith("^")) {
pattern = "^" + pattern;
}
if (!pattern.endsWith("$")) {
pattern = pattern + "$";
}
return pattern;
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Updated the configuration references for both cors.default.allowed.origins and cors.xhr.allowed.origins. Added a note to clarify that these origin patterns are evaluated as full-string matches (implicitly anchored with ^ and $). Included a helpful regex example for securely matching subdomains and optional ports (^https?://([a-zA-Z0-9-]+.)*example.com(:[0-9]+)?$).

Question as this is a document update and potential breaking change for operators how is it handled?

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 5 out of 5 changed files in this pull request and generated 1 comment.

Suppressed comments (2)

server/src/test/java/org/cloudfoundry/identity/uaa/security/web/CorsFilterNonDefaultZoneTests.java:100

  • The test uses an allowed-origin regex (^.*bunnyoutlet-shop\.com$) that would also allow spoofed origins like evilbunnyoutlet-shop.com. Since this PR changes origin evaluation to full-string matching to reject spoofed domains, it would be good for this test to use a non-spoofable pattern and assert a spoofed origin is rejected.
    void requestWithAllowedOriginPatterns() throws Exception {
        identityZone.getConfig().getCorsPolicy().getXhrConfiguration().getAllowedOrigins()
                .add("^.*bunnyoutlet-shop\\.com$");

        MockHttpServletRequest request = new MockHttpServletRequest("GET", "/uaa/userinfo");
        request.addHeader("Origin", "bunnyoutlet-shop.com");
        request.addHeader("X-Requested-With", "XMLHttpRequest");

server/src/test/java/org/cloudfoundry/identity/uaa/security/web/CorsFilterNonDefaultZoneTests.java:266

  • Similar to the XHR case, this allowed-origin regex (^.*bunnyoutlet\.com$) would also match spoofed origins like evilbunnyoutlet.com. Adding a negative assertion here helps ensure the full-string origin matching change actually rejects prefix-spoofed domains.
    @Test
    void defaultCorsWithAllowedOriginPatterns() throws Exception {
        identityZone.getConfig().getCorsPolicy().getDefaultConfiguration().getAllowedOrigins()
                .add("^.*bunnyoutlet\\.com$");

        MockHttpServletRequest request = new MockHttpServletRequest("GET", "/uaa/userinfo");
        request.addHeader("Origin", "bunnyoutlet.com");
        corsFilter.doFilter(request, response, filterChain);

Comment thread server/src/main/java/org/cloudfoundry/identity/uaa/security/web/CorsFilter.java Outdated
Avoid mutating CorsConfiguration per request by compiling patterns lazily into local lists and swapping them under a lock. Also adds `patternsCompiled` state in `CorsConfiguration` to track when compilation is needed, reducing CPU overhead from recompiling regex on every request.

Co-authored-by: Cursor <cursoragent@cursor.com>
@joemahady-comm
joemahady-comm marked this pull request as draft August 13, 2026 15:15
@joemahady-comm

Copy link
Copy Markdown
Contributor Author

Checking if this can be made non-breaking

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Development

Successfully merging this pull request may close these issues.

3 participants