📁 Sangam Dhakal - Portfolio v2.0 - Netscape Navigator 4.0
_
□
×

📚 Interview question

dotnet restore fails in CI with NU1301 or 401 Unauthorized against a private NuGet feed, but works on your machine. How do you diagnose and fix it?

Troubleshooting restore failures: how NuGet finds sources and credentials through nuget.config, credential providers and CI service connections, package source mapping, lock files, caches, NuGet Audit, and protecting against dependency confusion.

Mid-Level Debugging Intermediate azure-artifacts ci-cd dotnet-restore nuget supply-chain-security

🎯 Key points

  • Read the exact code: NU1301 is a source or network failure, NU1101 a missing package, 401/403 an authentication problem
  • Locally it works because your machine has cached packages, stored credentials and user-level config; a clean CI agent has none of them
  • nuget.config files are merged from the repo up to the user and machine level; commit one with <clear /> and explicit sources
  • Authenticate CI with a credential provider or packageSourceCredentials fed from the secret store (NuGetAuthenticate task in Azure DevOps), never a committed password
  • Package source mapping pins which source each package can come from, preventing dependency confusion
  • Lock files with --locked-mode make restores reproducible
  • Use dotnet restore -v detailed and dotnet nuget list source to see effective config; clearing caches is diagnostic, not a fix
  • NuGet Audit warns about vulnerable packages; for net10.0+ projects the .NET 10 SDK audits transitive packages by default

⏱ 30-second answer

First I read the exact error. NU1301 means a source couldn't be reached, 401 or 403 means authentication, and NU1101 means the package isn't on any configured source. It works on my machine because I have cached packages and stored credentials, while CI starts clean. Then I check which nuget.config CI actually uses, since config files are merged from the repo upwards. I make the repo's config explicit with <clear />, and I authenticate CI the supported way, for example the Azure Artifacts credential provider or a token from the pipeline's secret store, never a committed password. I also add package source mapping and lock files so restores are reproducible and protected against dependency confusion.

🗣 Full answer (senior)

How I would answer this in an interview

"I start by reading the exact code: NU1301 means a source couldn't be reached, 401 or 403 is authentication, and NU1101 means no configured source has the package. It works locally because my machine has cached packages, stored credentials and user-level config, while a clean CI agent has none of them. So I run dotnet restore -v detailed and dotnet nuget list source in the pipeline to see which merged nuget.config it actually uses, because nothing builds until restore has written project.assets.json.

For example, after [we moved to hosted build agents], restore failed for our internal [Contoso.Abn] package even though it worked for every developer. I needed to get builds green and understand why. The detailed log showed CI only knew about nuget.org; the internal feed lived in personal and machine config. I committed a repo-level nuget.config with <clear /> and package source mapping, added the NuGetAuthenticate task with Reader permission on the [Azure Artifacts] feed, and turned on lock files with --locked-mode. Builds were green [the same day], restores became reproducible, and we closed a dependency confusion risk.

I never commit a token in nuget.config; credentials come from the credential provider or the pipeline's secret store. Clearing the cache is a diagnostic step, not a fix, and the mistake I see is retrying that without reading the code. I leave NuGet Audit on, since the .NET 10 SDK checks transitive packages by default for net10.0 projects, and dotnet tool restore uses the same feeds, so the same fix applies there. The real fix is making the repo self-describing, not copying one developer's settings into the pipeline."


1. Read the code

Code / message Usually means
NU1301: Unable to load the service index Source URL wrong, network or proxy blocked, TLS issue, or an auth failure surfaced as a load failure
401 Unauthorized / 403 Forbidden Missing, expired or insufficient credentials (for example the build identity lacks feed read permission)
NU1101: Unable to find package No configured source has it, or source mapping excludes the source that does
NU1102 / NU1103 The package exists but not the requested version (or only prerelease)
NU1004 Lock file out of date in locked mode

2. Why it works locally

Your machine has packages in the global packages folder from earlier restores, credentials saved by Visual Studio or the credential provider, and possibly a user-level nuget.config with extra sources. A clean CI agent has none of these: user-level and machine-level settings from developer laptops simply do not exist there. CI is the honest test of what the repository itself declares.

3. Find the effective configuration

NuGet merges nuget.config files from the solution folder up to the drive root, then the user-level and machine-level files. Run dotnet restore -v detailed and look for the list of config files and sources used, or dotnet nuget list source in the repo folder. Commit a repo-level nuget.config with <clear /> so only the listed sources apply everywhere.

4. Authenticate properly

NuGet authenticates through credential providers and packageSourceCredentials; you do not hand-craft HTTP headers.

  • Azure DevOps + Azure Artifacts: run the NuGetAuthenticate@1 task before restore; it installs the Azure Artifacts credential provider and supplies a token for the pipeline identity. Grant that identity Reader on the feed (and check project-scoped feed permissions after moving projects).
  • GitHub Actions + Azure Artifacts or GitHub Packages: store a read-scoped token in the secret store, expose it as an environment variable, and reference it from packageSourceCredentials (NuGet expands %VAR% there), or run dotnet nuget update source <name> --username ... --password "$TOKEN" --store-password-in-clear-text inside the job only. For Azure Artifacts, the credential provider can also read a token from VSS_NUGET_EXTERNAL_FEED_ENDPOINTS.
  • Never commit credentials in nuget.config.

5. Make it reproducible and safe

  • Package source mapping (<packageSourceMapping>) says Contoso.* comes only from the internal feed and everything else from nuget.org, blocking dependency confusion attacks where an attacker publishes a same-named package publicly.
  • Lock files (RestorePackagesWithLockFile) plus dotnet restore --locked-mode in CI fail the build if the graph changes unexpectedly.
  • NuGet Audit reports known vulnerabilities (NU1901–NU1904) during restore. With the .NET 10 SDK, projects targeting .NET 10 or later audit transitive packages by default (NuGetAuditMode defaults to all); older target frameworks still default to direct references only unless you set it.
  • Clearing caches (dotnet nuget locals all --clear) is a diagnostic step to rule out a corrupt cache, not a fix.

Production example

A new pipeline failed with NU1301 on an internal Contoso.Abn package while every developer could restore. The repo had no nuget.config; developers had the internal feed in their user-level config, and CI only had nuget.org. Worse, a public package with a similar name existed. We committed a nuget.config with <clear />, both sources and source mapping, added NuGetAuthenticate, and gave the pipeline identity feed access. Restores became identical everywhere and the confusion risk was closed.

🔍 What the interviewer is checking

The interviewer wants a methodical troubleshooter with supply chain awareness.

Green flags

  • Distinguishes NU1301, NU1101 and auth failures.
  • Explains config merging and local caches.
  • Uses supported CI authentication and never commits secrets.
  • Mentions source mapping, lock files and NuGet Audit.

Red flags

  • "Clear the cache and retry."
  • Pastes a PAT into nuget.config.
  • Has never heard of dependency confusion.

📚 Learn in depth

1. How restore finds sources and credentials

 repo/nuget.config            \
 repo/../nuget.config          |  merged, closest file first;
 (each parent folder up        |  <clear /> discards every source
  to the drive root)           |  defined in files further up
 ~/.nuget/NuGet/NuGet.Config   |  user level   (developer laptops only)
 machine-wide config          /   machine level (old self-hosted agents)
        |
        v
 effective: packageSources + packageSourceMapping + packageSourceCredentials
        |
        v
 for each package id --> only the source(s) its mapping allows
        |
        v
 GET <source>/v3/index.json (service index)
        |
        +-- network, DNS, proxy or TLS failure --> NU1301
        +-- 401 / 403 --> ask credential providers:
        |       1. packageSourceCredentials in config (can use %ENV_VAR%)
        |       2. Azure Artifacts credential provider
        |          (NuGetAuthenticate task, VSS_NUGET_EXTERNAL_FEED_ENDPOINTS)
        |       3. stored credentials (Visual Studio, interactive sign-in)
        |       none work --> 401 / NU1301
        v
 search for the id and version --> not found --> NU1101 / NU1102 / NU1103
        |
        v
 download .nupkg --> verify hash and signature --> ~/.nuget/packages

Every box on that path behaves differently on a developer laptop and a clean CI agent. That is the whole story behind "works on my machine".

2. Error codes and what to check

Code Meaning Check first
NU1301 Couldn't load the service index Source URL, DNS, proxy, firewall, TLS inspection, or an auth failure reported as a load failure
401 Unauthorized No valid credentials Did NuGetAuthenticate run before restore? Is the token set and not expired?
403 Forbidden Credentials valid but not allowed Does the pipeline identity have Reader on the feed (and on upstream views)?
NU1101 Package not found on any allowed source Package name, source mapping patterns, upstream sources
NU1102 Package found, version not found Version range, whether the version was published or unlisted
NU1103 Only prerelease versions exist Version range, or publish a stable version
NU1004 Lock file out of date in locked mode Someone changed a reference without updating packages.lock.json
NU1100 Couldn't resolve a dependency for a framework Target framework compatibility
NU1901 to NU1904 Known vulnerability (low to critical) NuGet Audit; upgrade the package

3. Local versus CI, side by side

Developer laptop Clean CI agent
~/.nuget/packages Full of packages from earlier restores Empty (unless cached)
HTTP cache Recent feed responses Empty
User-level NuGet.Config Often has extra sources added years ago Doesn't exist
Credentials Stored by Visual Studio or the credential provider Only what the pipeline provides
Network Corporate VPN, proxy settings Different egress, maybe no access to internal networks
Identity Your user, with feed access A pipeline identity that may lack permissions

A restore can succeed locally without contacting the feed at all, because every package is already in the global packages folder. CI is the honest test of what the repository declares.

4. A diagnosis routine

  1. Read the exact code and the source name in the message.
  2. Run dotnet restore -v detailed (or -v diag) in the pipeline. It lists the config files it read, the sources it used and each HTTP call.
  3. Run dotnet nuget list source in the repo folder, locally and in CI, and compare.
  4. Reproduce CI locally with a clean state: dotnet nuget locals all --clear (or a fresh container) and no user-level config, for example docker run --rm -v "$PWD:/src" -w /src mcr.microsoft.com/dotnet/sdk:10.0 dotnet restore.
  5. Check authentication: did the authenticate step run before restore, does the token have read scope, and does the identity have feed permissions?
  6. Check network: can the agent resolve and reach the feed host? Is a corporate proxy or TLS inspection in the way?

5. Authenticating CI the supported way

Pipeline and feed Approach
Azure Pipelines + Azure Artifacts (same organisation) NuGetAuthenticate@1 before restore; grant the build identity Reader on the feed
Azure Pipelines + feed in another organisation NuGetAuthenticate@1 with a service connection
GitHub Actions + GitHub Packages GITHUB_TOKEN (or a token with read:packages) referenced from packageSourceCredentials through an environment variable
GitHub Actions + Azure Artifacts Azure Artifacts credential provider with VSS_NUGET_EXTERNAL_FEED_ENDPOINTS from a secret, or a read-scoped token referenced from config
Any CI + a token-based feed dotnet nuget update source <name> --username x --password "$TOKEN" --store-password-in-clear-text, run inside the job only, never committed

Tokens should be short-lived and read-only. Workload identity federation or the platform's built-in identity is better than a long-lived personal access token.

6. Package source mapping and dependency confusion

 Without source mapping:                    With source mapping:
 Contoso.Abn requested                      Contoso.Abn requested
   -> NuGet asks EVERY source                 -> pattern Contoso.* -> contoso feed ONLY
   -> an attacker has published a             -> nuget.org is never asked for it
      package called Contoso.Abn on
      nuget.org                              the internal package is always used
   -> if the range floats (1.*), the
      attacker publishes a higher
      version, or publishes the exact
      version you use, restore can take
      the public one
   -> the attacker's build logic runs on
      your CI agent

Without mapping, NuGet doesn't treat any source as authoritative for a package id, so the outcome depends on versions and which source answers. Source mapping makes the source for each package explicit. With mapping in place, NU1101 for an internal package often means the pattern doesn't cover it, so check patterns before anything else. An internal Azure Artifacts feed with upstream sources for nuget.org gives a single controlled entry point and keeps a copy of every public package you use.

7. Lock files

Setting Effect
RestorePackagesWithLockFile=true Restore writes packages.lock.json with exact versions and hashes
dotnet restore --locked-mode (or RestoreLockedMode=true in CI) Fails with NU1004 if the graph would change
Floating versions such as 4.* Resolved once and recorded, so later restores are reproducible

Commit the lock files and use locked mode in CI, so a new package version or a compromised feed can't change what you build without a reviewed commit.

8. Caches

Cache Location Notes
Global packages folder ~/.nuget/packages Extracted packages shared by all projects; safe to cache in CI
HTTP cache Shown by dotnet nuget locals http-cache --list Can hide a newly published version for up to about 30 minutes; --no-http-cache bypasses it
Temp Shown by dotnet nuget locals temp --list Scratch space

dotnet nuget locals all --clear is a diagnostic step to rule out a corrupt cache, not a fix for configuration or authentication problems.

9. NuGet Audit

Restore checks packages against known vulnerability data and reports NU1901 to NU1904. With the .NET 10 SDK, projects targeting .NET 10 or later audit transitive packages by default (NuGetAuditMode is all); older target frameworks audit direct references only unless you set it. Run dotnet list package --vulnerable --include-transitive for a full report, and treat high and critical findings as errors on main branches.

10. Security

  • Never commit passwords or tokens in nuget.config.
  • Use read-only, short-lived credentials from the platform; restrict who can publish to internal feeds.
  • trustedSigners in nuget.config can require signatures from specific repositories or authors.
  • Treat restore as the front door of the supply chain: source mapping, a controlled internal feed with upstreams, lock files and audit together close most attack paths, in line with Essential Eight guidance on restricting privileges and patching applications.

11. .NET 11 awareness

NuGet ships with the SDK, so audit defaults and source mapping features can change between SDK versions. Read the NuGet release notes when you move to the .NET 11 SDK.

12. Try it yourself

  1. Add the nuget.config from the code example to a solution and run dotnet restore -v detailed; find the list of config files and sources in the output.
  2. Run the same restore in a clean SDK container and compare.
  3. Add a package whose id isn't covered by any mapping pattern and read the error.
  4. Enable lock files, restore, change a version, and run dotnet restore --locked-mode to see NU1004.
  5. Run dotnet list package --vulnerable --include-transitive.
  6. Point a source at a URL that doesn't exist and read the NU1301 message.

💻 Code

Repo-level nuget.config with explicit sources and source mapping (no credentials committed):

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <packageSources>
    <clear />
    <add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
    <add key="contoso" value="https://pkgs.dev.azure.com/contoso/_packaging/internal/nuget/v3/index.json" />
  </packageSources>
  <packageSourceMapping>
    <packageSource key="contoso">
      <package pattern="Contoso.*" />
    </packageSource>
    <packageSource key="nuget.org">
      <package pattern="*" />
    </packageSource>
  </packageSourceMapping>
</configuration>

Directory.Build.props enabling lock files:

<Project>
  <PropertyGroup>
    <RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
  </PropertyGroup>
</Project>

Azure DevOps pipeline steps:

steps:
  - task: UseDotNet@2
    inputs:
      useGlobalJson: true
  - task: NuGetAuthenticate@1
  - script: dotnet restore Orders.slnx --locked-mode
    displayName: Restore

Diagnostics:

dotnet restore -v detailed          # shows config files, sources and why a package failed
dotnet nuget list source            # effective sources for this folder
dotnet nuget locals all --list      # cache locations
dotnet list package --vulnerable --include-transitive

⚠ Common mistakes

  • Mistake: clearing the NuGet cache and retrying without reading the error code. NU1301, 401, 403 and NU1101 have different causes, so retrying wastes time and the cache was rarely the problem. Instead: read the code and source name, then run dotnet restore -v detailed to see the config files, sources and HTTP calls.

  • Mistake: relying on a user-level or machine-level nuget.config. Sources and credentials that exist only on developer laptops or an old self-hosted agent are invisible to a clean CI agent. Instead: commit a repo-level nuget.config with <clear /> and every source the build needs.

  • Mistake: assuming a local restore proves the feed works. With packages already in ~/.nuget/packages, a local restore may never contact the feed. Instead: reproduce with a clean cache or a fresh SDK container before concluding the feed is fine.

  • Mistake: committing feed passwords or personal access tokens in nuget.config. Everyone with repository access, and every fork, now has the credential. Instead: use the platform's authentication (NuGetAuthenticate@1, credential provider, GITHUB_TOKEN) or reference a secret through an environment variable.

  • Mistake: forgetting that the pipeline identity needs feed permissions. Moving a project or feed changes the identity or scope, and restore fails with 401 or 403. Instead: grant the build identity Reader on the feed and its upstream views, and check after every move.

  • Mistake: mixing public and private sources without source mapping. An attacker can publish a same-named package with a higher version publicly, and restore picks it: dependency confusion. Instead: map internal prefixes such as Contoso.* to the internal feed only, or route everything through one internal feed with upstream sources.

  • Mistake: floating versions without a lock file. * versions resolve to whatever is newest, so CI restores something different from yesterday. Instead: pin versions and enable lock files with --locked-mode in CI.

  • Mistake: suppressing NuGet Audit warnings. Known vulnerabilities stay in production, and new transitive warnings after retargeting to net10.0 get ignored. Instead: upgrade the vulnerable package (or the parent that brings it in), and treat high and critical findings as errors on main.

  • Mistake: ignoring the network path. Hosted agents can't reach internal feeds behind a VPN, and TLS inspection proxies break certificate validation. Instead: check DNS and connectivity from the agent, use a feed reachable from CI, or a self-hosted agent inside the network.

🏛 Senior insight: trade-offs

Restore is the front door of your software supply chain. A senior engineer makes it explicit and boring: one committed nuget.config, an internal feed with upstream sources so all packages flow through one controlled place, source mapping, lock files in CI, read-only short-lived credentials from the platform, and NuGet Audit warnings treated as work items. The "works on my machine" symptom is usually a hidden dependency on personal configuration, so the fix is to make the repo self-describing rather than to copy one developer's settings into the pipeline.

🔗 Follow-ups the interviewer may ask

  1. When: Candidate fixes the feed but doesn't explain what restore writes to obj. Why: Checks understanding of what restore produces and why later steps depend on it.
    How to answer

    Restore works out the full NuGet package graph, downloads packages to the global packages folder and writes obj/project.assets.json. Build compiles against those assets into bin/<Configuration>/<TFM>. Test runs the test projects against the built assemblies. Publish produces a deployable folder with the app, its dependencies and runtime config, in Release by default since .NET 8. Each later command restores and builds implicitly, so in CI I restore once and pass --no-restore and --no-build. That way every step uses exactly the same outputs and nothing is repeated. The configuration has to match across steps.

  2. When: Candidate mentions an internal Azure Artifacts or GitHub Packages feed. Why: Looks at the other side of a private feed: producing and pushing the packages.
    How to answer

    dotnet pack builds the project and produces a .nupkg whose metadata comes from MSBuild properties in the project: PackageId, Version, Authors, Description, a licence expression, a repository URL and a README. I set the version from CI using Semantic Versioning, so a breaking change means a new major version. I publish symbols as a .snupkg with Source Link, so consumers can step into the exact source. I turn on package validation to catch accidental breaking changes. Then dotnet nuget push uploads to nuget.org or an internal feed, and organisations that need it can sign packages with dotnet nuget sign.

  3. When: Candidate mentions dotnet tool restore failing in the same pipeline. Why: Tool restore uses the same feeds and credentials, so the same failures appear there.
    How to answer

    A .NET tool is a console app shipped as a special NuGet package. Global tools (dotnet tool install -g) are installed per user and land on your PATH. Local tools are pinned per repository in a manifest, .config/dotnet-tools.json, created with dotnet new tool-manifest. Anyone who clones the repo runs dotnet tool restore and gets exactly the same versions. For team and CI work I prefer local tools, because the version lives in source control next to the code that depends on it.

🛠 Real-world problems: how you handle them

Private package restore failing only in a new CI pipeline
Situation
A team moved builds from a self-hosted agent to Microsoft-hosted agents. Restore failed with NU1301 for an internal `Contoso.Abn` package, while every developer could restore locally.
Signals
NU1301 for the internal package only, a clean agent with an empty packages folder, and no `nuget.config` in the repository.
Investigation
Ran `dotnet restore -v detailed` in the pipeline and saw only nuget.org listed. Developers had the internal feed in their user-level `NuGet.Config`, added years earlier, and the old self-hosted agent had it configured machine-wide. A public package with a similar name also existed on nuget.org.
Resolution
Committed a repo-level `nuget.config` with `<clear />`, the internal feed and nuget.org, and package source mapping for `Contoso.*`. Added the `NuGetAuthenticate@1` task and granted the pipeline identity Reader on the feed. Enabled lock files and `--locked-mode`.
Prevention
A template repository now includes the `nuget.config`, new pipelines use the shared template with authentication, and NuGet Audit warnings are reviewed in each sprint.

How to tell it (STAR)

**Situation:** After we moved to hosted build agents, restore failed for our internal packages even though it worked for every developer. **Task:** I needed to get builds green and understand why. **Action:** A detailed restore log showed CI only knew about nuget.org; the internal feed lived in personal and machine config. I committed an explicit `nuget.config` with source mapping, set up the supported authentication task and feed permissions, and turned on lock files. **Result:** Builds were green the same day, restores became reproducible, and we closed a dependency confusion risk [mention any security review outcome].

↩ Usually asked after

More in .NET CLI

Last updated 1 Nov 2026.

🌐 Sangam's Webring

⏪ Previous 🏠 Main Next ⏩
💬 Chat with me