# .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/@` PURLs - Declared packages not matching any installed package are emitted with `declaredOnly=true` and `installed.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/@` 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// ``` Where `version-source-hash` = first 8 characters of SHA-256(`||`) 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/@` 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.json` or `*.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.json` exists, check `MyApp`, `MyApp.exe`, `MyApp.dll`) - Skip files > 500 MB (emit `bundle.skipped=true` with `bundle.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 section - **`packages.lock.json`**: Dependencies from the lock file's per-package dependencies ### Example Configuration ```json { "emitDependencyEdges": true } ``` ## Central Package Management (CPM) The analyzer supports .NET CPM via `Directory.Packages.props`: 1. When `ManagePackageVersionsCentrally=true` in the project or props file 2. Package versions are resolved from `` items in `Directory.Packages.props` 3. If a package version cannot be found in CPM, it's marked as unresolved with `declared.unresolvedReason=cpm-missing` ## Known Limitations 1. **No full MSBuild evaluation**: The analyzer uses lightweight XML parsing, not MSBuild evaluation. Complex conditions and imports may not be fully resolved. 2. **No restore/feed access**: The analyzer does not perform NuGet restore or access package feeds. Only locally available information is used. 3. **Property resolution**: Property placeholders (`$(PropertyName)`) are resolved using `Directory.Build.props` and project properties, but transitive or complex property evaluation is not supported. 4. **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.cs` - `src/Scanner/__Libraries/StellaOps.Scanner.Analyzers.Lang.DotNet/Internal/DotNetDeclaredDependencyCollector.cs` - `src/Scanner/__Libraries/StellaOps.Scanner.Analyzers.Lang.DotNet/Internal/Bundling/DotNetBundlingSignalCollector.cs` ## Related Sprint See [SPRINT_0404_0001_0001_scanner_dotnet_detection_gaps.md](../../implplan/SPRINT_0404_0001_0001_scanner_dotnet_detection_gaps.md) for implementation details and decisions.