ASP.NET Core Security Audit
Audit ASP.NET Core applications (.NET 6, 7, 8, 9).
When this skill applies
- Reviewing ASP.NET Core middleware pipeline
- Auditing controllers / Minimal API endpoints
- Reviewing EF Core for SQL injection
- Checking authentication / authorization setup
- Auditing
appsettings.json for secret handling
Workflow
Follow ../_shared/audit-workflow.md.
Phase 1: Stack detection
find . -name '*.csproj' -not -path '*/bin/*' -not -path '*/obj/*'
find . -name 'Program.cs' -not -path '*/bin/*' -not -path '*/obj/*'
dotnet --version 2>/dev/null
Phase 2: Inventory
# Middleware pipeline
grep -rn 'app\.Use\|app\.Map\|builder\.Services' Program.cs Startup.cs 2>/dev/null
# Authorize attributes
grep -rn '\[Authorize\|\[AllowAnonymous' . --include='*.cs'
# EF queries
grep -rn 'FromSqlRaw\|ExecuteSqlRaw\|FromSqlInterpolated' . --include='*.cs'
# Configuration
ls appsettings*.json 2>/dev/null
Phase 3: Detection — the checks
Middleware pipeline order
// Program.cs (.NET 6+ minimal hosting)
var app = builder.Build();
app.UseHttpsRedirection();
app.UseHsts(); // HSTS
app.UseStaticFiles(); // Static files before auth (intentional)
app.UseRouting();
app.UseCors(policyName); // After routing, before auth
app.UseAuthentication(); // Authentication
app.UseAuthorization(); // Authorization (after authentication)
app.UseAntiforgery(); // .NET 8+ explicit
app.MapControllers();
app.Run();
- DNC-MW-1
UseAuthentication before UseAuthorization. Reverse = authorization runs before identity is set.
- DNC-MW-2
UseCors between UseRouting and UseAuthorization.
- DNC-MW-3
UseHsts enabled in production (typically inside if (!app.Environment.IsDevelopment())).
- DNC-MW-4
UseHttpsRedirection so HTTP → HTTPS.
Authentication
- DNC-AUTH-1
builder.Services.AddAuthentication(...) configured; scheme matches what controllers expect.
- DNC-AUTH-2 JWT:
AddJwtBearer configured with TokenValidationParameters:options.TokenValidationParameters = new TokenValidationParameters {
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = config["Jwt:Issuer"],
ValidAudience = config["Jwt:Audience"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(config["Jwt:Key"])),
ClockSkew = TimeSpan.Zero, // optional, tighter
};
- DNC-AUTH-3
ValidateLifetime, ValidateIssuer, ValidateAudience, ValidateIssuerSigningKey ALL true. Any false = bypass.
- DNC-AUTH-4 Identity uses Argon2/PBKDF2 (default PBKDF2 acceptable; verify iteration count).
- DNC-AUTH-5 Cookie authentication:
SecurePolicy = CookieSecurePolicy.Always, HttpOnly = true, SameSite = SameSiteMode.Lax.
Authorization
Antiforgery (CSRF)
- DNC-CSRF-1
[ValidateAntiForgeryToken] on Razor Pages POST handlers, or global filter for MVC.
- DNC-CSRF-2 API endpoints using cookie auth: CSRF protection still required. Use
[ValidateAntiForgeryToken] or send via header.
- DNC-CSRF-3 Bearer token APIs (no cookie auth): CSRF not needed.
Model binding — overposting
SQL injection (EF Core)
CORS
- DNC-COR-1 CORS policy defines specific origins, not
AllowAnyOrigin for credentialed APIs.
- DNC-COR-2
AllowCredentials() only with specific origins (combined with AllowAnyOrigin is rejected by spec).
Configuration / secrets
- DNC-CFG-1
appsettings.json and appsettings.Development.json don't contain real production secrets.
- DNC-CFG-2 Production secrets via environment variables, Azure Key Vault, AWS Secrets Manager — accessed through
IConfiguration.
- DNC-CFG-3 User Secrets used for local dev (
dotnet user-secrets set ...) — never in production.
- DNC-CFG-4 Connection strings without password baked in (use integrated auth or env vars).
Data Protection
ASP.NET Core's Data Protection provides keys for cookies, antiforgery tokens, etc.
- DNC-DP-1 Data Protection keys persisted to durable storage (Azure Blob, Redis, filesystem) — not in-memory if you have multiple instances.
- DNC-DP-2 Keys encrypted at rest if filesystem-based.
- DNC-DP-3
SetApplicationName set if multiple apps share the key ring.
File uploads
- DNC-UP-1
IFormFile size limited via RequestSizeLimit attribute or Kestrel.Limits.MaxRequestBodySize.
- DNC-UP-2 Content type validated via byte sniffing (use a library like
SixLabors.ImageSharp for images), not trusted from header.
- DNC-UP-3 Filenames sanitized; use UUIDs.
Logging
- DNC-LOG-1 Sensitive parameter logging disabled; EF Core
EnableSensitiveDataLogging NEVER true in production.
- DNC-LOG-2
[LogProperties] on sensitive DTOs excludes password/secret fields.
Exception handling
- DNC-EX-1 Production uses
app.UseExceptionHandler("/error") (not UseDeveloperExceptionPage).
- DNC-EX-2 Custom error response doesn't include stack traces.
- DNC-EX-3
app.UseStatusCodePages configured if custom 404/500 pages needed.
Headers
- DNC-HDR-1
app.UseSecurityHeaders(...) (NWebsec or similar) OR explicit middleware setting CSP, X-Content-Type-Options, X-Frame-Options.
- DNC-HDR-2
Server header removed (Kestrel: AddServerHeader = false).
Minimal API specifics
- DNC-MA-1 Minimal API endpoints use
.RequireAuthorization() for protected routes.
- DNC-MA-2 Endpoint filters for cross-cutting validation.
Razor Pages / MVC views
- DNC-RAZ-1 Razor auto-encodes
@Model.Foo. @Html.Raw(...) and Html.Raw(Model.Foo) skip encoding — review usages.
- DNC-RAZ-2 No
@(Model.Bar) patterns rendering unencoded HTML from user input.
Dependencies
- DNC-DEP-1 Target framework current (.NET 8 LTS or .NET 9 STS).
- DNC-DEP-2
dotnet list package --vulnerable clean.
- DNC-DEP-3 Old Newtonsoft.Json JSON serializer with TypeNameHandling.Auto / All on untrusted input = RCE. Use System.Text.Json or restrict TypeNameHandling.
Phase 4: Triage
Critical: [Authorize] missing on admin endpoints; JWT validation with any of the 4 validates false; FromSqlRaw with string interpolation; ASP.NET Core version with known CVE.
Phase 5: Report
Use ../_shared/findings-schema.md. Prefix IDs with DNC-.
1---2name: dotnet-aspnetcore-security3description: Security audit for ASP.NET Core applications including authentication middleware ordering, [Authorize] attribute usage, antiforgery, model binding (overposting), EF Core raw queries, data protection key management, appsettings.json secrets, identity/JWT setup, and .NET-specific patterns. Use this skill whenever the user mentions ASP.NET Core, .NET, dotnet, [Authorize], EF Core, Entity Framework, appsettings.json, IdentityServer, JWT in .NET, Minimal API, or asks "audit my .NET app", "ASP.NET Core security review". Trigger when the codebase contains `*.csproj`, `Program.cs`, `Startup.cs`, or `appsettings*.json`.4---56# ASP.NET Core Security Audit78Audit ASP.NET Core applications (.NET 6, 7, 8, 9).910## When this skill applies1112- Reviewing ASP.NET Core middleware pipeline13- Auditing controllers / Minimal API endpoints14- Reviewing EF Core for SQL injection15- Checking authentication / authorization setup16- Auditing `appsettings.json` for secret handling1718## Workflow1920Follow `../_shared/audit-workflow.md`.2122### Phase 1: Stack detection2324```bash25find . -name '*.csproj' -not -path '*/bin/*' -not -path '*/obj/*'26find . -name 'Program.cs' -not -path '*/bin/*' -not -path '*/obj/*'27dotnet --version 2>/dev/null28```2930### Phase 2: Inventory3132```bash33# Middleware pipeline34grep -rn 'app\.Use\|app\.Map\|builder\.Services' Program.cs Startup.cs 2>/dev/null3536# Authorize attributes37grep -rn '\[Authorize\|\[AllowAnonymous' . --include='*.cs'3839# EF queries40grep -rn 'FromSqlRaw\|ExecuteSqlRaw\|FromSqlInterpolated' . --include='*.cs'4142# Configuration43ls appsettings*.json 2>/dev/null44```4546### Phase 3: Detection — the checks4748#### Middleware pipeline order4950```csharp51// Program.cs (.NET 6+ minimal hosting)52var app = builder.Build();5354app.UseHttpsRedirection();55app.UseHsts(); // HSTS56app.UseStaticFiles(); // Static files before auth (intentional)57app.UseRouting();58app.UseCors(policyName); // After routing, before auth59app.UseAuthentication(); // Authentication60app.UseAuthorization(); // Authorization (after authentication)61app.UseAntiforgery(); // .NET 8+ explicit62app.MapControllers();63app.Run();64```6566- **DNC-MW-1** `UseAuthentication` before `UseAuthorization`. Reverse = authorization runs before identity is set.67- **DNC-MW-2** `UseCors` between `UseRouting` and `UseAuthorization`.68- **DNC-MW-3** `UseHsts` enabled in production (typically inside `if (!app.Environment.IsDevelopment())`).69- **DNC-MW-4** `UseHttpsRedirection` so HTTP → HTTPS.7071#### Authentication7273- **DNC-AUTH-1** `builder.Services.AddAuthentication(...)` configured; scheme matches what controllers expect.74- **DNC-AUTH-2** JWT: `AddJwtBearer` configured with `TokenValidationParameters`:75 ```csharp76 options.TokenValidationParameters = new TokenValidationParameters {77 ValidateIssuer = true,78 ValidateAudience = true,79 ValidateLifetime = true,80 ValidateIssuerSigningKey = true,81 ValidIssuer = config["Jwt:Issuer"],82 ValidAudience = config["Jwt:Audience"],83 IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(config["Jwt:Key"])),84 ClockSkew = TimeSpan.Zero, // optional, tighter85 };86 ```87- **DNC-AUTH-3** `ValidateLifetime`, `ValidateIssuer`, `ValidateAudience`, `ValidateIssuerSigningKey` ALL true. Any false = bypass.88- **DNC-AUTH-4** Identity uses Argon2/PBKDF2 (default PBKDF2 acceptable; verify iteration count).89- **DNC-AUTH-5** Cookie authentication: `SecurePolicy = CookieSecurePolicy.Always`, `HttpOnly = true`, `SameSite = SameSiteMode.Lax`.9091#### Authorization9293- **DNC-AZ-1** `[Authorize]` on controllers requiring auth. Or global filter:94 ```csharp95 builder.Services.AddControllers(opts => {96 var policy = new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build();97 opts.Filters.Add(new AuthorizeFilter(policy));98 });99 ```100 Public actions then need `[AllowAnonymous]`.101- **DNC-AZ-2** Policy-based authz (`[Authorize(Policy = "AdminOnly")]`) with handlers checking specific claims.102- **DNC-AZ-3** Resource-based authz via `IAuthorizationService.AuthorizeAsync(user, resource, policy)` for per-instance checks.103- **DNC-AZ-4** No `[Authorize]` missing from sensitive endpoints — common bug class.104105#### Antiforgery (CSRF)106107- **DNC-CSRF-1** `[ValidateAntiForgeryToken]` on Razor Pages POST handlers, or global filter for MVC.108- **DNC-CSRF-2** API endpoints using cookie auth: CSRF protection still required. Use `[ValidateAntiForgeryToken]` or send via header.109- **DNC-CSRF-3** Bearer token APIs (no cookie auth): CSRF not needed.110111#### Model binding — overposting112113- **DNC-MB-1** Action methods accept dedicated DTOs/ViewModels, NOT entity classes:114 ```csharp115 // BAD — User has IsAdmin property, attacker sets it116 public IActionResult Create([FromBody] User user) { ... }117 118 // GOOD119 public IActionResult Create([FromBody] CreateUserDto dto) { ... }120 ```121- **DNC-MB-2** `[Bind("Name,Email")]` attribute used to limit binding when entity must be used.122- **DNC-MB-3** Validation attributes (`[Required]`, `[StringLength]`, `[RegularExpression]`) on DTOs.123124#### SQL injection (EF Core)125126- **DNC-SQL-1** `FromSqlRaw($"SELECT * FROM Users WHERE Id = {id}")` is injection. Use:127 ```csharp128 // GOOD129 context.Users.FromSqlInterpolated($"SELECT * FROM Users WHERE Id = {id}");130 // OR131 context.Users.FromSqlRaw("SELECT * FROM Users WHERE Id = {0}", id);132 ```133- **DNC-SQL-2** `ExecuteSqlRaw` similarly — interpolated or parameterized only.134- **DNC-SQL-3** EF Core LINQ queries parameterize automatically — safe.135136#### CORS137138- **DNC-COR-1** CORS policy defines specific origins, not `AllowAnyOrigin` for credentialed APIs.139- **DNC-COR-2** `AllowCredentials()` only with specific origins (combined with `AllowAnyOrigin` is rejected by spec).140141#### Configuration / secrets142143- **DNC-CFG-1** `appsettings.json` and `appsettings.Development.json` don't contain real production secrets.144- **DNC-CFG-2** Production secrets via environment variables, Azure Key Vault, AWS Secrets Manager — accessed through `IConfiguration`.145- **DNC-CFG-3** User Secrets used for local dev (`dotnet user-secrets set ...`) — never in production.146- **DNC-CFG-4** Connection strings without password baked in (use integrated auth or env vars).147148#### Data Protection149150ASP.NET Core's Data Protection provides keys for cookies, antiforgery tokens, etc.151152- **DNC-DP-1** Data Protection keys persisted to durable storage (Azure Blob, Redis, filesystem) — not in-memory if you have multiple instances.153- **DNC-DP-2** Keys encrypted at rest if filesystem-based.154- **DNC-DP-3** `SetApplicationName` set if multiple apps share the key ring.155156#### File uploads157158- **DNC-UP-1** `IFormFile` size limited via `RequestSizeLimit` attribute or `Kestrel.Limits.MaxRequestBodySize`.159- **DNC-UP-2** Content type validated via byte sniffing (use a library like `SixLabors.ImageSharp` for images), not trusted from header.160- **DNC-UP-3** Filenames sanitized; use UUIDs.161162#### Logging163164- **DNC-LOG-1** Sensitive parameter logging disabled; EF Core `EnableSensitiveDataLogging` NEVER true in production.165- **DNC-LOG-2** `[LogProperties]` on sensitive DTOs excludes password/secret fields.166167#### Exception handling168169- **DNC-EX-1** Production uses `app.UseExceptionHandler("/error")` (not `UseDeveloperExceptionPage`).170- **DNC-EX-2** Custom error response doesn't include stack traces.171- **DNC-EX-3** `app.UseStatusCodePages` configured if custom 404/500 pages needed.172173#### Headers174175- **DNC-HDR-1** `app.UseSecurityHeaders(...)` (NWebsec or similar) OR explicit middleware setting CSP, X-Content-Type-Options, X-Frame-Options.176- **DNC-HDR-2** `Server` header removed (Kestrel: `AddServerHeader = false`).177178#### Minimal API specifics179180- **DNC-MA-1** Minimal API endpoints use `.RequireAuthorization()` for protected routes.181- **DNC-MA-2** Endpoint filters for cross-cutting validation.182183#### Razor Pages / MVC views184185- **DNC-RAZ-1** Razor auto-encodes `@Model.Foo`. `@Html.Raw(...)` and `Html.Raw(Model.Foo)` skip encoding — review usages.186- **DNC-RAZ-2** No `@(Model.Bar)` patterns rendering unencoded HTML from user input.187188#### Dependencies189190- **DNC-DEP-1** Target framework current (.NET 8 LTS or .NET 9 STS).191- **DNC-DEP-2** `dotnet list package --vulnerable` clean.192- **DNC-DEP-3** Old Newtonsoft.Json JSON serializer with TypeNameHandling.Auto / All on untrusted input = RCE. Use System.Text.Json or restrict TypeNameHandling.193194### Phase 4: Triage195196Critical: `[Authorize]` missing on admin endpoints; JWT validation with any of the 4 validates false; FromSqlRaw with string interpolation; ASP.NET Core version with known CVE.197198### Phase 5: Report199200Use `../_shared/findings-schema.md`. Prefix IDs with `DNC-`.