-
Notifications
You must be signed in to change notification settings - Fork 33
added host check for binary download url #99
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -22,6 +22,9 @@ | |
|
|
||
| class LocalBinary { | ||
|
|
||
| private static final String[] ALLOWED_DOWNLOAD_HOSTS = { "browserstack.com" }; | ||
| private static final String[] ALLOWED_DOWNLOAD_HOST_SUFFIXES = { ".browserstack.com" }; | ||
|
|
||
| private String binaryFileName; | ||
|
|
||
| private String sourceUrl; | ||
|
|
@@ -42,6 +45,39 @@ class LocalBinary { | |
| System.getProperty("java.io.tmpdir") | ||
| }; | ||
|
|
||
| // Each guard below covers a case the final host-equals check does not: | ||
| // - null/empty URL: new URL(null) throws NPE before the catch can run. | ||
| // - MalformedURLException: convert raw JVM exception to LocalException for the public contract. | ||
| // - HTTPS check: allowlist matches host only; without this, http://browserstack.com would pass. | ||
| // - null/empty host: getHost() returns null for URLs like https:///foo, which NPEs on toLowerCase(). | ||
| private static String validateSourceUrl(String url) throws LocalException { | ||
| if (url == null || url.isEmpty()) { | ||
| throw new LocalException("Refusing binary download: empty source URL"); | ||
| } | ||
| URL parsed; | ||
| try { | ||
| parsed = new URL(url); | ||
| } catch (java.net.MalformedURLException e) { | ||
| throw new LocalException("Refusing binary download: malformed source URL"); | ||
| } | ||
| if (!"https".equalsIgnoreCase(parsed.getProtocol())) { | ||
| throw new LocalException("Refusing binary download from non-HTTPS source URL"); | ||
| } | ||
| String host = parsed.getHost(); | ||
| if (host == null || host.isEmpty()) { | ||
| throw new LocalException("Refusing binary download: source URL has no host"); | ||
| } | ||
| host = host.toLowerCase(); | ||
| for (String allowed : ALLOWED_DOWNLOAD_HOSTS) { | ||
| if (host.equals(allowed)) return url; | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If you had to do this all along then why all the checks before this?
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Each check covers a different case the host equals on line 67 doesn't:
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ack got it, mind adding a small comment at the top to make sure the next dev understands the purpose?
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done |
||
| } | ||
| for (String suffix : ALLOWED_DOWNLOAD_HOST_SUFFIXES) { | ||
| if (host.endsWith(suffix)) return url; | ||
| } | ||
| throw new LocalException( | ||
| "Refusing binary download: host '" + host + "' is not in the allowed host list"); | ||
| } | ||
|
|
||
| LocalBinary(String path, String key) throws LocalException { | ||
| this.key = key; | ||
| initialize(); | ||
|
|
@@ -235,7 +271,7 @@ private void fetchSourceUrl() throws LocalException { | |
| if (json.has("error")) { | ||
| throw new Exception(json.getString("error")); | ||
| } | ||
| this.sourceUrl = json.getJSONObject("data").getString("endpoint"); | ||
| this.sourceUrl = validateSourceUrl(json.getJSONObject("data").getString("endpoint")); | ||
|
rounak610 marked this conversation as resolved.
|
||
| if(fallbackEnabled) downloadFailureThrowable = null; | ||
| } | ||
| } catch (Throwable e) { | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.