Closed
Harden AOT request header defaults to prevent CDN 428 responses#229
Conversation
Agent-Logs-Url: https://github.com/bezzad/Downloader/sessions/592c99ec-69ae-43b1-a6d4-e47e399d1076 Co-authored-by: bezzad <10928710+bezzad@users.noreply.github.com>
Agent-Logs-Url: https://github.com/bezzad/Downloader/sessions/592c99ec-69ae-43b1-a6d4-e47e399d1076 Co-authored-by: bezzad <10928710+bezzad@users.noreply.github.com>
Copilot
AI
changed the title
[WIP] Fix AOT download failure for specific links
Harden AOT request header defaults to prevent CDN 428 responses
May 9, 2026
Co-authored-by: codefactor-io[bot] <47775046+codefactor-io[bot]@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
In AOT/trimmed deployments, the default
User-Agentcould degrade to invalid forms (e.g.,Downloader/or zero-version variants), which some BunnyCDN/edge setups reject with HTTP 428. This change ensures request headers remain valid and deterministic in AOT while preserving explicit user configuration.AOT-safe default
User-Agentgeneration (RequestConfiguration)Assembly.GetExecutingAssembly().GetName().Versionusage with an explicit fallback chain ontypeof(RequestConfiguration).Assembly:AssemblyInformationalVersionAttributeAssemblyFileVersionAttribute5.0)Defensive header normalization in
SocketClientUser-Agentguardrails before attaching default headers:Accept: */*whenAcceptis not configured.User-AgentandAcceptvalues unchanged.Coverage for regression scenarios
User-Agentis non-empty,Downloader/..., and never malformed;SocketClientemits a safe fallback UA when configured UA is null/empty/invalid;Accept: */*is applied only when unset;User-Agent/Acceptvalues are preserved.Warning
Firewall rules blocked me from connecting to one or more addresses (expand for details)
I tried to connect to the following addresses, but was blocked by firewall rules:
nofile/usr/share/dotnet/dotnet /usr/share/dotnet/dotnet exec --runtimeconfig /home/REDACTED/work/Downloader/Downloader/src/Downloader.Test/bin/Debug/net10.0/Downloader.Test.runtimeconfig.json --depsfile /home/REDACTED/work/Downloader/Downloader/src/Downloader.Test/bin/Debug/net10.0/Downloader.Test.deps.json /home/REDACTED/work/Downloader/Downloader/src/Downloader.Test/bin/Debug/net10.0/testhost.dll --port 36289 --endpoint 127.0.0.1:036289 --role client --parentprocessid 8090 --telemetryoptedin false(dns block)/usr/share/dotnet/dotnet /usr/share/dotnet/dotnet exec --runtimeconfig /home/REDACTED/work/Downloader/Downloader/src/Downloader.Test/bin/Debug/net10.0/Downloader.Test.runtimeconfig.json --depsfile /home/REDACTED/work/Downloader/Downloader/src/Downloader.Test/bin/Debug/net10.0/Downloader.Test.deps.json /home/REDACTED/work/Downloader/Downloader/src/Downloader.Test/bin/Debug/net10.0/testhost.dll --port 43841 --endpoint 127.0.0.1:043841 --role client --parentprocessid 9649 --telemetryoptedin false(dns block)/usr/share/dotnet/dotnet /usr/share/dotnet/dotnet exec --runtimeconfig /home/REDACTED/work/Downloader/Downloader/src/Downloader.Test/bin/Debug/net10.0/Downloader.Test.runtimeconfig.json --depsfile /home/REDACTED/work/Downloader/Downloader/src/Downloader.Test/bin/Debug/net10.0/Downloader.Test.deps.json /home/REDACTED/work/Downloader/Downloader/src/Downloader.Test/bin/Debug/net10.0/testhost.dll --port 40769 --endpoint 127.0.0.1:040769 --role client --parentprocessid 11250 --telemetryoptedin false(dns block)www.a.com/usr/share/dotnet/dotnet /usr/share/dotnet/dotnet exec --runtimeconfig /home/REDACTED/work/Downloader/Downloader/src/Downloader.Test/bin/Debug/net10.0/Downloader.Test.runtimeconfig.json --depsfile /home/REDACTED/work/Downloader/Downloader/src/Downloader.Test/bin/Debug/net10.0/Downloader.Test.deps.json /home/REDACTED/work/Downloader/Downloader/src/Downloader.Test/bin/Debug/net10.0/testhost.dll --port 36289 --endpoint 127.0.0.1:036289 --role client --parentprocessid 8090 --telemetryoptedin false(dns block)/usr/share/dotnet/dotnet /usr/share/dotnet/dotnet exec --runtimeconfig /home/REDACTED/work/Downloader/Downloader/src/Downloader.Test/bin/Debug/net10.0/Downloader.Test.runtimeconfig.json --depsfile /home/REDACTED/work/Downloader/Downloader/src/Downloader.Test/bin/Debug/net10.0/Downloader.Test.deps.json /home/REDACTED/work/Downloader/Downloader/src/Downloader.Test/bin/Debug/net10.0/testhost.dll --port 43841 --endpoint 127.0.0.1:043841 --role client --parentprocessid 9649 --telemetryoptedin false(dns block)/usr/share/dotnet/dotnet /usr/share/dotnet/dotnet exec --runtimeconfig /home/REDACTED/work/Downloader/Downloader/src/Downloader.Test/bin/Debug/net10.0/Downloader.Test.runtimeconfig.json --depsfile /home/REDACTED/work/Downloader/Downloader/src/Downloader.Test/bin/Debug/net10.0/Downloader.Test.deps.json /home/REDACTED/work/Downloader/Downloader/src/Downloader.Test/bin/Debug/net10.0/testhost.dll --port 40769 --endpoint 127.0.0.1:040769 --role client --parentprocessid 11250 --telemetryoptedin false(dns block)If you need me to access, download, or install something from one of these locations, you can either:
Original prompt
Issue
Reference: #226
In the AOT (Ahead-Of-Time compiled) environment, downloading some links fails since version
4.1.0(still broken on5.3.0/5.4.0). The same URLs download fine in JIT mode and worked in version4.0.3.Failing URL
Stack trace from the user
HTTP 428 = "Precondition Required". The server (BunnyCDN / Cloudflare-style edge) requires the client to send specific headers such as
If-Match,If-Range, or — most commonly with this CDN — a real, well-formedUser-Agent. The CDN rejects requests whose defaultUser-Agentit considers "bad" or whose request shape it doesn't like.The user confirmed:
PublishAot=true). Same code, JIT-published, works.Investigation hints
The relevant request-building code lives in:
src/Downloader/SocketClient.cs—GetHttpClientWithSocketHandler(...)andFetchResponseHeaders(...)andSendRequestAsync(...).src/Downloader/RequestConfiguration.cs— defaultUserAgentis built using:src/Downloader/Request.cs—GetRequest()createsHttpRequestMessagewithConfiguration.ProtocolVersion(HttpVersion.Version11).Likely root causes (please verify and fix)
Assembly.GetExecutingAssembly().GetName().Versionreturnsnull(or0.0.0) under AOT / single-file / trimmed publish.When that happens, the default
UserAgentbecomes"Downloader/"(trailing slash, empty version) or"Downloader/0.0.0". BunnyCDN and similar edges respond with HTTP 428 to malformed/empty/suspicious User-Agents.Downloader/5.4.0and the CDN accepts it.Assembly.GetExecutingAssembly()works butGetName().Versionis often0.0.0.0for trimmed/AOT assemblies unless<AssemblyVersion>/<Version>is explicitly embedded — and even then, on some configurations the value is null.The current
AddHeaderIfNotEmptyskips empty values, so ifUserAgentends up being just"Downloader/"(non-empty), it's still sent and rejected. We must also guard against the version being missing.Some CDNs additionally reject requests that don't include an
Acceptheader. The defaultAcceptisnulland is not added. Adding*/*as a safe default for the request-levelAcceptheader (only when not configured by the user) avoids further 428s on these CDNs.Required fix
Please make the following changes:
1.
src/Downloader/RequestConfiguration.csChange the default
UserAgentso it always produces a valid, non-empty, version-bearing User-Agent string under AOT and trimmed/single-file deployments. Use a robust helper that:typeof(RequestConfiguration).Assembly.GetName().Version.AssemblyInformationalVersionAttribute(InformationalVersion) read viatypeof(RequestConfiguration).Assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion.AssemblyFileVersionAttribute."5.0").Final default should look like e.g.
"Downloader/5.4.0", never"Downloader/"or"Downloader/0.0.0.0".Replace the line:
with a call to a helper, e.g.:
and add a private static method
BuildDefaultUserAgent()implementing the fallback chain above. Usetypeof(RequestConfiguration).Assemblyinstead ofAssembly.GetExecutingAssembly()(more AOT-friendly and unambiguous in single-file scenarios).2.
src/Downloader/SocketClient.cs(GetHttpClientWithSocketHandler)After the existing logic that adds
AcceptandUser-Agent:"Downloader/"/ends with'/', replace it with a safe fallback such as"Downloader/5.0"before adding it toDefaultRequestHeaders. This is a defense-in-depth safeguard so that even a user who explicitly setsUserAgent = ""still produces a working request.requestConfig.Acceptis null/empty, add a default `Accep...This pull request was created from Copilot chat.