Seguridad
Autenticación
Sección titulada «Autenticación»- JWT con algoritmo
HS256. - Tokens de acceso y refresh.
- Blacklist de tokens y revocación masiva por usuario en Redis.
- Contraseñas hasheadas con bcrypt.
Implementación: backend/app/core/security.py.
Autorización (RBAC)
Sección titulada «Autorización (RBAC)»El sistema usa roles y permisos:
User→UserRole→Role→Permission.- Un permiso se define por
(modulo, accion).
require_permission (backend/app/core/deps.py:47) protege cada endpoint:
def require_permission(modulo: str, accion: str): async def _check( current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_session), ) -> User: if not current_user.user_roles: raise AuthorizationError("Sin rol asignado") perm_repo = PermissionRepository(db) for ur in current_user.user_roles: has = await perm_repo.user_has_permission(ur.role_id, modulo, accion) if has: return current_user raise AuthorizationError("Permiso denegado") return _checkIDOR y ownership
Sección titulada «IDOR y ownership»Para evitar que un usuario con permiso global budgets:leer acceda a presupuestos ajenos, se usa require_budget_access (backend/app/api/budget_ownership.py):
async def require_budget_access( budget_id: uuid.UUID, current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_session),) -> User: budget = await db.get(Budget, budget_id) if budget is None or budget.deleted_at is not None: raise HTTPException(status_code=404, detail="Presupuesto no encontrado") if budget.project_id is None: raise HTTPException(status_code=404, detail="Presupuesto no vinculado a ningún proyecto") return current_userEste guard se encadena en endpoints sensibles como /recalculate y /espacios.
Estados de edición
Sección titulada «Estados de edición»budget_editable_guard.py centraliza las reglas que impiden modificar un presupuesto según su estado:
if budget.estado in ("approved", "finalizado"): raise ConflictError(f"No se puede {operation} un presupuesto en estado '{budget.estado}'")Auditoría de seguridad
Sección titulada «Auditoría de seguridad»Según AGENTS.md, todo cambio de código pasa por @security-specialist:
hermes -p security-specialistAudita:
- El diff de la rama actual.
- Dependencias vulnerables (
pip-audit,npm audit). - No corrige, solo reporta.
Dependencias
Sección titulada «Dependencias»Backend
Sección titulada «Backend»Revisar vulnerabilidades con pip-audit:
cd backenduv run pip-auditFrontend
Sección titulada «Frontend»cd frontendnpm auditBuenas prácticas
Sección titulada «Buenas prácticas»- No exponer información interna en mensajes de error (p. ej. nombres de tablas).
- Sanitizar fórmulas de mediciones: no usar
eval(), usar AST seguro (backend/app/services/budget_breakdown_service.py:35). - Validar ownership en recursos multi-tenant.
- No almacenar secretos en el repositorio; usar
.env. - CORS restringido a orígenes configurados (
backend/app/core/config.py). - Rate limiting en login.
Secretos
Sección titulada «Secretos»Variables críticas en .env:
SECRET_KEY: clave para firmar JWT.DATABASE_URLREDIS_URLCORS_ORIGINS
Nunca commitear .env real.