Open a typical .csproj file and you might see a dozen PackageReference entries. Build the application and the restore graph may contain hundreds of packages.
The difference is transitive dependencies: packages selected because something you chose depends on them. They are convenient, necessary, and easy to overlook. They also contain code that ships with your application, so “we did not reference it directly” is not a security boundary.
A Package List Hides the Important Part
A flat inventory can tell you that Example.Json version 3.1 is present. It cannot tell you why it is present.
That distinction matters because dependency remediation is a graph problem. Suppose two top-level packages depend on different ranges of the same library. Changing one direct reference may leave the vulnerable version in place for another target framework, or restore may select a version that satisfies the graph but changes runtime behavior.
Before changing anything, answer:
- Which direct package introduced the vulnerable component?
- Is there more than one path to it?
- Which target frameworks and projects resolve the affected version?
- Does the fixed version satisfy the parent package's declared range?
- Will a central package version or repository-wide property alter the result?
Without that context, an apparently simple upgrade can become a fragile exception.
Remediation Options, in Order
There are four common ways to fix a vulnerable transitive dependency.
1. Upgrade the Direct Dependency
Prefer upgrading the package your application intentionally selected. Maintainers can adjust their dependency ranges, test compatible versions, and ship any code changes needed for the new transitive component.
This keeps ownership clear: your project selects the capability, and the package selects its implementation dependencies.
2. Replace the Direct Dependency
If the parent package is abandoned or repeatedly slow to patch important issues, replacement may be safer than accumulating local overrides. Evaluate the replacement's maintenance history, release cadence, and dependency footprint—not only whether its API looks familiar.
3. Apply a Direct Version Override
Adding a direct PackageReference can force NuGet to resolve a safe version when the parent package's range permits it:
<ItemGroup>
<PackageReference Include="Example.Json" Version="3.2.4" />
</ItemGroup>Treat this as an owned compatibility decision. Run the application's tests, record why the override exists, and create a follow-up to remove it after the parent package updates.
If the repository uses Central Package Management, place the version in Directory.Packages.props so projects do not drift:
<ItemGroup>
<PackageVersion Include="Example.Json" Version="3.2.4" />
</ItemGroup>4. Accept the Risk Temporarily
When no fixed, compatible version exists, document reachability, exposure, compensating controls, and an expiry date. “Transitive” is not a sufficient justification by itself.
Verify the Resolved Graph
Editing XML is not the same as fixing the artifact. Re-run restore and inspect the graph for every relevant project and target framework. Multi-targeted libraries can resolve different assets and versions from the same source declaration.
Then scan the repository again. The finding should disappear from the deployed graph, not merely from the project you happened to open.
This is also why lock files and repeatable restore inputs matter. A review is only durable when CI resolves the graph you inspected.
Make the Path Part of the Finding
Good tooling should show the chain from application to affected package. That one piece of context answers several operational questions at once:
- which team likely owns the fix;
- whether a parent upgrade is available;
- how broadly the package is used;
- whether one change can remove several findings.
Rorix builds the transitive graph across .NET projects so a vulnerability is presented with its introduction path. Start with a dependency scan, then use the graph to choose the smallest fix that preserves a coherent restore.
Your direct dependencies describe intent. Your resolved dependencies describe reality. Security review needs both.
