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

Roslyn Security Analyzer Rules for .NET: Complete CA Rule Reference

Complete Roslyn security analyzer rule reference for .NET: CA2100, CA3001–CA3012, CA5350–CA5403 explained with vulnerable code examples, fixes, and CI/CD enforcement YAML.

Offensive360 Security Research Team — min read
Roslyn analyzers .NET security SAST CA2100 RunAnalyzersDuringBuild dotnet security rules C# SAST static code analysis .NET static analysis Roslyn security rules

Microsoft’s Roslyn analyzer framework ships a comprehensive set of security rules as part of the Microsoft.CodeAnalysis.NetAnalyzers package — the default analyzer bundle included with every .NET 5+ project. These rules run at compile time, giving C# and VB.NET developers immediate feedback about security vulnerabilities without any additional tooling.

This reference covers every security-relevant Roslyn analyzer rule — what it detects, what the vulnerable code looks like, how to fix it, and how to enforce it as a build error in your CI/CD pipeline.


Enabling Security Rules in Your .NET Project

By default, most security rules are warnings. To enforce them as build errors — blocking your CI pipeline when violations are introduced — configure AnalysisLevel and TreatWarningsAsErrors in your .csproj:

<PropertyGroup>
  <!-- Enable all recommended rules at the latest level -->
  <AnalysisLevel>latest-recommended</AnalysisLevel>
  <!-- Treat all analyzer warnings as errors -->
  <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>

Or target specific rules only:

<PropertyGroup>
  <!-- Only treat specific security rule violations as errors -->
  <WarningsAsErrors>CA2100;CA3001;CA3002;CA3003;CA3006;CA3007;CA5350;CA5351;CA5369;CA5394</WarningsAsErrors>
</PropertyGroup>

To ensure analyzers run during dotnet build (not just in the IDE):

<PropertyGroup>
  <RunAnalyzersDuringBuild>true</RunAnalyzersDuringBuild>
</PropertyGroup>

CA2100 — SQL Injection (ADO.NET)

Category: Security
CWE: CWE-89 (SQL Injection)
Default severity: Warning

CA2100 fires when a SqlCommand, OleDbCommand, OdbcCommand, or OracleCommand is initialized with a CommandText value that is constructed via string operations rather than a parameterized query.

Vulnerable Code

// CA2100 violation — CommandText built with string concatenation
string username = Request.QueryString["username"];
string sql = "SELECT * FROM Users WHERE Username = '" + username + "'";

using var conn = new SqlConnection(_connectionString);
using var cmd = new SqlCommand(sql, conn);    // ← CA2100 fires here
conn.Open();
var reader = cmd.ExecuteReader();

Fix

// SECURE — parameterized query
string username = Request.QueryString["username"];
const string sql = "SELECT * FROM Users WHERE Username = @username";

using var conn = new SqlConnection(_connectionString);
using var cmd = new SqlCommand(sql, conn);
cmd.Parameters.AddWithValue("@username", username);   // ← safe
conn.Open();
var reader = cmd.ExecuteReader();

What CA2100 Misses

CA2100 flags obvious single-method patterns. It does not detect:

  • Second-order SQL injection (input stored in a database, then used unsafely in a later query)
  • Injection chains where user input passes through several methods before reaching SqlCommand
  • Entity Framework’s FromSqlRaw() misuse (a separate concern)

For these patterns, you need a SAST tool with interprocedural taint analysis such as Offensive360.


CA3001 — XSS via HttpResponse.Write

Category: Security
CWE: CWE-79 (XSS)
Default severity: Warning

CA3001 detects when user-controlled data flows into HttpResponse.Write() or Response.Write() without HTML encoding — a classic reflected XSS pattern in legacy ASP.NET Web Forms applications.

Vulnerable Code

// CA3001 violation — unencoded user input written to response
string searchTerm = Request.QueryString["q"];
Response.Write("<h1>Results for: " + searchTerm + "</h1>");
// If q = <script>alert(1)</script> — XSS executes

Fix

// SECURE — HTML encode before writing to response
string searchTerm = Request.QueryString["q"];
Response.Write("<h1>Results for: " + HttpUtility.HtmlEncode(searchTerm) + "</h1>");

// In ASP.NET MVC / Razor — the template engine encodes by default:
// @Model.SearchTerm   ← auto-encoded (safe)
// @Html.Raw(Model.SearchTerm)  ← bypasses encoding (dangerous)

CA3002 — LDAP Injection

Category: Security
CWE: CWE-90 (LDAP Injection)
Default severity: Warning

CA3002 fires when user-controlled data is used in LDAP search filter strings without escaping.

Vulnerable Code

// CA3002 violation — user input in LDAP filter
string username = Request.Form["username"];
string filter = $"(sAMAccountName={username})";

using var entry = new DirectoryEntry("LDAP://dc=company,dc=com");
using var searcher = new DirectorySearcher(entry, filter);  // ← CA3002
var result = searcher.FindOne();

Fix

// SECURE — encode the LDAP filter component
string username = Request.Form["username"];
// Use a library that escapes LDAP special characters: ( ) * \ NUL
string escapedUsername = LdapEncoder.FilterEncode(username);  // from AntiXSS library
string filter = $"(sAMAccountName={escapedUsername})";

using var entry = new DirectoryEntry("LDAP://dc=company,dc=com");
using var searcher = new DirectorySearcher(entry, filter);
var result = searcher.FindOne();

CA3003 — File Path Injection / Path Traversal

Category: Security
CWE: CWE-22 (Path Traversal)
Default severity: Warning

CA3003 detects when user-controlled data flows into File.ReadAllText, File.Open, FileStream, StreamReader, or similar file I/O operations without path validation.

Vulnerable Code

// CA3003 violation — user-controlled filename in File.ReadAllText
string filename = Request.QueryString["file"];
string content = File.ReadAllText("/uploads/" + filename);
// Attacker: ?file=../../etc/passwd

Fix

// SECURE — resolve and validate the full path
string filename = Request.QueryString["file"];
string uploadsDir = Path.GetFullPath("/uploads/");
string fullPath = Path.GetFullPath(Path.Combine(uploadsDir, filename));

// Verify the resolved path is within the allowed directory
if (!fullPath.StartsWith(uploadsDir, StringComparison.OrdinalIgnoreCase))
{
    return Forbid(); // Path traversal attempt
}

string content = File.ReadAllText(fullPath);

CA3006 — Process Command Injection

Category: Security
CWE: CWE-78 (OS Command Injection)
Default severity: Warning

CA3006 fires when user-controlled data flows into Process.Start() arguments.

Vulnerable Code

// CA3006 violation — user input in process arguments
string inputFile = Request.Form["filename"];
Process.Start("convert", inputFile + " output.pdf");
// Attacker: filename = "file.pdf; rm -rf /"

Fix

// SECURE — strict input validation + argument list (no shell interpretation)
string inputFile = Request.Form["filename"];

// Whitelist: only allow alphanumeric, hyphens, underscores, and .pdf extension
if (!Regex.IsMatch(inputFile, @"^[a-zA-Z0-9_\-]+\.pdf$"))
{
    return BadRequest("Invalid filename");
}

var psi = new ProcessStartInfo
{
    FileName = "convert",
    ArgumentList = { inputFile, "output.pdf" },  // List form bypasses shell parsing
    UseShellExecute = false,
    RedirectStandardOutput = true,
    RedirectStandardError = true
};
using var proc = Process.Start(psi)!;
await proc.WaitForExitAsync();

CA3007 — Open Redirect

Category: Security
CWE: CWE-601 (URL Redirection to Untrusted Site)
Default severity: Warning

CA3007 fires when user-controlled data flows into redirect responses without validation.

Vulnerable Code

// CA3007 violation — unvalidated redirect destination
string returnUrl = Request.QueryString["returnUrl"];
return Redirect(returnUrl);
// Attacker: ?returnUrl=https://evil.com

Fix

// SECURE — use ASP.NET's built-in local URL check
string returnUrl = Request.QueryString["returnUrl"];

if (Url.IsLocalUrl(returnUrl))
{
    return Redirect(returnUrl);
}

return RedirectToAction("Index", "Home");  // Default safe fallback

CA3012 — Regex Injection (ReDoS)

Category: Security
CWE: CWE-730 (ReDoS)
Default severity: Warning

CA3012 fires when user-controlled data flows into the Regex constructor pattern argument. This can allow Denial of Service via a specially crafted regex pattern (ReDoS).

Vulnerable Code

// CA3012 violation — user-controlled regex pattern
string userPattern = Request.QueryString["pattern"];
var regex = new Regex(userPattern);   // ← CA3012
bool isMatch = regex.IsMatch(inputText);
// Attacker submits: (a+)+  which causes catastrophic backtracking

Fix

// SECURE — never use user input as a regex pattern
// Option 1: Escape the input as a literal string (match it as-is, not as a pattern)
string userInput = Request.QueryString["search"];
string literalPattern = Regex.Escape(userInput);  // Makes all special chars literal
bool isMatch = Regex.IsMatch(inputText, literalPattern);

// Option 2: Use a fixed pattern and apply user input as the text being matched
// (most cases — user provides the search text, not the pattern)
bool containsInput = inputText.Contains(userInput, StringComparison.OrdinalIgnoreCase);

CA5350 — Use of Weak Cryptographic Algorithm (3DES)

Category: Security
CWE: CWE-326 (Inadequate Encryption Strength)
Default severity: Warning

CA5350 fires when TripleDES (3DES) is used. 3DES has a 112-bit effective key length and is no longer considered secure for new implementations.

Vulnerable Code

// CA5350 violation — TripleDES usage
using var tripleDes = TripleDES.Create();  // ← CA5350
tripleDes.Key = someKey;
// 3DES is deprecated — vulnerable to SWEET32 attack

Fix

// SECURE — use AES-256-GCM for authenticated encryption
byte[] key = RandomNumberGenerator.GetBytes(32);     // 256-bit key
byte[] nonce = RandomNumberGenerator.GetBytes(12);   // 96-bit nonce for GCM
byte[] tag = new byte[16];                           // 128-bit auth tag
byte[] ciphertext = new byte[plaintext.Length];

using var aes = new AesGcm(key, tagSizeInBytes: 16);
aes.Encrypt(nonce, plaintext, ciphertext, tag);
// Store: nonce + tag + ciphertext

CA5351 — Use of Broken Cryptographic Algorithm (MD5/DES)

Category: Security
CWE: CWE-327 (Use of a Broken or Risky Algorithm)
Default severity: Warning

CA5351 fires when MD5, DES, or RC2 are used. These algorithms are cryptographically broken.

Vulnerable Code

// CA5351 violation — MD5 for password hashing
using var md5 = MD5.Create();  // ← CA5351
byte[] hash = md5.ComputeHash(Encoding.UTF8.GetBytes(password));

// CA5351 violation — DES encryption
using var des = DES.Create();  // ← CA5351 — 56-bit key, trivially brute-forced

Fix

// SECURE — PBKDF2 for passwords (via ASP.NET Identity PasswordHasher or direct)
using var hasher = new Rfc2898DeriveBytes(
    password,
    salt: RandomNumberGenerator.GetBytes(16),
    iterations: 600_000,
    HashAlgorithmName.SHA512
);
byte[] hash = hasher.GetBytes(32);

// Or use ASP.NET Identity's PasswordHasher<TUser> directly
var passwordHasher = new PasswordHasher<ApplicationUser>();
string hashed = passwordHasher.HashPassword(user, plainPassword);
PasswordVerificationResult result = passwordHasher.VerifyHashedPassword(user, hashed, inputPassword);

CA5369 — Insecure Deserialization (XmlSerializer)

Category: Security
CWE: CWE-502 (Deserialization of Untrusted Data)
Default severity: Warning

CA5369 fires when XML deserialization is performed in a way that may allow external entity injection (XXE).

Vulnerable Code

// CA5369 — XmlReader without DTD prohibition (enables XXE)
var xmlReader = XmlReader.Create(inputStream);   // ← CA5369 if DTD not disabled
var serializer = new XmlSerializer(typeof(MyType));
var obj = serializer.Deserialize(xmlReader);

Fix

// SECURE — disable DTD processing to prevent XXE
var settings = new XmlReaderSettings
{
    DtdProcessing = DtdProcessing.Prohibit,  // ← Prevents XXE
    MaxCharactersFromEntities = 1024,
    XmlResolver = null
};

using var xmlReader = XmlReader.Create(inputStream, settings);
var serializer = new XmlSerializer(typeof(MyType));
var obj = serializer.Deserialize(xmlReader);

CA5394 — Insecure Randomness

Category: Security
CWE: CWE-338 (Use of Cryptographically Weak PRNG)
Default severity: Warning

CA5394 fires when System.Random is used in a security-sensitive context. System.Random is a pseudorandom number generator (PRNG) not suitable for cryptographic use — its output can be predicted by an attacker given enough observations.

Vulnerable Code

// CA5394 violation — System.Random for session tokens or security codes
var random = new Random();   // ← CA5394 in security contexts
string sessionToken = random.Next(1000000, 9999999).ToString();
string verificationCode = random.Next(100000, 999999).ToString();

Fix

// SECURE — use RandomNumberGenerator (CSPRNG)
// For a random number in a range:
int min = 100000;
int max = 999999;
int secureRandom = RandomNumberGenerator.GetInt32(min, max + 1);
string verificationCode = secureRandom.ToString();

// For random bytes (tokens, salts, keys):
byte[] tokenBytes = RandomNumberGenerator.GetBytes(32);   // 256 bits of entropy
string sessionToken = Convert.ToBase64String(tokenBytes);

Enforcing Rules in GitHub Actions

To enforce Roslyn security rules as build-blocking errors in a GitHub Actions workflow:

name: .NET Security Analysis

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

jobs:
  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 dependencies
        run: dotnet restore

      - name: Build with security rule enforcement
        run: |
          dotnet build \
            --configuration Release \
            --no-restore \
            /p:RunAnalyzersDuringBuild=true \
            /p:AnalysisLevel=latest-recommended \
            /warnaserror:CA2100,CA3001,CA3002,CA3003,CA3006,CA3007,CA3012,CA5350,CA5351,CA5369,CA5394
        # Build fails if any of the above security rules are violated

Enforcing Rules in Azure DevOps

# azure-pipelines.yml
trigger:
  - main
  - develop

pool:
  vmImage: 'ubuntu-latest'

steps:
  - task: DotNetCoreCLI@2
    displayName: 'Restore'
    inputs:
      command: 'restore'
      projects: '**/*.csproj'

  - task: DotNetCoreCLI@2
    displayName: 'Build with Security Analysis'
    inputs:
      command: 'build'
      projects: '**/*.csproj'
      arguments: >
        --configuration Release
        --no-restore
        /p:RunAnalyzersDuringBuild=true
        /p:AnalysisLevel=latest-recommended
        /warnaserror:CA2100,CA3001,CA3002,CA3003,CA3006,CA3007,CA5350,CA5351,CA5369,CA5394

Running EF Migrations Without Breaking Security Analysis

A common problem: dotnet ef migrations add or dotnet ef database update fails when Roslyn security analyzers are configured with TreatWarningsAsErrors=true.

The cause: dotnet ef internally runs dotnet build to extract your EF model, which triggers the full analyzer pipeline. If any analyzer warning is treated as an error, the EF build fails.

The solution: Pass RunAnalyzersDuringBuild=false only to the dotnet ef command via the -- argument separator:

# Disable analyzers only for the EF tool build — your app build is unaffected
dotnet ef migrations add MyMigration -- /p:RunAnalyzersDuringBuild=false
dotnet ef database update -- /p:RunAnalyzersDuringBuild=false

The -- passes arguments to MSBuild, not to the dotnet ef tool itself. This surgically disables analyzers for the migration build while leaving your application build’s security enforcement intact.

See the full guide: dotnet ef RunAnalyzersDuringBuild=false — Fix EF Migration Errors


Roslyn Analyzer Rules vs. Enterprise SAST

Roslyn security analyzers are a valuable first layer of defense — fast, in-IDE, zero-cost. But they have important limitations:

CapabilityRoslyn AnalyzersEnterprise SAST (Offensive360)
Single-method vulnerability detection✅ Good✅ Excellent
Interprocedural taint analysis❌ Not available✅ Full support
Second-order injection detection❌ Not detected✅ Detected
Cross-file data flow analysis❌ Not available✅ Full support
DAST (runtime testing)❌ Not available✅ Included
SCA (dependency scanning)❌ Not available✅ Included
Languages beyond C#/VB.NET❌ .NET only✅ 60+ languages
CI/CD blocking on security findings✅ Via /warnaserror✅ Via API / plugin
Air-gapped / on-premise✅ (runs locally)✅ OVA deployment

The combination of Roslyn analyzers (fast, in-IDE, catches obvious patterns) and a dedicated SAST platform (deep taint analysis, cross-file flows, second-order injection) gives .NET teams the broadest security coverage with the lowest friction.


Complete Roslyn Security Rule Summary

Rule IDDescriptionCWEDefault Severity
CA2100SQL injection via ADO.NETCWE-89Warning
CA3001XSS via HttpResponse.WriteCWE-79Warning
CA3002LDAP injectionCWE-90Warning
CA3003File path injectionCWE-22Warning
CA3004Information disclosure in exceptionsCWE-209Warning
CA3005LDAP DN injectionCWE-90Warning
CA3006Process command injectionCWE-78Warning
CA3007Open redirectCWE-601Warning
CA3008XPath injectionCWE-643Warning
CA3009XML injectionCWE-91Warning
CA3010XAML injectionCWE-91Warning
CA3011DLL injectionCWE-114Warning
CA3012Regex injection / ReDoSCWE-730Warning
CA5350Weak algorithm: TripleDESCWE-326Warning
CA5351Broken algorithm: MD5, DES, RC2CWE-327Warning
CA5358Unsafe cipher modesCWE-327Warning
CA5359Disabled certificate validationCWE-295Warning
CA5360Dangerous BinaryFormatterCWE-502Warning
CA5362Circular reference via DataSetCWE-502Warning
CA5363Disabled request validationCWE-554Warning
CA5364Deprecated security protocolsCWE-326Warning
CA5365HTTP header checking disabledCWE-116Warning
CA5366XmlReader.Create with XmlDocumentCWE-611Warning
CA5367Dangerous JavaScriptSerializer typeCWE-502Warning
CA5368Dangerous JavaScriptSerializerCWE-502Warning
CA5369XmlSerializer without DTD protectionCWE-611Warning
CA5370XmlReader in XmlReader.CreateCWE-611Warning
CA5372XPathNavigator via XmlSerializerCWE-611Warning
CA5373Obsolete key derivation functionsCWE-326Warning
CA5375Dangerous anonymous account useCWE-284Warning
CA5376SharedAccessProtocol.HttpsOnlyCWE-319Warning
CA5377Container-level access policyCWE-284Warning
CA5378Disabled ServicePointManager checkCWE-295Warning
CA5380Adding certificates to root storeCWE-295Warning
CA5381Adding certificates to root storeCWE-295Warning
CA5382Insecure cookieCWE-614Warning
CA5383Insecure cookieCWE-614Warning
CA5384Dangerous XSLT transformCWE-611Warning
CA5385Weak RSA key sizeCWE-326Warning
CA5386Hardcoded SecurityProtocolTypeCWE-326Warning
CA5390Hardcoded cryptographic keyCWE-321Warning
CA5391Missing anti-forgery tokenCWE-352Warning
CA5392Missing UseDefaultCredentialsCWE-319Warning
CA5393Unsafe DllImportSearchPathCWE-426Warning
CA5394Insecure randomness (System.Random)CWE-338Warning
CA5395Missing HttpVerb attributeCWE-352Warning
CA5396Insecure HttpOnly cookieCWE-1004Warning
CA5397Deprecated SslProtocolsCWE-326Warning
CA5398Hardcoded SslProtocolsCWE-326Warning
CA5399Insecure HttpClient TLS versionCWE-326Warning
CA5400HttpClient TLS certificate checkCWE-295Warning
CA5401Non-default IV in encryptionCWE-330Warning
CA5402CreateEncryptor overloadCWE-330Warning
CA5403Hardcoded certificateCWE-295Warning

For .NET security analysis beyond what Roslyn rules cover — including interprocedural taint analysis, second-order injection, and runtime DAST of your ASP.NET application — Offensive360 runs alongside Roslyn in your CI/CD pipeline. Book a demo or run a one-time scan for $500.

Offensive360 Security Research Team

Application Security Research

Find vulnerabilities before attackers do

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