Overview
NuGet.VerifyMicrosoftPackage is a developer-facing command-line tool that Microsoft teams use to catch NuGet package metadata compliance problems before pushing to nuget.org. Rather than discovering violations at upload time through server-side security policies, this tool runs the same rule-set locally so teams can fix packaging issues earlier in the release pipeline.
The tool validates .nupkg files against a configurable JSON rule set. The built-in default rule set encodes the MicrosoftTeamSubscription policy from NuGetGallery.Services: required co-owner username "Microsoft", allowed copyright notices in 18 locales, required authors field containing only "Microsoft", and mandatory licenseUrl/projectUrl fields.
The tool only validates
.nuspec metadata. It does not verify author signatures, package size limits, Authenticode on DLLs, or nuget.org-specific pre-release label rules. Those gaps are documented in the README as future work.Role in the NuGetGallery Ecosystem
Server-side mirror
The same compliance logic enforced by
MicrosoftTeamSubscription on the gallery server is exposed here for local use, keeping client and server behaviour in sync.Developer shift-left
Allows Microsoft package authors to run
verify.ps1 *.nupkg in CI before pushing, surfacing the exact error messages they would see on nuget.org.Distributed as a NuGet tool package
Packaged as
NuGet.VerifyMicrosoftPackage (tools layout) and published to nuget.org. verify.ps1 downloads it on demand via nuget.exe install.Configurable rule sets
Teams can export the default JSON rule set, customise it, and pass it back via
--rule-set, enabling policy variations without recompiling.Key Files and Classes
File path (relative to src/VerifyMicrosoftPackage/) | Class / Symbol | Purpose |
|---|---|---|
Program.cs | Program | Entry point; delegates to Application.Execute() and maps exit codes to error states |
Application.cs | Application | Extends CommandLineApplication; owns all CLI option parsing, wildcard file expansion, package loading, and compliance reporting |
VerifyMicrosoftPackage.csproj | — | net472 exe; references NuGetGallery.Services and Microsoft.Extensions.CommandLineUtils |
VerifyMicrosoftPackage.nuspec | — | Packs the net472 build output into tools/ for consumption as a NuGet tool package |
verify.ps1 | — | PowerShell bootstrap script; downloads nuget.exe and the tool package, then invokes the exe |
Fakes/FakeEntityRepository.cs | FakeEntityRepository<T> | In-memory IEntityRepository<T> with no-op mutations; satisfies PackageService constructor without a database |
Fakes/FakeEntitiesContext.cs | FakeEntitiesContext | Stub IEntitiesContext — all members throw NotImplementedException; only the constructor path through PackageService is exercised |
Fakes/FakeSecurityPolicyService.cs | FakeSecurityPolicyService | Stub ISecurityPolicyService; policy evaluation is not needed at validation time |
Fakes/FakeTelemetryService.cs | FakeTelemetryService | Stub ITelemetryService; most methods throw, but ArePatternSetTfmHeuristicsEnabled returns true to allow TFM parsing |
Fakes/FakeAuditingService.cs | FakeAuditingService | Stub IAuditingService |
Fakes/FakeContentObjectService.cs | FakeContentObjectService | Stub IContentObjectService |
Fakes/FakeFeatureFlagService.cs | FakeFeatureFlagService | Stub IFeatureFlagService; ArePatternSetTfmHeuristicsEnabled() returns true |
NuGetGallery.Services used at runtime:
File (in NuGetGallery.Services/Security/) | Class | Purpose |
|---|---|---|
MicrosoftTeamSubscription.cs | MicrosoftTeamSubscription | Defines the canonical Microsoft package policy and the 18-locale copyright allowlist |
RequirePackageMetadataState.cs | RequirePackageMetadataState | JSON-serialisable DTO representing one rule set (authors, copyright, URL requirements, error format) |
RequirePackageMetadataComplianceUtility.cs | RequirePackageMetadataComplianceUtility | Static utility; deserialises a RequirePackageMetadataState from UserSecurityPolicy values and runs the four compliance checks |
Dependencies
NuGet Package References
| Package | Purpose |
|---|---|
Microsoft.Extensions.CommandLineUtils | CLI argument / option parsing (CommandLineApplication base class) |
Newtonsoft.Json | (transitive via NuGetGallery.Services) JSON serialisation of rule sets |
NuGet.Packaging | PackageArchiveReader — reads .nupkg zip entries and nuspec |
Internal Project References
| Project | How it is used |
|---|---|
NuGetGallery.Services | Provides PackageService, MicrosoftTeamSubscription, RequirePackageMetadataComplianceUtility, RequirePackageMetadataState, CryptographyService, and all gallery entity types |
Framework / BCL
| Assembly | Reason |
|---|---|
System.Web | Required by ISecurityPolicyService signatures (HttpContextBase) even though policy evaluation is never called |
Notable Patterns and Implementation Details
Fake service wiring pattern.
Application.GetPackageService() constructs a real PackageService instance by injecting eight fake collaborators. None of the fakes implement actual behaviour — they exist solely to satisfy the constructor. Only the CreatePackageAsync code path is exercised, and it only reads from the PackageArchiveReader; no database, audit, or telemetry calls are made.Rule set derivation. The default rule set is not hard-coded in this project.
Application.GetDefaultRuleSet() instantiates MicrosoftTeamSubscription and deserialises its Policies collection through RequirePackageMetadataComplianceUtility.DeserializeState. This means the tool and the gallery server share exactly the same policy definition — a change to MicrosoftTeamSubscription automatically flows into the tool on next build.Exit code conventions. The exe returns
0 on full success, -1 for help/version/bad arguments, -2 for unexpected exceptions, and the count of invalid packages (a positive integer) when validation completes with failures. verify.ps1 treats any non-zero exit as an error via Write-Error.Wildcard support is directory-scoped. Wildcards in the filename portion of a path (e.g.,
*.nupkg) are supported and can be made recursive with --recursive. Wildcards or globs in the directory segment are not supported — the directory must be a literal path.