GlobalGuard.Analyzers
1.0.0-preview.1
dotnet add package GlobalGuard.Analyzers --version 1.0.0-preview.1
NuGet\Install-Package GlobalGuard.Analyzers -Version 1.0.0-preview.1
<PackageReference Include="GlobalGuard.Analyzers" Version="1.0.0-preview.1"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> </PackageReference>
<PackageVersion Include="GlobalGuard.Analyzers" Version="1.0.0-preview.1" />
<PackageReference Include="GlobalGuard.Analyzers"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> </PackageReference>
paket add GlobalGuard.Analyzers --version 1.0.0-preview.1
#r "nuget: GlobalGuard.Analyzers, 1.0.0-preview.1"
#:package GlobalGuard.Analyzers@1.0.0-preview.1
#addin nuget:?package=GlobalGuard.Analyzers&version=1.0.0-preview.1&prerelease
#tool nuget:?package=GlobalGuard.Analyzers&version=1.0.0-preview.1&prerelease
GlobalGuard.Analyzers
Compile-time privacy and performance enforcement for .NET — built for the African cloud market and reusable everywhere.
GlobalGuard is a Roslyn analyzer that catches two classes of bugs before they reach production: PII fields leaking into logs, and synchronous thread-blocking that inflates cloud costs. Both checks run inside the compiler: no runtime overhead, no separate scan step, no CI plugin to install.
Why GlobalGuard?
The compliance problem
Data-protection laws now cover every market where African companies operate:
| Regulation | Jurisdiction | Penalty ceiling |
|---|---|---|
| NDPR | Nigeria | ₦10 million or 2 % of annual gross revenue |
| GDPR | EU (including EU partnerships) | €20 million or 4 % of global turnover |
| LGPD | Brazil | 2 % of revenue in Brazil, up to R$50 million |
The single most common violation across all three: a developer writes _logger.LogInformation(user.Bvn) during a debugging session and the line ships to production. By the time a compliance audit catches it, the data is in log aggregators, backups, and third-party SIEM tools.
GlobalGuard makes that mistake a compiler error.
The cost problem
task.Result, task.Wait(), and task.GetAwaiter().GetResult() are the three most common causes of deadlocks and thread-pool starvation in .NET web applications. In cloud-hosted services (Azure App Service, AWS Elastic Beanstalk, any Kubernetes workload) each blocked thread is a unit of capacity that you're paying for but not using.
GlobalGuard flags every occurrence as a compiler warning and offers a one-click fix that rewrites the method signature to async/await.
Quick start
Install
<PackageReference Include="GlobalGuard.Analyzers" Version="1.0.0-preview.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
Or via the CLI:
dotnet add package GlobalGuard.Analyzers --version 1.0.0-preview.1
See it in action
Add a .globalguard.json to your project root (the package's MSBuild props auto-discovers it — no <AdditionalFiles> entry needed):
{
"activeRegions": ["Nigeria", "Global"],
"additionalPiiNames": ["staff_id", "salary"]
}
Then write any of the following and your build will immediately fail or warn:
// GG_SEC_001 — error: BVN flowing into a log
_logger.LogInformation("Processing {bvn}", bvn);
// GG_SEC_001 — error: NIN inside an interpolated string
_logger.LogError($"Retry for NIN {nin}");
// GG_PERF_001 — warning: thread-blocking a Task
var result = FetchUserAsync().Result;
var data = GetDataAsync().GetAwaiter().GetResult();
Diagnostics
GG_SEC_001 — PII Sentinel
| Property | Value |
|---|---|
| Severity | Error (blocks build and CI by default) |
| Category | Security |
| Fix | Wrap with GlobalGuard.Redact() or suppress with [SuppressMessage] |
What it detects
- Direct variable references in calls to any method containing "Log" or "Print" in the name
- PII inside interpolated strings:
$"id: {national_id}"is caught just as reliably asLog(national_id) - Fields and properties decorated with
[PersonalData]or[PiiData]attributes, regardless of name - Name-heuristic fallback for common identifiers across five regional packs (see Region packs)
What it does not flag (no false positives)
Log(GlobalGuard.Redact(bvn))— already wrapped; stops the walkLog(Sanitize(bvn))— user-written sanitising call; outer invocation stops the walkSaveToDatabase(bvn)— not a logging call
Two fix actions per diagnostic
- Wrap with
GlobalGuard.Redact()— insertsGlobalGuard.Redact(bvn)at the call site. You own theRedactimplementation: hash, mask, tokenise — your choice. - Suppress with
[SuppressMessage]— adds[System.Diagnostics.CodeAnalysis.SuppressMessage("Security", "GG_SEC_001:...", Justification = "Reviewed")]to the containing method. For the rare cases where logging the field is intentional and reviewed.
GG_PERF_001 — Naira Saver
| Property | Value |
|---|---|
| Severity | Warning (surfaced in IDE; treated as error if you set /warnaserror) |
| Category | Performance |
| Fix | One-click conversion to async/await |
What it detects
| Pattern | Example |
|---|---|
.Result on Task/ValueTask |
var x = someTask.Result; |
.Wait() on Task |
someTask.Wait(); |
.GetAwaiter().GetResult() |
someTask.GetAwaiter().GetResult(); |
What the fix does
Given:
string GetUser() {
return _client.FetchAsync().Result;
}
One click produces:
async Task<string> GetUser() {
return await _client.FetchAsync();
}
The fix handles all four return-type cases:
void→TaskT→Task<T>Task→Task(no change)Task<T>→Task<T>(no change)
It also adds using System.Threading.Tasks; when the namespace is not already present.
GG_PERF_002 — Queryable ForEach (reserved, coming in v1.1)
| Property | Value |
|---|---|
| Severity | None (off by default) |
| Status | Stub — diagnostic ID and .editorconfig key reserved |
Detects IQueryable<T>.ForEach(...) patterns that cause full table scans instead of server-side filtering. Implementation deferred to v1.1.
Region packs
PII name detection uses an attribute-first check ([PersonalData], [PiiData]) with a name-heuristic fallback. The heuristic draws from regional name packs. Nigeria is always active; others are opt-in via .globalguard.json.
| Region key | Included identifiers |
|---|---|
Nigeria (always on) |
bvn, nin, national_id, id_card, phone_number, email, passport |
India |
aadhaar, pan, voterid, voter_id, passportno |
Brazil |
cpf, rg, cnpj, tituloeleitor |
EU |
iban, ssn, nino, tax_id, nationalinsurance |
Global |
password, secret, apikey, token, creditcard, dob, dateofbirth |
Name matching is case-insensitive: BVN, Bvn, and bvn all trigger the diagnostic.
Adding your own names
{
"activeRegions": ["Nigeria", "EU", "Global"],
"additionalPiiNames": ["staff_id", "employee_number", "salary", "grade_level"]
}
Configuration reference
.globalguard.json
Place this file at your project root. The NuGet package's bundled MSBuild .props file automatically registers it as an AdditionalFiles input — no <AdditionalFiles> entry required in your .csproj.
{
"activeRegions": ["Nigeria", "Global"],
"additionalPiiNames": []
}
If the file is absent or malformed, GlobalGuard falls back to the default registry (Nigeria + Global packs), so your build never breaks due to a misconfigured config file.
.editorconfig severity overrides
[*.cs]
dotnet_diagnostic.GG_SEC_001.severity = error # default
dotnet_diagnostic.GG_PERF_001.severity = warning # default
dotnet_diagnostic.GG_PERF_002.severity = none # default (reserved)
Promote GG_PERF_001 to an error to block any PR that introduces a new blocking call:
dotnet_diagnostic.GG_PERF_001.severity = error
Silence a specific file or folder:
[**/Migrations/**.cs]
dotnet_diagnostic.GG_SEC_001.severity = none
CI / GitHub Actions
The package ships with a production-ready workflow at .github/workflows/globalguard-ci.yml.
Key properties of the design:
GG_SEC_001isDiagnosticSeverity.Error.dotnet buildexits non-zero if any PII leak is detected. The PR is blocked without any/warnaserrorflag or additional configuration.- SARIF output is emitted via
/p:ErrorLog=results/globalguard.sarif,version=2so violations appear as line-level annotations directly on the pull request diff in GitHub. upload-sarifrunsif: always()— annotations appear even when the build step fails, so the developer sees exactly which line to fix.- A separate
packjob runs only onmain, producing a versioned.nupkgartifact automatically.
What a blocked PR looks like
❌ Build (emit SARIF)
src/UserService.cs(42,38): error GG_SEC_001:
Argument 'bvn' may contain PII.
Wrap with GlobalGuard.Redact() or suppress with [SuppressMessage].
The GitHub Security tab also shows all violations grouped by rule, file, and commit, giving your security team a full audit trail.
Architecture
GlobalGuard.Analyzers.dll (in-process: IDE, MSBuild, CI)
┌─ Infrastructure
│ PiiNameRegistry ← AnalyzerConfigReader ← .globalguard.json (AdditionalFiles)
│ ITelemetrySink (no-op stub, extensible)
│ IAiFixer (no-op stub — no HTTP calls in-process)
├─ Security
│ GG_SEC_001 PiiSentinelAnalyzer + PiiSentinelCodeFixProvider
└─ Performance
GG_PERF_001 NairaSaverAnalyzer + NairaSaverCodeFixProvider
GG_PERF_002 QueryableForEachAnalyzer (stub, reserved)
── Roslyn compiler boundary ──────────────────────────────────────────────────
.editorconfig → severity overrides (automatic)
#pragma warning → inline suppression (automatic)
/p:ErrorLog=... → SARIF emission (automatic)
FUTURE — dotnet-globalguard CLI (out-of-process)
Implements IAiFixer via LLM API
References GlobalGuard.Analyzers as a plain library
Never loaded in-process by Roslyn
Why no HTTP calls in the analyzer? Roslyn analyzers run inside the IDE process (Visual Studio, Rider, VS Code via OmniSharp). An HTTP call from inside that process causes the IDE to hang while the request is in flight, blocks the Roslyn worker thread, and exposes API keys via dotnet userSecrets or environment variables that IDE host processes can read. The AI suggestion path is architecturally isolated behind IAiFixer for a future out-of-process CLI.
Test coverage
54 tests across six test classes, all green:
| Test class | Tests | What is covered |
|---|---|---|
PiiSentinelAnalyzerTests |
12 | Non-logging calls, direct variables, interpolated strings, [PersonalData]/[PiiData] attributes, custom config names, pragma suppression, region opt-in |
PiiSentinelCodeFixTests |
4 | Wrap with Redact(), wrap inside interpolation, no duplicate using, [SuppressMessage] on method |
NairaSaverAnalyzerTests |
7 | .Result, .Wait(), .GetAwaiter().GetResult(), ValueTask, non-Task .Result (no false positive), already-await-ed (no false positive) |
NairaSaverCodeFixTests |
5 | .Result→await, .Wait()→await, GetAwaiter().GetResult()→await, void→Task, string→Task<string> |
PiiNameRegistryTests |
5 | Default includes Nigeria, India opt-in, additional names, case-insensitive matching |
AnalyzerConfigReaderTests |
5 | Valid config, no config file, malformed JSON, unrelated AdditionalFiles |
Package structure
GlobalGuard.Analyzers.1.0.0-preview.1.nupkg
├── analyzers/
│ └── dotnet/
│ └── cs/
│ └── GlobalGuard.Analyzers.dll ← loaded by Roslyn, not added to references
├── build/
│ ├── GlobalGuard.Analyzers.props ← auto-discovers .globalguard.json
│ └── GlobalGuard.Analyzers.targets
└── GlobalGuard.Analyzers.nuspec
IncludeBuildOutput=false ensures there is no lib/ folder. The DLL is never added as a compile-time reference to consumer projects, preventing type-duplicate errors when multiple projects in a solution install the package.
SuppressDependenciesWhenPacking=true means the .nuspec has an empty <dependencies> section. Roslyn, Workspaces, and System.Text.Json are all PrivateAssets=all — they do not appear in the consumer's dependency graph.
Roadmap
| Version | Feature |
|---|---|
| v1.0 (current) | GG_SEC_001 PII Sentinel · GG_PERF_001 Naira Saver · Five region packs · GitHub Actions CI · SARIF |
| v1.1 | GG_PERF_002 Queryable ForEach — detect IQueryable.ForEach causing full table scans |
| v1.2 | dotnet-globalguard CLI with AI-suggested fix messages via configurable LLM provider |
| v2.0 | VS Code extension with inline redline + one-click fix outside the full IDE |
Contributing
- Fork and clone the repository
dotnet restore && dotnet build— should compile with zero errorsdotnet test— all 54 tests must pass before submitting a PR- Add a new region pack? Add names to
PiiNameRegistry.csand test cases toPiiNameRegistryTests.cs - New diagnostic rule? Follow the
NairaSaverAnalyzer+NairaSaverCodeFixProviderpair as a template
License
MIT © 2026 GlobalGuard Contributors
Learn more about Target Frameworks and .NET Standard.
This package has 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 |
|---|---|---|
| 1.0.0-preview.1 | 78 | 4/26/2026 |