lostindark/DriverStoreExplorer · error · InvalidOperationException
SHA256 hash of the downloaded file does not match the expect
Error message
SHA256 hash of the downloaded file does not match the expected value.
What it means
Integrity gate after the download completes. ApplyUpdateAsync recomputes SHA256 over the saved zip and compares it (case-insensitive, hex) to versionInfo.Sha256, which GetLatestVersionInfo parses from the GitHub asset's digest field ('sha256:<hex>'). On mismatch the temp zip is deleted and the update aborts — this is the protection against truncated downloads, MITM, or a swapped asset. The check only runs when versionInfo.Sha256 is non-empty, so a release missing the digest field silently skips verification.
Source
Thrown at Rapr/UpdateManager.cs:124
// Download the zip
using (var fileStream = new FileStream(tempZipPath, FileMode.Create, FileAccess.Write, FileShare.None))
{
await this.httpClient.DownloadAsync(versionInfo.DownloadUrl, fileStream, progress).ConfigureAwait(false);
}
// Verify SHA256 hash
if (!string.IsNullOrEmpty(versionInfo.Sha256))
{
using (var sha256 = SHA256.Create())
using (var fileStream = new FileStream(tempZipPath, FileMode.Open, FileAccess.Read, FileShare.Read))
{
var hashBytes = sha256.ComputeHash(fileStream);
var actualHash = BitConverter.ToString(hashBytes).Replace("-", string.Empty);
if (!actualHash.Equals(versionInfo.Sha256, StringComparison.OrdinalIgnoreCase))
{
File.Delete(tempZipPath);
throw new InvalidOperationException("SHA256 hash of the downloaded file does not match the expected value.");
}
}
}
// Extract zip
ZipFile.ExtractToDirectory(tempZipPath, tempExtractPath);
// Find the actual content directory (zip may have a single root folder)
string sourceDir = tempExtractPath;
var subDirs = Directory.GetDirectories(tempExtractPath);
if (subDirs.Length == 1 && Directory.GetFiles(tempExtractPath).Length == 0)
{
sourceDir = subDirs[0];
}
string appDir = Path.GetFullPath(DSEFormHelper.GetApplicationFolder());
string currentExePath = Assembly.GetExecutingAssembly().Location;
View on GitHub (pinned to 958fcd481b)
Solutions
- Retry the update once — transient truncation is the most common cause and the next attempt often hashes clean.
- Verify the published digest: compare versionInfo.Sha256 to the value shown on the GitHub release page asset (shown as a SHA-256 checksum).
- Check the temp file size vs. GitHub's reported asset size: Path.Combine(Path.GetTempPath(), "DriverStoreExplorer") — a short file confirms truncation.
- Disable any intercepting proxy or AV for the download domain and retry.
- If you author releases, ensure GitHub computes and exposes the sha256 digest (asset uploaded as a binary release file yields the digest automatically).
Example fix
// before (single attempt, throws on mismatch)
await this.updateManager.ApplyUpdateAsync(this.latestVersionInfo, progress);
// after (retry the download once before surfacing failure)
for (int attempt = 0; attempt < 2; attempt++)
{
try
{
await this.updateManager.ApplyUpdateAsync(this.latestVersionInfo, progress);
break;
}
catch (InvalidOperationException ex) when (attempt == 0 && ex.Message.Contains("SHA256"))
{
// Likely a truncated download; loop will re-download from scratch.
continue;
}
} Defensive patterns
Strategy: retry
Validate before calling
// You cannot pre-validate a hash before download, but you can surface the
// expected digest to the user and confirm the release exposes one.
private static bool ReleaseExposesSha256(VersionInfo info)
=> !string.IsNullOrWhiteSpace(info?.Sha256)
&& info.Sha256.Length == 64
&& System.Text.RegularExpressions.Regex.IsMatch(info.Sha256, @"^[0-9a-fA-F]{64}$");
if (!ReleaseExposesSha256(this.latestVersionInfo))
{
// Verification would be silently skipped (UpdateManager checks only when non-empty).
Logger.Warn("Release does not expose a SHA256 digest; integrity check will be bypassed.");
} Try / catch
// Retry once on a hash mismatch (most often a truncated download), then surface.
int attempts = 0;
while (true)
{
try
{
await this.updateManager.ApplyUpdateAsync(this.latestVersionInfo, progress);
break;
}
catch (InvalidOperationException ex) when (attempts++ == 0 && ex.Message.Contains("SHA256"))
{
continue;
}
} Prevention
- Always publish releases with an exposed sha256 digest so the check is active, not skipped.
- Retry a hash mismatch once before reporting failure — truncated downloads are the common cause.
- Confirm the temp file size matches the GitHub asset size before hashing to catch truncation early.
- Disable HTTPS-intercepting proxies for the download domain so the bytes are not rewritten.
- Pin releases by tag and asset name so a re-pushed asset cannot silently change the digest your code expects.
When it happens
Trigger: Line 113–127: bytes downloaded to tempZipPath don't hash to versionInfo.Sha256. Causes: a truncated/partial download (HttpClient.DownloadAsync dropped bytes), a proxy injecting an error page, a release whose digest was computed against a different asset, or an attacker who swapped the zip but could not forge the digest.
Common situations: Flaky connection leaving a short file; corporate proxy returns an HTML block page with 200; the release tag was force-pushed and asset[0] no longer matches the cached digest; the digest field format changed and the hex parsing produced a wrong value; local antivirus rewrote the downloaded file.
Related errors
- Download URL is not from a trusted GitHub domain.
- 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/8163375916439c4b.
Report an issue: GitHub.