miércoles, 17 de junio de 2026

Programando en C# no. 15: ASP .NET Core

Recordemos lo que es .NET:

".NET es una plataforma de desarrollo gratuita, multiplataforma y de código abierto para crear diversos tipos de aplicaciones".
"Con .NET, puedes usar múltiples lenguajes, editores y bibliotecas para desarrollar aplicaciones web, móviles, de escritorio, juegos, IoT y mucho más".

En entregas pasadas vimos una comparativa entre Spring Boot y ASP .NET Core. Ahora veremos cómo crear una aplicación sencilla usando ésta tecnología.

Nos enfocaremos en las API mínimas para ASP.NET Core.

Las API mínimas son un enfoque simplificado para crear API HTTP rápidas con ASP.NET Core.

Puedes crear endpoints REST completamente funcionales con un mínimo de código y configuración. Evita el uso de la estructura tradicional y los controladores innecesarios declarando de forma fluida las rutas y acciones de la API.

API mínima en ASP .NET Core

Las API mínimas están diseñadas para crear API HTTP con dependencias mínimas. Son ideales para microservicios y aplicaciones que desean incluir solo los archivos, las características y las dependencias mínimas en ASP .NET Core.

Ventajas de ASP .NET Core:

  • Serialización sencilla: ASP.NET se diseñó para experiencias web modernas. Los puntos de conexión serializan automáticamente las clases en JSON con el formato correcto de serie. No se necesita ninguna configuración especial. Puede personalizar la serialización para los puntos de conexión que tienen requisitos únicos. 
  • Autenticación y autorización: para la seguridad, los puntos de conexión de API tienen compatibilidad integrada con tokens web JSON (JWT) estándar del sector. La autorización basada en directivas ofrece la flexibilidad necesaria para definir reglas de control de acceso eficaces en el código. 
  • Enrutamiento junto con el código: ASP.NET permite definir rutas y verbos alineados con el código mediante atributos. Los datos de la ruta de acceso de la solicitud, la cadena de consulta y el cuerpo de la solicitud se enlazan automáticamente a parámetros de método. 
  • HTTPS de forma predeterminada: HTTPS es una parte importante de las API web modernas y profesionales. Se basa en el cifrado de un extremo a otro para proporcionar privacidad y ayudar a garantizar que las llamadas API no se intercepten ni se modifiquen entre el cliente y el servidor.

Creando una minimal API con .NET

1. Abrimos una terminal y tecleamos:

$ dotnet --version
$ dotnet --list-sdks

Con esto obtenemos la versión de ``dotnet`` y los SDKs instalados.

2. Creamos un sencillo proyecto webapi:

$ dotnet new webapi -o MyApiSimple
$ cd MyApiSimple

Esto creará el proyecto tipo webapi y nos ubicará en el directorio principal del proyecto.

3. Modificamos el contenido del programa principal para que tenga solo lo necesario.

Program.cs

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/hola",()=> Results.Ok("alive"));

app.Run();

4. Modificamos el archivo ``MyApiSimple.http``:

@MyApiSimple_HostAddress = http://localhost:5058

GET {{MyApiSimple_HostAddress}}/hola/
Accept: application/json

###

El archivo de configuración ``MyApiSimple.csproj`` lucirá así:

<Project Sdk="Microsoft.NET.Sdk.Web">

  <PropertyGroup>
    <TargetFramework>net10.0</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.1" />
  </ItemGroup>

</Project>

5. Construimos y ejecutamos:

$ dotnet build
$ dotnet run

Abrimos un navegador en la ruta: http://localhost:5058/hola

Si todo va bien, entonces mostrará el siguiente mensaje:

alive

Continuemos.

Consumiendo una API

Lo siguiente a realizar es consumir una API externa. En este caso la del sitio: https://api.chucknorris.io/

Será una aplicación modular por lo que nuestro proyecto lucirá de esta manera:

MyApiSimple/

├── Program.cs
├── Services/
   └── ChuckService.cs
├── Models/
   └── ChuckJoke.cs

1. Crearemos una clase sencilla en el directorio Models:

ChuckJoke.cs

namespace MyApiSimple.Models
{
    public class ChuckJoke
    {
        public string? value { get; set; }
    }
}

Esta clase representa la respuesta JSON de la API.

2. Ahora crearemos la clase tipo service en el directorio Services:

ChuckService.cs

using System.Text.Json;
using MyApiSimple.Models;

namespace MyApiSimple.Services
{
    public class ChuckService
    {
        private readonly HttpClient _httpClient;

        public ChuckService(HttpClient httpClient)
        {
            _httpClient = httpClient;
        }

        public async Task<ChuckJoke?> GetRandomJokeAsync()
        {
            var url = "https://api.chucknorris.io/jokes/random?category=food";

            var response = await _httpClient.GetAsync(url);

            if (!response.IsSuccessStatusCode)
            {
                return null;
            }

            var content = await response.Content.ReadAsStringAsync();

            return JsonSerializer.Deserialize<ChuckJoke>(content);
        }
    }
}

Esta clase tipo service nos ayudará a encapsular la llamada HTTP.

3. Modificamos la clase principal, registrando el servicio y definiendo los endpoints:

Program.cs

using MyApiSimple.Services;

var builder = WebApplication.CreateBuilder(args);


builder.Services.AddHttpClient<ChuckService>();

var app = builder.Build();

app.MapGet("/hola", () => Results.Ok("alive"));


app.MapGet("/joke", async (ChuckService chuckService) =>
{
    var joke = await chuckService.GetRandomJokeAsync();

    if (joke == null)
    {
        return Results.Problem("Error al obtener el chiste");
    }

    return Results.Ok(joke);
});

app.Run();

4. Modificamos el archivo ``MyApiSimple.http``:

@MyApiSimple_HostAddress = http://localhost:5058

GET {{MyApiSimple_HostAddress}}/hola/
Accept: application/json


GET {{MyApiSimple_HostAddress}}/joke
Accept: application/json

###

5. Construimos y ejecutamos:

$ dotnet build
$ dotnet run

Abrimos un navegador en la ruta: http://localhost:5058/joke

Si todo va bien, entonces mostrará algún mensaje (aleatorio):

{
"value": "Most tough men eat nails for breakfast. Chuck Norris does all of his grocery shopping at Home Depot."
}

Agregaremos Swagger.

6. Modificamos el programa ``Program.cs``:

using MyApiSimple.Services;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddHttpClient<ChuckService>();
builder.Services.AddOpenApi(); 

var app = builder.Build();


if (app.Environment.IsDevelopment())
{
    app.MapOpenApi(); 
}

app.MapGet("/hola", () => Results.Ok("alive"))
   .WithName("Hola")
   .WithOpenApi();

app.MapGet("/joke", async (ChuckService chuckService) =>
{
    var joke = await chuckService.GetRandomJokeAsync();

    if (joke == null)
    {
        return Results.Problem("Error al obtener el chiste");
    }

    return Results.Ok(joke);
})
.WithName("GetJoke")
.WithOpenApi();

app.Run();

7. Construimos y ejecutamos:

$ dotnet build
$ dotnet run

Abrimos un navegador en la ruta: http://localhost:5058/openapi/v1.json

Esto es para ver el OpenAPI JSON, cuya salida será:

{
"openapi": "3.1.1",
"info": {
"title": "MyApiSimple | v1",
"version": "1.0.0"
},
"servers": [
{
"url": "http://localhost:5058/"
}
],
"paths": {
"/hola": {
"get": {
"tags": [
"MyApiSimple"
],
"operationId": "Hola",
"responses": {
"200": {
"description": "OK"
}
}
}
},
"/joke": {
"get": {
"tags": [
"MyApiSimple"
],
"operationId": "GetJoke",
"responses": {
"200": {
"description": "OK"
}
}
}
}
},
"tags": [
{
"name": "MyApiSimple"
}
]
}

8. Agregar Swagger UI:

$ dotnet add package Swashbuckle.AspNetCore

9. Modificaremos el programa principal:

Program.cs

using MyApiSimple.Services;

var builder = WebApplication.CreateBuilder(args);


builder.Services.AddHttpClient<ChuckService>();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();


if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.MapGet("/hola", () => Results.Ok("alive"))
   .WithName("Hola");

app.MapGet("/joke", async (ChuckService chuckService) =>
{
    var joke = await chuckService.GetRandomJokeAsync();

    return joke is not null 
        ? Results.Ok(joke)
        : Results.Problem("Error al obtener el chiste");
})
.WithName("GetJoke");

app.Run();

10. Construimos y ejecutamos:

$ dotnet build
$ dotnet run

Si todo va bien, abrimos el navegador en la ruta: http://localhost:5058/swagger

Lucirá de esta manera:

¡Hemos creado un proyecto WebApi con ASP .NET Core!

Continuaremos en próximas entregas hablando de este tema.

Enlaces:

https://codemonkeyjunior.blogspot.com/2019/05/crear-una-aplicacion-web-con-asp-net.html
https://codemonkeyjunior.blogspot.com/2025/11/el-roadmap-para-ser-desarrollador-c.html
https://codemonkeyjunior.blogspot.com/2025/11/asp-net-core-vs-spring-boot-cual-elegir.html
https://codemonkeyjunior.blogspot.com/2025/11/programando-en-c-no-12-asp-net-core.html
https://codemonkeyjunior.blogspot.com/2025/12/equivalencias-entre-un-proyecto-spring.html
https://dotnet.microsoft.com/en-us/learn/back-end-web-dev
https://www.ibm.com/mx-es/think/topics/api-endpoint
https://www.arsys.es/blog/guia-completa-para-el-diseno-de-restful-api-conceptos-y-mejores-practicas
https://robertdelwood.medium.com/learning-api-documentation-with-chuck-norris-jokes-1414435fb84e

martes, 16 de junio de 2026

Lo que es un agente de IA

En pocas palabras, un agente de IA es una herramienta capaz de automatizar tareas y servirse de otras aplicaciones para desempeñar actividades más complejas o elaboradas.

Estos sistemas autónomos pueden razonar, planificar y ejecutar acciones usando modelos de inteligencia artificial y herramientas externas.

En este blog ya hemos hablado de OpenClaw, un agente de IA de código abierto que actúa como un asistente personal avanzado. Además de OpenCode, un agente de codificación IA de código abierto.

En 2026, los principales agentes y plataformas de IA incluyen ChatGPT, Claude, Gemini, Perplexity, Midjourney, GitHub Copilot y nuevas propuestas como OpenClaw, Claude Cowork y OpenAI Frontier.

Los agentes de IA actúan de manera autónoma para cumplir objetivos, combinando:

  • Razonamiento (decidir qué hacer). 
  • Planificación (organizar pasos). 
  • Acción (usar herramientas, APIs o interactuar con usuarios).

La diferencia entre un chatbot y un agente de IA es que mientras un chatbot responde preguntas, un agente puede tomar decisiones, ejecutar tareas y coordinar múltiples recursos.

Comparativa entre agentes IA

Observemos la siguiente tabla:

Agente/Plataforma Tipo Fortalezas Ideal para
ChatGPT (OpenAI) Asistente conversacional Versatilidad, plugins, memoria Uso general, escritura, código
Claude (Anthropic) Asistente con contexto largo Manejo de documentos extensos, análisis profundo Profesionales, investigación
Gemini (Google) Multimodal integrado Conexión con ecosistema Google, búsqueda avanzada Productividad, integración empresarial
Perplexity Computer Agente en la nube Investigación con fuentes verificables Profesionales sin perfil técnico
Midjourney / DALL-E 3 Generadores de imágenes Creatividad visual, arte digital Diseño, marketing
GitHub Copilot Asistente de programación Autocompletado, sugerencias de código Desarrolladores
OpenClaw Agente personal self-hosted Control local, bajo costo Makers, usuarios avanzados
Claude Cowork Agente desktop con plugins Integración empresarial Equipos corporativos
OpenAI Frontier Plataforma enterprise Seguridad y escalabilidad Grandes empresas, sectores regulados
Kimi K2.5 Modelo multimodal Procesamiento masivo, swarm agents Investigación avanzada

Agentes IA y el Vibe Coding

En este blog hemos hablado de algunos aspectos positivos y negativos de las herramientas IA.

Un profesional puede servirse de estás herramientas para automatizar procesos y mejorar su productividad.

Una persona no profesional puede creer que se convertirá en profesional debido a ilusión que ofrece el uso de éstas.

Las empresas actualmente cuentan con licencias para sus empleados pues consideran que mejorará la productividad (y sus ingresos).

Hoy en día existen herramientas IA (agentes) especializados en investigación, programación, diseño, productividad y automatización.

Consideraciones y riesgos

Algunas cosas a tomar en consideración pueden ser:

  • Privacidad: Los agentes que operan en la nube requieren políticas claras de seguridad (ej. OpenAI Frontier cumple con SOC 2 e ISO 27001). 
  • Costo: Algunos agentes son gratuitos (self-hosted), pero los empresariales pueden costar cientos de dólares al mes. 
  • Madurez tecnológica: Herramientas como PicoClaw aún están en desarrollo y pueden ser inestables.

Hemos visto lo que es un agente de IA. Como se diferencian de un chatbot y las consideraciones en cuanto a la seguridad.

Continuaremos con este tema en próximas entregas.

Enlaces:

https://learn.microsoft.com/es-es/shows/ai-agents-for-beginners/
https://blogthinkbig.com/agentes-de-ia-rapidos-sencillos-automatizar-tareas
https://codemonkeyjunior.blogspot.com/2026/02/opencode-un-agente-para-escribir-codigo.html
https://opencode.ai/
https://codemonkeyjunior.blogspot.com/2026/01/claude-code-un-nuevo-enfoque-de-la.html
https://codemonkeyjunior.blogspot.com/2026/05/vibe-coding-cuando-el-programador-es-un.html

lunes, 8 de junio de 2026

LINQ: operaciones comunes

Continuamos con esta serie sobre LINQ y la lectura de archivos XML y JSON. Ahora profundizaremos un poco con las operaciones básicas que podemos realizar:

  • Where: Filtrar. 
  • Select: Proyecta campos. 
  • OrderBy: Ordenar. 
  • GroupBy: Agrupar y contar. 
  • Join: Unir colecciones. 
  • Any: Comprobar existencia. 
  • All: Comprobar condición en todos. 
  • Count: Devolver cantidad.

Consultas en archivos XML con LINQ

Realizaremos algunas consultas teniendo como entrada el siguiente archivo XML:

empleados.xml

<?xml version="1.0" encoding="UTF-8"?>
<Empleados>
<Empleado> 
<Id>4392552</Id>
<Nombre>Horacio</Nombre>
<Apellidos>Gomez Torres</Apellidos>
<Correo>horacio.gomez.tor@infotec.com</Correo>
<Salario>25000.00</Salario>
<Departamento>202</Departamento>
</Empleado>

<Empleado>
<Id>4292856</Id>
<Nombre>Veronica</Nombre>
<Apellidos>Uribe Gomora</Apellidos>
<Correo>veronica.uribe.gom@infotec.com</Correo>
<Salario>30000.00</Salario>
<Departamento>204</Departamento>
</Empleado>


<Empleado>
<Id>4701330</Id>
<Nombre>Karla</Nombre>
<Apellidos>Perez Perez</Apellidos>
<Correo>karla.perez.per@infotec.com</Correo>
<Salario>28000.00</Salario>
<Departamento>200</Departamento>
</Empleado>

<Empleado>
<Id>4701366</Id>
<Nombre>Juan</Nombre>
<Apellidos>Archundia Lara</Apellidos>
<Correo>juan.archundia.lar@infotec.com</Correo>
<Salario>38000.00</Salario>
<Departamento>204</Departamento>
</Empleado>

</Empleados>

Mapearemos las etiquetas XML en una clase C#.

Empleado.cs

using System.Xml.Serialization;

namespace model
{
    public class Empleado
    {
        public string Id { get; set; } = string.Empty;
        public string Nombre { get; set; } = string.Empty;
        public string Apellidos { get; set; } = string.Empty;
        public string Correo { get; set; } = string.Empty;
        public decimal Salario { get; set; } = decimal.Zero;
        public string Departamento { get; set; } = string.Empty;
    }

    [XmlRoot("Empleados")]
    public class Empleados
    {
        [XmlElement("Empleado")]
        public List<Empleado> Lista { get; set; }
    }
}

En una entrega anterior vimos cómo consultar documentos XML.

El siguiente bloque es importante para serializar los datos a partir de un documento XML.

// Ruta del archivo XML
 public static readonly string PATH_XML = "empleados.xml";

// Serializar datos
var serializer = new XmlSerializer(typeof(Empleados));

// Lectura del XML
using var reader = new StreamReader(PATH_XML);

Comenzaremos con la operación Where:

          Console.WriteLine("Empleados mejor pagados:");
             var empleadosBienPagados = from emp in empleados.Lista
                               where emp.Salario > 28000
                               select emp;
            foreach (var emp in empleadosBienPagados)
            {
                Console.WriteLine($"{emp.Nombre} - {emp.Salario}");
            }

En SQL sería:

SELECT *
FROM empleados
WHERE Salario > 28000;

Continuamos con la operación Select:

           Console.WriteLine("Nombre y correo:");
            var contactosDev = from dev in empleados.Lista
                   select new { dev.Nombre, dev.Correo };

            foreach (var emp in contactosDev)
            {
                Console.WriteLine($"{emp.Nombre} - {emp.Correo}");
            }

En SQL sería:

SELECT Nombre, Correo
FROM empleados;

Continuamos con la operación OrderBy:

           Console.WriteLine("Empleados [por salario ascendente]:");
            var empleadosOrdenados = from emp in empleados.Lista
                         orderby emp.Salario
                         select emp;
            foreach (var emp in empleadosOrdenados)
            {
                Console.WriteLine($"{emp.Nombre} {emp.Apellidos}, Depto: {emp.Departamento} , Salario: {emp.Salario}");
            }

En SQL sería:

SELECT Nombre, Apellidos, Departamento, Salario
FROM empleados
ORDER BY Salario ASC;

Continuamos con la operación GroupBy y Count:

             Console.WriteLine("Agrupar empleados por departamento:");
            var empleadosPorDepto = from emp in empleados.Lista
                        group emp by emp.Departamento into deptGroup
                        select new
                        {
                            Departamento = deptGroup.Key,
                            Cantidad = deptGroup.Count()
                        };
            foreach (var emp in empleadosPorDepto)
            {
                Console.WriteLine($"Departamento: {emp.Departamento} , Cantidad: {emp.Cantidad}");
            }

En SQL sería:

SELECT Departamento, COUNT(*) AS Cantidad
FROM empleados
GROUP BY Departamento;

Para el conteo (COUNT) sería:

SELECT COUNT(*) AS TotalEmpleados
FROM empleados;

Continuamos con la operación Any:

bool hayAltosSueldos = empleados.Lista.Any(e => e.Salario > 30000);
            if(hayAltosSueldos)
            {
                Console.WriteLine("Existe al menos un empleado con un salario relativamente grande.");
            }

En SQL sería:

SELECT CASE 
         WHEN EXISTS (SELECT 1 FROM empleados WHERE Salario > 30000) 
         THEN 'TRUE' 
         ELSE 'FALSE' 
       END AS HayAltosSueldos;

Finalizamos con la operación All:

bool todosConCorreoEdu = empleados.Lista.All(d => d.Correo.EndsWith("infotec.com"));
            if(todosConCorreoEdu)
            {
                Console.WriteLine("Todos los empleados tienen correo institucional.");
            }

En SQL sería:

SELECT CASE 
         WHEN NOT EXISTS (
              SELECT 1 FROM empleados 
              WHERE Correo NOT LIKE '%infotec.com'
         )
         THEN 'TRUE'
         ELSE 'FALSE'
       END AS TodosConCorreoInstitucional;

Programa completo:

Program.cs

using System;
using System.IO;
using System.Linq;
using System.Collections.Generic;
using System.Xml.Linq;
using model;
using System.Xml.Serialization;

class Program
{
    public static readonly string PATH_XML = "empleados.xml";
    
    static void Main()
    {
        var serializer = new XmlSerializer(typeof(Empleados));
        using var reader = new StreamReader(PATH_XML);
        Empleados empleados = (Empleados)serializer.Deserialize(reader);
        if(empleados != null)
        {
             Console.WriteLine("Empleados mejor pagados:");
             var empleadosBienPagados = from emp in empleados.Lista
                               where emp.Salario > 28000
                               select emp;
            foreach (var emp in empleadosBienPagados)
            {
                Console.WriteLine($"{emp.Nombre} - {emp.Salario}");
            }

            Console.WriteLine("Nombre y correo:");
            var contactosDev = from dev in empleados.Lista
                   select new { dev.Nombre, dev.Correo };

            foreach (var emp in contactosDev)
            {
                Console.WriteLine($"{emp.Nombre} - {emp.Correo}");
            }

            Console.WriteLine("Empleados [por salario ascendente]:");
            var empleadosOrdenados = from emp in empleados.Lista
                         orderby emp.Salario
                         select emp;
            foreach (var emp in empleadosOrdenados)
            {
                Console.WriteLine($"{emp.Nombre} {emp.Apellidos}, Depto: {emp.Departamento} , Salario: {emp.Salario}");
            }

            Console.WriteLine("Agrupar empleados por departamento:");
            var empleadosPorDepto = from emp in empleados.Lista
                        group emp by emp.Departamento into deptGroup
                        select new
                        {
                            Departamento = deptGroup.Key,
                            Cantidad = deptGroup.Count()
                        };
            foreach (var emp in empleadosPorDepto)
            {
                Console.WriteLine($"Departamento: {emp.Departamento} , Cantidad: {emp.Cantidad}");
            }


            bool hayAltosSueldos = empleados.Lista.Any(e => e.Salario > 30000);
            if(hayAltosSueldos)
            {
                Console.WriteLine("Existe al menos un empleado con un salario relativamente grande.");
            }

            bool todosConCorreoEdu = empleados.Lista.All(d => d.Correo.EndsWith("infotec.com"));
            if(todosConCorreoEdu)
            {
                Console.WriteLine("Todos los empleados tienen correo institucional.");
            }

        }

        

    }
}

Construimos y ejecutamos:

$ dotnet build
$ dotnet run

Salida:

Empleados mejor pagados:
Veronica - 30000.00
Juan - 38000.00
Nombre y correo:
Horacio - horacio.gomez.tor@infotec.com
Veronica - veronica.uribe.gom@infotec.com
Karla - karla.perez.per@infotec.com
Juan - juan.archundia.lar@infotec.com
Empleados [por salario ascendente]:
Horacio Gomez Torres, Depto: 202 , Salario: 25000.00
Karla Perez Perez, Depto: 200 , Salario: 28000.00
Veronica Uribe Gomora, Depto: 204 , Salario: 30000.00
Juan Archundia Lara, Depto: 204 , Salario: 38000.00
Agrupar empleados por departamento:
Departamento: 202 , Cantidad: 1
Departamento: 204 , Cantidad: 2
Departamento: 200 , Cantidad: 1
Existe al menos un empleado con un salario relativamente grande.
Todos los empleados tienen correo institucional.

¡Hemos creado consultas básicas con LINQ!

Continuaremos con esta serie sobre C# y LINQ.

Enlaces:

https://codemonkeyjunior.blogspot.com/2026/06/linq-consultando-documentos-json.html
https://codemonkeyjunior.blogspot.com/2025/12/linq-consultando-documentos-xml.html
https://codemonkeyjunior.blogspot.com/2025/11/linq-un-lenguaje-de-consulta-para-c.html

sábado, 6 de junio de 2026

OWASP: el top ten en seguridad y vulnerabilidad

En una entrega anterior vimos las 10 vulnerabilidades de seguridad más críticas en aplicaciones web.

La lista OWASP 2025 es la siguiente:

El top ten de las vulnerabilidades web

Número Vulnerabilidad
A01:2025 Broken Access Control
A02:2025 Security Misconfiguration
A03:2025 Software Supply Chain Failures
A04:2025 Cryptographic Failures
A05:2025 Injection
A06:2025 Insecure Design
A07:2025 Authentication Failures
A08:2025 Software or Data Integrity Failures
A09:2025 Security Logging and Alerting Failures
A10:2025 Mishandling of Exceptional Conditions

La tabla del OWASP Top Ten 2025 no es solo un listado teórico: como desarrollador podemos usarla como guía práctica de control de calidad y seguridad en cada etapa del ciclo de vida de tu software.

Ejemplos del OWASP Top Ten

Broken Access Control (A01)

  • Implementa roles y permisos explícitos en tu código (RBAC/ABAC). 
  • Usa pruebas automatizadas para verificar que un usuario no pueda acceder a recursos fuera de su rol. 
  • Ejemplo: en APIs REST, valida siempre el userId contra el token JWT.

Inseguro:

# Cualquiera puede ver datos de otro usuario
user_id = request.args.get("id")
data = db.get_user(user_id)

Seguro:

# Validar que el usuario autenticado coincide
if user_id == current_user.id:
    data = db.get_user(user_id)

Security Misconfiguration (A02)

  • Mantén configuraciones seguras por defecto (no exponer puertos innecesarios, deshabilitar directory listing). 
  • Automatiza revisiones con herramientas como Docker Bench Security o kube-bench en Kubernetes.

Inseguro:

# Exponer listado de directorios
autoindex on;

Seguro:

# Deshabilitar listado
autoindex off;

Software Supply Chain Failures (A03)

  • Usa dependabot o equivalentes para monitorear librerías vulnerables. 
  • Firma y verifica paquetes (ejemplo: npm audit, sigstore). 
  • Evita dependencias sin mantenimiento activo.

Inseguro:

$ npm install paquete-desconocido

Seguro:

$ npm audit
$ npm install paquete-verificado@1.2.3

Cryptographic Failures (A04)

  • Aplica cifrado fuerte (AES‑256, TLS 1.3). 
  • Nunca inventes tu propio algoritmo: usa librerías probadas. 
  • Rotación periódica de claves y certificados.

Inseguro:

# Cifrado débil
cipher = DES.new(key)

Seguro:

# Cifrado fuerte
cipher = AES.new(key, AES.MODE_GCM)

Injection (A05)

  • Usa ORMs o consultas parametrizadas (LINQ, SQL prepared statements). 
  • Escapa entradas en plantillas HTML/JS. 
  • Haz pruebas de fuzzing para detectar inyecciones.

Inseguro:

cursor.execute("SELECT * FROM users WHERE name = '" + name + "'")

Seguro:

cursor.execute("SELECT * FROM users WHERE name = %s", (name,))

Insecure Design (A06)

  • Aplica Threat Modeling desde el inicio (STRIDE, DFD). 
  • Diseña con principios de seguridad por defecto y mínimo privilegio. 
  • Documenta decisiones arquitectónicas con foco en seguridad.

Inseguro:

# Contraseña en texto plano
user.password = "123456"

Seguro:

# Hash seguro
user.password = bcrypt.hashpw(password, bcrypt.gensalt())

Authentication Failures (A07)

  • Implementa MFA (Multi‑Factor Authentication). 
  • Usa protocolos modernos: OAuth2, OpenID Connect
  • Evita sesiones largas sin expiración.

Inseguro:

# Sesión sin expiración
session['user'] = user.id

Seguro:

session['user'] = user.id
session['expires'] = datetime.now() + timedelta(minutes=30)

Software or Data Integrity Failures (A08)

  • Verifica integridad con hashes y firmas digitales. 
  • Usa pipelines CI/CD con validación de integridad. 
  • Protege contra ataques de deserialización insegura.

Inseguro:

# Ejecutar código sin verificar
exec(downloaded_code)

Seguro:

# Verificar firma digital antes de ejecutar
if verify_signature(downloaded_code, signature):
    exec(downloaded_code)

Security Logging and Alerting Failures (A09)

  • Centraliza logs en sistemas como ELK o Azure Monitor. 
  • Define alertas automáticas para eventos críticos (intentos de login fallidos, escalamiento de privilegios). 
  • Asegura que los logs no contengan datos sensibles en texto plano.

Inseguro:

# No se registra intento de login fallido

Seguro:

logger.warning(f"Login fallido para usuario {username}")

Mishandling of Exceptional Conditions (A10)

  • Maneja errores con mensajes genéricos hacia el usuario, pero detallados en logs. 
  • Evita exponer stack traces en producción. 
  • Diseña flujos de fallback seguros (ejemplo: si falla autenticación externa, no dar acceso por defecto).

Inseguro:

# Mostrar error completo al usuario
except Exception as e:
    return str(e)

Seguro:

except Exception as e:
    logger.error(f"Error interno: {e}")
    return "Ha ocurrido un error, inténtalo más tarde."

La tabla del OWASP Top Ten es como nuestro mapa de riesgos más comunes. Si la convertimos en criterios de aceptación en nuestros proyectos, reducimos drásticamente la superficie de ataque.

Enlaces:

https://codemonkeyjunior.blogspot.com/2025/08/owasp-el-top-ten-de-las.html
https://owasp.org/Top10/2025/

Programando en C# no. 15: ASP .NET Core

Recordemos lo que es .NET: ".NET es una plataforma de desarrollo gratuita, multiplataforma y de código abierto para crear diversos...

Etiquetas

Archivo del blog