MSBuild Guard is a security analysis tool for .NET projects that detects risky or untrusted MSBuild configurations — including imported .targets, .props, NuGet package assets, wildcard imports, and more — before they can execute arbitrary code on your machine.
It integrates into your workflow through two complementary surfaces:
| Module | Description |
|---|---|
| Scanning Library | .NET Standard 2.1 library for MSBuild analysis and policy evaluation |
| Visual Studio Extension (VSIX) | Inline security review panel inside Visual Studio 2026+ |
MSBuild project files (.csproj, .targets, .props, .sln, etc.) are XML documents that can execute arbitrary shell commands, download files, and run code during a simple dotnet restore or solution load. Supply-chain attacks increasingly abuse this attack surface.
MSBuild Guard scans these files before execution and:
- Identifies imported files from untrusted or unexpected sources
- Flags NuGet packages that introduce
.targetsor.propsfiles - Highlights wildcard imports that could be hijacked
- Compares the current state against a trusted baseline
- Evaluates findings against a configurable policy
- Reports risk levels (None / Low / Medium / High / Critical)
MSBuildGuard.sln
├── MSBuildGuard.Core # Shared scanning engine and models
├── MSBuildGuard.Core.Tests # Unit tests for the scanning engine
└── MSBuildGuard.VisualStudio # Visual Studio 2026 extension (VSIX)
The shared scanning engine used by all other modules. It provides:
- MsBuildScanner — recursively parses solution and project files, resolves imports and NuGet assets
- NuGetLockFileParser / NuGetConfigurationParser / PackageAssetsFileParser — parse NuGet lock files,
NuGet.Config, andproject.assets.json - PackageProvenanceResolver — determines the origin and trust status of each package
- PolicyService / PolicyDecisionEvaluator — evaluates findings against a YAML policy file
- BaselineService / BaselineComparer — compares the current scan to a previously saved trusted baseline
- TrustStoreService — manages per-project trusted versions
Target framework: .NET 10 (Windows)
A Visual Studio 2026 extension (VSIX) that provides integrated security review, policy editing, and build enforcement workflows.
Features:
- Automatic scan on solution open, NuGet restore, and package-change triggers
- Status bar shield icon (green / orange / red) reflecting the current risk level
- Project Security Review and Solution Security Review tool windows
- Scope-correct solution review filtering (All / per project) with target/risk/action summary updates
- Bottom Reasoning panel for selected findings in review windows
- Per-finding double-click navigation to source file and line
- Policy Editor with machine, solution, and project policy scope selection
- Automatic rescan and UI refresh after policy changes
- Build-time enforcement with user prompt (shows step/rule/risk, supports Continue or Cancel build)
- Security menu commands for solution review, project review, policy editing, and baseline creation
- Background monitoring with non-blocking UI feedback
Target: Visual Studio 2026 (Community, Professional, Enterprise) — amd64 Target framework: .NET Framework 4.7.2
- Visual Studio 2026 with the Visual Studio extension development workload
- .NET 10 SDK
dotnet build MSBuildGuard-public.slnx -c Releasedotnet build MSBuildGuard.VisualStudio\MSBuildGuard.VisualStudio.csproj -c ReleaseThe .vsix file is emitted to MSBuildGuard.VisualStudio\bin\Release\.
dotnet test MSBuildGuard-public.slnx -c ReleaseThe repository includes a GitHub Actions workflow (.github/workflows/build-vsix.yml) that:
- Restores NuGet packages
- Builds the solution in Release configuration
- Runs all tests
- Uploads the
.vsixas a build artifact
See .github/workflows/build-vsix.yml for details.
Contributions are welcome for non-commercial use cases. Please follow these guidelines:
- Fork the repository and create a feature branch from
master. - Follow the existing code style (C# 14, block-scoped namespaces, XML doc comments on all public/internal members).
- Add or update unit tests for every changed or new class. Tests use NUnit, Moq, and Shouldly.
- Ensure
dotnet build MSBuildGuard-public.slnx -c Releasesucceeds with no warnings before opening a pull request. - Open a pull request with a clear description of the change and its motivation.
By contributing you agree that your contribution is licensed under the same PolyForm Noncommercial License 1.0.0 as the rest of the project.
This project is licensed under the PolyForm Noncommercial License 1.0.0. Commercial use requires a separate written agreement with the author.
See LICENSE for the full license text.
Hefaistos68 — https://github.com/Hefaistos68
MSBuild Guard currently detects the following risky patterns in MSBuild files:
| ID | Rule | Default Severity | Description |
|---|---|---|---|
| MBG000 | No issues detected | None | Clean scan result |
| MBG001 | UsingTask contains inline Code |
Medium | Task executes inline C# code directly |
| MBG002 | TaskFactory using RoslynCodeTaskFactory or CodeTaskFactory |
Medium | Task factory can dynamically compile and execute code |
| MBG003 | InitialTargets present or changed from baseline |
High | Targets run before normal build pipeline; high code execution risk |
| MBG004 | Early lifecycle hooks (BeforeBuild, PrepareForBuild, BeforeTargets on early targets) |
Medium | Code executes in early phases before user awareness |
| MBG005 | Exec task invokes shell, PowerShell, script host, or command interpreter |
High | Direct shell/process invocation for arbitrary commands |
| MBG006 | Inline code references process creation APIs | High | Code can spawn child processes for command execution |
| MBG007 | Inline code references reflection, dynamic loading, or native interop | High | Code can load external assemblies or invoke native code |
| MBG008 | Inline code contains large base64/byte arrays/encoded blobs | High | Suspicious encoded payloads that may hide malicious logic |
| MBG009 | Import path resolves to user-writable, temporary, remote, or traversal path | High | Import target may be hijackable or from untrusted source |
| MBG010 | New .props or .targets file appears compared with baseline |
Medium | New build configuration not previously approved |
| MBG011 | Project/build file has Mark-of-the-Web | Medium | File downloaded from internet and may have origin restrictions |
| MBG012 | Parse errors or unsupported constructs prevent full analysis | Medium | Incomplete scan; hidden logic may not be detected |
Each finding is assigned a base score indicating its severity level:
| Level | Score |
|---|---|
| Info | 0 |
| Low | 5 |
| Medium | 20 |
| High | 50 |
| Critical | 100 |
Base scores are adjusted by:
- +30 — File has Mark-of-the-Web (downloaded from internet)
- +20 — Finding appears in a file imported by multiple projects (wider blast radius)
- +25 — Finding is new versus the trusted baseline (unapproved change)
- −30 — Finding fingerprint is explicitly approved in the trust store
- −20 — Repository remote and commit match a trusted baseline state
Policy evaluator uses final score to determine action:
| Score Range | Recommended Action |
|---|---|
| 0–19 | Allow — permit build to continue |
| 20–49 | Warn — log warning, continue build |
| 50–99 | Require Approval — block until explicitly approved |
| 100+ | Block — prevent build/load |
MSBuild Guard uses a layered governance model to keep decisions deterministic and auditable:
Policy files control how findings are interpreted and enforced. They define:
- Minimum action by severity — floor action for each severity level (cannot be weakened by lower-priority policies)
- Per-rule overrides — custom action for specific rules (e.g., treat MBG005 as
Blockinstead ofWarn) - Mode — behavior in automation (
warnvs.blockon violations) - Baseline requirement — whether a trusted baseline must exist to proceed
- Path filters —
include/excludepatterns to scope analysis
Example policy:
{
"version": 1,
"mode": "block",
"baselineRequired": true,
"minimumActionBySeverity": {
"Critical": "Block",
"High": "Block",
"Medium": "RequireApproval",
"Low": "Warn",
"Info": "Allow"
},
"rules": {
"MBG005": { "action": "Block" },
"MBG009": { "action": "RequireApproval" }
},
"include": ["**/*.csproj", "**/*.targets", "**/*.props"],
"exclude": ["bin/**", "obj/**", ".git/**"]
}Baselines record an approved snapshot of normalized findings and file states for drift detection. Use baselines to:
- Detect newly introduced risky behavior (score modifier +25)
- Separate known-approved legacy risk from new risk
- Gate CI/CD and pre-commit hooks on unapproved changes
Typical baseline workflow:
- Create baseline after security review:
MSBuildGuardScanner.CreateBaseline(solution, output) - Store baseline under source control policy (e.g.,
.msbuildguard/baseline.json) - Compare future scans:
MSBuildGuardScanner.CompareAgainstBaseline(solution, baseline) - Update baseline only after explicit security review and approval
Trust decisions allow approved exceptions without disabling rules globally. Common trust scopes:
- Finding — one fingerprinted finding
- File — specific file content hash
- Repository — repository state (remote + commit)
Trust decisions must include explicit reason text and are auditable:
{
"trustedFindings": [
{
"fingerprint": "...",
"reason": "Reviewed by security team on 2024-01-15",
"expiresAt": "2025-01-15"
}
]
}Effective policy merge order (highest priority first):
- Machine policy —
%PROGRAMDATA%\MSBuildGuard\policy.json - Solution policy —
.msbuildguard\policy.jsonin repository root - User settings — Visual Studio options or user config
- Built-in defaults — safe, permissive baseline
Lower-priority layers cannot weaken stricter machine policy requirements.
- Initialize policy — set organization minimum actions and rule overrides
- Create baseline — capture current approved state
- Configure trust store — pre-approve known-safe findings
- Run gated scan — evaluate against policy + baseline + trust
- Act on results — block/warn/allow based on final score
- Update trust — record justified exceptions with audit trail
- Baseline refresh — update baseline only after explicit review
Module-specific documentation is split into dedicated files: