workspace-v1/orchestration/patrones/PATRON-SEGURIDAD.md
rckrdmrd 66161b1566 feat: Workspace-v1 complete migration with NEXUS v3.4
Sistema NEXUS v3.4 migrado con:

Estructura principal:
- core/orchestration: Sistema SIMCO + CAPVED (27 directivas, 28 perfiles)
- core/catalog: Catalogo de funcionalidades reutilizables
- shared/knowledge-base: Base de conocimiento compartida
- devtools/scripts: Herramientas de desarrollo
- control-plane/registries: Control de servicios y CI/CD
- orchestration/: Configuracion de orquestacion de agentes

Proyectos incluidos (11):
- gamilit (submodule -> GitHub)
- trading-platform (OrbiquanTIA)
- erp-suite con 5 verticales:
  - erp-core, construccion, vidrio-templado
  - mecanicas-diesel, retail, clinicas
- betting-analytics
- inmobiliaria-analytics
- platform_marketing_content
- pos-micro, erp-basico

Configuracion:
- .gitignore completo para Node.js/Python/Docker
- gamilit como submodule (git@github.com:rckrdmrd/gamilit-workspace.git)
- Sistema de puertos estandarizado (3005-3199)

Generated with NEXUS v3.4 Migration System
EPIC-010: Configuracion Git y Repositorios
2026-01-04 03:37:42 -06:00

19 KiB

PATRON DE SEGURIDAD

Version: 1.0.0 Fecha: 2025-12-08 Prioridad: OBLIGATORIA - Seguir en todo el codigo Sistema: SIMCO + CAPVED


PROPOSITO

Definir patrones de seguridad obligatorios para prevenir vulnerabilidades comunes (OWASP Top 10) y proteger datos sensibles.


1. OWASP TOP 10 - RESUMEN

╔══════════════════════════════════════════════════════════════════════╗
║  OWASP TOP 10 - 2021                                                 ║
╠══════════════════════════════════════════════════════════════════════╣
║                                                                       ║
║  A01 - Broken Access Control                                         ║
║  A02 - Cryptographic Failures                                        ║
║  A03 - Injection                                                     ║
║  A04 - Insecure Design                                              ║
║  A05 - Security Misconfiguration                                     ║
║  A06 - Vulnerable Components                                         ║
║  A07 - Authentication Failures                                       ║
║  A08 - Software Integrity Failures                                   ║
║  A09 - Logging & Monitoring Failures                                 ║
║  A10 - Server-Side Request Forgery (SSRF)                           ║
║                                                                       ║
╚══════════════════════════════════════════════════════════════════════╝

2. SANITIZACION DE INPUT

Backend - Validacion con class-validator

// src/modules/user/dto/create-user.dto.ts
import {
  IsEmail,
  IsString,
  MinLength,
  MaxLength,
  Matches,
  IsNotEmpty,
} from 'class-validator';
import { Transform } from 'class-transformer';
import { ApiProperty } from '@nestjs/swagger';

export class CreateUserDto {
  @ApiProperty({ example: 'user@example.com' })
  @IsEmail({}, { message: 'Email invalido' })
  @MaxLength(255)
  @Transform(({ value }) => value?.toLowerCase().trim())  // Sanitizar
  email: string;

  @ApiProperty({ example: 'John' })
  @IsString()
  @IsNotEmpty()
  @MinLength(2)
  @MaxLength(100)
  @Matches(/^[a-zA-ZÀ-ÿ\s'-]+$/, {
    message: 'Nombre solo puede contener letras',
  })
  @Transform(({ value }) => value?.trim())  // Sanitizar espacios
  firstName: string;

  @ApiProperty()
  @IsString()
  @MinLength(8)
  @MaxLength(128)
  @Matches(
    /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]+$/,
    { message: 'Password debe tener mayuscula, minuscula, numero y simbolo' },
  )
  password: string;
}

Sanitizacion de HTML (Prevenir XSS)

// src/shared/utils/sanitizer.ts
import DOMPurify from 'isomorphic-dompurify';

export function sanitizeHtml(dirty: string): string {
  return DOMPurify.sanitize(dirty, {
    ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'p', 'br'],
    ALLOWED_ATTR: [],
  });
}

export function stripHtml(dirty: string): string {
  return DOMPurify.sanitize(dirty, {
    ALLOWED_TAGS: [],
    ALLOWED_ATTR: [],
  });
}

// Uso en DTO
@Transform(({ value }) => stripHtml(value))
@IsString()
comment: string;

Prevenir SQL Injection

// ❌ INCORRECTO: SQL Injection vulnerable
async findByName(name: string) {
  return this.repository.query(
    `SELECT * FROM users WHERE name = '${name}'`  // VULNERABLE
  );
}

// ✅ CORRECTO: Usar parametros
async findByName(name: string) {
  return this.repository.query(
    'SELECT * FROM users WHERE name = $1',
    [name],  // Parametrizado
  );
}

// ✅ MEJOR: Usar QueryBuilder de TypeORM
async findByName(name: string) {
  return this.repository
    .createQueryBuilder('user')
    .where('user.name = :name', { name })  // Automaticamente seguro
    .getMany();
}

3. AUTENTICACION

Password Hashing

// src/shared/utils/password.util.ts
import * as bcrypt from 'bcrypt';

const SALT_ROUNDS = 12;  // Minimo 10 para produccion

export async function hashPassword(password: string): Promise<string> {
  return bcrypt.hash(password, SALT_ROUNDS);
}

export async function verifyPassword(
  password: string,
  hash: string,
): Promise<boolean> {
  return bcrypt.compare(password, hash);
}

JWT con Refresh Tokens

// src/modules/auth/services/auth.service.ts
@Injectable()
export class AuthService {
  constructor(
    private readonly jwtService: JwtService,
    private readonly configService: ConfigService,
    private readonly userService: UserService,
    private readonly tokenService: RefreshTokenService,
  ) {}

  async login(dto: LoginDto): Promise<AuthTokens> {
    const user = await this.validateUser(dto.email, dto.password);
    if (!user) {
      // Mensaje generico para no revelar si email existe
      throw new UnauthorizedException('Credenciales invalidas');
    }

    const tokens = await this.generateTokens(user);

    // Guardar refresh token hasheado en BD
    await this.tokenService.saveRefreshToken(
      user.id,
      await hashPassword(tokens.refreshToken),
    );

    return tokens;
  }

  private async generateTokens(user: UserEntity): Promise<AuthTokens> {
    const payload: JwtPayload = {
      sub: user.id,
      email: user.email,
      roles: user.roles.map(r => r.name),
    };

    const [accessToken, refreshToken] = await Promise.all([
      this.jwtService.signAsync(payload, {
        secret: this.configService.get('JWT_SECRET'),
        expiresIn: '15m',  // Corta duracion
      }),
      this.jwtService.signAsync(
        { sub: user.id, type: 'refresh' },
        {
          secret: this.configService.get('JWT_REFRESH_SECRET'),
          expiresIn: '7d',
        },
      ),
    ]);

    return { accessToken, refreshToken };
  }

  async refresh(refreshToken: string): Promise<AuthTokens> {
    try {
      const payload = await this.jwtService.verifyAsync(refreshToken, {
        secret: this.configService.get('JWT_REFRESH_SECRET'),
      });

      // Verificar que token existe en BD y no fue revocado
      const storedToken = await this.tokenService.findByUserId(payload.sub);
      if (!storedToken || !await verifyPassword(refreshToken, storedToken.hash)) {
        throw new UnauthorizedException('Token invalido');
      }

      const user = await this.userService.findById(payload.sub);
      return this.generateTokens(user);
    } catch {
      throw new UnauthorizedException('Token invalido o expirado');
    }
  }

  async logout(userId: string): Promise<void> {
    // Revocar todos los refresh tokens del usuario
    await this.tokenService.revokeAllUserTokens(userId);
  }
}

Guard de Autenticacion

// src/shared/guards/jwt-auth.guard.ts
import { Injectable, ExecutionContext, UnauthorizedException } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { Reflector } from '@nestjs/core';
import { IS_PUBLIC_KEY } from '../decorators/public.decorator';

@Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {
  constructor(private reflector: Reflector) {
    super();
  }

  canActivate(context: ExecutionContext) {
    // Verificar si es ruta publica
    const isPublic = this.reflector.getAllAndOverride<boolean>(IS_PUBLIC_KEY, [
      context.getHandler(),
      context.getClass(),
    ]);

    if (isPublic) {
      return true;
    }

    return super.canActivate(context);
  }

  handleRequest(err: any, user: any, info: any) {
    if (err || !user) {
      throw err || new UnauthorizedException('No autorizado');
    }
    return user;
  }
}

4. AUTORIZACION (RBAC)

Roles y Permisos

// src/shared/enums/roles.enum.ts
export enum Role {
  SUPER_ADMIN = 'super_admin',
  ADMIN = 'admin',
  MANAGER = 'manager',
  USER = 'user',
  GUEST = 'guest',
}

export enum Permission {
  // Users
  USER_CREATE = 'user:create',
  USER_READ = 'user:read',
  USER_UPDATE = 'user:update',
  USER_DELETE = 'user:delete',

  // Products
  PRODUCT_CREATE = 'product:create',
  PRODUCT_READ = 'product:read',
  PRODUCT_UPDATE = 'product:update',
  PRODUCT_DELETE = 'product:delete',
}

// Mapeo de roles a permisos
export const ROLE_PERMISSIONS: Record<Role, Permission[]> = {
  [Role.SUPER_ADMIN]: Object.values(Permission),
  [Role.ADMIN]: [
    Permission.USER_CREATE,
    Permission.USER_READ,
    Permission.USER_UPDATE,
    Permission.PRODUCT_CREATE,
    Permission.PRODUCT_READ,
    Permission.PRODUCT_UPDATE,
    Permission.PRODUCT_DELETE,
  ],
  [Role.MANAGER]: [
    Permission.USER_READ,
    Permission.PRODUCT_CREATE,
    Permission.PRODUCT_READ,
    Permission.PRODUCT_UPDATE,
  ],
  [Role.USER]: [
    Permission.PRODUCT_READ,
  ],
  [Role.GUEST]: [],
};

Guard de Roles

// src/shared/guards/roles.guard.ts
import { Injectable, CanActivate, ExecutionContext, ForbiddenException } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { Role, Permission, ROLE_PERMISSIONS } from '../enums/roles.enum';

@Injectable()
export class RolesGuard implements CanActivate {
  constructor(private reflector: Reflector) {}

  canActivate(context: ExecutionContext): boolean {
    const requiredRoles = this.reflector.getAllAndOverride<Role[]>('roles', [
      context.getHandler(),
      context.getClass(),
    ]);

    const requiredPermissions = this.reflector.getAllAndOverride<Permission[]>(
      'permissions',
      [context.getHandler(), context.getClass()],
    );

    if (!requiredRoles && !requiredPermissions) {
      return true;  // Sin restricciones
    }

    const { user } = context.switchToHttp().getRequest();

    if (!user) {
      throw new ForbiddenException('Usuario no autenticado');
    }

    // Verificar roles
    if (requiredRoles?.length > 0) {
      const hasRole = requiredRoles.some(role => user.roles?.includes(role));
      if (!hasRole) {
        throw new ForbiddenException('Rol insuficiente');
      }
    }

    // Verificar permisos
    if (requiredPermissions?.length > 0) {
      const userPermissions = this.getUserPermissions(user.roles);
      const hasPermission = requiredPermissions.every(
        permission => userPermissions.includes(permission),
      );
      if (!hasPermission) {
        throw new ForbiddenException('Permiso insuficiente');
      }
    }

    return true;
  }

  private getUserPermissions(roles: Role[]): Permission[] {
    const permissions = new Set<Permission>();
    for (const role of roles) {
      for (const permission of ROLE_PERMISSIONS[role] || []) {
        permissions.add(permission);
      }
    }
    return Array.from(permissions);
  }
}

Decoradores

// src/shared/decorators/roles.decorator.ts
import { SetMetadata } from '@nestjs/common';
import { Role, Permission } from '../enums/roles.enum';

export const Roles = (...roles: Role[]) => SetMetadata('roles', roles);
export const Permissions = (...permissions: Permission[]) =>
  SetMetadata('permissions', permissions);

Uso en Controller

// src/modules/user/controllers/user.controller.ts
@Controller('users')
@UseGuards(JwtAuthGuard, RolesGuard)
export class UserController {
  @Get()
  @Roles(Role.ADMIN, Role.MANAGER)
  findAll() {
    return this.userService.findAll();
  }

  @Post()
  @Permissions(Permission.USER_CREATE)
  create(@Body() dto: CreateUserDto) {
    return this.userService.create(dto);
  }

  @Delete(':id')
  @Roles(Role.SUPER_ADMIN)  // Solo super admin puede eliminar
  remove(@Param('id') id: string) {
    return this.userService.remove(id);
  }
}

5. PROTECCION DE DATOS

Encriptacion de Datos Sensibles

// src/shared/utils/encryption.util.ts
import * as crypto from 'crypto';

const ALGORITHM = 'aes-256-gcm';
const IV_LENGTH = 16;
const AUTH_TAG_LENGTH = 16;

export function encrypt(text: string, key: string): string {
  const iv = crypto.randomBytes(IV_LENGTH);
  const cipher = crypto.createCipheriv(
    ALGORITHM,
    Buffer.from(key, 'hex'),
    iv,
  );

  let encrypted = cipher.update(text, 'utf8', 'hex');
  encrypted += cipher.final('hex');

  const authTag = cipher.getAuthTag();

  // IV + AuthTag + Encrypted
  return iv.toString('hex') + authTag.toString('hex') + encrypted;
}

export function decrypt(encryptedText: string, key: string): string {
  const iv = Buffer.from(encryptedText.slice(0, IV_LENGTH * 2), 'hex');
  const authTag = Buffer.from(
    encryptedText.slice(IV_LENGTH * 2, (IV_LENGTH + AUTH_TAG_LENGTH) * 2),
    'hex',
  );
  const encrypted = encryptedText.slice((IV_LENGTH + AUTH_TAG_LENGTH) * 2);

  const decipher = crypto.createDecipheriv(
    ALGORITHM,
    Buffer.from(key, 'hex'),
    iv,
  );
  decipher.setAuthTag(authTag);

  let decrypted = decipher.update(encrypted, 'hex', 'utf8');
  decrypted += decipher.final('utf8');

  return decrypted;
}

Columnas Encriptadas en Entity

// src/shared/transformers/encrypted.transformer.ts
import { ValueTransformer } from 'typeorm';
import { encrypt, decrypt } from '../utils/encryption.util';

export class EncryptedTransformer implements ValueTransformer {
  constructor(private readonly key: string) {}

  to(value: string | null): string | null {
    if (!value) return null;
    return encrypt(value, this.key);
  }

  from(value: string | null): string | null {
    if (!value) return null;
    return decrypt(value, this.key);
  }
}

// Uso en Entity
@Column({
  type: 'text',
  transformer: new EncryptedTransformer(process.env.ENCRYPTION_KEY),
})
ssn: string;  // Se guarda encriptado en BD

Nunca Exponer Datos Sensibles

// ❌ INCORRECTO: Exponer password en response
@Get(':id')
async findOne(@Param('id') id: string) {
  return this.userRepository.findOne({ where: { id } });
  // Retorna { id, email, password, ... } - PASSWORD EXPUESTO!
}

// ✅ CORRECTO: Usar ResponseDto que excluye campos sensibles
@Get(':id')
async findOne(@Param('id') id: string): Promise<UserResponseDto> {
  const user = await this.userService.findOne(id);
  return plainToClass(UserResponseDto, user, {
    excludeExtraneousValues: true,
  });
}

// ResponseDto solo expone campos seguros
export class UserResponseDto {
  @Expose() id: string;
  @Expose() email: string;
  @Expose() firstName: string;
  // password NO esta expuesto
}

6. RATE LIMITING

Implementacion con Throttler

// src/app.module.ts
import { ThrottlerModule, ThrottlerGuard } from '@nestjs/throttler';

@Module({
  imports: [
    ThrottlerModule.forRoot([
      {
        name: 'short',
        ttl: 1000,    // 1 segundo
        limit: 3,     // 3 requests por segundo
      },
      {
        name: 'medium',
        ttl: 10000,   // 10 segundos
        limit: 20,    // 20 requests por 10 segundos
      },
      {
        name: 'long',
        ttl: 60000,   // 1 minuto
        limit: 100,   // 100 requests por minuto
      },
    ]),
  ],
  providers: [
    {
      provide: APP_GUARD,
      useClass: ThrottlerGuard,
    },
  ],
})
export class AppModule {}

Rate Limiting por Endpoint

// Rate limit especifico para login (prevenir brute force)
@Post('login')
@Throttle({ default: { limit: 5, ttl: 60000 } })  // 5 intentos por minuto
async login(@Body() dto: LoginDto) {
  return this.authService.login(dto);
}

// Endpoint sin rate limit
@Get('health')
@SkipThrottle()
healthCheck() {
  return { status: 'ok' };
}

7. HEADERS DE SEGURIDAD

Helmet Middleware

// src/main.ts
import helmet from 'helmet';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  // Headers de seguridad
  app.use(helmet({
    contentSecurityPolicy: {
      directives: {
        defaultSrc: ["'self'"],
        styleSrc: ["'self'", "'unsafe-inline'"],
        scriptSrc: ["'self'"],
        imgSrc: ["'self'", 'data:', 'https:'],
      },
    },
    hsts: {
      maxAge: 31536000,  // 1 año
      includeSubDomains: true,
    },
  }));

  // CORS configurado
  app.enableCors({
    origin: process.env.CORS_ORIGINS?.split(',') || false,
    methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'],
    credentials: true,
  });

  await app.listen(3000);
}

8. FRONTEND - SEGURIDAD

Almacenamiento de Tokens

// ❌ INCORRECTO: Token en localStorage (vulnerable a XSS)
localStorage.setItem('token', accessToken);

// ✅ MEJOR: HttpOnly cookies (configurado desde backend)
// El token se maneja automaticamente por el navegador

// ✅ ALTERNATIVA: Si debe estar en JS, usar memoria
class TokenStore {
  private accessToken: string | null = null;

  setToken(token: string) {
    this.accessToken = token;
  }

  getToken(): string | null {
    return this.accessToken;
  }

  clearToken() {
    this.accessToken = null;
  }
}

export const tokenStore = new TokenStore();

Prevenir XSS en React

// ❌ INCORRECTO: dangerouslySetInnerHTML sin sanitizar
<div dangerouslySetInnerHTML={{ __html: userInput }} />

// ✅ CORRECTO: Sanitizar primero
import DOMPurify from 'dompurify';

<div dangerouslySetInnerHTML={{
  __html: DOMPurify.sanitize(userInput)
}} />

// ✅ MEJOR: Evitar dangerouslySetInnerHTML cuando sea posible
<div>{userInput}</div>  // React escapa automaticamente

Validacion en Frontend (Defense in Depth)

// src/shared/schemas/user.schema.ts
import { z } from 'zod';

export const createUserSchema = z.object({
  email: z.string()
    .email('Email invalido')
    .max(255)
    .transform(v => v.toLowerCase().trim()),

  firstName: z.string()
    .min(2, 'Minimo 2 caracteres')
    .max(100)
    .regex(/^[a-zA-ZÀ-ÿ\s'-]+$/, 'Solo letras permitidas')
    .transform(v => v.trim()),

  password: z.string()
    .min(8, 'Minimo 8 caracteres')
    .max(128)
    .regex(
      /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])/,
      'Debe incluir mayuscula, minuscula, numero y simbolo',
    ),
});

9. CHECKLIST DE SEGURIDAD

Input/Output:
[ ] Todos los inputs validados con class-validator
[ ] HTML sanitizado antes de renderizar
[ ] SQL usa queries parametrizadas
[ ] Datos sensibles nunca en logs
[ ] ResponseDto excluye campos sensibles

Autenticacion:
[ ] Passwords hasheados con bcrypt (rounds >= 10)
[ ] JWT con expiracion corta (< 15min)
[ ] Refresh tokens almacenados hasheados
[ ] Logout revoca tokens
[ ] Mensajes de error genericos (no revelar info)

Autorizacion:
[ ] Guards en todos los endpoints protegidos
[ ] Verificacion de ownership en recursos
[ ] Roles y permisos implementados
[ ] Principio de minimo privilegio

Infraestructura:
[ ] HTTPS obligatorio
[ ] Headers de seguridad (Helmet)
[ ] CORS configurado correctamente
[ ] Rate limiting implementado
[ ] Secrets en variables de entorno

Frontend:
[ ] No localStorage para tokens sensibles
[ ] CSP configurado
[ ] Validacion client-side (defense in depth)
[ ] No exponer errores detallados a usuarios

10. RECURSOS ADICIONALES


Version: 1.0.0 | Sistema: SIMCO | Tipo: Patron de Seguridad