nulic 0.0.5

dotnet tool install --global nulic --version 0.0.5
                    
This package contains a .NET tool you can call from the shell/command line.
dotnet new tool-manifest
                    
if you are setting up this repo
dotnet tool install --local nulic --version 0.0.5
                    
This package contains a .NET tool you can call from the shell/command line.
#tool dotnet:?package=nulic&version=0.0.5
                    
nuke :add-package nulic --version 0.0.5
                    

nulic

A .NET global tool that collects and produces a license disclosure package for all NuGet dependencies in a project.

CI NuGet

📋 See a live example of the report: open the latest CI run and click the Summary tab.

What it does

Given a solution, project, or folder, nulic:

  1. Enumerates all NuGet packages — direct and transitive
  2. Downloads or copies the actual license text for each package
  3. Writes a third-party-notices/ folder next to the solution containing:
    • One license text file per unique license (shared across packages using the same SPDX id)
    • A nulic-packages.json manifest with full metadata per package

The goal is a complete, shippable artifact — every package gets an actual license file on disk.

Install

dotnet tool install -g nulic

Usage

nulic [<path>] [options]
Argument / Option Description
<path> Solution file, project file, or folder. Default: .
-d, --show-defaults Print the default nulic.json to stdout and exit
-l, --log-level Log verbosity: Verbose, Debug, Information (default), Warning, Error
-m, --merge <dir> Merge a third-party-notices/ directory from another nulic-processed project (repeatable)
-o, --output <dir> Output folder for license files and report. Default: <path>/third-party-notices

Examples

# Run on the current directory (auto-discovers solution)
nulic

# Run on a specific solution
nulic path/to/MyApp.sln

# Only show warnings and errors
nulic --log-level Warning

# Combine licenses from two sub-projects into one disclosure package
nulic MyApp.sln --merge ../firmware/third-party-notices --merge ../safety/third-party-notices

# Write output to a custom folder
nulic MyApp.sln --output D:/artifacts/third-party-notices

nulic.json

On first run, nulic creates a nulic.json next to the solution as a starting template.
Use nulic --show-defaults to print the default config.

{
  "exclude": [],      // glob patterns for project files to exclude from scanning
  "ignore":  [],      // packages to ignore — id:* or author:* patterns
  "allow":   [],      // SPDX allowlist; non-empty enables allowlist mode (exits 1 on violation)
  "overrides": []     // patch metadata or inject packages without a real NuGet entry
}

Ignore patterns

Patterns are glob-style (* = any characters). The id: prefix is optional — a bare pattern also matches against package id.

"ignore": [
  "Tyrell.Corp.*",             // id glob — id: prefix is optional
  "id:*Nexus.Test*",           // id: prefix is explicit but equivalent
  "author:Rick Deckard",       // match on author name (glob)
  "developmentDependency",     // packages marked developmentDependency in packages.config
  "PrivateAssets"              // packages with PrivateAssets=all in PackageReference
]

Overrides

Override or inject a package entry — useful for packages with dead license URLs or internal packages. All fields except id are optional:

"overrides": [
  {
    // All available fields:
    "id":         "DeathStar.TurboLaser",
    "version":    "3.0.0",                             // pin to a specific version (omit = all versions)
    "license":    "LicenseRef-Imperial",               // SPDX expression or custom LicenseRef
    "licenseUrl": "licenses/IMPERIAL_LICENSE.txt",     // local path or https://
    "authors":    ["Emperor Palpatine"],
    "projectUrl": "https://empire.gov/deathstar",
    "copyright":  "Copyright © 19 BBY Galactic Empire. All sectors reserved."
  },
  {
    // Minimal — just fix the license URL for a package with a dead link:
    "id":         "HanSolo.Kessel",
    "licenseUrl": "https://raw.githubusercontent.com/hansolo/kessel/main/LICENSE"
  }
]

Allowlist (CI gate)

When allow is non-empty, nulic validates every package against the list and exits with code 1 if any violation is found — suitable for use as a CI gate:

"allow": [
  "MIT",
  "Apache-2.0",
  "BSD-3-Clause",
  "WITH LicenseRef-linking-exception"   // permits GPL files carrying a linking exception
]

Compound SPDX expressions (e.g. MIT AND LicenseRef-foo) must have every component covered by the allowlist.

NOASSERTION

If nulic cannot obtain or identify a license for a package, the license field in nulic-packages.json is set to NOASSERTION and a warning is printed. Common causes:

  • The package has no license metadata and no recognizable license file
  • The license URL is dead or returns an unrecognizable format
  • The package is not in the local NuGet cache (run dotnet restore first)

Fix with an override:

"overrides": [
  {
    "id": "Some.Package",
    "license": "MIT",
    "licenseUrl": "https://raw.githubusercontent.com/org/repo/main/LICENSE"
  }
]

Merging projects (--merge)

When a product bundles multiple independently-built components (e.g. a main application together with subsystems from different repos), each component should first be processed by nulic on its own. Then use --merge to produce a combined disclosure package:

# Step 1: process each component independently (in their own CI)
nulic HAL9000/           # produces HAL9000/third-party-notices/
nulic AE35Unit/          # produces AE35Unit/third-party-notices/

# Step 2: produce the combined disclosure for the product
nulic Discovery/ --merge ../HAL9000/third-party-notices --merge ../AE35Unit/third-party-notices

The merge step:

  • Reads nulic-packages.json from each merged directory
  • Copies license files into the target third-party-notices/ folder (skips identical files, errors on content mismatch)
  • Unions all packages, deduplicating by Id + Version
  • Validates the combined set against the target project's allow list
  • Regenerates third-party-notices.md from the combined data

Running nulic on the nulic repo itself produces (abbreviated):

third-party-notices/
  MIT.txt                          # shared — AngleSharp, System.CommandLine, Textify, ...
  Apache-2.0.txt                   # shared — all NuGet.* packages, Serilog, ...
  Microsoft.Build.18.6.3/
    notices/THIRDPARTYNOTICES.txt  # supplementary file from the package
  ...                              # one entry per unique package
  nulic-packages.json

nulic-packages.json entry (real example from nulic's own dependencies):

{
  "id": "AngleSharp",
  "version": "1.4.0",
  "authors": ["AngleSharp"],
  "projectUrl": "https://anglesharp.github.io/",
  "copyright": "Copyright 2013-2025, AngleSharp.",
  "license": "MIT",
  "licenseUrl": "https://licenses.nuget.org/MIT",
  "licenseFiles": ["MIT.txt"]
}

Supported project types

Project type Package source
SDK-style .csproj / .fsproj / .vbproj obj/project.assets.json (requires dotnet restore)
Classic .NET Framework .csproj packages.config
Native C++ .vcxproj packages.config
Solutions .sln / .slnx All constituent projects
Folder Auto-discovers first .sln / .slnx, then .csproj, etc.
Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

This package has no dependencies.

Version Downloads Last Updated
0.0.5 148 10/23/2026
0.0.4 164 5/25/2026
0.0.3 117 5/25/2026
0.0.2 134 5/25/2026
0.0.1 124 5/25/2026
0.0.1-alpha1 139 5/25/2026