Implement URL validation and security checks in ClientHttpRedirect#26395
Implement URL validation and security checks in ClientHttpRedirect#26395YoussefAhmed256 wants to merge 1 commit intojenkinsci:masterfrom
Conversation
There was a problem hiding this comment.
Pull request overview
Adds URL scheme validation to ClientHttpRedirect to prevent client-side redirects to non-HTTP(S) schemes (per #26387), and introduces unit tests covering allowed vs blocked URL patterns.
Changes:
- Add scheme/URL safety checks to
ClientHttpRedirect.generateResponseand block unsafe redirects with a 403 error. - Extend class-level Javadoc to document the new redirect restrictions.
- Add a new JUnit test class exercising allowed (http/https, relative) and blocked (javascript/data/file/custom) redirect targets.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
| core/src/main/java/jenkins/util/ClientHttpRedirect.java | Introduces redirect URL validation and blocks unsafe schemes before emitting the client-side redirect HTML. |
| test/src/test/java/jenkins/util/ClientHttpRedirectTest.java | Adds coverage for allowed/blocked redirect URLs, including mixed-case schemes. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| public record ClientHttpRedirect(String redirectUrl) implements HttpResponse { | ||
|
|
||
| private static boolean isSafeToRedirectTo(@NonNull String url) { | ||
| if (Util.isSafeToRedirectTo(url)) { | ||
| return true; | ||
| } | ||
|
|
||
| String urlLower = url.toLowerCase(Locale.ENGLISH); | ||
| return urlLower.startsWith("http://") || urlLower.startsWith("https://"); | ||
| } | ||
|
|
||
| @Override | ||
| public void generateResponse(StaplerRequest2 req, StaplerResponse2 rsp, Object o) throws IOException, ServletException { | ||
| if (!isSafeToRedirectTo(redirectUrl)) { | ||
| throw hudson.util.HttpResponses.error(403, |
There was a problem hiding this comment.
redirectUrl can be null (record component is unannotated) but generateResponse and isSafeToRedirectTo assume non-null and will NPE. Consider annotating the record component with @NonNull and enforcing it in a compact constructor (or otherwise handle null safely).
| writer.flush(); | ||
| String output = baos.toString(); | ||
| assert output.length() > 0; | ||
| } |
There was a problem hiding this comment.
These tests use Java assert statements, which are skipped unless the JVM is started with -ea; this can make the test suite pass without actually verifying behavior. Use JUnit assertions (e.g., Assertions.assertTrue/assertFalse/assertEquals) instead.
| try { | ||
| redirect.generateResponse(req, rsp, null); | ||
| throw new AssertionError("Should have thrown an exception"); | ||
| } catch (Exception e) { | ||
| assert e.getMessage().contains("Unsafe redirect blocked"); | ||
| } | ||
| } |
There was a problem hiding this comment.
The blocked-URL tests use manual try/catch plus throw new AssertionError(...), and then assert on the exception message. Prefer Assertions.assertThrows to assert the expected exception type and inspect the message; it produces clearer failures and avoids false positives from unexpected exception types.
| } | ||
|
|
||
| rsp.setContentType("text/html;charset=UTF-8"); | ||
| Util.printRedirect(req.getContextPath(), redirectUrl, redirectUrl, rsp.getWriter()); |
There was a problem hiding this comment.
Util.printRedirect writes the message argument into the HTML body without escaping (see hudson.Util#printRedirect). Passing redirectUrl as the message here can therefore lead to HTML/JS injection if a caller ever supplies an attacker-controlled value. Use an HTML-escaped message (or a fixed string) rather than the raw URL.
| Util.printRedirect(req.getContextPath(), redirectUrl, redirectUrl, rsp.getWriter()); | |
| Util.printRedirect(req.getContextPath(), redirectUrl, Util.escape(redirectUrl), rsp.getWriter()); |
|
Closing this pull request, since you did not describe any of the testing that was done and did not complete the submitter portion of the pull request checklist, nor did you include screenshots showing the result of the failed redirect. The tests that were written look promising, but they use outdated techniques like You are welcome to create a new pull request with those issues resolved. You should also review the comments from GitHub Copilot and include them in your new pull request. |
i will resolve all these issues then back again with a new pull request |
Fixes #26387 >
Testing done
Screenshots (UI changes only)
Before
After
Proposed changelog entries
/label skip-changelog
Proposed changelog category
/label
Proposed upgrade guidelines
N/A
Submitter checklist
@Restrictedor have@since TODOJavadocs, as appropriate.@Deprecated(since = "TODO")or@Deprecated(forRemoval = true, since = "TODO"), if applicable.evalto ease future introduction of Content Security Policy (CSP) directives (see documentation).Desired reviewers
@mention
Before the changes are marked as
ready-for-merge:Maintainer checklist
upgrade-guide-neededlabel is set and there is a Proposed upgrade guidelines section in the pull request title (see example).lts-candidateto be considered.