dotnet-security

.NET Security - Quick Reference

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "dotnet-security" with this command: npx skills add claude-dev-suite/claude-dev-suite/claude-dev-suite-claude-dev-suite-dotnet-security

.NET Security - Quick Reference

When NOT to Use This Skill

  • General OWASP concepts - Use owasp or owasp-top-10 skill

  • Java security - Use java-security skill

  • Python security - Use python-security skill

  • Secrets management - Use secrets-management skill

Deep Knowledge: Use mcp__documentation__fetch_docs with technology: dotnet for ASP.NET Core security documentation.

Dependency Auditing

.NET built-in audit

dotnet list package --vulnerable

Detailed audit with transitive dependencies

dotnet list package --vulnerable --include-transitive

Check outdated packages

dotnet list package --outdated

Snyk for .NET

snyk test

CI/CD Integration

GitHub Actions

  • name: Security audit run: | dotnet list package --vulnerable --include-transitive dotnet tool install -g snyk snyk test

NuGet.config Security

<configuration> <packageSources> <!-- Use only trusted sources --> <clear /> <add key="nuget.org" value="https://api.nuget.org/v3/index.json" /> </packageSources> <packageSourceCredentials> <!-- Use environment variables for private feeds --> </packageSourceCredentials> </configuration>

ASP.NET Core Security Configuration

Program.cs Security Setup

var builder = WebApplication.CreateBuilder(args);

// Security headers builder.Services.AddHsts(options => { options.MaxAge = TimeSpan.FromDays(365); options.IncludeSubDomains = true; options.Preload = true; });

// CORS configuration builder.Services.AddCors(options => { options.AddPolicy("Production", policy => { policy.WithOrigins("https://myapp.com") .AllowCredentials() .WithMethods("GET", "POST", "PUT", "DELETE") .WithHeaders("Authorization", "Content-Type"); }); });

// Authentication builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { options.TokenValidationParameters = new TokenValidationParameters { ValidateIssuer = true, ValidateAudience = true, ValidateLifetime = true, ValidateIssuerSigningKey = true, ValidIssuer = builder.Configuration["Jwt:Issuer"], ValidAudience = builder.Configuration["Jwt:Audience"], IssuerSigningKey = new SymmetricSecurityKey( Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"]!)) }; });

// Rate limiting (.NET 7+) builder.Services.AddRateLimiter(options => { options.AddFixedWindowLimiter("login", limiter => { limiter.Window = TimeSpan.FromMinutes(15); limiter.PermitLimit = 5; limiter.QueueLimit = 0; }); });

var app = builder.Build();

// Security middleware order matters app.UseHsts(); app.UseHttpsRedirection(); app.UseCors("Production"); app.UseAuthentication(); app.UseAuthorization(); app.UseRateLimiter();

Security Headers Middleware

app.Use(async (context, next) => { context.Response.Headers.Add("X-Content-Type-Options", "nosniff"); context.Response.Headers.Add("X-Frame-Options", "DENY"); context.Response.Headers.Add("X-XSS-Protection", "0"); // Use CSP instead context.Response.Headers.Add("Referrer-Policy", "strict-origin-when-cross-origin"); context.Response.Headers.Add("Content-Security-Policy", "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'");

await next();

});

SQL Injection Prevention

Entity Framework Core - Safe

// SAFE - LINQ queries var user = await context.Users .FirstOrDefaultAsync(u => u.Email == email);

// SAFE - Parameterized raw SQL var users = await context.Users .FromSqlRaw("SELECT * FROM Users WHERE Email = {0}", email) .ToListAsync();

// SAFE - Interpolated (converted to parameters) var users = await context.Users .FromSqlInterpolated($"SELECT * FROM Users WHERE Email = {email}") .ToListAsync();

Entity Framework Core - UNSAFE

// UNSAFE - String concatenation var query = $"SELECT * FROM Users WHERE Email = '{email}'"; // NEVER! var users = await context.Users.FromSqlRaw(query).ToListAsync();

// UNSAFE - FormattableString with raw var users = await context.Users .FromSqlRaw($"SELECT * FROM Users WHERE Email = '{email}'") // NEVER! .ToListAsync();

Dapper - Safe

// SAFE - Anonymous parameters var user = await connection.QueryFirstOrDefaultAsync<User>( "SELECT * FROM Users WHERE Email = @Email", new { Email = email } );

// SAFE - DynamicParameters var parameters = new DynamicParameters(); parameters.Add("Email", email); var user = await connection.QueryFirstOrDefaultAsync<User>( "SELECT * FROM Users WHERE Email = @Email", parameters );

XSS Prevention

Razor Pages (Auto-encoding)

<!-- SAFE - Auto-encoded --> <p>@Model.UserInput</p>

<!-- UNSAFE - Raw HTML --> <p>@Html.Raw(Model.UserInput)</p> <!-- Avoid if possible -->

Manual Sanitization

using Ganss.Xss;

var sanitizer = new HtmlSanitizer(); sanitizer.AllowedTags.Add("p"); sanitizer.AllowedTags.Add("b"); sanitizer.AllowedTags.Add("i");

string safeHtml = sanitizer.Sanitize(userInput);

API Response Encoding

using System.Text.Encodings.Web;

var encoder = HtmlEncoder.Default; var safeString = encoder.Encode(userInput);

Authentication & Authorization

JWT Token Generation

public class JwtService { private readonly IConfiguration _config;

public JwtService(IConfiguration config) => _config = config;

public string GenerateToken(User user)
{
    var key = new SymmetricSecurityKey(
        Encoding.UTF8.GetBytes(_config["Jwt:Key"]!));
    var credentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);

    var claims = new[]
    {
        new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
        new Claim(ClaimTypes.Email, user.Email),
        new Claim(ClaimTypes.Role, user.Role)
    };

    var token = new JwtSecurityToken(
        issuer: _config["Jwt:Issuer"],
        audience: _config["Jwt:Audience"],
        claims: claims,
        expires: DateTime.UtcNow.AddHours(1),
        signingCredentials: credentials
    );

    return new JwtSecurityTokenHandler().WriteToken(token);
}

}

Password Hashing with Identity

// Use ASP.NET Core Identity's PasswordHasher var hasher = new PasswordHasher<User>();

// Hash password string hashed = hasher.HashPassword(user, password);

// Verify password var result = hasher.VerifyHashedPassword(user, hashed, password); if (result == PasswordVerificationResult.Success) { // Password matches }

Authorization Policies

// Program.cs builder.Services.AddAuthorization(options => { options.AddPolicy("AdminOnly", policy => policy.RequireRole("Admin"));

options.AddPolicy("ResourceOwner", policy =>
    policy.Requirements.Add(new ResourceOwnerRequirement()));

});

// Custom requirement handler public class ResourceOwnerHandler : AuthorizationHandler<ResourceOwnerRequirement, Resource> { protected override Task HandleRequirementAsync( AuthorizationHandlerContext context, ResourceOwnerRequirement requirement, Resource resource) { var userId = context.User.FindFirstValue(ClaimTypes.NameIdentifier); if (resource.OwnerId == userId) { context.Succeed(requirement); } return Task.CompletedTask; } }

// Controller usage [Authorize(Policy = "AdminOnly")] public IActionResult AdminDashboard() => View();

Input Validation

Data Annotations

public class CreateUserRequest { [Required] [EmailAddress] [StringLength(255)] public string Email { get; set; } = default!;

[Required]
[StringLength(128, MinimumLength = 12)]
[RegularExpression(@"^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&#x26;]).+$",
    ErrorMessage = "Password must contain uppercase, lowercase, number and special character")]
public string Password { get; set; } = default!;

[Required]
[StringLength(100, MinimumLength = 2)]
[RegularExpression(@"^[a-zA-Z\s\-']+$")]
public string Name { get; set; } = default!;

}

[HttpPost] public IActionResult CreateUser([FromBody] CreateUserRequest request) { if (!ModelState.IsValid) return BadRequest(ModelState);

// request is validated

}

FluentValidation

public class CreateUserValidator : AbstractValidator<CreateUserRequest> { public CreateUserValidator() { RuleFor(x => x.Email) .NotEmpty() .EmailAddress() .MaximumLength(255);

    RuleFor(x => x.Password)
        .NotEmpty()
        .MinimumLength(12)
        .MaximumLength(128)
        .Matches(@"[A-Z]").WithMessage("Must contain uppercase")
        .Matches(@"[a-z]").WithMessage("Must contain lowercase")
        .Matches(@"\d").WithMessage("Must contain digit")
        .Matches(@"[@$!%*?&#x26;]").WithMessage("Must contain special character");

    RuleFor(x => x.Name)
        .NotEmpty()
        .Length(2, 100)
        .Matches(@"^[a-zA-Z\s\-']+$");
}

}

Secure File Upload

[HttpPost("upload")] [RequestSizeLimit(10 * 1024 * 1024)] // 10 MB public async Task<IActionResult> Upload(IFormFile file) { // Validate content type var allowedTypes = new[] { "image/jpeg", "image/png", "application/pdf" }; if (!allowedTypes.Contains(file.ContentType)) return BadRequest("File type not allowed");

// Validate file extension
var allowedExtensions = new[] { ".jpg", ".jpeg", ".png", ".pdf" };
var extension = Path.GetExtension(file.FileName).ToLowerInvariant();
if (!allowedExtensions.Contains(extension))
    return BadRequest("File extension not allowed");

// Generate safe filename
var safeName = $"{Guid.NewGuid()}{extension}";
var uploadPath = Path.Combine(_uploadDirectory, safeName);

// Save file
await using var stream = new FileStream(uploadPath, FileMode.Create);
await file.CopyToAsync(stream);

return Ok(new { filename = safeName });

}

Secrets Management

User Secrets (Development)

Initialize user secrets

dotnet user-secrets init

Set secrets

dotnet user-secrets set "Jwt:Key" "your-secret-key" dotnet user-secrets set "ConnectionStrings:Default" "your-connection-string"

appsettings.json (DO NOT store secrets)

{ "Jwt": { "Issuer": "https://myapp.com", "Audience": "https://myapp.com" // Key should come from environment or secrets manager } }

Environment Variables

// Program.cs - Load from environment builder.Configuration.AddEnvironmentVariables();

// Access var jwtKey = builder.Configuration["Jwt:Key"] ?? throw new InvalidOperationException("JWT Key not configured");

Azure Key Vault Integration

builder.Configuration.AddAzureKeyVault( new Uri($"https://{vaultName}.vault.azure.net/"), new DefaultAzureCredential() );

Logging Security Events

public class SecurityLogger { private readonly ILogger<SecurityLogger> _logger;

public SecurityLogger(ILogger&#x3C;SecurityLogger> logger) => _logger = logger;

public void LogLoginAttempt(string username, bool success, string ipAddress)
{
    _logger.LogInformation(
        "Login attempt: User={Username}, Success={Success}, IP={IpAddress}",
        username, success, ipAddress
    );
}

public void LogAccessDenied(string userId, string resource, string ipAddress)
{
    _logger.LogWarning(
        "Access denied: User={UserId}, Resource={Resource}, IP={IpAddress}",
        userId, resource, ipAddress
    );
}

// NEVER log sensitive data
// _logger.LogInformation("Password: {Password}", password);  // NEVER!

}

Anti-Patterns

Anti-Pattern Why It's Bad Correct Approach

String interpolation in SQL SQL injection Use parameterized queries

Html.Raw(userInput)

XSS vulnerability Use default encoding

Storing secrets in appsettings Secret exposure Use User Secrets/Key Vault

[AllowAnonymous] everywhere No authentication Apply selectively

Disabling HTTPS redirection Man-in-the-middle Keep HTTPS enabled

Custom crypto implementation Weak encryption Use built-in libraries

Catching all exceptions Hides security issues Log and handle specifically

Quick Troubleshooting

Issue Likely Cause Solution

401 Unauthorized JWT validation failed Check issuer, audience, key

CORS error Origin not allowed Add origin to CORS policy

Rate limit triggered Too many requests Adjust rate limiter settings

Password validation fails Policy requirements Check Identity password options

NuGet vulnerability Outdated package Update to patched version

User Secrets not loading Not in Development Check ASPNETCORE_ENVIRONMENT

Security Scanning Commands

Dependency audit

dotnet list package --vulnerable --include-transitive

Security analyzers (add NuGet packages)

Microsoft.CodeAnalysis.NetAnalyzers

SecurityCodeScan.VS2019

Snyk

snyk test

Check for secrets

gitleaks detect trufflehog git file://.

SAST with Semgrep

semgrep --config=p/csharp .

Related Skills

  • OWASP Top 10:2025

  • OWASP General

  • Secrets Management

  • Supply Chain Security

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Security

rust-security

No summary provided by upstream source.

Repository SourceNeeds Review
Security

spring-security

No summary provided by upstream source.

Repository SourceNeeds Review
Security

go-security

No summary provided by upstream source.

Repository SourceNeeds Review