Bitwarden.Server.Sdk.RestrictedDependencies
0.1.0
Prefix Reserved
dotnet add package Bitwarden.Server.Sdk.RestrictedDependencies --version 0.1.0
NuGet\Install-Package Bitwarden.Server.Sdk.RestrictedDependencies -Version 0.1.0
<PackageReference Include="Bitwarden.Server.Sdk.RestrictedDependencies" Version="0.1.0"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> </PackageReference>
<PackageVersion Include="Bitwarden.Server.Sdk.RestrictedDependencies" Version="0.1.0" />
<PackageReference Include="Bitwarden.Server.Sdk.RestrictedDependencies"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> </PackageReference>
paket add Bitwarden.Server.Sdk.RestrictedDependencies --version 0.1.0
#r "nuget: Bitwarden.Server.Sdk.RestrictedDependencies, 0.1.0"
#:package Bitwarden.Server.Sdk.RestrictedDependencies@0.1.0
#addin nuget:?package=Bitwarden.Server.Sdk.RestrictedDependencies&version=0.1.0
#tool nuget:?package=Bitwarden.Server.Sdk.RestrictedDependencies&version=0.1.0
Bitwarden.Server.Sdk.RestrictedDependencies
About
Some types outgrow their purpose: a service that every team injects, a settings class that every team adds a property to. This package lets a repository freeze one, record every existing use in a committed baseline, and make the build reject new ones — so the type can only shrink while it is dissolved into owned replacements.
It ships a Roslyn analyzer, a source generator that emits the attributes, and the baseline document format so a repository can regenerate and diff its own baselines.
How to use
In your repository's root Directory.Build.props, turn the analysis on for the projects you want gated and point it at your committed baselines. RepoRoot must be the repository root. $(MSBuildThisFileDirectory) is the repository root only in that file; in a project file it is the project directory. Gate projects with a Condition on RestrictedDependencyAnalysis, or set it in each project file:
<PropertyGroup>
<RestrictedDependencyAnalysis>true</RestrictedDependencyAnalysis>
<RepoRoot>$(MSBuildThisFileDirectory)</RepoRoot>
<RestrictedDependencyBaselinesPath>$(RepoRoot)baselines</RestrictedDependencyBaselinesPath>
<RestrictedDependencyUpdateCommand>dotnet run --project tools/Baselines -- update</RestrictedDependencyUpdateCommand>
</PropertyGroup>
<ItemGroup Condition="'$(RestrictedDependencyAnalysis)' == 'true'">
<PackageReference Include="Bitwarden.Server.Sdk.RestrictedDependencies" Version="..." IncludeAssets="analyzers;build" />
</ItemGroup>
| Property | Meaning |
|---|---|
RestrictedDependencyAnalysis |
true to analyze this project. Anything else is skipped before a symbol is looked at. |
RepoRoot |
Repository root, so paths in messages and AllowedPaths globs are repo-relative. |
RestrictedDependencyBaselinesPath |
Directory of committed <Type>.json baselines. |
RestrictedDependencyUpdateCommand |
Your regenerate command, quoted back in BW0013 and BW0015. |
Then mark a type. The attributes are generated into every analyzed compilation, so there is nothing to reference:
[RestrictedDependency(AllowExistingUses = true, AllowNewUses = false,
Tracking = "PM-43148", Owner = "@my-team",
SealMembers = true, AllowedPaths = new[] { "src/Core/Services/Implementations/UserService.cs" })]
public interface IUserService
{
[RestrictedDependency(Replacement = "IHasPremiumAccessQuery.HasPremiumAccessAsync")]
Task<bool> CanAccessPremium(User user);
}
The (AllowExistingUses, AllowNewUses) pair decides how a use is treated:
| Rule | Meaning |
|---|---|
(true, true) |
Tracked only. Counted in the baseline as a tracked row, never a diagnostic, and exempt from the shrink-only check. |
(true, false) |
Gated. Baselined uses pass; anything beyond the baseline is an error. |
(false, false) |
Forbidden. Every use is an error, baselined or not. |
(false, true) |
Invalid; rejected by BW0015. |
AllowedPaths is a full exemption: a matching file is neither gated, baselined nor observed for this type, so name the implementation file rather than its folder.
Restrict the types a restricted type inherits from too. Member rules, baselines and SealMembers cover only the members the restricted type declares, so a member it inherits from an unrestricted interface or base class is neither counted nor sealed, and code can reach it through that base type without touching the restricted one.
Exceptions
When a new use is unavoidable, put an owned, expiring exception on the site rather than widening the baseline. All three properties are required, and an invalid exception excepts nothing:
[RestrictedDependencyException(typeof(IUserService),
Owner = "team-billing", Reason = "PM-12345", Expires = "2026-12-01")]
public class ProviderBillingController(IUserService userService);
Expiresis advisory. BW0011 is a warning, and excepted sites are deliberately kept out of the baselines, so a shrink-only check never sees them either. Nothing chases an expiry for you. Take an exception because the owner and reason are worth recording, not because the date will be enforced.
Baselines
A baseline is one JSON document per restricted type, listing every use keyed by the documentation-comment id of the type that contains it, so it survives line moves and changes to the parameters or names of the constructors and methods that hold the uses. A member row keeps the restricted member's full id, parameters included, so changing that member's signature reports BW0014 on a sealed type, and BW0006 and BW0013 when the member's rule is gated. It carries no timestamp or commit hash: two runs on the same tree serialize byte-identically. Its type field is the name Compilation.GetTypeByMetadataName accepts: namespace, dot, nested names joined by +, arity suffix included.
A row whose rule is tracked-only carries "tracked": true, which BudgetRatchet.FindGrowth reads to exempt it: that total is expected to rise, so comparing it would fail the check for exactly the code the rule permits. The flag is written only when true, so a document with no tracked-only rows is byte-identical to one written without it. Your tool takes the value off the tracked property of each BW0017 usage row.
BudgetModel, BudgetEntry, BudgetRatchet.FindGrowth, DependencyUsageType and DependencyUsageTypeExtensions are the supported way to read, write and diff those documents from your own tooling. Your tool project references the package without the IncludeAssets trim, so the library is compiled in. The package is a development dependency, so it stays with the project that references it and does not flow to anything that depends on that project.
Your tool hosts the same analyzer the compiler does rather than reimplementing the scan. Take the analyzer from each project's Project.AnalyzerReferences rather than constructing it: the package ships the analyzer assembly only under analyzers/, so it is not on your tool's compile path. Enable BW0017 through CompilationOptions.WithSpecificDiagnosticOptions, supply the analyzer-config keys on AnalyzerConfigConstants (Analysis, RepositoryRoot, SeedTypes, joined by SeedTypeSeparator), and read the rows back off Diagnostic.Properties using ObservationConstants. AttributeConstants gives you the attribute name to discover restricted types by, and the attribute Text and HintName to compile in when the generator has not run. BW0017 is disabled by default, so it emits nothing in a real build. DiagnosticConstants carries the family's ids for your own configuration checks.
Code Roslyn classifies as generated is not analyzed: nothing in it is counted, baselined or reported. Roslyn treats a tree as generated when it opens with an
<auto-generated>comment, has a name like*.g.csor*.designer.cs, or is markedgenerated_code = trueby an.editorconfig, and a symbol when it carries[GeneratedCode].
Diagnostics
| ID | Severity | Fires when |
|---|---|---|
| BW0005 | Error | A constructor takes a restricted type at a site not in the baseline. |
| BW0006 | Error | A restricted member is used beyond its baselined count, or at all when forbidden. |
| BW0007 | Error | A restricted type is resolved from the service locator at a site not in the baseline. |
| BW0008 | Error | An implementing type is referenced directly. |
| BW0009 | Error | A restricted type escapes its consumer. |
| BW0010 | Error | An exception is missing Owner, Reason or a yyyy-MM-dd Expires. |
| BW0011 | Warning | An exception has expired. |
| BW0012 | Error | A [SuppressMessage] names one of these ids. |
| BW0013 | Error | A baseline entry no longer matches the code. |
| BW0014 | Error | A sealed type gained a member, nested type or property. |
| BW0015 | Error | An attribute or a baseline is invalid, missing or orphaned. |
| BW0016 | Error | One of these ids is configured below error; the setting has no effect. |
| BW0017 | Hidden | Never in a real build. The observation channel for baseline tooling. |
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. 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. |
| .NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.0 is compatible. netstandard2.1 was computed. |
| .NET Framework | net461 was computed. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen40 was computed. tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.0
- No dependencies.
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 0.1.0 | 105 | 10/27/2026 |