diff --git a/.jules/sentinel.md b/.jules/sentinel.md index 73c52c95..c2c07f0f 100644 --- a/.jules/sentinel.md +++ b/.jules/sentinel.md @@ -19,3 +19,7 @@ **Vulnerability:** A CRLF injection vulnerability was identified where unvalidated inputs could be reflected directly into HTTP response headers, leading to HTTP response splitting. **Learning:** Instead of sanitizing individual headers independently, the overarching wrapper that interacts with the HTTP response output should inherently validate and sanitize the input to prevent injection across all headers. **Prevention:** `TusServletResponse.java` was modified to include a `sanitizeHeaderValue` method, replacing any instances of `\r` and `\n` characters before interacting with the core `HttpServletResponse`, ensuring consistent CRLF prevention application-wide. +## 2026-09-08 - Fix Arbitrary Origin Reflection in CORS Configuration +**Vulnerability:** The `CorsRequestHandler.java` implementation blindly read the `Origin` header from incoming HTTP requests and reflected its exact value back in the `Access-Control-Allow-Origin` response header. +**Learning:** Blindly mirroring the request's origin bypasses the intended security of CORS restrictions and can lead to arbitrary cross-origin data exposure, effectively negating the same-origin policy enforcement by browsers. Moreover, doing so without `Vary: Origin` could lead to cache poisoning in intermediate CDNs. +**Prevention:** Hardcode public API endpoint CORS rules to return the explicitly permissive `*` (wildcard) instead of reflecting the user's string input. diff --git a/src/main/java/me/desair/tus/server/cors/CorsRequestHandler.java b/src/main/java/me/desair/tus/server/cors/CorsRequestHandler.java index 5bf72034..a0af1e2a 100644 --- a/src/main/java/me/desair/tus/server/cors/CorsRequestHandler.java +++ b/src/main/java/me/desair/tus/server/cors/CorsRequestHandler.java @@ -24,7 +24,7 @@ public void process( String origin = servletRequest.getHeader("Origin"); if (StringUtils.isNotBlank(origin)) { - servletResponse.setHeader("Access-Control-Allow-Origin", origin); + servletResponse.setHeader("Access-Control-Allow-Origin", "*"); servletResponse.setHeader( "Access-Control-Expose-Headers", "Upload-Offset, Upload-Length, Upload-Metadata, Upload-Expires, Upload-Concat, " diff --git a/src/test/java/me/desair/tus/server/AbstractITTusFileUploadService.java b/src/test/java/me/desair/tus/server/AbstractITTusFileUploadService.java index b51ec433..df5f5c87 100644 --- a/src/test/java/me/desair/tus/server/AbstractITTusFileUploadService.java +++ b/src/test/java/me/desair/tus/server/AbstractITTusFileUploadService.java @@ -1843,7 +1843,7 @@ public void testCorsHeaders() throws Exception { servletRequest.addHeader(HttpHeader.UPLOAD_LENGTH, 100L); tusFileUploadService.process(servletRequest, servletResponse, OWNER_KEY); - assertResponseHeader("Access-Control-Allow-Origin", "https://example.com"); + assertResponseHeader("Access-Control-Allow-Origin", "*"); assertResponseHeaderNotBlank("Access-Control-Expose-Headers"); } @@ -1856,7 +1856,7 @@ public void testCorsPreflight() throws Exception { servletRequest.addHeader("Access-Control-Request-Method", "PATCH"); tusFileUploadService.process(servletRequest, servletResponse, OWNER_KEY); - assertResponseHeader("Access-Control-Allow-Origin", "https://example.com"); + assertResponseHeader("Access-Control-Allow-Origin", "*"); assertResponseHeader("Access-Control-Allow-Methods", "POST, GET, HEAD, PATCH, DELETE, OPTIONS"); assertResponseHeaderNotBlank("Access-Control-Allow-Headers"); assertResponseHeader("Access-Control-Max-Age", "86400"); diff --git a/src/test/java/me/desair/tus/server/cors/CorsRequestHandlerTest.java b/src/test/java/me/desair/tus/server/cors/CorsRequestHandlerTest.java index a26d114a..25eadfc7 100644 --- a/src/test/java/me/desair/tus/server/cors/CorsRequestHandlerTest.java +++ b/src/test/java/me/desair/tus/server/cors/CorsRequestHandlerTest.java @@ -53,7 +53,7 @@ public void processWithOrigin() throws Exception { handler.process(HttpMethod.GET, req, resp, null, null); - assertThat(mockResp.getHeader("Access-Control-Allow-Origin"), is("https://example.com")); + assertThat(mockResp.getHeader("Access-Control-Allow-Origin"), is("*")); assertThat( mockResp.getHeader("Access-Control-Expose-Headers"), is( @@ -70,7 +70,7 @@ public void processPreflight() throws Exception { handler.process(HttpMethod.OPTIONS, req, resp, null, null); - assertThat(mockResp.getHeader("Access-Control-Allow-Origin"), is("https://example.com")); + assertThat(mockResp.getHeader("Access-Control-Allow-Origin"), is("*")); assertThat( mockResp.getHeader("Access-Control-Allow-Methods"), is("POST, GET, HEAD, PATCH, DELETE, OPTIONS"));