# Regla: Seguridad — C# / .NET

ASP.NET Core tiene un buen punto de partida en seguridad, pero muchas de sus
protecciones deben habilitarse explícitamente. Esta regla cubre las configuraciones
obligatorias para que el punto de partida seguro sea la norma, no la excepción.

---

## OWASP Top 10 para .NET

Antes de cerrar cualquier ticket que toque autenticación, autorización o datos,
verificar que no se introduce ninguna vulnerabilidad del OWASP Top 10:

- **Inyección (A03)**: EF Core usa parámetros por defecto — nunca interpolar variables
  en `FromSqlRaw()` ni `ExecuteSqlRaw()`.
- **Control de acceso roto (A01)**: `[Authorize]` en todos los controllers y endpoints
  por defecto; `[AllowAnonymous]` explícito solo en los que lo requieren.
- **Fallos criptográficos (A02)**: HTTPS obligatorio, datos sensibles cifrados en reposo
  con `IDataProtectionProvider`, no con implementaciones propias.
- **Componentes vulnerables (A06)**: `dotnet list package --vulnerable` en CI.
- **Fallos de logging y monitoreo (A09)**: No loggear datos sensibles (passwords, tokens, PII).

---

## Entity Framework Core: queries parametrizadas por defecto

EF Core parametriza automáticamente. Los errores ocurren cuando se sale de ese camino:

```csharp
// MAL — interpolación directa en SQL crudo, susceptible a inyección
var email = userInput; // potencialmente malicioso
var facturas = await _db.Facturas
    .FromSqlRaw($"SELECT * FROM facturas WHERE email = '{email}'")
    .ToListAsync();

// MAL — aunque se use parámetro, la interpolación de C# no es SQL parameter
var facturas = await _db.Database
    .ExecuteSqlRawAsync($"DELETE FROM facturas WHERE id = {id}");

// BIEN — FromSqlInterpolated usa parámetros seguros automáticamente
var facturas = await _db.Facturas
    .FromSqlInterpolated($"SELECT * FROM facturas WHERE email = {email}")
    .ToListAsync();

// BIEN — parámetro explícito con FromSqlRaw
var facturas = await _db.Facturas
    .FromSqlRaw("SELECT * FROM facturas WHERE email = {0}", email)
    .ToListAsync();

// MEJOR — LINQ con EF Core (parámetros automáticos siempre)
var facturas = await _db.Facturas
    .Where(f => f.ClienteEmail == email)
    .ToListAsync();
```

- Regla simple: si hay interpolación de strings en SQL → es un bug de seguridad.
- `FromSqlInterpolated` es la excepción segura cuando se necesita SQL crudo.

---

## ASP.NET Core: [Authorize] por defecto

```csharp
// Program.cs — política de autorización por defecto
builder.Services.AddAuthorization(opts =>
{
    // Todo endpoint requiere autenticación por defecto
    opts.FallbackPolicy = new AuthorizationPolicyBuilder()
        .RequireAuthenticatedUser()
        .Build();
});

// Los endpoints públicos deben marcarse EXPLÍCITAMENTE
[AllowAnonymous]
app.MapGet("/health", () => Results.Ok("healthy"));

// Los endpoints con roles específicos
[Authorize(Roles = "Admin,Facturador")]
app.MapPost("/facturas", async (IMediator mediator) => { ... });
```

- El modelo por defecto es: todo requiere autenticación, lo público se declara explícitamente.
- NUNCA depender de que los endpoints "no están en producción" para no protegerlos.
- `[Authorize(Policy = "nombre")]` para autorización basada en policies (más flexible que roles).

---

## Anti-forgery tokens en formularios

```csharp
// En páginas Razor o controladores MVC
// Program.cs — habilitado por defecto en Razor Pages
builder.Services.AddRazorPages();
// o con configuración explícita
builder.Services.AddControllersWithViews(opts =>
{
    opts.Filters.Add(new AutoValidateAntiforgeryTokenAttribute());
});
```

```html
<!-- En el formulario Razor -->
<form method="post">
    @Html.AntiForgeryToken()
    <!-- campos del formulario -->
</form>
```

- APIs que usan JWT en el header `Authorization` son inmunes a CSRF por diseño —
  documentar explícitamente en el controller que no se necesita CSRF protection y por qué:
  ```csharp
  [ApiController] // implica [ValidateAntiForgeryToken] deshabilitado para JSON APIs con JWT
  [Route("api/[controller]")]
  // CSRF no aplica: esta API usa JWT en Authorization header, no cookies
  public class FacturasController : ControllerBase { ... }
  ```

---

## User secrets para desarrollo local

```bash
# Inicializar user secrets en el proyecto
dotnet user-secrets init

# Agregar secreto para desarrollo
dotnet user-secrets set "ConnectionStrings:Default" "Server=localhost;Database=dev;..."
dotnet user-secrets set "Jwt:SecretKey" "clave-solo-para-desarrollo"
```

- User secrets nunca se versionan en git — se almacenan en `%APPDATA%/Microsoft/UserSecrets/`.
- Solo para ambiente de desarrollo. En staging y producción: Azure Key Vault o AWS Secrets Manager.
- El archivo `appsettings.Development.json` NUNCA contiene secretos reales — solo configuración
  no sensible de desarrollo.

---

## Azure Key Vault / AWS Secrets Manager en producción

```csharp
// Program.cs — integración con Azure Key Vault
if (builder.Environment.IsProduction())
{
    var keyVaultUri = new Uri(builder.Configuration["KeyVaultUri"]!);
    builder.Configuration.AddAzureKeyVault(
        keyVaultUri,
        new DefaultAzureCredential()); // usa Managed Identity en Azure
}
```

- `DefaultAzureCredential` usa Managed Identity automáticamente en Azure — sin API keys.
- Las variables de entorno y configuración de producción NUNCA se almacenan en el repositorio.
- Los secretos de Key Vault se cargan en el `IConfiguration` estándar — el código no cambia.

---

## No usar BinaryFormatter

`BinaryFormatter` fue deprecado y tiene vulnerabilidades conocidas de ejecución
remota de código (RCE) via deserialización maliciosa.

```csharp
// PROHIBIDO — BinaryFormatter puede ejecutar código arbitrario
var formatter = new BinaryFormatter();
objeto = (MiTipo)formatter.Deserialize(stream); // CVE-2022-34716 y otros

// BIEN — alternativas seguras
// Para datos internos estructurados:
var json = JsonSerializer.Serialize(objeto);
var restaurado = JsonSerializer.Deserialize<MiTipo>(json);

// Para datos binarios de alto rendimiento:
// MessagePack, protobuf-net (con tipos explícitos registrados)
```

- El compilador emite SYSLIB0011 cuando se usa `BinaryFormatter` — tratar como error.
- Si se necesita serialización binaria: usar `System.Text.Json`, `MessagePack` o `ProtoBuf`.

---

## Content Security Policy headers

```csharp
// Program.cs — agregar headers de seguridad en todas las respuestas
app.Use(async (context, next) =>
{
    context.Response.Headers.Append("X-Content-Type-Options", "nosniff");
    context.Response.Headers.Append("X-Frame-Options", "DENY");
    context.Response.Headers.Append("X-XSS-Protection", "1; mode=block");
    context.Response.Headers.Append(
        "Content-Security-Policy",
        "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'");
    context.Response.Headers.Append(
        "Strict-Transport-Security",
        "max-age=31536000; includeSubDomains");
    await next();
});
```

- Usar el paquete `NWebsec.AspNetCore.Middleware` para configuración más completa de CSP.
- Ajustar la CSP según los recursos externos que la aplicación necesita cargar.
- Probar la CSP con https://csp-evaluator.withgoogle.com/.

---

## Rate limiting con el middleware nativo de .NET 7+

```csharp
// Program.cs
builder.Services.AddRateLimiter(opts =>
{
    // Límite global por IP
    opts.GlobalLimiter = PartitionedRateLimiter.Create<HttpContext, string>(
        context => RateLimitPartition.GetFixedWindowLimiter(
            context.Connection.RemoteIpAddress?.ToString() ?? "unknown",
            _ => new FixedWindowRateLimiterOptions
            {
                PermitLimit = 100,
                Window = TimeSpan.FromMinutes(1)
            }));

    // Límite estricto para endpoints de autenticación
    opts.AddPolicy("autenticacion", context =>
        RateLimitPartition.GetFixedWindowLimiter(
            context.Connection.RemoteIpAddress?.ToString() ?? "unknown",
            _ => new FixedWindowRateLimiterOptions
            {
                PermitLimit = 5,
                Window = TimeSpan.FromMinutes(1)
            }));

    opts.OnRejected = async (context, ct) =>
    {
        context.HttpContext.Response.StatusCode = 429;
        await context.HttpContext.Response.WriteAsync(
            "Demasiadas solicitudes. Intenta de nuevo en un minuto.", ct);
    };
});

app.UseRateLimiter();
```

- .NET 7+ tiene rate limiting nativo — no requiere paquetes externos para casos comunes.
- Aplicar políticas más estrictas en endpoints de autenticación (`/login`, `/register`).

---

## Checklist de seguridad antes de hacer merge

- [ ] Sin interpolación de strings en `FromSqlRaw` o `ExecuteSqlRaw`
- [ ] Todos los endpoints protegidos con `[Authorize]` o `[AllowAnonymous]` explícito
- [ ] Sin secretos en `appsettings*.json`, `user-secrets` solo en desarrollo
- [ ] Azure Key Vault o equivalente configurado para producción
- [ ] `BinaryFormatter` no utilizado en ningún lugar del código nuevo
- [ ] Headers de seguridad configurados (`X-Content-Type-Options`, `X-Frame-Options`, CSP)
- [ ] Rate limiting en endpoints de autenticación
- [ ] `dotnet list package --vulnerable` pasa sin vulnerabilidades HIGH o CRITICAL
- [ ] Logs sin datos sensibles (passwords, tokens, datos personales)
