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:
| Capability | Roslyn Analyzers | Enterprise 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 ID | Description | CWE | Default Severity |
|---|---|---|---|
| CA2100 | SQL injection via ADO.NET | CWE-89 | Warning |
| CA3001 | XSS via HttpResponse.Write | CWE-79 | Warning |
| CA3002 | LDAP injection | CWE-90 | Warning |
| CA3003 | File path injection | CWE-22 | Warning |
| CA3004 | Information disclosure in exceptions | CWE-209 | Warning |
| CA3005 | LDAP DN injection | CWE-90 | Warning |
| CA3006 | Process command injection | CWE-78 | Warning |
| CA3007 | Open redirect | CWE-601 | Warning |
| CA3008 | XPath injection | CWE-643 | Warning |
| CA3009 | XML injection | CWE-91 | Warning |
| CA3010 | XAML injection | CWE-91 | Warning |
| CA3011 | DLL injection | CWE-114 | Warning |
| CA3012 | Regex injection / ReDoS | CWE-730 | Warning |
| CA5350 | Weak algorithm: TripleDES | CWE-326 | Warning |
| CA5351 | Broken algorithm: MD5, DES, RC2 | CWE-327 | Warning |
| CA5358 | Unsafe cipher modes | CWE-327 | Warning |
| CA5359 | Disabled certificate validation | CWE-295 | Warning |
| CA5360 | Dangerous BinaryFormatter | CWE-502 | Warning |
| CA5362 | Circular reference via DataSet | CWE-502 | Warning |
| CA5363 | Disabled request validation | CWE-554 | Warning |
| CA5364 | Deprecated security protocols | CWE-326 | Warning |
| CA5365 | HTTP header checking disabled | CWE-116 | Warning |
| CA5366 | XmlReader.Create with XmlDocument | CWE-611 | Warning |
| CA5367 | Dangerous JavaScriptSerializer type | CWE-502 | Warning |
| CA5368 | Dangerous JavaScriptSerializer | CWE-502 | Warning |
| CA5369 | XmlSerializer without DTD protection | CWE-611 | Warning |
| CA5370 | XmlReader in XmlReader.Create | CWE-611 | Warning |
| CA5372 | XPathNavigator via XmlSerializer | CWE-611 | Warning |
| CA5373 | Obsolete key derivation functions | CWE-326 | Warning |
| CA5375 | Dangerous anonymous account use | CWE-284 | Warning |
| CA5376 | SharedAccessProtocol.HttpsOnly | CWE-319 | Warning |
| CA5377 | Container-level access policy | CWE-284 | Warning |
| CA5378 | Disabled ServicePointManager check | CWE-295 | Warning |
| CA5380 | Adding certificates to root store | CWE-295 | Warning |
| CA5381 | Adding certificates to root store | CWE-295 | Warning |
| CA5382 | Insecure cookie | CWE-614 | Warning |
| CA5383 | Insecure cookie | CWE-614 | Warning |
| CA5384 | Dangerous XSLT transform | CWE-611 | Warning |
| CA5385 | Weak RSA key size | CWE-326 | Warning |
| CA5386 | Hardcoded SecurityProtocolType | CWE-326 | Warning |
| CA5390 | Hardcoded cryptographic key | CWE-321 | Warning |
| CA5391 | Missing anti-forgery token | CWE-352 | Warning |
| CA5392 | Missing UseDefaultCredentials | CWE-319 | Warning |
| CA5393 | Unsafe DllImportSearchPath | CWE-426 | Warning |
| CA5394 | Insecure randomness (System.Random) | CWE-338 | Warning |
| CA5395 | Missing HttpVerb attribute | CWE-352 | Warning |
| CA5396 | Insecure HttpOnly cookie | CWE-1004 | Warning |
| CA5397 | Deprecated SslProtocols | CWE-326 | Warning |
| CA5398 | Hardcoded SslProtocols | CWE-326 | Warning |
| CA5399 | Insecure HttpClient TLS version | CWE-326 | Warning |
| CA5400 | HttpClient TLS certificate check | CWE-295 | Warning |
| CA5401 | Non-default IV in encryption | CWE-330 | Warning |
| CA5402 | CreateEncryptor overload | CWE-330 | Warning |
| CA5403 | Hardcoded certificate | CWE-295 | Warning |
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.