lostindark/DriverStoreExplorer · error · InvalidOperationException
Download URL is not from a trusted GitHub domain.
Error message
Download URL is not from a trusted GitHub domain.
What it means
A security pre-check inside UpdateManager.ApplyUpdateAsync. Before any network I/O, IsGitHubUrl verifies the DownloadUrl scheme is HTTPS and the host is github.com, *.github.com, or *.githubusercontent.com. This blocks SSRF, supply-chain swaps, and accidental fetches from attacker-controlled mirrors. The check fails closed: any release whose browser_download_url leaves the GitHub domain set aborts the whole update.
Source
Thrown at Rapr/UpdateManager.cs:80
DownloadUrl = new Uri(downloadUrl),
Sha256 = sha256
};
}
return null;
}
}
public async Task ApplyUpdateAsync(VersionInfo versionInfo, IProgress<float> progress)
{
if (versionInfo == null)
{
throw new ArgumentNullException(nameof(versionInfo));
}
if (!IsGitHubUrl(versionInfo.DownloadUrl))
{
throw new InvalidOperationException("Download URL is not from a trusted GitHub domain.");
}
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
string tempBaseDir = Path.Combine(Path.GetTempPath(), "DriverStoreExplorer");
string downloadFileName = Path.GetFileName(versionInfo.DownloadUrl.LocalPath);
string tempZipPath = Path.Combine(tempBaseDir, downloadFileName);
string tempExtractPath = Path.Combine(tempBaseDir, "Update");
if (!Directory.Exists(tempBaseDir))
{
Directory.CreateDirectory(tempBaseDir);
}
// Clean up any previous update artifacts
if (File.Exists(tempZipPath))
{
File.Delete(tempZipPath);View on GitHub (pinned to 958fcd481b)
Solutions
- Keep release assets on GitHub so browser_download_url stays on objects.githubusercontent.com — the supported, default path.
- If you must allow an additional trusted host, extend IsGitHubUrl's host whitelist rather than disabling the check.
- Inspect the release JSON: curl -sSL https://api.github.com/repos/<owner>/<repo>/releases/latest | jq '.assets[].browser_download_url'.
- For GitHub Enterprise, add the enterprise host (e.g. github.<company>.com) to the host check.
- In tests, build VersionInfo with a Uri("https://github.com/owner/repo/releases/download/v1/x.zip").
Example fix
// before
private static bool IsGitHubUrl(Uri url)
{
return url.Scheme == Uri.UriSchemeHttps
&& (url.Host.Equals("github.com", StringComparison.OrdinalIgnoreCase)
|| url.Host.EndsWith(".github.com", StringComparison.OrdinalIgnoreCase)
|| url.Host.EndsWith(".githubusercontent.com", StringComparison.OrdinalIgnoreCase));
}
// after — allow a configurable allowlist for GitHub Enterprise / mirrors
private static readonly string[] TrustedHostSuffixes =
{
"github.com",
"githubusercontent.com",
"github.mycompany.com",
};
private static bool IsTrustedUrl(Uri url)
{
if (url == null || url.Scheme != Uri.UriSchemeHttps) return false;
foreach (var suffix in TrustedHostSuffixes)
{
if (url.Host.Equals(suffix, StringComparison.OrdinalIgnoreCase)
|| url.Host.EndsWith("." + suffix, StringComparison.OrdinalIgnoreCase))
return true;
}
return false;
} Defensive patterns
Strategy: validation
Validate before calling
// Validate before invoking ApplyUpdateAsync
private static bool TryGetSafeDownloadUrl(VersionInfo info, out Uri url, out string error)
{
url = null;
error = null;
if (info?.DownloadUrl == null) { error = "No download URL present."; return false; }
if (info.DownloadUrl.Scheme != Uri.UriSchemeHttps) { error = "Download URL must use HTTPS."; return false; }
string host = info.DownloadUrl.Host;
bool trusted = host.Equals("github.com", StringComparison.OrdinalIgnoreCase)
|| host.EndsWith(".github.com", StringComparison.OrdinalIgnoreCase)
|| host.EndsWith(".githubusercontent.com", StringComparison.OrdinalIgnoreCase);
if (!trusted) { error = "Download URL host is not a trusted GitHub domain: " + host; return false; }
url = info.DownloadUrl;
return true;
}
// Usage:
if (!TryGetSafeDownloadUrl(this.latestVersionInfo, out var url, out var err))
{
MessageBox.Show(err, Language.Product_Name, MessageBoxButtons.OK, MessageBoxIcon.Warning);
return;
} Type guard
private static bool IsTrustedGitHubReleaseUrl(VersionInfo info)
=> info?.DownloadUrl is Uri u
&& u.Scheme == Uri.UriSchemeHttps
&& (u.Host.Equals("github.com", StringComparison.OrdinalIgnoreCase)
|| u.Host.EndsWith(".github.com", StringComparison.OrdinalIgnoreCase)
|| u.Host.EndsWith(".githubusercontent.com", StringComparison.OrdinalIgnoreCase)); Prevention
- Keep all release assets on GitHub so browser_download_url always resolves under objects.githubusercontent.com.
- When forking, either re-host on GitHub or extend IsGitHubUrl's host list explicitly.
- For GitHub Enterprise, add the enterprise host suffix to the whitelist.
- In tests, build VersionInfo with a Uri on https://github.com/ to avoid tripping the guard.
- Never construct a VersionInfo from untrusted user/URL input without routing it through the same host check first.
When it happens
Trigger: versionInfo.DownloadUrl is built in GetLatestVersionInfo from releaseInfo["assets[0].browser_download_url"]. If a release attaches an asset whose URL is HTTPS but on a CDN outside *.githubusercontent.com, or a caller hand-constructs a VersionInfo pointing elsewhere, IsGitHubUrl (line 199) returns false and line 80 throws.
Common situations: A fork re-hosts the release zip on a private server; a GitHub Enterprise release points to an enterprise host (not *.github.com); a test VersionInfo built in a unit test uses localhost or example.com; a proxy rewrites the asset URL; the GitHub API response is mocked with a non-GitHub asset URL.
Related errors
- SHA256 hash of the downloaded file does not match the expect
- Update package contains a file that escapes the application
- Failed to restart the application. Please restart manually.
AI-assisted analysis of lostindark/DriverStoreExplorer@958fcd481b (2026-08-13).
Data as JSON: /api/errors/92e12af6466cb164.
Report an issue: GitHub.