From 3d60c44b4a0abb4eb80865fffbcb14dbd8f9ae07 Mon Sep 17 00:00:00 2001 From: rounak bhatia Date: Mon, 25 May 2026 17:20:37 +0530 Subject: [PATCH 1/2] fixes for LOC-6730 --- .github/workflows/Semgrep.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/Semgrep.yml b/.github/workflows/Semgrep.yml index 0347afd..6b1e0ac 100644 --- a/.github/workflows/Semgrep.yml +++ b/.github/workflows/Semgrep.yml @@ -27,7 +27,8 @@ jobs: container: # A Docker image with Semgrep installed. Do not change this. - image: returntocorp/semgrep + # Pinned by digest (LOC-6730 / INF-002) — tag-mutation is a supply-chain vector. + image: returntocorp/semgrep@sha256:9349edbadf90c3f3c0c3f55867625354e89680e6fa10d9034042af52fdb0e0d0 # Skip any PR created by dependabot to avoid permission issues: if: (github.actor != 'dependabot[bot]') From 5a014718c897c8be8618ef8a9ccbf2ea25d00f3d Mon Sep 17 00:00:00 2001 From: rounak bhatia Date: Thu, 4 Jun 2026 19:21:25 +0530 Subject: [PATCH 2/2] added host check for binary download url --- .../com/browserstack/local/LocalBinary.java | 33 ++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/browserstack/local/LocalBinary.java b/src/main/java/com/browserstack/local/LocalBinary.java index b7abd79..bc48b36 100644 --- a/src/main/java/com/browserstack/local/LocalBinary.java +++ b/src/main/java/com/browserstack/local/LocalBinary.java @@ -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,34 @@ class LocalBinary { System.getProperty("java.io.tmpdir") }; + 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; + } + 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 +266,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")); if(fallbackEnabled) downloadFailureThrowable = null; } } catch (Throwable e) {