6.5 KiB
.NET Analyzer
The .NET analyzer detects NuGet package dependencies in .NET applications by analyzing multiple dependency sources with defined precedence rules.
Detection Sources and Precedence
The analyzer uses the following sources in order of precedence (highest to lowest fidelity):
| Priority | Source | Description |
|---|---|---|
| 1 | packages.lock.json |
Locked resolved versions; highest trust for version accuracy |
| 2 | *.deps.json |
Installed/published packages; authoritative for "what shipped" |
| 3 | SDK-style project files | *.csproj/*.fsproj/*.vbproj + Directory.Packages.props (CPM) + Directory.Build.props |
| 4 | packages.config |
Legacy format; lowest precedence |
Operating Modes
Installed Mode (deps.json present)
When *.deps.json files exist, the analyzer operates in installed mode:
- Installed packages are emitted with
pkg:nuget/<id>@<ver>PURLs - Declared packages not matching any installed package are emitted with
declaredOnly=trueandinstalled.missing=true - Installed packages without corresponding declared records are tagged with
declared.missing=true
Declared-Only Mode (no deps.json)
When no *.deps.json files exist, the analyzer falls back to declared-only mode:
- Dependencies are collected from declared sources in precedence order
- All packages are emitted with
declaredOnly=true - Resolved versions use
pkg:nuget/<id>@<ver>PURLs - Unresolved versions use explicit keys (see below)
Declared-Only Components
Components emitted from declared sources include these metadata fields:
| Field | Description |
|---|---|
declaredOnly |
Always "true" for declared-only components |
declared.source |
Source file type (e.g., csproj, packages.lock.json, packages.config) |
declared.locator |
Relative path to source file |
declared.versionSource |
How version was determined: direct, centralpkg, lockfile, property, unresolved |
declared.tfm[N] |
Target framework(s) |
declared.isDevelopmentDependency |
"true" if marked as development dependency |
provenance |
"declared" for declared-only components |
Unresolved Version Identity
When a version cannot be resolved (e.g., CPM enabled but missing version, unresolved property placeholder), the component uses an explicit key format:
declared:nuget/<normalized-id>/<version-source-hash>
Where version-source-hash = first 8 characters of SHA-256(<source>|<locators>|<raw-version-string>)
Additional metadata for unresolved versions:
| Field | Description |
|---|---|
declared.versionResolved |
"false" |
declared.unresolvedReason |
One of: cpm-missing, property-unresolved, version-omitted |
declared.rawVersion |
Original unresolved string (e.g., $(SerilogVersion)) |
This explicit key format prevents collisions with real pkg:nuget/<id>@<ver> PURLs.
Bundling Detection
The analyzer detects bundled executables (single-file apps, ILMerge/ILRepack assemblies) using bounded candidate selection:
Candidate Selection Rules
- Only scan files in the same directory as
*.deps.jsonor*.runtimeconfig.json - Only scan files with executable extensions:
.exe,.dll, or no extension - Only scan files named matching the app name (e.g., if
MyApp.deps.jsonexists, checkMyApp,MyApp.exe,MyApp.dll) - Skip files > 500 MB (emit
bundle.skipped=truewithbundle.skipReason=size-exceeded)
Bundling Metadata
When bundling is detected, metadata is attached to entrypoint components (or synthetic bundle markers):
| Field | Description |
|---|---|
bundle.detected |
"true" |
bundle.filePath |
Relative path to bundled executable |
bundle.kind |
singlefile, ilmerge, ilrepack, costurafody, unknown |
bundle.sizeBytes |
File size in bytes |
bundle.estimatedAssemblies |
Estimated number of bundled assemblies |
bundle.indicator[N] |
Detection indicators (top 5) |
bundle.skipped |
"true" if file was skipped |
bundle.skipReason |
Reason for skipping (e.g., size-exceeded) |
Dependency Edges
When emitDependencyEdges=true is set in the analyzer configuration (dotnet-il.config.json), the analyzer emits dependency edge metadata for both installed and declared packages.
Edge Metadata Format
Each edge is emitted with the following metadata fields:
| Field | Description |
|---|---|
edge[N].target |
Normalized package ID of the dependency |
edge[N].reason |
Relationship type (e.g., declared-dependency) |
edge[N].confidence |
Confidence level (high, medium, low) |
edge[N].source |
Source of the edge information (deps.json, packages.lock.json) |
Edge Sources
deps.json: Dependencies from the runtime dependencies sectionpackages.lock.json: Dependencies from the lock file's per-package dependencies
Example Configuration
{
"emitDependencyEdges": true
}
Central Package Management (CPM)
The analyzer supports .NET CPM via Directory.Packages.props:
- When
ManagePackageVersionsCentrally=truein the project or props file - Package versions are resolved from
<PackageVersion>items inDirectory.Packages.props - If a package version cannot be found in CPM, it's marked as unresolved with
declared.unresolvedReason=cpm-missing
Known Limitations
-
No full MSBuild evaluation: The analyzer uses lightweight XML parsing, not MSBuild evaluation. Complex conditions and imports may not be fully resolved.
-
No restore/feed access: The analyzer does not perform NuGet restore or access package feeds. Only locally available information is used.
-
Property resolution: Property placeholders (
$(PropertyName)) are resolved usingDirectory.Build.propsand project properties, but transitive or complex property evaluation is not supported. -
Bundled content: Bundling detection identifies likely bundles but cannot extract embedded dependency information.
Files Created/Modified
src/Scanner/__Libraries/StellaOps.Scanner.Analyzers.Lang.DotNet/DotNetLanguageAnalyzer.cssrc/Scanner/__Libraries/StellaOps.Scanner.Analyzers.Lang.DotNet/Internal/DotNetDeclaredDependencyCollector.cssrc/Scanner/__Libraries/StellaOps.Scanner.Analyzers.Lang.DotNet/Internal/Bundling/DotNetBundlingSignalCollector.cs
Related Sprint
See SPRINT_0404_0001_0001_scanner_dotnet_detection_gaps.md for implementation details and decisions.