From cdb72240e9ab597a5d5f14d88b235b8c98336e16 Mon Sep 17 00:00:00 2001 From: David Larsen Date: Sat, 11 Apr 2026 00:22:05 -0400 Subject: [PATCH 1/2] fix(rules): improve precision of 4 high-FP dotnet opengrep rules Addresses customer SAST evaluation feedback where 4 rules produced 150/170 false positives (88% of all FPs), inflating the reported FP rate to 91%. Rules fixed: - dotnet-xss-response-write: Convert to taint mode. Previously matched any .Write() call including Serilog ITextFormatter log sinks. Now requires data flow from user input sources to Response.Write sinks. - dotnet-hardcoded-credentials: Add value inspection and credential API patterns. Previously matched on variable names alone, flagging config key paths like "UseCaptchaOnResetPassword". - dotnet-crypto-failures: Target actual weak algorithms (3DES, DES, RC2, RijndaelManaged) instead of Encoding.UTF8.GetBytes() which flagged the recommended SHA256.HashData(Encoding.UTF8.GetBytes(...)) pattern. - dotnet-path-traversal: Convert to taint mode. Previously matched all Path.Combine() calls including those using framework-provided paths like _env.WebRootPath. Validated with opengrep v1.19.0 against NIST Juliet C# test suite: xss-response-write: Prec 41.6% -> 100%, Recall 47.8% -> 24.3% hardcoded-credentials: Prec 0.0% -> 100%, Recall 0.0% -> 3.6% crypto-failures: Prec 36.7% -> 100%, Recall 51.4% -> 50.0% path-traversal: Prec 0.0% -> 100%, Recall 0.0% -> 45.2% --- socket_basics/rules/dotnet.yml | 181 +++++++++++++++++++++++++++------ 1 file changed, 150 insertions(+), 31 deletions(-) diff --git a/socket_basics/rules/dotnet.yml b/socket_basics/rules/dotnet.yml index a93061f..87ad6fd 100644 --- a/socket_basics/rules/dotnet.yml +++ b/socket_basics/rules/dotnet.yml @@ -141,23 +141,37 @@ rules: # === High Severity Rules === - # Hardcoded credentials + # Hardcoded credentials - matches both variable-name patterns AND credential API usage - id: dotnet-hardcoded-credentials message: "Hard-coded credentials detected. Embedding secrets in source code makes them easily discoverable and impossible to rotate. Use environment variables or a secrets manager instead." severity: HIGH languages: [csharp] - patterns: - - pattern-either: - # C# - - pattern: | - private const string $VAR = "..."; - - pattern: | - public const string $VAR = "..."; - - pattern: | - string $VAR = "..."; - - metavariable-regex: - metavariable: $VAR - regex: (?i).*(password|passwd|pwd|secret|token|key|api_key|connection_string).* + pattern-either: + # Pattern 1: Variable name suggests credential AND value is a non-empty literal + - patterns: + - pattern-either: + - pattern: | + private const string $VAR = "$VALUE"; + - pattern: | + public const string $VAR = "$VALUE"; + - pattern: | + string $VAR = "$VALUE"; + - metavariable-regex: + metavariable: $VAR + regex: (?i).*(password|passwd|pwd|secret|token|api_key|connection_string).* + - metavariable-regex: + metavariable: $VALUE + # Must look like an actual secret: 6+ chars, not a config path or empty + regex: ^(?!$).{6,} + - metavariable-regex: + metavariable: $VALUE + # Exclude config key paths and property names + regex: ^(?!.*\.\w+\.\w+)(?!.*\b(Use|Enable|Disable|Is|Has|Get|Set|On)\w+).*$ + # Pattern 2: Credential APIs called with hardcoded string literals + - pattern: new NetworkCredential($USER, "...", ...) + - pattern: new NetworkCredential("...", "...", ...) + - pattern: new SqlConnection("..."); + - pattern: new PasswordDeriveBytes("...", ...) metadata: category: security cwe: CWE-798 @@ -223,35 +237,129 @@ rules: owasp: "A07:2021" fix: "Never return true from ServerCertificateCustomValidationCallback. Use the default certificate validation from ServicePointManager." - # XSS vulnerabilities + # XSS vulnerabilities - taint mode for accurate user-input tracking - id: dotnet-xss-response-write message: "Cross-site scripting (XSS) vulnerability detected. User input is rendered in HTML output without proper escaping, allowing attackers to inject malicious scripts. Sanitize or escape all user input before rendering." severity: HIGH languages: [csharp] - pattern-either: - - pattern: Response.Write($USER_INPUT) - - pattern: $RESPONSE.Write($USER_INPUT) - - pattern: HttpContext.Response.Write($USER_INPUT) + mode: taint + pattern-sources: + - pattern-either: + # ASP.NET request input sources + - pattern: Request.QueryString[...] + - pattern: Request.Form[...] + - pattern: Request.Params[...] + - pattern: Request.Cookies[...] + - pattern: Request[$KEY] + - pattern: Request.Headers[...] + - pattern: Request.UserAgent + - pattern: Request.RawUrl + - pattern: Request.Url + - pattern: Request.Path + - pattern: Request.PathInfo + - pattern: (HttpRequest $REQ).QueryString[...] + - pattern: (HttpRequest $REQ).Form[...] + - pattern: (HttpRequest $REQ).Params[...] + - pattern: (HttpRequest $REQ)[$KEY] + # ASP.NET Core model binding + - pattern: $REQ.Query[...] + - pattern: $REQ.Form[...] + - pattern: $REQ.Headers[...] + - pattern: $REQ.Cookies[...] + # Network input sources (Juliet-style) + - pattern: (StreamReader $SR).ReadLine() + - pattern: (TextReader $TR).ReadLine() + - pattern: Console.ReadLine() + pattern-propagators: + - pattern: (string $A) + (string $B) + from: $B + to: $A + - pattern: String.Format($FMT, ..., (string $X), ...) + from: $X + to: $FMT + - pattern: String.Concat(..., (string $X), ...) + from: $X + to: String.Concat + pattern-sinks: + - pattern-either: + - pattern: Response.Write(...) + - pattern: HttpContext.Response.Write(...) + # HttpResponse parameter pattern (Juliet, ASP.NET handlers) + - pattern: $RESP.Write(...) + pattern-sanitizers: + - pattern-either: + - pattern: HttpUtility.HtmlEncode(...) + - pattern: HtmlEncoder.Default.Encode(...) + - pattern: WebUtility.HtmlEncode(...) + - pattern: Server.HtmlEncode(...) + - pattern: AntiXssEncoder.HtmlEncode(...) metadata: category: security cwe: CWE-79 - confidence: medium + confidence: high subcategory: xss vulnerability_class: "Cross-Site Scripting (XSS)" owasp: "A03:2021" fix: "Use Razor auto-encoding or HtmlEncoder.Default.Encode(). Never use Html.Raw() with user input. Validate input on both client and server." - # Path traversal + # Path traversal - taint mode for accurate user-input tracking - id: dotnet-path-traversal message: "Path traversal vulnerability detected. User input is used in file paths without validation, allowing attackers to access files outside the intended directory. Validate and canonicalize paths before use." severity: HIGH languages: [csharp] - pattern-either: - - pattern: File.ReadAllText($PATH + $USER_INPUT) - - pattern: File.ReadAllBytes($PATH + $USER_INPUT) - - pattern: File.WriteAllText($PATH + $USER_INPUT, ...) - - pattern: new FileStream($PATH + $USER_INPUT, ...) - - pattern: Path.Combine($BASE, $USER_INPUT) + mode: taint + pattern-sources: + - pattern-either: + # ASP.NET request sources + - pattern: Request.QueryString[...] + - pattern: Request.Form[...] + - pattern: Request.Params[...] + - pattern: Request[$KEY] + - pattern: Request.Path + - pattern: Request.PathInfo + # ASP.NET Core + - pattern: $REQ.Query[...] + - pattern: $REQ.Form[...] + - pattern: $REQ.RouteValues[...] + # Network input sources (Juliet-style) + - pattern: (StreamReader $SR).ReadLine() + - pattern: (TextReader $TR).ReadLine() + - pattern: Console.ReadLine() + # Environment and system sources + - pattern: Environment.GetEnvironmentVariable(...) + pattern-propagators: + - pattern: (string $A) + (string $B) + from: $B + to: $A + - pattern: Path.Combine(..., (string $X), ...) + from: $X + to: Path.Combine + - pattern: String.Format($FMT, ..., (string $X), ...) + from: $X + to: $FMT + pattern-sinks: + - pattern-either: + - pattern: File.ReadAllText(...) + - pattern: File.ReadAllBytes(...) + - pattern: File.WriteAllText(...) + - pattern: File.WriteAllBytes(...) + - pattern: File.Open(...) + - pattern: File.OpenRead(...) + - pattern: File.OpenWrite(...) + - pattern: File.Exists(...) + - pattern: File.Delete(...) + - pattern: new FileStream(...) + - pattern: new StreamReader(...) + - pattern: new StreamWriter(...) + - pattern: Directory.GetFiles(...) + - pattern: Directory.EnumerateFiles(...) + pattern-sanitizers: + - pattern-either: + - pattern: Path.GetFullPath(...) + - pattern: Path.GetFileName(...) + # Framework-provided base paths are safe sources, not sanitizers, + # but if the result is validated against a base we consider it sanitized + - pattern: $X.StartsWith($BASE) metadata: category: security cwe: CWE-22 @@ -623,23 +731,34 @@ rules: vulnerability_class: "Access Control Violation" fix: "Review authorization logic for bypass conditions. Use policy-based authorization with IAuthorizationHandler. Test authorization with different user roles." - # A02: Cryptographic Failures + # A02: Cryptographic Failures - targets actual weak algorithm usage - id: dotnet-crypto-failures message: "Weak cryptographic algorithm detected. Using broken or outdated algorithms may allow attackers to decrypt data or forge signatures. Use modern algorithms like AES-256, SHA-256, or Ed25519." severity: HIGH languages: [csharp] pattern-either: - - pattern: Encoding.UTF8.GetBytes($PASSWORD) - - pattern: Convert.ToBase64String(Encoding.UTF8.GetBytes($SECRET)) + # Weak symmetric ciphers + - pattern: new TripleDESCryptoServiceProvider() + - pattern: new DESCryptoServiceProvider() + - pattern: new RC2CryptoServiceProvider() + - pattern: TripleDES.Create() + - pattern: DES.Create() + - pattern: RC2.Create() + # Obsolete RijndaelManaged (use Aes.Create() instead) + - pattern: new RijndaelManaged() + # Using raw password bytes directly as crypto key (no KDF) - pattern: new RijndaelManaged() { Key = Encoding.UTF8.GetBytes($KEY) } + - pattern: new AesCryptoServiceProvider() { Key = Encoding.UTF8.GetBytes($KEY) } + # Encoding password for storage without hashing (storing plaintext) + - pattern: Convert.ToBase64String(Encoding.UTF8.GetBytes($SECRET)) metadata: category: security owasp: A02 cwe: CWE-327 - confidence: medium + confidence: high subcategory: crypto vulnerability_class: "Cryptographic Weakness" - fix: "Use SHA256.Create() instead of MD5/SHA1. Use Aes.Create() with CipherMode.CBC or AesGcm for encryption." + fix: "Use Aes.Create() instead of 3DES/DES/RC2. Use Rfc2898DeriveBytes or HKDF for key derivation from passwords. Never use raw Encoding.GetBytes() as a crypto key." # A03: Injection (additional patterns) - id: dotnet-xpath-injection From 580a2631b0e0a16c61fd747ece91cc835e45b9d1 Mon Sep 17 00:00:00 2001 From: David Larsen Date: Sat, 11 Apr 2026 08:10:09 -0400 Subject: [PATCH 2/2] fix(rules): add ASP.NET Core taint sources and System.IO sinks to dotnet rules Add controller parameter binding sources ([FromQuery], [FromBody], [FromRoute], [FromForm]) and IFormFile.FileName to path-traversal and XSS taint rules. Add Response.WriteAsync and Html.Raw as XSS sinks. Add fully-qualified System.IO.File.* sink variants for ASP.NET Core code that uses explicit namespace qualification. E2E tested against two vulnerable .NET repos: 7 true positives found, zero false positives. --- socket_basics/rules/dotnet.yml | 52 ++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/socket_basics/rules/dotnet.yml b/socket_basics/rules/dotnet.yml index 87ad6fd..9ce8652 100644 --- a/socket_basics/rules/dotnet.yml +++ b/socket_basics/rules/dotnet.yml @@ -266,6 +266,26 @@ rules: - pattern: $REQ.Form[...] - pattern: $REQ.Headers[...] - pattern: $REQ.Cookies[...] + # ASP.NET Core controller parameter binding + - patterns: + - pattern: $PARAM + - pattern-inside: | + public $RET $METHOD(..., [FromQuery] $TYPE $PARAM, ...) { ... } + - patterns: + - pattern: $PARAM + - pattern-inside: | + public $RET $METHOD(..., [FromBody] $TYPE $PARAM, ...) { ... } + - patterns: + - pattern: $PARAM + - pattern-inside: | + public $RET $METHOD(..., [FromRoute] $TYPE $PARAM, ...) { ... } + - patterns: + - pattern: $PARAM + - pattern-inside: | + public $RET $METHOD(..., [FromForm] $TYPE $PARAM, ...) { ... } + # IFormFile upload sources + - pattern: (IFormFile $F).FileName + - pattern: (IFormFile $F).ContentType # Network input sources (Juliet-style) - pattern: (StreamReader $SR).ReadLine() - pattern: (TextReader $TR).ReadLine() @@ -283,9 +303,14 @@ rules: pattern-sinks: - pattern-either: - pattern: Response.Write(...) + - pattern: Response.WriteAsync(...) - pattern: HttpContext.Response.Write(...) + - pattern: HttpContext.Response.WriteAsync(...) # HttpResponse parameter pattern (Juliet, ASP.NET handlers) - pattern: $RESP.Write(...) + - pattern: $RESP.WriteAsync(...) + # Razor unencoded output + - pattern: Html.Raw(...) pattern-sanitizers: - pattern-either: - pattern: HttpUtility.HtmlEncode(...) @@ -321,6 +346,25 @@ rules: - pattern: $REQ.Query[...] - pattern: $REQ.Form[...] - pattern: $REQ.RouteValues[...] + # ASP.NET Core controller parameter binding + - patterns: + - pattern: $PARAM + - pattern-inside: | + public $RET $METHOD(..., [FromQuery] $TYPE $PARAM, ...) { ... } + - patterns: + - pattern: $PARAM + - pattern-inside: | + public $RET $METHOD(..., [FromBody] $TYPE $PARAM, ...) { ... } + - patterns: + - pattern: $PARAM + - pattern-inside: | + public $RET $METHOD(..., [FromRoute] $TYPE $PARAM, ...) { ... } + - patterns: + - pattern: $PARAM + - pattern-inside: | + public $RET $METHOD(..., [FromForm] $TYPE $PARAM, ...) { ... } + # IFormFile upload sources + - pattern: (IFormFile $F).FileName # Network input sources (Juliet-style) - pattern: (StreamReader $SR).ReadLine() - pattern: (TextReader $TR).ReadLine() @@ -348,6 +392,14 @@ rules: - pattern: File.OpenWrite(...) - pattern: File.Exists(...) - pattern: File.Delete(...) + # Fully-qualified System.IO variants (common in ASP.NET Core) + - pattern: System.IO.File.ReadAllText(...) + - pattern: System.IO.File.ReadAllBytes(...) + - pattern: System.IO.File.WriteAllText(...) + - pattern: System.IO.File.WriteAllBytes(...) + - pattern: System.IO.File.Exists(...) + - pattern: System.IO.File.Open(...) + - pattern: System.IO.File.Delete(...) - pattern: new FileStream(...) - pattern: new StreamReader(...) - pattern: new StreamWriter(...)