GlobalGuard.Analyzers 1.0.0-preview.1

This is a prerelease version of GlobalGuard.Analyzers.
dotnet add package GlobalGuard.Analyzers --version 1.0.0-preview.1
                    
NuGet\Install-Package GlobalGuard.Analyzers -Version 1.0.0-preview.1
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="GlobalGuard.Analyzers" Version="1.0.0-preview.1">
  <PrivateAssets>all</PrivateAssets>
  <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="GlobalGuard.Analyzers" Version="1.0.0-preview.1" />
                    
Directory.Packages.props
<PackageReference Include="GlobalGuard.Analyzers">
  <PrivateAssets>all</PrivateAssets>
  <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add GlobalGuard.Analyzers --version 1.0.0-preview.1
                    
#r "nuget: GlobalGuard.Analyzers, 1.0.0-preview.1"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package GlobalGuard.Analyzers@1.0.0-preview.1
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=GlobalGuard.Analyzers&version=1.0.0-preview.1&prerelease
                    
Install as a Cake Addin
#tool nuget:?package=GlobalGuard.Analyzers&version=1.0.0-preview.1&prerelease
                    
Install as a Cake Tool

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 as Log(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 walk
  • Log(Sanitize(bvn)) — user-written sanitising call; outer invocation stops the walk
  • SaveToDatabase(bvn) — not a logging call

Two fix actions per diagnostic

  1. Wrap with GlobalGuard.Redact() — inserts GlobalGuard.Redact(bvn) at the call site. You own the Redact implementation: hash, mask, tokenise — your choice.
  2. 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 → Task
  • T → 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_001 is DiagnosticSeverity.Error. dotnet build exits non-zero if any PII leak is detected. The PR is blocked without any /warnaserror flag or additional configuration.
  • SARIF output is emitted via /p:ErrorLog=results/globalguard.sarif,version=2 so violations appear as line-level annotations directly on the pull request diff in GitHub.
  • upload-sarif runs if: always() — annotations appear even when the build step fails, so the developer sees exactly which line to fix.
  • A separate pack job runs only on main, producing a versioned .nupkg artifact 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

  1. Fork and clone the repository
  2. dotnet restore && dotnet build — should compile with zero errors
  3. dotnet test — all 54 tests must pass before submitting a PR
  4. Add a new region pack? Add names to PiiNameRegistry.cs and test cases to PiiNameRegistryTests.cs
  5. New diagnostic rule? Follow the NairaSaverAnalyzer + NairaSaverCodeFixProvider pair as a template

License

MIT © 2026 GlobalGuard Contributors

There are no supported framework assets in this package.

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