csharp
Seeded .NET defects — async void, sync-over-async deadlocks, per-request HttpClient.
What this is
10 deliberate defects, each carrying a marker naming the rule in standards/ it violates. It measures recall, and its pass condition is that every one of the 7 BLOCKER markers is flagged. The rule id in each marker means the scorer grades attribution too: a reviewer that finds the bug but cites the wrong rule counts as caught, and separately as misattributed.
How to onboard it
Nothing is installed and nothing here is ever merged. The corpus is used by opening a throwaway pull request on a pilot repository already onboarded for this stack, adding this directory and seeded/clean together. Label it redline-exempt so the readiness gate does not block a pull request nobody will merge, wait for the automated review to finish, then score it.
seed-canary.yml does exactly this on a schedule and closes the pull request afterwards, including when the run fails.
How to use it
$ GH_TOKEN=... npx redlinegate metrics score-seeds --repo <org>/<repo> --pr <n>
| Seed | Severity | Rule it violates | Defect |
|---|---|---|---|
| 1 | BLOCKER | core/hardcoded-secrets | hardcoded connection string with credentials |
| 2 | BLOCKER | csharp/httpclient-per-request | HttpClient instantiated per request — socket exhaustion |
| 3 | BLOCKER | csharp/async-void | async void — the exception escapes and takes the process down |
| 4 | BLOCKER | csharp/sql-string-concatenation | SQL built by concatenation with request input |
| 5 | BLOCKER | csharp/sync-over-async | sync-over-async deadlocks and starves the thread pool |
| 6 | BLOCKER | csharp/swallowed-exceptions | exception swallowed, failure reported as success |
| 10 | BLOCKER | core/type-checker-suppression | compiler warning suppressed with no explanation and no ticket |
| 7 | HIGH | csharp/entity-in-controller-response | entity returned straight from the controller |
| 8 | HIGH | csharp/datetime-now | DateTime.Now in new code instead of DateTimeOffset.UtcNow |
| 9 | HIGH | csharp/exception-detail-in-response | exception detail leaked to the caller |
Expected output
A score from scripts/score-seeds.mjs: BLOCKER recall, false positives, and rule attribution. The canary appends it to data/seed-scores.jsonl and fails the run if BLOCKER recall drops below 1.0 or the clean corpus attracts a false positive. Of the 10 defects here, 7 BLOCKER and 3 HIGH — only the BLOCKER count is a pass condition.
How to edit it
Add a defect by adding the code and one marker line above it. There is no separate expectations file to keep in sync — scripts/score-seeds.mjs parses the markers straight out of the source:
SEED <n> [BLOCKER|HIGH|SUGGESTION] (<stack>/<rule-slug>) <short description>scripts/validate.mjs fails CI if a seed cites a rule that does not exist, or claims a severity higher than that rule carries in the standard — so a marker cannot quietly drift away from the rule it is testing.
The full file
// DO NOT MERGE — Redline validation seed.
// Every `SEED n [SEVERITY] (rule-id)` marker must be flagged at that severity or higher,
// citing that rule id. Score with scripts/score-seeds.mjs.
using System;
using System.Data.SqlClient;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
namespace Redline.Seed;
[ApiController]
[Route("accounts")]
public class SeededViolationsController : ControllerBase
{
// SEED 1 [BLOCKER] (core/hardcoded-secrets) hardcoded connection string with credentials
private const string ConnectionString =
"Server=prod-sql;Database=billing;User Id=sa;Password=Pr0d-S3cret-2026!;";
// SEED 2 [BLOCKER] (csharp/httpclient-per-request) HttpClient instantiated per request — socket exhaustion
private readonly HttpClient _http = new HttpClient();
// SEED 3 [BLOCKER] (csharp/async-void) async void — the exception escapes and takes the process down
public async void FireAndForget(string id)
{
await _http.GetAsync($"https://internal/audit/{id}");
}
[HttpGet]
public IActionResult Find(string name)
{
using var conn = new SqlConnection(ConnectionString);
conn.Open();
// SEED 4 [BLOCKER] (csharp/sql-string-concatenation) SQL built by concatenation with request input
var cmd = new SqlCommand("SELECT * FROM accounts WHERE name = '" + name + "'", conn);
// SEED 5 [BLOCKER] (csharp/sync-over-async) sync-over-async deadlocks and starves the thread pool
var balance = _http.GetStringAsync("https://internal/balance").Result;
try
{
cmd.ExecuteNonQuery();
}
catch
{
// SEED 6 [BLOCKER] (csharp/swallowed-exceptions) exception swallowed, failure reported as success
}
// SEED 7 [HIGH] (csharp/entity-in-controller-response) entity returned straight from the controller
return Ok(new AccountEntity { Msisdn = name, Balance = balance });
}
// SEED 8 [HIGH] (csharp/datetime-now) DateTime.Now in new code instead of DateTimeOffset.UtcNow
[HttpGet("now")]
public DateTime Now() => DateTime.Now;
// SEED 9 [HIGH] (csharp/exception-detail-in-response) exception detail leaked to the caller
[HttpGet("boom")]
public IActionResult Boom()
{
try
{
throw new InvalidOperationException("db=prod-sql user=sa");
}
catch (Exception ex)
{
return StatusCode(500, ex.ToString());
}
}
}
public class AccountEntity
{
public string Msisdn { get; set; } = "";
public string Balance { get; set; } = "";
}
public static class Coercion
{
// SEED 10 [BLOCKER] (core/type-checker-suppression) compiler warning suppressed with no explanation and no ticket
#pragma warning disable CS0168
public static string Read(string raw)
{
Exception unused;
return raw;
}
#pragma warning restore CS0168
}