sota-dotnet
State-of-the-art C# / .NET engineering rules (2026 baseline, .NET 10 LTS / C# 14) that Claude applies when writing or auditing .NET code. Covers modern idioms (records, nullable reference types, pattern matching, spans, file-scoped namespaces), API/null/immutability/`IDisposable`
Install
npx skills add https://github.com/martinholovsky/SOTA-skills/tree/main/skills/sota-dotnet
claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install martinholovsky-sota-skills@llmmart
git clone https://github.com/martinholovsky/SOTA-skills.git
The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole martinholovsky/sota-skills collection as a plugin from our marketplace. Git is the plain clone.
Skill manifest
SOTA C# / .NET (2026)
Expert-level rules for producing and auditing production .NET. The runtime is
memory-safe, so risk concentrates in injection, deserialization, async
correctness, and dependency supply chain. Baseline: .NET 10 LTS (released
Nov 2025, supported to Nov 2028) and C# 14 (records, nullable reference
types, pattern matching, spans, extension members, the field keyword) — flag
where a control needs a specific version. Every rule states the why; every
rules file ends with an audit checklist of grep/analyzer patterns.
Purpose
Two consumers, one source of truth:
- BUILD mode — generating C#/.NET: follow the rules as defaults. Enable nullable reference types and treat analyzer warnings as errors; prefer immutability and the async-all-the-way model. Deviate only with a comment.
- AUDIT mode — reviewing existing code: hunt violations with the audit checklists, classify by severity, report in the finding format below. SQL string-building and legacy deserialization are presumed exploitable.
BUILD mode
- Before writing, read the rules files relevant to the task (see index). A web
API touching untrusted input + a DB + async needs
02,03,04. - Apply the top-10 non-negotiables (below) unconditionally.
- New projects: target the current LTS (
net10.0),<Nullable>enable</Nullable>,<TreatWarningsAsErrors>true</TreatWarningsAsErrors>,<AnalysisLevel>latest-Recommended</AnalysisLevel>, NuGet lockfile +RestoreLockedModein CI, anddotnet formatfrom day one (rules/06). - Async all the way down — never block on async (
.Result/.Wait()/GetAwaiter().GetResult()) (rules/03). UseCancellationTokenend to end. - Prefer the BCL and well-known libraries; parameterize all data access; use
the framework's auth/Data Protection rather than rolling your own (
rules/04). - When you take a sharp path (reflection,
unsafe,DynamicMethod, suppressing a nullable/analyzer warning), leave a// NOTE(sota):explaining why.
AUDIT mode
Work each relevant rules file's audit checklist against the target. Run the greps and the Roslyn analyzers (incl. the security CA rules); confirm hits manually. Check the dependency tree against known-CVE databases.
Severity conventions
| Severity | Meaning | Examples |
|---|---|---|
| CRITICAL | Exploitable on reachable input | SQL via string interpolation/concat into FromSqlRaw/ExecuteSqlRaw/Dapper, BinaryFormatter/NetDataContractSerializer/LosFormatter or JSON TypeNameHandling.All on untrusted data, command injection, deserialization gadget |
| HIGH | Likely incident or security weakness | Missing auth on an endpoint, disabled cert validation (ServerCertificateCustomValidationCallback => true), MD5/SHA-1 or DES/ECB for security, Random for tokens, blocking on async causing deadlock/thread-pool starvation, secrets in config/source |
| MEDIUM | Correctness/maintainability hazard | async void (non-handler), missing ConfigureAwait(false) in a library, IDisposable not disposed / no using, nullable warnings suppressed with !, swallowed exceptions, mutable static state |
| LOW | Idiom/perf debt | Sync-over-collection LINQ on hot path, needless allocations/boxing, class where a record/struct fits, not using Span/pooling on hot path |
| INFO | Style/doc/hygiene | formatting, naming, missing XML docs, nullable annotations absent (not enabled) |
Finding format
[SEVERITY] File.cs:LINE — short title
Rule: rules/NN-name.md § section
Evidence: the offending line(s), verbatim
Impact: one sentence — what executes/leaks/deadlocks, under what input
Fix: concrete replacement code or action
Effort: trivial | small | medium | large
Group findings by severity, CRITICAL first. End with: counts per severity, the three highest-leverage fixes, and which checklists/analyzers were run.
Rules index
| File | Read this when... |
|---|---|
rules/01-idioms.md |
Writing/reviewing any C#: records & record struct, nullable reference types, pattern matching/switch expressions, spans, LINQ discipline, var, expression vs statement, file-scoped namespaces, error handling, modern C# 12–14 features |
rules/02-design-api.md |
Designing types/APIs: nullable reference type discipline, int? over a magic int and using TryParse's bool (its out is 0 on failure), immutability, IDisposable/IAsyncDisposable and using, exceptions, value vs reference types, internal/visibility, DI (the built-in container), options pattern |
rules/03-async-concurrency.md |
Anything async/Task/threads: async-all-the-way, never block (.Result/.Wait()), ConfigureAwait(false) in libraries, CancellationToken flow, async void, Channel<T>, IAsyncEnumerable, TPL/Parallel, thread-safety, ValueTask |
rules/04-security.md |
Any input crossing a trust boundary: SQL (EF Core/Dapper parameterization), legacy serializers (BinaryFormatter removed .NET 9) + JSON TypeNameHandling, command/path injection, ASP.NET Core authn/authz, antiforgery/CORS, Data Protection, crypto (RandomNumberGenerator, AES-GCM), secrets; OWASP .NET |
rules/05-performance.md |
Latency/throughput/memory work: GC (gen/SOH/LOH, server vs workstation), allocation reduction, Span<T>/Memory<T>/ArrayPool, struct/record struct, BenchmarkDotNet, async overhead, Native AOT / trimming, string handling |
rules/06-build-tooling-ci.md |
Setting up or auditing build/CI: SDK/TFM targeting, Directory.Build.props, nullable + warnings-as-errors, Roslyn analyzers (incl. security CA rules), dotnet format, NuGet lockfiles + supply chain (lock mode, source mapping, signed packages, CVE scan), SBOM. Test strategy lives in sota-testing; this owns .NET build/test mechanics (xUnit/NUnit, Testcontainers). |
Top-10 non-negotiables
- No string-built SQL. Parameterize: EF Core LINQ or parameters
(
FromSql/interpolatedFromSql, neverFromSqlRaw/ExecuteSqlRawwith concatenation); Dapper with parameters. Interpolated/concatenated SQL is CRITICAL. (rules/04) - No unsafe deserialization.
BinaryFormatteris removed in .NET 9+ (throws); never reintroduce it orNetDataContractSerializer/LosFormatter/SoapFormatter, and never useJson.NETTypeNameHandling.Auto/AllorJsonSerializerwith an unrestricted type resolver on untrusted data. UseSystem.Text.Jsonwith known types. (rules/04) - Async all the way; never block on async. No
.Result,.Wait(),.GetAwaiter().GetResult()on a hot/request path (deadlock + thread-pool starvation).async voidonly for event handlers. (rules/03) - Flow
CancellationTokenend to end through async APIs and honor it. (rules/03) ConfigureAwait(false)in library code (code with no sync context that doesn't need to resume on the original context). (rules/03)- Nullable reference types enabled and honored.
<Nullable>enable</Nullable>; don't paper over warnings with the null-forgiving!. (rules/01,rules/02) - Deterministic disposal. Everything
IDisposable/IAsyncDisposableis in ausing/await usingor owned by a DI-managed lifetime —HttpClientviaIHttpClientFactory, not new-per-call. (rules/02) - Crypto uses the right primitives.
RandomNumberGenerator(neverRandom) for tokens/keys/IVs; AES-GCM (not ECB); no MD5/SHA-1 for security; ASP.NET Core Data Protection for at-rest tokens; never disable TLS cert validation. (rules/04) - AuthN/AuthZ enforced server-side on every non-public endpoint
(
[Authorize]/policies/endpoint auth), antiforgery for cookie-auth POSTs, CORS locked to known origins. (rules/04) - Analyzers + nullable + lockfile gate CI.
TreatWarningsAsErrors, Roslyn analyzers (incl. security CA rules),dotnet format --verify-no-changes, NuGet locked-mode restore + CVE scan. (rules/06)
Files (sota-skills)
-
rules
-
01-idioms.md 4.2 KB
# 01 — Idioms: modern C#, nullability, error handling Write to current C# (14 / .NET 10), not C# 7. The through-line is **immutability, the nullable-aware type system, and expression-oriented code**. Reference: [What's new in C#](https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/), [.NET 10](https://learn.microsoft.com/en-us/dotnet/core/whats-new/dotnet-10/overview). ## 1. Records and immutability - **`record`** (reference) and **`record struct`** for immutable data carriers — value equality, `with` expressions, deconstruction, concise `ToString`. Replace hand-written DTO/value classes. Use `init`-only setters for immutable-after-construction. - Prefer immutable by default: `readonly` fields, `init` properties, immutable collections where it matters. Immutable types are thread-safe (`rules/03`). - `record class` for entities with identity-by-value semantics; plain `class` for mutable services/stateful objects; `struct`/`record struct` for small values (`rules/05`). ## 2. Nullable reference types (NRT) - Enable `<Nullable>enable</Nullable>` solution-wide. The compiler then tracks null-flow: `string` is non-null, `string?` may be null. This eliminates a huge class of `NullReferenceException` at compile time. - **Honor the warnings** — don't silence with the null-forgiving operator `!` unless you've genuinely proven non-null (and comment why). Scattered `!` is a MEDIUM finding: it disables the very safety you enabled. - Annotate APIs precisely (`?`, `[NotNullWhen]`, `[MaybeNull]`); guard external/ deserialized input at the boundary (it can be null regardless of annotations). ## 3. Pattern matching & expressions - Prefer `switch` **expressions** and pattern matching (type/property/relational/ list patterns) over if-cascades and `switch` statements — exhaustive, concise, value-returning. - Use expression-bodied members, target-typed `new`, collection expressions (`[1, 2, 3]`), and `nameof`. Use `var` when the type is obvious from the RHS. ## 4. C# 12–14 niceties - Primary constructors (classes/structs), collection expressions, file-scoped namespaces (`namespace Foo;`), required members (`required`), raw string literals. - C# 14: **extension members** (extension properties/operators/static members), the **`field`** contextual keyword (access the synthesized backing field in an accessor without declaring it), and broader `Span<T>`/`ReadOnlySpan<T>`/`T[]` conversions. Use where they clarify; don't chase novelty. ## 5. LINQ discipline - LINQ for clarity over hand loops, but beware: multiple enumeration of an `IEnumerable` (materialize with `ToList()` once if iterated repeatedly), hidden N+1 with `IQueryable` (`rules/04`/perf), and allocation/closure cost on hot paths (`rules/05`). Know when a query executes (deferred vs eager). ## 6. Error handling - Throw the most specific exception type; don't catch `Exception`/`Exception e` just to swallow or log-and-continue on a path that must abort. Preserve stack with `throw;` (not `throw ex;`). - Don't use exceptions for control flow. Use the `TryParse`/`Try...` pattern or a result type for expected failures on hot paths. `ArgumentNullException.ThrowIfNull` and `ArgumentException.ThrowIf...` for guard clauses. - Exceptions are unchecked in C#; document what a public API throws. ## Audit checklist ```bash # Null-forgiving overuse — MEDIUM (defeats NRT) grep -rnE '[A-Za-z0-9_)\]]\!\.' --include='*.cs' . | grep -v '!=' | head # x!.Member # Is NRT even enabled? grep -rniE '<Nullable>\s*enable' . --include='*.csproj' --include='Directory.Build.props' || echo "NRT not enabled — HIGH" # Swallowed exceptions / throw ex — MEDIUM grep -rnzoE 'catch\s*\([^)]*\)\s*\{\s*\}' --include='*.cs' . grep -rnE 'throw ex;' --include='*.cs' . # loses stack trace grep -rnE 'catch \(Exception' --include='*.cs' . | head # Legacy idioms — LOW grep -rnE '\bclass\b' --include='*.cs' . | head # DTOs that should be records? grep -rnE 'namespace [A-Za-z0-9_.]+\s*\{' --include='*.cs' . # non-file-scoped namespaces # Multiple enumeration / LINQ on hot path — LOW (verify) grep -rnE '\.Where\(|\.Select\(|\.Count\(\)' --include='*.cs' . | head # Broad analyzer pass (idioms): enable .NET analyzers + IDE rules in CI (rules/06) ``` -
02-design-api.md 5.3 KB
# 02 — API design, disposal, exceptions, DI Good .NET APIs make nullability and lifetimes explicit and lean on the built-in DI and options patterns. Idioms are in `01`, async in `03`. ## 1. Nullability as contract - With NRT enabled (`rules/01`), the API signature *is* the null contract: return `T?` only when null is meaningful; accept `T` to require non-null. Annotate with `[NotNullWhen(true)]` etc. for `Try` patterns. - Validate external input at the boundary regardless of annotations (deserialized/wire data can violate them): `ArgumentNullException.ThrowIfNull`, range/format guards. ## 1a. In-band sentinels vs `int?` and the Try pattern `String.IndexOf` returns `-1` for not-found, and `Int32.TryParse` sets its `out` parameter to **zero on failure** — *"contains the … value equivalent … if the conversion succeeded, or zero if the conversion failed"* (learn.microsoft.com, `System.Int32.TryParse`, .NET 10; some overloads say *"an undefined value on failure"*). So `TryParse` is the right shape — the `bool` return is out-of-band — but the `out` value **is** an in-band sentinel the moment the `bool` is ignored. - §1's nullability contract extends to value types: `int?`/`Nullable<T>` is the answer for an absent number, not `-1` or `0`. `int?` participates in `HasValue`/pattern matching; a magic `int` participates in nothing. - Prefer `TryParse` over `Parse`, and **use the `bool`** — `if (int.TryParse(s, out var n))`, never `int.TryParse(s, out var n); use(n);`. A discarded `bool` converts a correct API into the class in `sota-architecture` rules/02 §8a. - Nullable reference types cover references only; NRT being on says nothing about a `-1` in an `int`. Don't let a clean NRT build read as absence being modeled. - Audit: `grep -rnE 'return -1;|out var [a-z]+\);' --include='*.cs' src/` and comparisons where one operand is `-1`-filtered. ## 2. Immutability & value semantics - Prefer immutable public types (records, `init` properties, `IReadOnlyList<T>`/ `IReadOnlyDictionary<T>` return types). Don't expose mutable internal collections — return read-only views/copies. - Choose `struct`/`record struct` for small, short-lived values; `class`/`record` for entities and larger objects (`rules/05` for the perf trade-offs). ## 3. Disposal & resource lifetime - Everything `IDisposable`/`IAsyncDisposable` is scoped with `using`/`await using` or owned by a DI lifetime — never a manual `Dispose()` you can skip on an exception. Implement the dispose pattern correctly (and `IAsyncDisposable` for async cleanup). - **`HttpClient`**: never `new HttpClient()` per call (socket exhaustion) — use `IHttpClientFactory` (typed/named clients) or a single long-lived instance. - Don't dispose objects you don't own (e.g. injected/DI-managed singletons, `HttpClient` from the factory). ## 4. Exceptions in the contract - Throw specific BCL exceptions (`ArgumentException`, `InvalidOperationException`, `ArgumentNullException`) before custom ones; document thrown types. Don't leak low-level exceptions across an abstraction — wrap, preserving `InnerException`. - Validate arguments with guard helpers (`ArgumentNullException.ThrowIfNull`, `ArgumentOutOfRangeException.ThrowIf...`). ## 5. Dependency injection & options - Use the built-in `Microsoft.Extensions.DependencyInjection` container; register with the correct **lifetime** (`Singleton`/`Scoped`/`Transient`). Classic bug: a `Scoped` (e.g. `DbContext`) captured by a `Singleton` → captive dependency / cross-request state. Don't inject `IServiceProvider` and resolve manually (service-locator anti-pattern) except at composition roots. - Constructor injection over property/field; avoid mutable static state (MEDIUM — concurrency + testability hazard). Use the **options pattern** (`IOptions<T>`/`IOptionsMonitor<T>`) for configuration, validated on start (`ValidateOnStart`). ## 6. Visibility & API surface - Keep the public surface minimal: `internal` by default, `public` deliberately; `sealed` classes not designed for inheritance. Use `InternalsVisibleTo` for test access rather than widening visibility. ## Audit checklist ```bash # HttpClient per-call — HIGH (socket exhaustion) grep -rnE 'new HttpClient\(' --include='*.cs' . | head # prefer IHttpClientFactory # IDisposable not in using — MEDIUM grep -rnE 'new (SqlConnection|FileStream|StreamReader|StreamWriter|MemoryStream|HttpResponseMessage)\(' --include='*.cs' . \ | head # verify using/await using # DI lifetime bugs — MEDIUM/HIGH (captive dependency) grep -rnE 'AddSingleton|AddScoped|AddTransient' --include='*.cs' . | head grep -rnE 'GetService|GetRequiredService|IServiceProvider' --include='*.cs' . | head # service locator? # Mutable static state — MEDIUM grep -rnE 'static (?!readonly|class|void|async|partial)[A-Za-z<>\[\]?]+ [A-Za-z]' --include='*.cs' . | head # throw ex / swallow — MEDIUM (see rules/01) grep -rnE 'throw ex;' --include='*.cs' . # Mutable collection exposed — LOW grep -rnE 'public (List|Dictionary|HashSet)<' --include='*.cs' . | head # prefer IReadOnly* / encapsulate # In-band sentinels (§1a) — `int?` over a magic int; TryParse's bool is the signal grep -rnE 'return -1;' --include='*.cs' . # producer; prefer int? grep -rnE 'TryParse\([^)]*out var [a-z]+\);' --include='*.cs' . # bool DISCARDED -> out is 0 on failure ``` -
03-async-concurrency.md 3.8 KB
# 03 — Async/await and concurrency `async`/`await` is the .NET concurrency model, and misusing it causes the two classic production failures: **deadlocks** (blocking on async under a sync context) and **thread-pool starvation** (sync-over-async at scale). The rules are mechanical — follow them. Reference: [async guidance](https://learn.microsoft.com/en-us/dotnet/csharp/asynchronous-programming/). ## 1. Async all the way; never block on async - Once a call chain is async, keep it async to the entry point. **Never** block: `.Result`, `.Wait()`, `.GetAwaiter().GetResult()` on a Task in request/UI/hot paths deadlocks under a sync context (classic ASP.NET/UI) and starves the thread pool under load. CRITICAL/HIGH depending on reachability. - Expose async APIs (`...Async`) for I/O; don't wrap sync I/O in `Task.Run` to "make it async" on the server (it just burns a thread). ## 2. ConfigureAwait - In **library** code (no dependence on a sync context), `await ... .ConfigureAwait(false)` so continuations don't capture/marshal back to a context — avoids deadlocks and overhead. In app code (ASP.NET Core has no sync context; modern UI differs) it matters less, but libraries should always do it. ## 3. async void and exceptions - **`async void`** only for event handlers. Elsewhere it's fire-and-forget with unobservable exceptions that can crash the process — use `async Task`. A non-handler `async void` is a MEDIUM–HIGH finding. - Don't fire-and-forget Tasks without observing them (lost exceptions, no back-pressure). If intentional, document and handle faults. ## 4. CancellationToken everywhere - Flow a `CancellationToken` through every async method and pass it to inner calls (I/O, EF Core, HttpClient); honor it (`ThrowIfCancellationRequested`, or the token-aware API). Endpoints should bind the request-aborted token. Ignoring cancellation wastes work and delays shutdown. ## 5. Concurrency primitives - Shared mutable state needs synchronization: `lock` (don't `await` inside a `lock` — use `SemaphoreSlim` for async mutual exclusion), `Interlocked` for counters, `Concurrent*` collections (`ConcurrentDictionary`) over manual locking. Immutability avoids the problem. - `System.Threading.Channels` (`Channel<T>`) for producer/consumer with back-pressure; `IAsyncEnumerable<T>` + `await foreach` for async streams. - `Task.WhenAll` for parallel awaits (observe all exceptions — `WhenAll` aggregates); `Parallel.ForEachAsync` for bounded data parallelism. Don't spin unbounded concurrent work — bound it (`SemaphoreSlim`, channel, `Parallel` options). - `ValueTask` for very hot, often-synchronous paths — but don't await a `ValueTask` twice or store it (`rules/05`). ## Audit checklist ```bash # Blocking on async — CRITICAL/HIGH (deadlock / thread-pool starvation) grep -rnE '\.(Result|Wait\(\))|GetAwaiter\(\)\.GetResult\(\)' --include='*.cs' . | head grep -rnE 'Task\.Run\(' --include='*.cs' . | head # sync wrapped as async on server? # async void (non-handler) — MEDIUM/HIGH grep -rnE 'async void ' --include='*.cs' . | grep -viE 'EventHandler|_Click|on[A-Z]' | head # Missing ConfigureAwait(false) in libraries — MEDIUM grep -rnE 'await ' --include='*.cs' . | grep -v 'ConfigureAwait' | head # in library projects # Cancellation not flowed — MEDIUM grep -rnE 'async Task[<A-Za-z, >]* [A-Za-z]+\([^)]*\)' --include='*.cs' . | grep -v 'CancellationToken' | head # await inside lock — HIGH (won't compile for lock, but SemaphoreSlim misuse / sync-over-async) grep -rnE 'lock\s*\(' --include='*.cs' . | head grep -rnE 'new (Dictionary|List)<' --include='*.cs' . | grep -i 'static\|shared' # non-concurrent shared # Unbounded parallelism — MEDIUM (verify bounding) grep -rnE 'Task\.WhenAll|Parallel\.(For|ForEach)' --include='*.cs' . | head ``` -
04-security.md 7 KB
# 04 — Security: injection, deserialization, ASP.NET Core, crypto The CLR is memory-safe, so the dominant .NET vulnerabilities are **injection, unsafe deserialization, auth gaps, and crypto misuse**. Treat every byte from network/file/DB/config as untrusted. Reference: [OWASP .NET cheat sheet](https://cheatsheetseries.owasp.org/cheatsheets/DotNet_Security_Cheat_Sheet.html), [ASP.NET Core security](https://learn.microsoft.com/en-us/aspnet/core/security/). ## 1. SQL injection - **EF Core**: LINQ is parameterized and safe. `FromSql`/`ExecuteSql` (the *interpolated* `FromSqlInterpolated`-style) parameterize interpolated values. **`FromSqlRaw`/`ExecuteSqlRaw` with string concatenation/interpolation is CRITICAL** — they don't parameterize a built string. - **Dapper / ADO.NET**: always pass parameters (`new { id }` / `SqlParameter`), never concatenate input into the SQL text. Identifiers (table/column/ORDER BY) can't be parameters — allowlist them. ## 2. Deserialization - **`BinaryFormatter` is removed in .NET 9+** (the API throws `PlatformNotSupportedException`); it was a notorious RCE vector. Never reintroduce it (or `NetDataContractSerializer`, `SoapFormatter`, `LosFormatter`, `ObjectStateFormatter`) — CRITICAL on sight. - **JSON**: prefer `System.Text.Json` with known types. Newtonsoft `TypeNameHandling.Auto/All/Objects` (or `System.Text.Json` with an unrestricted polymorphic type resolver) on untrusted input enables gadget-style RCE — don't. Bind to explicit DTOs. - **`XmlSerializer`/`DataContractSerializer`** with attacker-controlled types is risky; disable DTD processing on XML readers (XXE) — `XmlReaderSettings { DtdProcessing = DtdProcessing.Prohibit, XmlResolver = null }`. ## 3. Command / path / other injection - **OS command**: avoid shelling out; if you must, use `ProcessStartInfo` with `ArgumentList` (no `UseShellExecute`, no concatenated `Arguments`/shell). - **Path traversal**: combine with a known root and verify the resolved `Path.GetFullPath` stays under it; reject `..`. Don't pass user input straight to file APIs. - **LDAP/XPath/regex (ReDoS)**: parameterize/escape; bound regex with timeouts (`Regex` `matchTimeout`) on untrusted input. ## 4. ASP.NET Core authn/authz & web - **AuthZ on every non-public endpoint**: `[Authorize]`/policies/role checks / endpoint authorization; default-deny. Missing auth is HIGH. - **Antiforgery** for cookie-authenticated state-changing requests (`[ValidateAntiForgeryToken]` / the antiforgery middleware). **CORS** locked to specific origins — never `AllowAnyOrigin()` with credentials. - **Passkeys**: ASP.NET Core Identity has built-in passkey (WebAuthn) support in .NET 10+ — prefer them for new interactive logins (depth: `sota-code-security`, `sota-identity-access`). - Validate/bind model input (data annotations / explicit validation); don't over-post (use DTOs/`[Bind]` allowlists, not the EF entity directly). Set security headers/HSTS; don't leak stack traces in production responses. - **Secrets**: never in source/`appsettings.json` committed to git — use user secrets (dev), env, or a vault (`sota-secrets-management`); don't log them. - **Runtime patch level is an audit surface**: the memory-safe runtime's residual risk includes framework CVEs — e.g. CVE-2025-55315 (Kestrel HTTP request smuggling, fixed in 8.0.21/9.0.10/10.0 RC2) and CVE-2026-45591 (SignalR/Blazor Server MessagePack nested-array DoS, fixed in 8.0.28/9.0.17/10.0.9, June 2026). Self-contained/AOT-published apps embed the framework — they need a rebuild and redeploy, not just host patching. ## 5. Cryptography & transport - **Randomness**: `System.Security.Cryptography.RandomNumberGenerator` (e.g. `RandomNumberGenerator.GetBytes`) for tokens/keys/IVs/salts — never `System.Random` (HIGH). - **Symmetric**: AES-GCM (`AesGcm`) for authenticated encryption; never ECB, never unauthenticated CBC. **Hashing**: SHA-256+; passwords via a KDF (`Rfc2898DeriveBytes`/PBKDF2, or Argon2/bcrypt via a library) — never plain MD5/SHA-1 (HIGH). - Use ASP.NET Core **Data Protection** for at-rest tokens/cookies rather than hand-rolled crypto — and keep the package patched: `Microsoft.AspNetCore.DataProtection` 10.0.0–10.0.6 let attackers forge authentication cookies and decrypt protected payloads (CVE-2026-40372, fixed in **10.0.7**). Patching alone isn't enough after exposure: forged artifacts stay valid, so revoke the key ring (`RevokeAllKeys()`) and rotate tokens/API keys issued during the vulnerable window. Constant-time compare (`CryptographicOperations.FixedTimeEquals`) for MACs/tokens. - **Post-quantum**: .NET 10 ships PQC in the BCL — `MLKem` (FIPS 203) plus `MLDsa`/`SlhDsa`/`CompositeMLDsa` (FIPS 204/205; still `[Experimental]`, SYSLIB5006), backed by OpenSSL 3.5+ or Windows CNG with PQC support. For new long-lived signatures/key exchange, plan migration on these built-ins rather than unvetted packages. - **TLS**: never disable validation — `ServerCertificateCustomValidationCallback` returning `true` (or `HttpClientHandler` accepting all certs) is HIGH/CRITICAL. ## Audit checklist ```bash # SQL injection — CRITICAL grep -rnE 'FromSqlRaw|ExecuteSqlRaw' --include='*.cs' . | head grep -rnE '(FromSqlRaw|ExecuteSqlRaw|CommandText|new SqlCommand)\([^)]*(\+|\$")' --include='*.cs' . grep -rnE '\.Query[^(]*\(\s*\$?"[^"]*\{' --include='*.cs' . # Dapper string-interpolated SQL # Deserialization — CRITICAL grep -rnE 'BinaryFormatter|NetDataContractSerializer|LosFormatter|SoapFormatter|ObjectStateFormatter' --include='*.cs' . grep -rnE 'TypeNameHandling\.(Auto|All|Objects|Arrays)' --include='*.cs' . # XXE / command / path — HIGH/CRITICAL grep -rnE 'DtdProcessing|XmlResolver|new XmlDocument|XmlReader' --include='*.cs' . | head grep -rnE 'Process\.Start|ProcessStartInfo|UseShellExecute' --include='*.cs' . | head # Auth / CORS / antiforgery — HIGH grep -rnE 'AllowAnyOrigin|AllowAnyHeader|AllowAnyMethod' --include='*.cs' . grep -rnLE '\[Authorize\]|RequireAuthorization|\[AllowAnonymous\]' --include='*Controller.cs' . | head # endpoints w/o auth? # Crypto misuse — HIGH grep -rnE '\bnew Random\(|System\.Random' --include='*.cs' . | grep -iE 'token|key|iv|salt|nonce|password|secret' grep -rnE 'MD5|SHA1|TripleDES|\bDES\b|CipherMode\.ECB' --include='*.cs' . grep -rnE 'ServerCertificateCustomValidationCallback|RemoteCertificateValidationCallback' --include='*.cs' . | head # Secrets in config/source — HIGH grep -rniE '(password|pwd|secret|apikey|api_key|connectionstring)\s*[=:]' appsettings*.json --include='*.cs' . | head # Vulnerable framework/package patch levels — HIGH grep -rnE 'Microsoft\.AspNetCore\.DataProtection' --include='*.csproj' --include='packages.lock.json' . # 10.0.0–10.0.6 = CVE-2026-40372 (need 10.0.7+); if exposed while vulnerable: key ring revoked + tokens rotated? dotnet --list-runtimes # ASP.NET Core < 8.0.21/9.0.10 (CVE-2025-55315) or < 8.0.28/9.0.17/10.0.9 (CVE-2026-45591)? Check container base-image tags; self-contained/AOT apps need rebuild # Static security analysis: enable security CA rules + a SAST (rules/06) ``` -
05-performance.md 4 KB
# 05 — Performance: GC, allocations, spans, AOT .NET performance is mostly about **allocation pressure and the GC**, plus using the modern low-allocation primitives. Measure with BenchmarkDotNet and a profiler before optimizing. Cross-reference `sota-performance` for methodology. ## 1. The GC and allocation pressure - The .NET GC is generational (gen0/1/2) with a separate **Large Object Heap** (objects ≥ 85,000 bytes). High allocation rate → frequent gen0 collections; large/pinned objects fragment the LOH. The win is usually **allocate less**, not tune the GC. - **Server GC** (`<ServerGarbageCollection>true`) for throughput on multi-core servers; workstation GC for client/low-latency-single-core. Know which you're running. Don't tune GC knobs before profiling shows GC is the bottleneck. ## 2. Reduce allocations - Avoid needless allocations on hot paths: reuse buffers (`ArrayPool<T>`), avoid LINQ allocations in tight loops (closures, iterators, intermediate collections), avoid boxing (value type → `object`/non-generic API), and avoid `params object[]` / string concatenation in loops (`StringBuilder`, or `string.Create`/interpolated handlers). - Prefer `struct`/`record struct` for small short-lived values to keep them off the heap — but beware large structs being copied (pass by `in`/`ref`). ## 3. Span and friends - `Span<T>`/`ReadOnlySpan<T>`/`Memory<T>` for slicing arrays/strings/stack buffers **without copying** — parsing, formatting, buffer processing. `stackalloc` for small fixed buffers. C# 14 broadens span conversions (`rules/01`). - `Utf8` APIs, `System.Text.Json` source-gen, and `IBufferWriter<T>` for low-allocation I/O. Pool large/reused buffers with `ArrayPool`/`MemoryPool`. ## 4. Async & throughput - Async done right scales I/O (`rules/03`); done wrong (blocking, sync-over-async) it starves the thread pool. `ValueTask`/`ValueTask<T>` for very hot, frequently-synchronous paths to avoid a `Task` allocation — but never await a `ValueTask` twice, store it, or block on it. ## 5. Measuring - **BenchmarkDotNet** for microbenchmarks — it handles warmup/JIT, isolates runs, and reports allocations (`[MemoryDiagnoser]`). Never `Stopwatch` around a loop. Profile with `dotnet-trace`/`dotnet-counters`/Visual Studio profiler / PerfView for allocation and GC analysis. Report distributions (`sota-performance`). ## 6. Native AOT & trimming - **Native AOT** (`<PublishAot>true`) ahead-of-time compiles to a native binary: fast startup, low memory, small footprint — ideal for CLIs, serverless, and containerized microservices. .NET 10 improves AOT (broader support, smaller/ faster). Costs: no JIT/runtime codegen, so **reflection/dynamic loading must be trim-safe** (use source generators; annotate with `IsAotCompatible`); not all libraries are AOT/trim-compatible. Test the published binary. - Choose AOT for startup/footprint-bound workloads; stay on the JIT for long-running throughput-bound compute where peak JIT throughput wins. ## Audit checklist ```bash # Allocation/boxing on hot paths — LOW/MEDIUM (verify with profiler) grep -rnE '\+ ?"' --include='*.cs' . | grep -iE 'for ?\(|foreach|while' # string concat in loops grep -rnE 'string\.Format|\$"' --include='*.cs' . | head # hot-path formatting grep -rnE '\.ToList\(\)|\.ToArray\(\)' --include='*.cs' . | head # needless materialization in loops # Server GC configured for a server app? grep -rnE 'ServerGarbageCollection|ConcurrentGarbageCollection' *.csproj runtimeconfig* 2>/dev/null # Span/pooling opportunities (hot path) — LOW grep -rnE 'new byte\[|Substring\(|Split\(' --include='*.cs' . | head # Span/ArrayPool candidates # Benchmark hygiene — verify BenchmarkDotNet, not Stopwatch loops grep -rnE 'Stopwatch' --include='*.cs' . | grep -i bench | head grep -rnE '\[Benchmark\]|MemoryDiagnoser' --include='*.cs' . || echo "no BenchmarkDotNet benchmarks" # Native AOT / trimming used? verify trim-safety grep -rnE 'PublishAot|PublishTrimmed|IsAotCompatible' *.csproj 2>/dev/null ``` -
06-build-tooling-ci.md 4.6 KB
# 06 — Build, tooling, supply chain, CI .NET project safety lives in the build: nullable + analyzers as errors, NuGet locking and CVE scanning, and consistent CI gates. This file owns build/test *mechanics*; test **strategy** (suite shape, doubles, coverage philosophy) lives in `sota-testing`. ## 1. Targeting & project hygiene - Target the current LTS TFM (`<TargetFramework>net10.0</TargetFramework>`); pin the SDK with `global.json` so every machine/CI builds with the same version. - Centralize settings in **`Directory.Build.props`** and dependency versions in **`Directory.Packages.props`** (Central Package Management) so versions are consistent and reviewed in one place. - Enable broadly: `<Nullable>enable</Nullable>`, `<TreatWarningsAsErrors>true</TreatWarningsAsErrors>`, `<AnalysisLevel>latest-Recommended</AnalysisLevel>` (or `All`), `<EnableNETAnalyzers>true</EnableNETAnalyzers>`, `<ImplicitUsings>enable</ImplicitUsings>`. ## 2. Analyzers & formatting - **Roslyn analyzers** (.NET analyzers ship in the SDK) catch correctness, reliability, and **security CA rules** (CA2100 SQL injection, CA53xx/CA54xx crypto, CA2300-series deserialization). Run as errors in CI. Add focused analyzers (e.g. for async) where useful. - **`dotnet format --verify-no-changes`** in CI so style/whitespace never enters review. EditorConfig holds the rules. - Consider a SAST (the security CA rules, or a dedicated scanner) for deeper taint analysis on web apps. ## 3. NuGet supply chain - **Lock dependencies**: enable `<RestorePackagesWithLockFile>true</...>` to generate `packages.lock.json`, and restore with **`--locked-mode`** in CI so builds are reproducible and a changed transitive dependency fails loudly. - **Package Source Mapping** (`nuget.config`) so each package only resolves from its intended feed — defeats **dependency confusion** (an internal name resolving from nuget.org). Restrict feeds to trusted sources over HTTPS. - **NuGetAudit** checks every restore against advisory data automatically (since NuGet 6.8/.NET 8 SDK; `NuGetAuditMode` defaults to `all` — transitive included — on net10.0+ projects). Gate CI by making the audit warnings errors: `<WarningsAsErrors>$(WarningsAsErrors);NU1903;NU1904</WarningsAsErrors>` (high/critical; add NU1901/NU1902 to be stricter). Configure `auditSources` in `nuget.config` if your feed lacks vulnerability data; suppress an accepted advisory explicitly with a `NuGetAuditSuppress` item (last resort); remediate with `dotnet package update --vulnerable`. Keep `dotnet list package --vulnerable --include-transitive` as the ad-hoc query; Dependabot/external scanners complement, not replace. - Verify **signed packages**; generate an **SBOM** for releases. See `sota-devsecops`. ## 4. CI gates - A PR build runs: `dotnet build` with warnings-as-errors (nullable + analyzers), `dotnet test` (xUnit/NUnit/MSTest) with coverage (coverlet) and a threshold, `dotnet format --verify-no-changes`, `--locked-mode` restore, and the NuGetAudit gate (NU190x as errors). Fail on any. - **Testcontainers for .NET** for real-dependency integration tests (DB/broker) — wire them here; *strategy* is `sota-testing`. Run with fixed culture/timezone for determinism (`InvariantGlobalization` where applicable). - Build deterministically (`<Deterministic>true</Deterministic>`, ContinuousIntegrationBuild) and produce symbols. ## Audit checklist ```bash # TFM/SDK pinned? settings centralized? grep -rnE '<TargetFramework' **/*.csproj 2>/dev/null | head ls global.json Directory.Build.props Directory.Packages.props 2>/dev/null | grep -q . || echo "no central build config" # Nullable + warnings-as-errors + analyzers? grep -rniE 'TreatWarningsAsErrors|<Nullable>|EnableNETAnalyzers|AnalysisLevel' **/*.csproj Directory.Build.props 2>/dev/null \ || echo "nullable/analyzers/warnings-as-errors not enforced — HIGH" # NuGet locking + source mapping + CVE scan? ls packages.lock.json 2>/dev/null && grep -rn 'RestorePackagesWithLockFile' **/*.csproj Directory.Build.props 2>/dev/null \ || echo "no NuGet lockfile — supply-chain risk" grep -rniE 'packageSourceMapping|locked-mode|NuGetAudit|NU190[0-9]|auditSources|dependabot' \ nuget.config Directory.Build.props **/*.csproj .github/ *.yml 2>/dev/null \ || echo "no source mapping / locked restore / NuGetAudit CI gate" # Formatting + deterministic build in CI? grep -rniE 'dotnet format|verify-no-changes|Deterministic|ContinuousIntegrationBuild' .github/ *.yml **/*.csproj 2>/dev/null | head # Test runner + coverage? grep -rniE 'xunit|nunit|mstest|coverlet|testcontainers' **/*.csproj 2>/dev/null | head ```
-
-
SKILL.md 8.9 KB
--- name: sota-dotnet description: >- State-of-the-art C# / .NET engineering rules (2026 baseline, .NET 10 LTS / C# 14) that Claude applies when writing or auditing .NET code. Covers modern idioms (records, nullable reference types, pattern matching, spans, file-scoped namespaces), API/null/immutability/`IDisposable` design, async/await & concurrency (ConfigureAwait, channels, cancellation, TPL), security (OWASP .NET, deserialization — BinaryFormatter removed in .NET 9, EF/Dapper SQL injection, ASP.NET Core auth, Data Protection, crypto), performance (GC, Span and Memory, BenchmarkDotNet, Native AOT), and build/tooling/CI (dotnet CLI, NuGet lockfiles & supply chain, Roslyn analyzers, nullable). Trigger keywords - C#, .NET, dotnet, ASP.NET Core, async, await, Task, record, nullable reference types, Span, EF Core, Dapper, LINQ, NuGet, Roslyn analyzer, BinaryFormatter, Native AOT, BenchmarkDotNet, IDisposable, ConfigureAwait. Use for BOTH building .NET services/libraries and auditing them. --- # SOTA C# / .NET (2026) Expert-level rules for producing and auditing production .NET. The runtime is memory-safe, so risk concentrates in **injection, deserialization, async correctness, and dependency supply chain**. Baseline: **.NET 10 LTS** (released Nov 2025, supported to Nov 2028) and **C# 14** (records, nullable reference types, pattern matching, spans, extension members, the `field` keyword) — flag where a control needs a specific version. Every rule states the *why*; every rules file ends with an audit checklist of grep/analyzer patterns. ## Purpose Two consumers, one source of truth: - **BUILD mode** — generating C#/.NET: follow the rules as defaults. Enable **nullable reference types** and treat analyzer warnings as errors; prefer immutability and the async-all-the-way model. Deviate only with a comment. - **AUDIT mode** — reviewing existing code: hunt violations with the audit checklists, classify by severity, report in the finding format below. SQL string-building and legacy deserialization are presumed exploitable. ## BUILD mode 1. Before writing, read the rules files relevant to the task (see index). A web API touching untrusted input + a DB + async needs `02`, `03`, `04`. 2. Apply the **top-10 non-negotiables** (below) unconditionally. 3. New projects: target the current LTS (`net10.0`), `<Nullable>enable</Nullable>`, `<TreatWarningsAsErrors>true</TreatWarningsAsErrors>`, `<AnalysisLevel>latest-Recommended</AnalysisLevel>`, NuGet lockfile + `RestoreLockedMode` in CI, and `dotnet format` from day one (`rules/06`). 4. Async all the way down — never block on async (`.Result`/`.Wait()`/ `GetAwaiter().GetResult()`) (`rules/03`). Use `CancellationToken` end to end. 5. Prefer the BCL and well-known libraries; parameterize all data access; use the framework's auth/Data Protection rather than rolling your own (`rules/04`). 6. When you take a sharp path (reflection, `unsafe`, `DynamicMethod`, suppressing a nullable/analyzer warning), leave a `// NOTE(sota):` explaining why. ## AUDIT mode Work each relevant rules file's audit checklist against the target. Run the greps and the Roslyn analyzers (incl. the security CA rules); confirm hits manually. Check the dependency tree against known-CVE databases. ### Severity conventions | Severity | Meaning | Examples | |---|---|---| | **CRITICAL** | Exploitable on reachable input | SQL via string interpolation/concat into `FromSqlRaw`/`ExecuteSqlRaw`/Dapper, `BinaryFormatter`/`NetDataContractSerializer`/`LosFormatter` or JSON `TypeNameHandling.All` on untrusted data, command injection, deserialization gadget | | **HIGH** | Likely incident or security weakness | Missing auth on an endpoint, disabled cert validation (`ServerCertificateCustomValidationCallback => true`), MD5/SHA-1 or `DES`/ECB for security, `Random` for tokens, blocking on async causing deadlock/thread-pool starvation, secrets in config/source | | **MEDIUM** | Correctness/maintainability hazard | `async void` (non-handler), missing `ConfigureAwait(false)` in a library, `IDisposable` not disposed / no `using`, nullable warnings suppressed with `!`, swallowed exceptions, mutable static state | | **LOW** | Idiom/perf debt | Sync-over-collection LINQ on hot path, needless allocations/boxing, `class` where a `record`/`struct` fits, not using `Span`/pooling on hot path | | **INFO** | Style/doc/hygiene | formatting, naming, missing XML docs, nullable annotations absent (not enabled) | ### Finding format ``` [SEVERITY] File.cs:LINE — short title Rule: rules/NN-name.md § section Evidence: the offending line(s), verbatim Impact: one sentence — what executes/leaks/deadlocks, under what input Fix: concrete replacement code or action Effort: trivial | small | medium | large ``` Group findings by severity, CRITICAL first. End with: counts per severity, the three highest-leverage fixes, and which checklists/analyzers were run. ## Rules index | File | Read this when... | |---|---| | `rules/01-idioms.md` | Writing/reviewing any C#: records & `record struct`, nullable reference types, pattern matching/`switch` expressions, spans, LINQ discipline, `var`, expression vs statement, file-scoped namespaces, error handling, modern C# 12–14 features | | `rules/02-design-api.md` | Designing types/APIs: nullable reference type discipline, `int?` over a magic `int` and **using `TryParse`'s `bool` (its `out` is `0` on failure)**, immutability, `IDisposable`/`IAsyncDisposable` and `using`, exceptions, value vs reference types, `internal`/visibility, DI (the built-in container), options pattern | | `rules/03-async-concurrency.md` | Anything `async`/`Task`/threads: async-all-the-way, never block (`.Result`/`.Wait()`), `ConfigureAwait(false)` in libraries, `CancellationToken` flow, `async void`, `Channel<T>`, `IAsyncEnumerable`, TPL/`Parallel`, thread-safety, `ValueTask` | | `rules/04-security.md` | Any input crossing a trust boundary: SQL (EF Core/Dapper parameterization), legacy serializers (`BinaryFormatter` removed .NET 9) + JSON `TypeNameHandling`, command/path injection, ASP.NET Core authn/authz, antiforgery/CORS, Data Protection, crypto (`RandomNumberGenerator`, AES-GCM), secrets; OWASP .NET | | `rules/05-performance.md` | Latency/throughput/memory work: GC (gen/SOH/LOH, server vs workstation), allocation reduction, `Span<T>`/`Memory<T>`/`ArrayPool`, `struct`/`record struct`, BenchmarkDotNet, async overhead, Native AOT / trimming, string handling | | `rules/06-build-tooling-ci.md` | Setting up or auditing build/CI: SDK/TFM targeting, `Directory.Build.props`, nullable + warnings-as-errors, Roslyn analyzers (incl. security CA rules), `dotnet format`, NuGet lockfiles + supply chain (lock mode, source mapping, signed packages, CVE scan), SBOM. **Test *strategy* lives in `sota-testing`; this owns .NET build/test mechanics (xUnit/NUnit, Testcontainers).** | ## Top-10 non-negotiables 1. **No string-built SQL.** Parameterize: EF Core LINQ or parameters (`FromSql`/interpolated `FromSql`, never `FromSqlRaw`/`ExecuteSqlRaw` with concatenation); Dapper with parameters. Interpolated/concatenated SQL is CRITICAL. (`rules/04`) 2. **No unsafe deserialization.** `BinaryFormatter` is removed in .NET 9+ (throws); never reintroduce it or `NetDataContractSerializer`/`LosFormatter`/`SoapFormatter`, and never use `Json.NET` `TypeNameHandling.Auto/All` or `JsonSerializer` with an unrestricted type resolver on untrusted data. Use `System.Text.Json` with known types. (`rules/04`) 3. **Async all the way; never block on async.** No `.Result`, `.Wait()`, `.GetAwaiter().GetResult()` on a hot/request path (deadlock + thread-pool starvation). `async void` only for event handlers. (`rules/03`) 4. **Flow `CancellationToken` end to end** through async APIs and honor it. (`rules/03`) 5. **`ConfigureAwait(false)` in library code** (code with no sync context that doesn't need to resume on the original context). (`rules/03`) 6. **Nullable reference types enabled and honored.** `<Nullable>enable</Nullable>`; don't paper over warnings with the null-forgiving `!`. (`rules/01`, `rules/02`) 7. **Deterministic disposal.** Everything `IDisposable`/`IAsyncDisposable` is in a `using`/`await using` or owned by a DI-managed lifetime — `HttpClient` via `IHttpClientFactory`, not new-per-call. (`rules/02`) 8. **Crypto uses the right primitives.** `RandomNumberGenerator` (never `Random`) for tokens/keys/IVs; AES-GCM (not ECB); no MD5/SHA-1 for security; ASP.NET Core **Data Protection** for at-rest tokens; never disable TLS cert validation. (`rules/04`) 9. **AuthN/AuthZ enforced server-side** on every non-public endpoint (`[Authorize]`/policies/endpoint auth), antiforgery for cookie-auth POSTs, CORS locked to known origins. (`rules/04`) 10. **Analyzers + nullable + lockfile gate CI.** `TreatWarningsAsErrors`, Roslyn analyzers (incl. security CA rules), `dotnet format --verify-no-changes`, NuGet locked-mode restore + CVE scan. (`rules/06`)
Comments (0)
Sign in to join the conversation.
Reviews (0)
No reviews yet.
No comments yet.