Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 41 additions & 1 deletion BrowserStackLocal/BrowserStackLocal/BrowserStackTunnel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ public enum LocalState { Idle, Connecting, Connected, Error, Disconnected };

public class BrowserStackTunnel : IDisposable
{
private static readonly string[] AllowedDownloadHosts = new string[] { "browserstack.com" };
private static readonly string[] AllowedDownloadHostSuffixes = new string[] { ".browserstack.com" };

static readonly string uname = Util.GetUName();
static readonly string binaryName = GetBinaryName();

Expand Down Expand Up @@ -229,11 +232,48 @@ private string fetchSourceUrl(string accessKey)
throw new Exception((string)jsonResponse["error"]);
}

sourceUrl = jsonResponse["data"]?["endpoint"]?.ToString();
sourceUrl = ValidateSourceUrl(jsonResponse["data"]?["endpoint"]?.ToString());
return sourceUrl;
}
}

// Each guard below covers a case the final host-equals check does not:
// - null/empty URL: skip a parse attempt and give a clear error.
// - Uri.TryCreate failure: malformed URL surfaces our own exception instead of leaving parsed null.
// - HTTPS check: allowlist matches host only; without this, http://browserstack.com would pass.
// - null/empty host: parsed.Host can be empty for some URL forms; give a clear error before reaching the allowlist loop.
private static string ValidateSourceUrl(string url)
{
if (string.IsNullOrEmpty(url))
{
throw new Exception("Refusing binary download: empty source URL");
}
Uri parsed;
if (!Uri.TryCreate(url, UriKind.Absolute, out parsed))
{
throw new Exception("Refusing binary download: malformed source URL");
}
if (!string.Equals(parsed.Scheme, "https", StringComparison.OrdinalIgnoreCase))
{
throw new Exception("Refusing binary download from non-HTTPS source URL");
}
string host = parsed.Host;
if (string.IsNullOrEmpty(host))
{
throw new Exception("Refusing binary download: source URL has no host");
}
host = host.ToLowerInvariant();
foreach (var allowed in AllowedDownloadHosts)
{
if (host.Equals(allowed)) return url;
}
foreach (var suffix in AllowedDownloadHostSuffixes)
{
if (host.EndsWith(suffix)) return url;
}
throw new Exception("Refusing binary download: host '" + host + "' is not in the allowed host list");
}

public void downloadBinary()
{
string binaryDirectory = Path.Combine(this.binaryAbsolute, "..");
Expand Down