# 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 `PackageReference`s 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`**, so `packageSourceMapping` entries that name nuget.org keep working.

- **A solution's own `NuGet.Config`** can add nuget.org back. `dotnet nuget list source` in the project's folder shows the sources it actually uses.

## What changes

- **The publish time** is each version's `published` in 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 restore` reads, of the registration and of search, so `dotnet add package` without 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`, or `NU1103` when 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:
   ```
   warning 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.
   ```
   So fail the build on `NU1603`, with the `Directory.Build.props` above.

- **An exact pin** (`[6.0.30]`) fails with `NU1102: Unable to find package Contoso.Data with version (= 6.0.30)`. Asking for the package file itself gets `425 Too Early` with `Retry-After`; `dotnet` shows 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` (`NU1301` in `dotnet`). Every `.nupkg` is 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-restore
```

In CI, run the Setup page's one-line command before `dotnet restore`. [Building images](https://slowshield.org/docs/container-builds/) 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.exe` read the same `NuGet.Config`, but haven't been tried.

- Not served: publishing (`dotnet nuget push` goes 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 --clear` empties it.

- Block api.nuget.org at the firewall for machines that should only use SlowShield.

---

This page as HTML: https://slowshield.org/docs/csharp/. All of the guide in one file: https://slowshield.org/llms-full.txt
