Ir al contenido

sudo() en Odoo: cuándo sí y cuándo nunca

Auditoría de usos peligrosos en controladores, computes y crons
17 de junio de 2026 por
sudo() en Odoo: cuándo sí y cuándo nunca
Aitor Atencia

Odoo · Seguridad · sudo()

sudo() ejecuta código como superusuario: ignora ACL y record rules. Es útil en crons y hooks internos; es peligroso en controladores web, APIs públicas y campos compute que el usuario puede influir. Esta entrada es una guía de auditoría, no un permiso para abusar de él.

Logo Odoo Escudo seguridad
sudo() salta toda la seguridad del ORM. Úsalo con criterio y documentación.

Qué hace

Anatomía de sudo()

# Usuario portal (permisos limitados)
partner = request.env['res.partner'].browse(partner_id)
partner.read(['name'])  # AccessError si no tiene ACL

# Mismo código con sudo — bypass total
partner = request.env['res.partner'].sudo().browse(partner_id)
partner.read(['name'])  # Siempre funciona
partner.write({{'phone': '600000000'}})  # También escribe, sin restricciones

sudo() devuelve un nuevo entorno con uid=SUPERUSER_ID. No «eleva» solo la lectura: aplica a create, write y unlink.

Regla de oro: si el código corre en nombre de un usuario externo (HTTP, RPC, portal), no uses sudo() salvo para operaciones acotadas y justificadas, con validación previa de identidad y datos.

Cuándo sí

Usos legítimos

ContextoEjemploPor qué es aceptable
Cron / scheduled actionEnviar recordatorios masivosNo hay usuario interactivo; sistema automatizado
post_init_hookCrear datos de configuración inicialInstalación con privilegios de módulo
Computed field técnicoLeer parámetro ir.config_parameterDato global, no expone registros ajenos
Mail / notificacionesmessage_post cross-companyPatrón interno de Odoo, acotado
TestssetUpClass crea datos baseEntorno aislado, no producción
@api.model
def _cron_send_reminders(self):
    # Cron corre como usuario del job; sudo para leer todos los pendientes
    tests = self.sudo().search([
        ('state', '=', 'report_pending'),
        ('deadline', '<=', fields.Date.today()),
    ])
    for test in tests:
        test._send_reminder_email()  # Vuelve a env con permisos del método

Cuándo nunca

Antipatrones peligrosos

❌ PatrónRiesgo✅ Alternativa
sudo().browse(id).write(data) en controllerCualquier usuario modifica cualquier registroVerificar permisos + write sin sudo
sudo().search([]) en ruta auth='public'Filtración masiva de datosRecord rules + dominio en búsqueda
Compute con sudo que expone campos sensiblesUsuario ve datos de terceros en vistaCalcular sin sudo o restringir groups= en campo
sudo() «para que no dé error»Máscara de ACL mal diseñadosCorregir CSV y record rules
Encadenar sudo en toda la call stackImposible auditar quién hizo quésudo mínimo, scope local
# ❌ MAL — controller portal
@http.route('/my/diagnostic/<int:test_id>', auth='user')
def portal_diagnostic(self, test_id, **kw):
    test = request.env['diagnostic.test'].sudo().browse(test_id)
    test.write(kw)  # Escribe cualquier campo que llegue por POST

# ✅ BIEN — permisos del usuario actual
@http.route('/my/diagnostic/<int:test_id>', auth='user')
def portal_diagnostic(self, test_id, **kw):
    test = request.env['diagnostic.test'].browse(test_id)
    if not test.exists():
        raise NotFound()
    allowed = {{k: v for k, v in kw.items() if k in ('note',)}}
    test.write(allowed)
Desarrollo seguro Odoo
Un Access Error en desarrollo es una señal para mejorar permisos, no para añadir sudo().

Alternativas

Herramientas más seguras que sudo()

with_user(user)

Ejecuta como otro usuario respetando sus reglas. Ideal para probar permisos y para server actions que deben actuar «en nombre de» alguien.

with_company(company)

Cambia contexto multi-compañía sin saltarse ACL. Combinable con usuario específico.

sudo(flag=False) / salir de sudo

Si heredas código con sudo, puedes volver al usuario original con env.sudo(False) para operaciones sensibles.

Acciones de servidor

Configurar cron con usuario dedicado (ej. «Bot automatización») con grupos mínimos, en lugar de sudo omnipotente.

Auditoría

Cómo revisar sudo en tu código

  1. rg "\.sudo\(" odoo/addons/custom/ — lista todos los usos
  2. Clasifica cada uno: cron / hook / controller / compute / test
  3. En controladores: debe haber validación de identidad antes de cualquier write
  4. En computes: ¿el valor filtrado depende de reglas del usuario? → no sudo
  5. Documenta en comentario por qué es necesario (una línea basta)
# Ejemplo de sudo documentado y acotado
def _get_default_sequence(self):
    # System parameter readable only by admin; safe sudo for default value
    param = self.env['ir.config_parameter'].sudo().get_param(
        'diagnostic.default_sequence', 'DIAG'
    )
    return param

Tests

Probar que la seguridad no se bypassa

def test_portal_user_cannot_read_other_diagnostic(self):
    other = self.env['diagnostic.test'].create({{...}})
    portal_user = self.env.ref('base.demo_user0')
    with self.assertRaises(AccessError):
        other.with_user(portal_user).read(['name'])

Si este test falla tras añadir sudo() en un compute o controller, has introducido una regresión de seguridad. Los tests de permisos son tan importantes como los de negocio.

Resumen

sudo() es para operaciones de sistema con contexto controlado: crons, hooks, parámetros globales. En rutas HTTP, portal y RPC es casi siempre una mala idea. Prefiere ACL bien diseñados, with_user() para pruebas y whitelist de campos en writes. Si necesitas sudo, que sea local, documentado y revisado en code review.

en Odoo
ACL y record rules en Odoo: paso a paso con ejemplo real
Crear grupos, ir.model.access.csv y reglas por dominio