Skip to main content

Free 30-min security demo  — We'll scan your real code and show live findings, no commitment Book Now

Offensive360
Application Security

BTreatWarningsAsErrors & TargetRules in .NET

BTreatWarningsAsErrors vs TargetRules in .NET: enforce Roslyn security rules as build errors, scope them per project and fix dotnet ef failures in CI/CD.

Offensive360 Security Research Team — min read
BTreatWarningsAsErrors TreatWarningsAsErrors TargetRules Roslyn analyzers .NET security SAST static code analysis dotnet build MSBuild CI/CD security C# security RunAnalyzersDuringBuild dotnet ef

When setting up a security-gated .NET build pipeline, two MSBuild properties cause confusion: BTreatWarningsAsErrors and the related concept of TargetRules for scoping which Roslyn analyzer warnings become build errors. Getting these right means your CI pipeline blocks on real security violations without generating noise from auto-generated or design-time code.

This guide covers:

  • The difference between TreatWarningsAsErrors, BTreatWarningsAsErrors, and WarningsAsErrors
  • How to scope TargetRules to specific security CA rules
  • The correct MSBuild configuration for enforcing Roslyn security analysis in CI/CD
  • Why dotnet ef migrations fight with TreatWarningsAsErrors=true — and the safe fix
  • What Roslyn enforcement misses (and when you need full taint-analysis SAST)

TreatWarningsAsErrors vs BTreatWarningsAsErrors

MSBuild has several overlapping properties for promoting warnings to errors. Their precedence and scope differ in ways that matter for security pipelines.

TreatWarningsAsErrors

The standard property. When set to true, every compiler warning and analyzer warning becomes a build error:

<PropertyGroup>
  <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>

Scope: Applies to the entire build — both C# compiler warnings (CS-prefixed) and Roslyn analyzer warnings (CA-prefixed, IDE-prefixed, custom rules).

Problem: Too broad for most codebases. Setting this globally turns style warnings, IDE suggestions, and documentation warnings into hard build failures alongside genuine security violations. The result is teams disabling the setting rather than fixing the noise.

BTreatWarningsAsErrors (MSBuild-level)

BTreatWarningsAsErrors is the MSBuild-level equivalent of TreatWarningsAsErrors. It applies at the MSBuild project build level (not the Roslyn compiler level), meaning it converts MSBuild task warnings into errors — not necessarily C# compiler or Roslyn analyzer warnings.

It is less commonly needed in modern .NET SDK projects where the distinction between MSBuild warnings and Roslyn warnings is mostly academic. However, it appears in some project configurations and CI pipeline outputs, which is why it shows up in build logs.

<!-- MSBuild-level: convert MSBuild task warnings to errors -->
<PropertyGroup>
  <BTreatWarningsAsErrors>true</BTreatWarningsAsErrors>
</PropertyGroup>

For security analysis enforcement in .NET projects, TreatWarningsAsErrors combined with WarningsAsErrors is almost always what you want — not BTreatWarningsAsErrors alone.

WarningsAsErrors (Targeted)

The most useful property for security builds. Instead of promoting all warnings, you specify exactly which rule IDs should be treated as errors:

<PropertyGroup>
  <!-- Only these specific Roslyn security rules become build errors -->
  <WarningsAsErrors>CA2100;CA3001;CA3002;CA3003;CA3006;CA3007;CA3012;CA5350;CA5351;CA5369;CA5394</WarningsAsErrors>
</PropertyGroup>

This is the recommended approach for security-gated pipelines. You get hard build failures on genuine security violations without noise from style rules, nullable reference warnings, or documentation requirements.


TargetRules: Scoping Which Analyzer Rules Run

TargetRules is not a single MSBuild property — it’s a concept encompassing several mechanisms that control which Roslyn analyzer rules are active for a given build target.

Using AnalysisLevel to Scope Rules

AnalysisLevel controls which built-in .NET SDK analyzer rules are active:

<PropertyGroup>
  <!-- Run all recommended security + quality rules for the latest SDK -->
  <AnalysisLevel>latest-recommended</AnalysisLevel>
  
  <!-- Or pin to a specific SDK version's rule set -->
  <AnalysisLevel>8.0</AnalysisLevel>
  
  <!-- Or enable all available rules (includes experimental) -->
  <AnalysisLevel>latest-all</AnalysisLevel>
</PropertyGroup>

latest-recommended is the right choice for most security teams — it enables the current SDK’s recommended rule set without pulling in experimental rules that generate noise.

Using AnalysisMode to Tune Rule Scope

AnalysisMode controls the default severity of rules within the active analysis level:

<PropertyGroup>
  <!-- Default: recommended rules only -->
  <AnalysisMode>Default</AnalysisMode>
  
  <!-- Enable all rules as suggestions (error-level controlled by WarningsAsErrors) -->
  <AnalysisMode>All</AnalysisMode>
  
  <!-- Disable all rules, then selectively enable only what you explicitly configure -->
  <AnalysisMode>None</AnalysisMode>
</PropertyGroup>

For a security-focused build, combine AnalysisMode=All with WarningsAsErrors targeting specific security CA rules — this ensures security rules are active and fail builds, while non-security rules remain as warnings.

EditorConfig for Per-Rule Severity

The .editorconfig file provides the most granular control over individual rule severities:

# .editorconfig — per-rule severity configuration
[*.cs]

# Enforce SQL injection detection as a build error
dotnet_diagnostic.CA2100.severity = error

# Enforce XSS detection
dotnet_diagnostic.CA3001.severity = error

# Enforce process injection detection
dotnet_diagnostic.CA3006.severity = error

# Enforce weak cryptography detection
dotnet_diagnostic.CA5350.severity = error
dotnet_diagnostic.CA5351.severity = error

# Keep code style as a warning (not a build error)
dotnet_diagnostic.IDE0001.severity = warning
dotnet_diagnostic.IDE0002.severity = warning

# Suppress nullable warnings in generated files
[*Generated*.cs]
dotnet_diagnostic.CS8600.severity = none
dotnet_diagnostic.CS8602.severity = none

This approach is ideal when different rules need different severities across different file patterns — for example, enforcing all security rules as errors everywhere, while suppressing style warnings in auto-generated migration files.


The Complete Security Build Configuration

Here is a .csproj configuration that enforces Roslyn security rules as build errors while minimizing noise from style and quality rules:

<Project Sdk="Microsoft.NET.Sdk.Web">

  <PropertyGroup>
    <TargetFramework>net8.0</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
    
    <!-- Enable analyzers during every build -->
    <RunAnalyzersDuringBuild>true</RunAnalyzersDuringBuild>
    
    <!-- Use latest recommended rules -->
    <AnalysisLevel>latest-recommended</AnalysisLevel>
    
    <!-- Promote specific security rules to build errors -->
    <!-- SQL Injection, XSS, LDAP, Path Traversal, OS Command, Open Redirect -->
    <!-- Regex ReDoS, Weak/Broken Crypto, Insecure Deserialization, Insecure Random -->
    <WarningsAsErrors>
      CA2100;
      CA3001;CA3002;CA3003;CA3006;CA3007;CA3012;
      CA5350;CA5351;CA5358;CA5360;
      CA5369;CA5394
    </WarningsAsErrors>
  </PropertyGroup>

  <!-- Disable analyzers only for EF design-time builds -->
  <!-- This prevents dotnet ef migrations from failing on security rule violations -->
  <PropertyGroup Condition="'$(DesignTimeBuild)' == 'true'">
    <RunAnalyzersDuringBuild>false</RunAnalyzersDuringBuild>
  </PropertyGroup>

</Project>

This configuration:

  • Runs analyzers on every regular dotnet build
  • Treats all major Roslyn security rules as build errors
  • Automatically skips analyzer execution when dotnet ef or Visual Studio triggers a design-time build

Why TreatWarningsAsErrors=true Breaks dotnet ef

When your project has TreatWarningsAsErrors=true or specific security rules in WarningsAsErrors, running dotnet ef migrations add or dotnet ef database update often fails with:

Build FAILED.
Error CA2100: Review SQL queries for security vulnerabilities

Why this happens: The dotnet ef CLI builds your project internally (to discover the DbContext and migration history) by running a full MSBuild pipeline. This internal build triggers all Roslyn analyzers — including the security rules you’ve configured as errors.

EF migration files are auto-generated and sometimes include raw SQL, System.Random seed logic, or patterns that trigger CA rules. The EF tool doesn’t need clean security analysis to find your DbContext — but it runs analyzers anyway because it triggers a standard build.

The Fix: Pass RunAnalyzersDuringBuild=false via --

The -- separator in dotnet ef commands forwards arguments directly to MSBuild:

# Disable analyzers only for the EF tool's internal build
dotnet ef migrations add MyMigration -- /p:RunAnalyzersDuringBuild=false
dotnet ef database update -- /p:RunAnalyzersDuringBuild=false

# With explicit project paths
dotnet ef database update \
  --project src/DataAccess \
  --startup-project src/Api \
  -- /p:RunAnalyzersDuringBuild=false

This disables Roslyn analyzer execution only for that specific EF tool build. Your main application build — and the security rules you’ve configured — are entirely unaffected.

Do not solve this by setting RunAnalyzersDuringBuild=false globally in your .csproj. That silently disables all security analysis on every build, including your CI security gate.

Conditional .csproj Approach

Alternatively, configure the suppression to apply only when DesignTimeBuild is active:

<!-- Disable analyzers only during EF/IDE design-time builds -->
<PropertyGroup Condition="'$(DesignTimeBuild)' == 'true'">
  <RunAnalyzersDuringBuild>false</RunAnalyzersDuringBuild>
</PropertyGroup>

This is cleaner for teams that want the config in source control rather than having to remember the -- flag on every EF command.


CI/CD Pipeline: Security Gate + EF Migration Pattern

The correct structure separates the security-gated application build from the EF migration step:

GitHub Actions

name: .NET Security Build + Migrate

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

jobs:
  security-build:
    name: Build with Security Analysis
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Setup .NET
        uses: actions/setup-dotnet@v4
        with:
          dotnet-version: '8.x'

      - name: Restore
        run: dotnet restore

      # ✅ Security gate — TreatWarningsAsErrors scoped to CA rules
      - name: Build + Roslyn Security Analysis
        run: |
          dotnet build --configuration Release --no-restore \
            /p:RunAnalyzersDuringBuild=true \
            /p:AnalysisLevel=latest-recommended \
            /warnaserror:CA2100,CA3001,CA3002,CA3003,CA3006,CA3007,CA5350,CA5351,CA5369,CA5394

  migrate:
    name: Apply EF Migrations
    runs-on: ubuntu-latest
    needs: security-build          # Run only after clean security build
    if: github.ref == 'refs/heads/main'
    steps:
      - uses: actions/checkout@v4

      - name: Setup .NET
        uses: actions/setup-dotnet@v4
        with:
          dotnet-version: '8.x'

      - name: Install EF CLI
        run: dotnet tool install --global dotnet-ef

      # ✅ Analyzers suppressed only for the EF migration build step
      - name: Apply Migrations
        run: |
          dotnet ef database update \
            --project src/DataAccess \
            --startup-project src/Api \
            -- /p:RunAnalyzersDuringBuild=false
        env:
          ConnectionStrings__Default: ${{ secrets.PROD_DB_CONNECTION }}

Azure DevOps

trigger:
  - main

stages:
  - stage: SecurityBuild
    jobs:
      - job: BuildAndAnalyze
        pool:
          vmImage: 'ubuntu-latest'
        steps:
          - task: DotNetCoreCLI@2
            displayName: Restore
            inputs:
              command: restore

          # ✅ Security-gated build — BTreatWarningsAsErrors via /warnaserror
          - task: DotNetCoreCLI@2
            displayName: Build with Security Analysis
            inputs:
              command: build
              arguments: >
                --configuration Release
                --no-restore
                /p:RunAnalyzersDuringBuild=true
                /p:AnalysisLevel=latest-recommended
                /warnaserror:CA2100,CA3001,CA3006,CA5350,CA5351,CA5394

  - stage: Migrate
    dependsOn: SecurityBuild
    condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main'))
    jobs:
      - job: ApplyMigrations
        pool:
          vmImage: 'ubuntu-latest'
        steps:
          - script: dotnet tool install --global dotnet-ef
            displayName: Install EF Tools

          # ✅ Analyzers disabled only for EF migration
          - script: |
              dotnet ef database update \
                --project src/DataAccess \
                -- /p:RunAnalyzersDuringBuild=false
            displayName: Apply EF Migrations
            env:
              ConnectionStrings__Default: $(ProdDbConnection)

Key Roslyn Security Rules to Enforce

When configuring WarningsAsErrors or BTreatWarningsAsErrors with TargetRules, these are the Roslyn security rules worth promoting to build errors:

Rule IDVulnerabilityCWE
CA2100SQL injection in ADO.NET commandsCWE-89
CA3001XSS via HttpResponse.WriteCWE-79
CA3002LDAP injectionCWE-90
CA3003File path injection (path traversal)CWE-22
CA3006Process command injectionCWE-78
CA3007Open redirectCWE-601
CA3012Regex injection / ReDoSCWE-730
CA5350Weak cryptography (3DES)CWE-326
CA5351Broken cryptography (MD5, DES)CWE-327
CA5360Insecure BinaryFormatter deserializationCWE-502
CA5369XML deserialization without DTD protectionCWE-611
CA5394Insecure randomness (System.Random)CWE-338

These rules are all available in the Microsoft.CodeAnalysis.NetAnalyzers package, which is included by default in .NET 5+ SDK projects.


What Roslyn Enforcement Misses

Configuring TreatWarningsAsErrors, BTreatWarningsAsErrors, and TargetRules for Roslyn security analysis is a valuable first layer. But Roslyn analyzers have hard limitations:

No interprocedural taint analysis. Roslyn CA rules detect vulnerabilities within a single method. They cannot trace SQL injection across three function calls, or XSS through a data transformation pipeline. CA2100 only fires when the SqlCommand is constructed with string concatenation in the same method as the user input. If the query building is delegated to a helper method, the rule doesn’t fire.

No second-order injection detection. If user input is stored in a database in one request and then retrieved and used unsafely in a later query, Roslyn analyzers don’t detect it. The database is an opaque boundary to Roslyn’s analysis scope.

No stored XSS detection. Roslyn can detect Response.Write(Request.QueryString["x"]) (reflected XSS in the same method), but not the pattern where user input is stored, retrieved, and then rendered without encoding across two separate controller actions.

No DAST. Roslyn analyzes source code only. Runtime vulnerabilities — authentication flaws, insecure session management, business logic errors — require Dynamic Application Security Testing against the running application.

For these cases, a SAST platform with full interprocedural taint analysis — like Offensive360 — complements Roslyn analyzers in the same CI/CD pipeline. Roslyn runs fast in the IDE and catches obvious patterns during compilation. Offensive360 runs a deeper analysis step with full data-flow tracking across your entire codebase, including second-order SQL injection and cross-file taint flows that Roslyn cannot see.


Common Configuration Mistakes

Setting TreatWarningsAsErrors=true Globally Without WarningsAsErrors Scoping

This promotes every compiler warning to a build error — including CS8600 nullable reference warnings, IDE0xxx style suggestions, and documentation warnings. Teams quickly disable it when the noise is overwhelming, eliminating the security value.

Fix: Use WarningsAsErrors to target only the security CA rules you want as errors.

Setting RunAnalyzersDuringBuild=false Globally to Fix dotnet ef

This silently disables all Roslyn security analysis on every build. The CI log shows green with no security findings — because analyzers aren’t running.

Fix: Pass -- /p:RunAnalyzersDuringBuild=false only to dotnet ef commands. Keep RunAnalyzersDuringBuild=true in your main build step.

Suppressing Specific Warnings in Migration Files Without Reviewing Them

Auto-generated migration files sometimes trigger CA2100 because they contain inline SQL for complex schema operations. The correct response is to suppress at the file level (via #pragma warning disable CA2100) or to move raw SQL to a safe parameterized helper — not to disable CA2100 globally.


Frequently Asked Questions

What is the difference between TreatWarningsAsErrors and BTreatWarningsAsErrors?

TreatWarningsAsErrors is the C#/Roslyn compiler property — it converts C# compiler warnings (CS-prefixed) and Roslyn analyzer warnings (CA-prefixed) into build errors. BTreatWarningsAsErrors is an MSBuild-level property that converts MSBuild task-level warnings into errors, which is a different layer of the build pipeline. For enforcing Roslyn security rules in .NET projects, TreatWarningsAsErrors (or the more targeted WarningsAsErrors) is the relevant property.

What does TargetRules mean in the context of .NET builds?

TargetRules isn’t a single MSBuild property — it refers to the combination of mechanisms that scope which analyzer rules are active and at what severity: AnalysisLevel, AnalysisMode, WarningsAsErrors, and .editorconfig severity directives. Together these form your “target rule set” for a given build configuration.

Can I use WarningsAsErrors per-project in a solution?

Yes. Each .csproj file has its own WarningsAsErrors configuration. In a multi-project solution, you can enforce stricter rules on security-critical projects (API layer, data access layer) while keeping more relaxed settings on infrastructure projects.

Why does dotnet ef fail when I set TreatWarningsAsErrors=true?

The EF CLI builds your project internally to find the DbContext. That build triggers all Roslyn analyzers. If any warning is treated as an error, the EF build fails. Solution: pass -- /p:RunAnalyzersDuringBuild=false to dotnet ef commands, or add <Condition="'$(DesignTimeBuild)' == 'true'"> in your .csproj to disable analyzers during design-time builds only.

Does BTreatWarningsAsErrors=true disable dotnet ef too?

If BTreatWarningsAsErrors is causing MSBuild task-level warnings to fail the EF internal build, the same fix applies: pass /p:BTreatWarningsAsErrors=false after the -- separator. However, this is less common than the TreatWarningsAsErrors/WarningsAsErrors issue.


Summary

PropertyScopeUse For
TreatWarningsAsErrors=trueAll compiler + analyzer warningsStrict enforcement — broad noise
WarningsAsErrors=CA2100;CA3001...Specific rule IDsTargeted security enforcement
BTreatWarningsAsErrors=trueMSBuild task warningsMSBuild-level enforcement
RunAnalyzersDuringBuild=trueWhether analyzers run at allEnable for CI security gates
AnalysisLevel=latest-recommendedWhich rules are availableLatest SDK recommended rules
AnalysisMode=AllDefault severity of rulesEnable all rules, scope via WarningsAsErrors

Recommended production configuration:

  1. Set WarningsAsErrors to the specific Roslyn security CA rules you want enforced
  2. Set RunAnalyzersDuringBuild=true in your main build
  3. Disable RunAnalyzersDuringBuild only for dotnet ef commands via -- /p:RunAnalyzersDuringBuild=false
  4. Use .editorconfig for granular per-file and per-rule severity overrides

For deeper coverage beyond what Roslyn analyzers provide, Offensive360 SAST integrates alongside your Roslyn configuration as an independent analysis step — detecting the complex injection chains and data-flow vulnerabilities that CA rules cannot see. Run a one-time scan for $500 or book a demo.

Offensive360 Security Research Team

Application Security Research

Updated June 28, 2026

Find vulnerabilities before attackers do

Run Offensive360 SAST and DAST against your applications and get a full vulnerability report in minutes.