fix(rules): improve precision of 4 high-FP dotnet opengrep rules#63
fix(rules): improve precision of 4 high-FP dotnet opengrep rules#63David Larsen (dc-larsen) wants to merge 3 commits intomainfrom
Conversation
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%
4958b6f to
cdb7224
Compare
E2E Validation: dotnet rule precision (round 1)Validated the updated rules against two intentionally vulnerable .NET repositories using opengrep v1.19.0 and the full socket-basics pipeline. Test Targets
Resultsthe-most-vulnerable-dotnet-app
AspGoat
Pipeline Integration
ObservationThe taint-mode rules correctly eliminate pattern-match false positives. Initial taint sources target classic ASP.NET WebForms ( |
E2E Validation: ASP.NET Core coverage added (round 2)Extended taint sources and sinks to cover ASP.NET Core patterns, then re-validated against both test repos. ChangesSources (both
Sinks (
Sinks (
Resultsthe-most-vulnerable-dotnet-app
AspGoat
Validation
|
…net 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.
There was a problem hiding this comment.
David Larsen (@dc-larsen) Thanks for putting this together! Reducing 150 false positives from 4 rules is meaningful, and the move toward taint-style matching is directionally the right fix for the worst offenders here. I also validated the updated dotnet.yml on my end with opengrep --validate, and the config is syntactically valid.
I do see a few semantic issues worth tightening before merge - summarized here, but see my review comments for more details:
dotnet-path-traversal:Path.GetFullPath(...)is currently treated as a sanitizer atsocket_basics/rules/dotnet.yml:410dotnet-hardcoded-credentials: the variable-name regex got much narrower atsocket_basics/rules/dotnet.yml:161dotnet-crypto-failures: one broad pattern still looks FP-prone atsocket_basics/rules/dotnet.yml:805
Overall, I think the main idea in this PR is solid: move away from simple pattern matching where it was clearly overfiring, and add more context-aware logic. I’d just recommend tightening the three cases above so we don’t trade one class of false positives for a quieter set of false negatives or misclassifications.
| - pattern: Directory.EnumerateFiles(...) | ||
| pattern-sanitizers: | ||
| - pattern-either: | ||
| - pattern: Path.GetFullPath(...) |
There was a problem hiding this comment.
GetFullPath only normalizes the path; it does not make attacker-controlled input safe on its own. For example, ../../etc/passwd becomes a full absolute path, but it can still point outside the intended directory. In practice, code is only safe after the normalized path is checked against an allowed base directory. As written, the rule may now miss real path traversal cases.
| string $VAR = "$VALUE"; | ||
| - metavariable-regex: | ||
| metavariable: $VAR | ||
| regex: (?i).*(password|passwd|pwd|secret|token|api_key|connection_string).* |
There was a problem hiding this comment.
The new matcher looks for names like api_key and connection_string, but idiomatic C# code often uses apiKey, connectionString, privateKey, accessKey, etc. That means this version is likely to miss common .NET secret names even though it improves precision.
| - 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)) |
There was a problem hiding this comment.
Convert.ToBase64String(Encoding.UTF8.GetBytes($SECRET)) is not necessarily "weak crypto" by itself. It can also appear in benign encoding/transport code. So while the rule is much better than before, this particular pattern could still generate noise and doesn’t map as cleanly to actual weak algorithm usage as the rest of the rewrite.
- Remove Path.GetFullPath() as path-traversal sanitizer (normalizes but does not prevent traversal on its own) - Broaden hardcoded-credentials variable regex to cover idiomatic C# naming: apiKey, connectionString, privateKey, accessKey, authToken - Remove overly broad Base64 encoding pattern from crypto-failures (benign encoding/transport use generates noise)
Summary
Fixes 4 dotnet opengrep rules that produced 150 of 170 total false positives (88%) in a customer SAST evaluation, inflating the reported FP rate to 91%.
.Write()including SerilogITextFormatterlog sinks (74 FPs). Now tracks data flow from user input toResponse.Write.UseCaptchaOnResetPassword(31 FPs).Encoding.UTF8.GetBytes()which triggers on the recommendedSHA256.HashData()pattern (30 FPs).Path.Combine()calls including framework paths like_env.WebRootPath(15 FPs).Benchmark data (NIST Juliet C# Test Suite)
All 4 rules achieve 100% precision (zero false positives) post-fix. Recall trade-offs are acceptable: taint-mode rules only fire when user input actually reaches the sink, which is the correct behavior for security analysis.
Customer impact
Eliminates all 150 FPs from these 4 rules. Remaining findings (36 total, 20 FP) produce a ~56% FP rate, consistent with pattern-matching SAST tools. Further tuning via community rules and per-language scoping can reduce this further.
Testing
opengrep --validatepasses on full dotnet.yml (40 rules, 0 errors)pytestpasses (139 tests, 0 failures)