C#
SlowShield serves nuget.org at https://slowshield.example.com/nuget/v3/index.json, for the
.NET SDK (dotnet), and so for C#, F# and Visual Basic projects. Projects keep their
PackageReferences as they are; only where packages come from changes.
Set it up
NuGet takes its sources from a NuGet.Config, not from environment variables. The one in your home
directory covers every project on the machine:
NuGet
~/.nuget/NuGet/NuGet.Config (Windows: %AppData%\NuGet\NuGet.Config), or next to a solution
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<!-- required: NuGet asks every enabled source, so nuget.org would go around SlowShield -->
<clear />
<add key="nuget.org" value="https://slowshield.example.com/nuget/v3/index.json" protocolVersion="3" />
</packageSources>
</configuration>CI and Dockerfiles (replaces ~/.nuget/NuGet/NuGet.Config)
mkdir -p "$HOME/.nuget/NuGet" && printf '%s\n' '<?xml version="1.0" encoding="utf-8"?>' '<configuration>' ' <packageSources>' ' <clear />' ' <add key="nuget.org" value="https://slowshield.example.com/nuget/v3/index.json" protocolVersion="3" />' ' </packageSources>' '</configuration>' > "$HOME/.nuget/NuGet/NuGet.Config"Directory.Build.props next to the solution: fail the build when a held version is skipped (NU1603)
<Project>
<PropertyGroup>
<!-- NU1603: a held version was skipped and a higher one restored. TreatWarningsAsErrors works too. -->
<WarningsAsErrors>$(WarningsAsErrors);NU1603</WarningsAsErrors>
</PropertyGroup>
</Project><clear />is required. NuGet asks every enabled source and takes the first good answer, so a nuget.org source left enabled goes around SlowShield.- The source is named
nuget.org, sopackageSourceMappingentries that name nuget.org keep working. - A solution's own
NuGet.Configcan add nuget.org back.dotnet nuget list sourcein the project's folder shows the sources it actually uses.
What changes
- The publish time is each version's
publishedin nuget.org's registration, which nuget.org sets. The first one SlowShield sees is kept, so it can't move earlier. An unlisted version, which nuget.org dates 1900-01-01, is timed from when SlowShield first listed it. - Held versions are left out of the version list
dotnet restorereads, of the registration and of search, sodotnet add packagewithout a version picks the newest version that is old enough. - A reference to a held version (
Version="6.0.30", a minimum) restores the next version up that is old enough, with only a warning. Usually the next version up is newer still, so held too, and restore fails (NU1102, orNU1103when only prereleases are left). But on a package with several maintained lines, 6.0.30 released yesterday and 7.0.0 a year ago, it moves to the other line:So fail the build onwarning NU1603: App depends on Contoso.Data (>= 6.0.30) but Contoso.Data 6.0.30 was not found. Contoso.Data 7.0.0 was resolved instead.NU1603, with theDirectory.Build.propsabove. - An exact pin (
[6.0.30]) fails withNU1102: Unable to find package Contoso.Data with version (= 6.0.30). Asking for the package file itself gets425 Too EarlywithRetry-After;dotnetshows only the status line, never the message. - Brand-new packages none of whose versions is old enough are held too: NuGet doesn't fail open, because brand-new packages are the realistic attack (typosquats, impersonations).
- Malware from OSV and GitHub is refused with
451(NU1301indotnet). Every.nupkgis checked against nuget.org's SHA512 and the fingerprint SlowShield recorded the first time, and never changed, so its repository signature stays valid. - NuGetAudit keeps working: nuget.org's vulnerability data comes through SlowShield.
The second layer
NuGet has no release-age setting, so SlowShield is the only layer.
In Docker and CI
Write the NuGet.Config in the build stage. With a build argument, the same Dockerfile still builds
without SlowShield when the argument is empty:
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
ARG NUGET_SOURCE=https://slowshield.example.com/nuget/v3/index.json
RUN if [ -n "$NUGET_SOURCE" ]; then mkdir -p "$HOME/.nuget/NuGet" && printf '%s\n' \
'<?xml version="1.0" encoding="utf-8"?>' '<configuration>' ' <packageSources>' ' <clear />' \
" <add key=\"nuget.org\" value=\"$NUGET_SOURCE\" protocolVersion=\"3\" />" \
' </packageSources>' '</configuration>' > "$HOME/.nuget/NuGet/NuGet.Config"; fi
WORKDIR /src
COPY *.csproj ./
RUN dotnet restore
COPY . .
RUN dotnet publish -c Release -o /app --no-restoreIn CI, run the Setup page's one-line command before dotnet restore. Building
images covers the base image too, and how to check that nothing in a build goes around SlowShield.
Limits
- Checked with the .NET SDK 10. Visual Studio, Rider and
nuget.exeread the sameNuGet.Config, but haven't been tried. - Not served: publishing (
dotnet nuget pushgoes to nuget.org), symbol packages, and clients older than NuGet 4.3. - Private feeds stay sources of their own, mapped with
packageSourceMapping. - Packages already in the global packages folder (
~/.nuget/packages) are used without asking any source.dotnet nuget locals global-packages --clearempties it. - Block api.nuget.org at the firewall for machines that should only use SlowShield.
For agents: this page as Markdown · llms.txt · the SlowShield skill