Conversation
…-11956) Several endpoints take a URL from the caller and make the server fetch it: the factory devfile resolvers, the SCM file endpoint and the devfile `file://`-style references resolved by URLFileContentProvider. Neither the destination nor the scheme of those URLs was checked, and the user's personal access token was attached to the request regardless of which host it ended up being sent to. CWE-918 (SSRF) Add UrlTargetValidator in che-core-commons-lang, which rejects anything that is not http/https and resolves the host, refusing addresses that are not publicly routable: loopback, link-local (169.254.169.254 and the cloud metadata endpoints behind it), private and site-local ranges, multicast, 100.64.0.0/10 shared space used by CNI plugins, 192.0.0.0/24, 198.18.0.0/15, 240.0.0.0/4 and IPv6 fc00::/7 unique local addresses. All records returned for a host are checked, so a name publishing both a public and an internal address cannot pass the check and then be connected to on the internal one. URLFetcher applies the check on every hop instead of letting the JDK follow redirects: redirects are as much under the control of whoever supplied the URL as the URL itself, so validating only the first hop left the check bypassable. Redirects are now followed manually, capped at MAX_REDIRECTS. The GitHub and GitLab URL parsers probe unknown hosts with an API call to decide whether a URL belongs to a self-hosted instance. That probe is now limited to publicly routable hosts, otherwise the factory endpoint's answer turns into an internal port scanner. Private SCM servers are still reached through the configured provider endpoints or a personal access token, both of which are matched before it comes to probing. CWE-73 (external control of file name or path) AuthorizingFileContentProvider.formatUrl and URLFileContentProvider reject absolute devfile references whose scheme is not http/https, so a devfile can no longer make the server read `file:///etc/...` or a `jar:`/`ftp:` target and return it in the response. UrlTargetValidator reads the scheme off the raw string so that opaque URLs such as `jar:file:/x!/y` are rejected as schemes rather than reported as parse failures. CWE-522 (insufficiently protected credentials) Credentials are now bound to the origin they belong to. AuthorizingFileContentProvider sends the personal access token only to the provider's own origins - provider URL, host name and the raw content host, plus the Bitbucket API host for the Bitbucket provider - and fetches anything else anonymously. URLFileContentProvider sends the credentials taken from the devfile URL only back to the origin the devfile was loaded from. Both compare the full origin, not just the host, so a devfile cannot downgrade the request to plain http and put the token on the wire in the clear. URLFetcher drops the Authorization header when a redirect crosses origins, and refuses https -> http redirects outright. Also fix a copy-paste bug in ScmService#getFileContent, where the `file` parameter was never null-checked because `repository` was checked twice. Signed-off-by: Ihor Vinokur <ivinokur@redhat.com> Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
[APPROVALNOTIFIER] This PR is NOT APPROVED This pull-request has been approved by: vinokurig The full list of commands accepted by this bot can be found here. DetailsNeeds approval from an approver in each of these files:Approvers can indicate their approval by writing |
|
Docker image build succeeded: quay.io/eclipse/che-server:pr-1061 kubectl patch commandkubectl patch -n eclipse-che "checluster/eclipse-che" --type=json -p="[{"op": "replace", "path": "/spec/components/cheServer/deployment", "value": {containers: [{image: "quay.io/eclipse/che-server:pr-1061", name: che}]}}]" |
…input (CRW-11956) Two families of outbound request still chose their destination from something the caller controls, so either could be used to have the server reach a service only it can see and report back on the outcome (CWE-918). Bitbucket Server URL parser: a repository URL that matches no configured provider was probed to find out whether it is a Bitbucket Server. The URL comes straight off the factory endpoint, so the probe is now limited to publicly routable hosts, matching the guard the GitHub and GitLab parsers already have. A Bitbucket Server on a private network is still reached through che.integration.bitbucket.server_endpoints or a personal access token, both of which are matched first. Token and user-data fetchers: seven fetchers built an API client from the provider URL of a personal access token. That URL comes from a secret in the user's namespace, so anyone holding a namespace could name any host. Each now refuses a URL that is neither the configured provider endpoint nor publicly routable. Guarding the fetchers also closes the paths that reach a host by way of a stored token: KubernetesPersonalAccessTokenManager only returns a token whose provider URL some fetcher accepted, so a secret naming a private address no longer makes AbstractGithubURLParser.isUserTokenPresent accept the host, and no longer reaches getPullRequest, getLatestCommit or BitbucketServerUserDataFetcher. Note for private deployments: UrlTargetValidator has no allowlist, so an install whose SCM server is on a private network cannot be reached this way at all. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
tolusha
left a comment
There was a problem hiding this comment.
Great work on the defense-in-depth design - the four independent SSRF layers (UrlTargetValidator → URLFetcher → AuthorizingFileContentProvider → URLFileContentProvider) are exactly the right approach for this class of vulnerability. The per-hop redirect validation, origin-scoped credential binding, and scheme enforcement are all architecturally sound. The PR description's CWE-by-CWE breakdown makes the security intent unusually clear.
Requesting changes on one critical gap and a few items worth addressing before merge.
🔴 Critical: BitbucketServerURLParser.isApiRequestRelevant() is not guarded
BitbucketServerURLParser.isApiRequestRelevant() (line 160 in BitbucketServerURLParser.java) probes unknown hosts by instantiating HttpBitbucketServerApiClient from the user-supplied URL and calling getUser() — the exact same SSRF-vulnerable pattern this PR fixes in AbstractGithubURLParser and AbstractGitlabUrlParser. The Bitbucket Server parser was not given a canProbe() guard.
Adding the same guard as the GitHub/GitLab parsers would fix it:
if (!canProbe(serverUrl)) {
LOG.warn("Not probing {}: it does not point to a publicly routable host.", serverUrl);
return false;
}a55daf3
The failure list includes every provider (GitHub, GitLab, Bitbucket, Azure, Gitea, and the smoke test). This may indicate the SSRF validator is rejecting legitimate in-cluster traffic - e.g., if test SCM servers run on private addresses. Please investigate before merging. If this is expected (e.g., flaky infra), a retest result confirming that would help reviewers.
UrlTargetValidator resolves the host at check time, URLConnection re-resolves at connect time
Between InetAddress.getAllByName(host) in UrlTargetValidator.validate() and the subsequent currentUrl.openConnection() in URLFetcher, a malicious DNS server can switch from a public IP (passes the check) to a private IP (actually connected to). This is a known TOCTOU gap in SSRF defenses. Mitigating it would require pinning the resolved address for the connection (e.g., by resolving the host once, connecting by IP with the Host header set manually). This can be a follow-up if addressing it now is out of scope.
After this PR, GitHub Enterprise and GitLab instances on private IP ranges (10.x.x.x, 172.16.x.x, etc.) will no longer be auto-discovered via the isApiRequestRelevant probe. Operators who relied on this must configure the endpoint explicitly (e.g., che.integration.github.server_endpoints). The "How to test this PR" section is empty and no release note documents this. Operators upgrading will hit silent failures with no obvious cause. Could you add a note in the release notes / upgrade guide?
Suggestion: canProbe() is not enforced by the type system
The standard review found that BitbucketServerURLParser was missed. A future SCM provider that probes unknown hosts could silently omit the guard. Consider adding canProbe(String serverUrl) to an abstract URL parser base class or interface so new parser implementations are forced to decide. Even a default-false no-op with a doc comment explaining the security implication would help.
This is not relevant to the latest commit, the check does exist in the |
InetAddress.isAnyLocalAddress matches 0.0.0.0 alone, not the whole 0.0.0.0/8 "this network" block of RFC 791, so an address such as 0.1.2.3 passed the destination check. On Linux the kernel treats 0.0.0.0/8 as the local host, which makes those addresses another spelling of a loopback target. The documentation ranges of RFC 5737 - 192.0.2.0/24, 198.51.100.0/24 and 203.0.113.0/24 - are not routable on the public internet either, and can be configured on an internal interface. They are now refused as well. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…forgery' Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
…(CRW-11956) Suppress the CodeQL SSRF alert on URLFetcher with an explanation: the target host is user supplied by design, since devfiles are fetched from arbitrary SCM hosts including self hosted ones, so no fixed allowlist applies. validateTarget enforces http/https and a publicly routable destination on every redirect hop. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Docker image build succeeded: quay.io/eclipse/che-server:pr-1061 kubectl patch commandkubectl patch -n eclipse-che "checluster/eclipse-che" --type=json -p="[{"op": "replace", "path": "/spec/components/cheServer/deployment", "value": {containers: [{image: "quay.io/eclipse/che-server:pr-1061", name: che}]}}]" |
|
/retest |
|
@vinokurig: The following tests failed, say
Full PR test history. Your PR dashboard. DetailsInstructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. I understand the commands that are listed here. |
| * checked before it comes to this. | ||
| */ | ||
| @VisibleForTesting | ||
| boolean canProbe(String serverUrl) { |
There was a problem hiding this comment.
just to mention. It is named canContact in other files
| */ | ||
| @VisibleForTesting | ||
| boolean canContact(String scmServerUrl) { | ||
| return serverUrl.equals(scmServerUrl) || UrlTargetValidator.isAllowed(scmServerUrl); |
There was a problem hiding this comment.
I didn't see serverUrl.equals(scmServerUrl) for other provides. Is it only for gitlab?
What does this PR do?
Several endpoints take a URL from the caller and make the server fetch it: the factory devfile resolvers, the SCM file endpoint and the devfile
file://-style references resolved by URLFileContentProvider. Neither the destination nor the scheme of those URLs was checked, and the user's personal access token was attached to the request regardless of which host it ended up being sent to.CWE-918 (SSRF)
Add UrlTargetValidator in che-core-commons-lang, which rejects anything that is not http/https and resolves the host, refusing addresses that are not publicly routable: loopback, link-local (169.254.169.254 and the cloud metadata endpoints behind it), private and site-local ranges, multicast, 100.64.0.0/10 shared space used by CNI plugins, 192.0.0.0/24, 198.18.0.0/15, 240.0.0.0/4 and IPv6 fc00::/7 unique local addresses. All records returned for a host are checked, so a name publishing both a public and an internal address cannot pass the check and then be connected to on the internal one.
URLFetcher applies the check on every hop instead of letting the JDK follow redirects: redirects are as much under the control of whoever supplied the URL as the URL itself, so validating only the first hop left the check bypassable. Redirects are now followed manually, capped at MAX_REDIRECTS.
The GitHub and GitLab URL parsers probe unknown hosts with an API call to decide whether a URL belongs to a self-hosted instance. That probe is now limited to publicly routable hosts, otherwise the factory endpoint's answer turns into an internal port scanner. Private SCM servers are still reached through the configured provider endpoints or a personal access token, both of which are matched before it comes to probing.
CWE-73 (external control of file name or path)
AuthorizingFileContentProvider.formatUrl and URLFileContentProvider reject absolute devfile references whose scheme is not http/https, so a devfile can no longer make the server read
file:///etc/...or ajar:/ftp:target and return it in the response. UrlTargetValidator reads the scheme off the raw string so that opaque URLs such asjar:file:/x!/yare rejected as schemes rather than reported as parse failures.CWE-522 (insufficiently protected credentials)
Credentials are now bound to the origin they belong to. AuthorizingFileContentProvider sends the personal access token only to the provider's own origins - provider URL, host name and the raw content host, plus the Bitbucket API host for the Bitbucket provider - and fetches anything else anonymously. URLFileContentProvider sends the credentials taken from the devfile URL only back to the origin the devfile was loaded from. Both compare the full origin, not just the host, so a devfile cannot downgrade the request to plain http and put the token on the wire in the clear. URLFetcher drops the Authorization header when a redirect crosses origins, and refuses https -> http redirects outright.
Also fix a copy-paste bug in ScmService#getFileContent, where the
fileparameter was never null-checked becauserepositorywas checked twice.Screenshot/screencast of this PR
What issues does this PR fix or reference?
https://redhat.atlassian.net/browse/CRW-11956
How to test this PR?
PR Checklist
As the author of this Pull Request I made sure that:
What issues does this PR fix or referenceandHow to test this PRcompletedRelease Notes
Reviewers
Reviewers, please comment how you tested the PR when approving it.