Saltar al contenido
Operaciones de Safe y Web3

Una matriz de permisos para agentes IA que operan con activos digitales

Una matriz de permisos fija, acción por acción, qué prepara un agente IA, qué aprueban las personas y qué canal lo ejecuta.

Publicado Actualizado 10 min de lectura
permissionsSafeAI agentsWeb3 opsmultisig

Este artículo es educativo y no sustituye una auditoría formal de smart contracts, asesoría legal, asesoría financiera ni una revisión de seguridad específica del protocolo.

Un agente IA que trabaja con activos digitales necesita algo más que una lista de herramientas. Necesita una matriz de permisos que fije, acción por acción, qué puede leer y preparar el agente, qué aprueban las personas y qué canal autorizado lo ejecuta.

Una mano gira una llave en una columna de cerraduras ámbar de una consola con iconos azules de archivos y perfiles de usuario.
Cada cerradura es un permiso distinto con su propio responsable. Ilustración conceptual.

La unidad útil es la capacidad, no la herramienta. Firmar, acceder a una clave, habilitar un módulo, cambiar el fallback handler o aprobar una operación DELEGATECALL abre autoridad sobre la cuenta, así que cada una necesita su propia fila, su propia aprobación y su propio responsable.

Este artículo convierte esa idea en una matriz para un equipo pequeño que opera un flujo basado en Safe: las filas, los recursos de cada permiso, lo que cubre el motor de políticas de un proveedor de custodia y lo que queda en manos del equipo, y los casos negativos que comprueban la matriz antes de que un agente toque producción.

Para los principios generales de los permisos de agentes en producción, consulta el artículo enlazado debajo. Esta matriz se centra en claves, firma, módulos, guards y las rutas que los esquivan.

Empezar por las capacidades que abren autoridad

El nombre de una herramienta dice poco del riesgo. Una herramienta llamada propose puede limitarse a redactar un payload o puede tener la clave que lo firma. Por eso la matriz enumera capacidades y solo después asigna herramientas a cada una.

En una smart account de Safe, estas capacidades cambian quién puede mover fondos o cómo se comporta la cuenta:

  • Firmar o enviar una transacción, o tener la clave que lo hace.
  • Añadir o quitar owners y cambiar el umbral.
  • Habilitar o deshabilitar un módulo. Según la documentación de Safe, los módulos pueden ejecutar transacciones arbitrarias y un módulo malicioso puede tomar el control de un Safe.
  • Configurar un transaction guard o, desde Safe 1.5.0, un module guard. Según la documentación, un guard defectuoso puede causar una denegación de servicio del Safe.
  • Cambiar el fallback handler. La documentación advierte de que permite ejecutar funciones externas arbitrarias dentro de la cuenta.
  • Aprobar una transacción cuyo campo operation sea DELEGATECALL. Safe admite CALL y DELEGATECALL, y con DELEGATECALL el código del destino se ejecuta en el contexto del Safe, con su almacenamiento y su saldo.
  • Actualizar, pausar o cambiar parámetros de los contratos propios del protocolo.

Un detalle de versión cambia la matriz. Desde Safe 1.3.0, que introdujo los guards, hasta la 1.4.1, el transaction guard revisa las transacciones ejecutadas con execTransaction y no cubre las que ejecutan los módulos. Safe 1.5.0 añade un module guard separado para esa ruta, pero solo actúa cuando alguien lo configura: actualizar a la 1.5.0, o configurar solo un transaction guard, deja a los módulos sin revisar. Lo comprobamos en forks locales: en un Safe 1.4.1 con un guard que rechaza todas las llamadas, la transacción firmada por el owner revirtió y la del módulo se ejecutó; en un Safe 1.5.0, la transacción del módulo solo revirtió después de configurar el module guard.

La matriz, fila a fila

En resumen: el agente lee, decodifica, simula y redacta borradores; las personas aprueban el payload exacto o el cambio concreto; un canal autorizado, fuera del agente, firma y ejecuta. Cada fila tiene tres partes: qué puede hacer el agente, quién aprueba y qué ejecuta.

  • Consultar balances y configuración. Agente: lecturas tipadas, limitadas a las redes y los Safes de la lista. Aprobación: una política de lectura acordada de antemano. Ejecución: una herramienta de solo lectura.
  • Decodificar y simular. Agente: prepara la evidencia, fijada a un bloque. Aprobación: una persona revisa la cobertura y los resultados. Ejecución: un simulador autorizado.
  • Preparar una transferencia. Agente: un borrador sin firmar. Aprobación: los firmantes revisan el payload exacto. Ejecución: un canal autorizado fuera del agente.
  • Firmar o enviar. Agente: fuera de su alcance. Aprobación: el procedimiento de firma humana. Ejecución: la wallet o el canal autorizado.
  • Rotar claves, cambiar owners o cambiar el umbral. Agente: una propuesta documentada. Aprobación: el responsable del cambio y los firmantes. Ejecución: el canal de administración.
  • Cambiar módulos, guards, el fallback handler o upgrades, o firmar cualquier DELEGATECALL. Agente: análisis y borrador. Aprobación: una revisión específica de ese cambio. Ejecución: el canal de administración.
  • Pausar un contrato o cambiar parámetros. Agente: diagnóstico y propuesta. Aprobación: el responsable que nombra el procedimiento. Ejecución: una herramienta externa restringida.

Dos filas se parecen y no son iguales. Preparar una transferencia y aprobar una DELEGATECALL pueden acabar en una llamada a execTransaction, pero la segunda puede cambiar la propia cuenta. Por eso la matriz la envía a la misma revisión que un cambio de módulo o de guard.

Concretar los recursos de cada permiso

Una fila solo se puede aplicar si sus recursos tienen nombre. Para cada permiso, el equipo anota las redes, los Safes, los contratos de token, los destinatarios aprobados, las funciones que el agente puede redactar y los límites que se aplican.

Lo mismo vale para las excepciones. Si un flujo necesita DELEGATECALL, por ejemplo para agrupar llamadas, la matriz debe nombrar el contrato de destino revisado y su versión, y cualquier otro destino de DELEGATECALL queda fuera de los borradores del agente. Si una integración necesita un módulo, la matriz nombra la dirección del módulo, su responsable y la fecha en que debería retirarse.

Qué cubre el motor de políticas de un proveedor de custodia

Algunos equipos conectan el agente a una clave custodiada por un proveedor de custodia o de infraestructura de wallets con su propio motor de políticas, como Turnkey o Fireblocks. Esos motores aplican reglas a las peticiones que les llegan. La matriz tiene que dejar constancia de qué filas aplica cada motor y cuáles dependen de otros componentes.

Según la documentación de Turnkey, todas las políticas de una organización se evalúan en cada petición: cualquier deny que coincida prevalece; si no, un allow que coincida; y todo lo demás se deniega de forma implícita. Las reglas de Ethereum pueden leer campos ya interpretados de la transacción, como el destinatario, el valor y la calldata; los nombres de función y los argumentos solo están disponibles cuando se ha subido la interfaz del contrato; también se pueden evaluar datos tipados. La misma documentación indica que las acciones aprobadas por el root quorum no pasan por el motor de políticas y que ninguna política puede limitarlas.

Según la documentación de Fireblocks, su Policy Engine aplica las políticas a todas las transacciones creadas desde la Fireblocks Console o su API. Las reglas se aplican con el criterio de la primera coincidencia, de arriba abajo, así que su orden importa. Las reglas de contract call pueden filtrar por método. Solo el Owner del workspace o los usuarios con nivel Admin gestionan las políticas, y un grupo de aprobación tiene que aprobar los cambios propuestos.

Un Safe añade rutas de autoridad que define el contrato, no el motor del proveedor:

  • Una clave gobernada por un motor puede ser uno de varios owners. Las claves de los demás owners firman en otro sitio, con otras reglas.
  • Un módulo habilitado ejecuta mediante execTransactionFromModule, y en esa ruta el Safe no comprueba firmas de los owners; cualquier comprobación es la del propio módulo. El motor de un proveedor solo ve esa ejecución si gobierna la clave que llama al módulo o que lo autoriza, y entonces ve una petición al módulo, no una transacción del Safe firmada por los owners, así que las reglas escritas para esas transacciones no se le aplican.
  • Los guards y el fallback handler se configuran en el propio Safe. El transaction guard, si hay uno configurado, revisa cada execTransaction, firme la clave que firme, pero no lo que ejecuta un módulo; en Safe 1.5.0 esa ruta necesita su propio module guard. El fallback handler responde a las llamadas a funciones que el Safe no implementa. Un módulo habilitado también puede cambiarlos todos sin firmas de owners, porque son llamadas que el Safe se hace a sí mismo.
  • Una DELEGATECALL viaja en el campo operation, tanto en el SafeTx que firman los owners, normalmente como datos tipados EIP-712, como en la llamada a execTransaction que lo envía. Una regla solo la detiene si lee ese campo en el formato que recibe el motor; si la clave firma solo el hash del SafeTx, el motor no tiene ningún campo que leer.

El motor del proveedor es un punto de control dentro de la matriz, no la matriz. Para un owner de un Safe, el destinatario que lee el motor suele ser el propio Safe; el destino real, el importe y los campos de reembolso de gas van dentro del calldata o de los datos tipados, y una regla solo los comprueba si los decodifica. El equipo sigue decidiendo qué reglas escribe, quién puede cambiarlas, qué rutas las esquivan, como un root quorum, un módulo u otro owner, y cómo probar esas rutas con casos negativos.

Un módulo nuevo que exige una decisión propia

Escenario ilustrativo. Una integración de pagos pide al equipo que habilite un módulo para ejecutar transferencias periódicas. El agente puede leer el código y la documentación del módulo, enumerar las funciones que llama en el Safe y redactar la transacción enableModule. No puede decidir habilitarlo.

Habilitar y deshabilitar un módulo son llamadas que el Safe se hace a sí mismo: pasan por una transacción firmada por el umbral o por un módulo que ya esté habilitado. Una vez habilitado, el módulo puede actuar hasta que alguien ejecute disableModule; retirar la integración de las herramientas del equipo no lo retira del Safe. Hasta Safe 1.4.1, ningún guard revisa lo que ejecuta el módulo; en la 1.5.0, solo lo hace un module guard, y solo si hay uno configurado. Por eso la matriz debe tratar la habilitación, la retirada posterior y la cuestión del guard como tres decisiones separadas, cada una con un responsable.

Verificar la matriz con casos negativos

Una matriz solo sirve si las herramientas que rodean al agente la aplican. La primera comprobación clasifica cada borrador en una fila antes de que nadie lo firme. Este ejemplo usa bash y cast de Foundry; las reglas son cortas a propósito y deben seguir tu propia matriz. La función recibe los nueve campos de la transacción del Safe, no solo el destino y los datos, porque los campos de reembolso de gas también pueden mover fondos.

bash
# Clasifica en la matriz un borrador del agente antes de que nadie lo firme.
# Requiere bash y cast de Foundry. Las reglas son un ejemplo: adáptalas a tu matriz.
SAFE=0x...                        # el Safe para el que el agente prepara borradores
TOKEN=0x...                       # el único contrato de token que este flujo puede mover
RECIPIENT=0x...                   # el único destinatario aprobado para este flujo
TRANSFER=$(cast sig "transfer(address,uint256)")
ZERO=0x0000000000000000000000000000000000000000

classify() {                      # argumentos: los nueve campos del SafeTx, en orden
  [ "$#" -eq 9 ] || { echo "STOP: pass all nine SafeTx fields"; return; }
  local to value=$2 data=${3,,} op=$4 selector=${3:0:10}
  to=$(cast to-check-sum-address "$1")
  if [ "$op" != "0" ]; then
    echo "ADMIN REVIEW: operation $op is not CALL; with 1, DELEGATECALL, the target code runs with the Safe's storage"
  elif [ "$to" = "$(cast to-check-sum-address "$SAFE")" ]; then
    echo "ADMIN REVIEW: call to the Safe itself, which can change owners, threshold, modules, guard or fallback handler"
  elif [ "$5 $6 $7" != "0 0 0" ] || [ "$(cast to-check-sum-address "$8")" != "$ZERO" ] || [ "$(cast to-check-sum-address "$9")" != "$ZERO" ]; then
    echo "STOP: gas refund fields are set; the Safe would pay a refund to refundReceiver"
  elif [ "$value" = "0" ] && [ "$to" = "$(cast to-check-sum-address "$TOKEN")" ] && [ "${selector,,}" = "$TRANSFER" ] && [ "${#data}" -eq 138 ] \
    && [ "$(cast decode-calldata "transfer(address,uint256)" "$data" | head -n 1)" = "$(cast to-check-sum-address "$RECIPIENT")" ]; then
    echo "SIGNERS: token transfer to the approved recipient; review the exact payload and amount"
  else
    echo "STOP: target, function, recipient or value outside the matrix"
  fi
}

Los casos que más importan son los que no deben pasar como una transferencia rutinaria: el mismo payload enviado como DELEGATECALL, una llamada al Safe que habilita un módulo o cambia el fallback handler, un approve en lugar de una transferencia, una transferencia dirigida a un contrato o a un destinatario que no está en la lista y un borrador que rellena los campos de reembolso de gas, con lo que el Safe pagaría a un tercero además de la transferencia.

bash
MODULE=0x...                      # dirección del módulo en revisión
NOREFUND="0 0 0 $ZERO $ZERO"      # safeTxGas baseGas gasPrice gasToken refundReceiver
PAY=$(cast calldata "transfer(address,uint256)" $RECIPIENT 5000000000)
classify $TOKEN 0 $PAY 0 $NOREFUND                                                   # SIGNERS
classify $TOKEN 0 $PAY 1 $NOREFUND                                                   # ADMIN REVIEW
classify $SAFE 0 $(cast calldata "enableModule(address)" $MODULE) 0 $NOREFUND        # ADMIN REVIEW
classify $SAFE 0 $(cast calldata "setFallbackHandler(address)" $MODULE) 0 $NOREFUND  # ADMIN REVIEW
classify $TOKEN 0 $(cast calldata "approve(address,uint256)" $RECIPIENT 5000000000) 0 $NOREFUND  # STOP
classify $MODULE 0 $PAY 0 $NOREFUND                                                  # STOP
classify $TOKEN 0 $(cast calldata "transfer(address,uint256)" $MODULE 5000000000) 0 $NOREFUND    # STOP
classify $TOKEN 0 $PAY 0 0 100000 5000 $TOKEN $MODULE                                # STOP

La segunda comprobación mira la cadena. Cuando se retira una integración, su módulo no debería seguir habilitado, y no debería aparecer ningún módulo, guard o fallback handler que la matriz no nombre. Las lecturas de abajo listan lo que el Safe tiene configurado y después simulan una llamada desde la dirección del módulo retirado, sin enviar nada.

bash
export ETH_RPC_URL=...            # RPC de la red del Safe
cast call $SAFE "VERSION()(string)"
# Todos los módulos habilitados, no solo el que esperas (primera página de hasta 10; pide más si el segundo valor no es 0x...01).
cast call $SAFE "getModulesPaginated(address,uint256)(address[],address)" 0x0000000000000000000000000000000000000001 10
# Transaction guard y fallback handler, leídos de sus slots de almacenamiento (cero: no hay ninguno).
cast storage $SAFE 0x4a204f620c8c5ccdca3fd54d003badd85ba500436a431f0cbda4f558c93c34c8
cast storage $SAFE 0x6c9a6c4a39284e37ed1cf53d337577d14212a4870fb976a4366c693b939918d5
# Solo Safe 1.5.0: module guard (cero: ningún guard revisa lo que ejecutan los módulos).
cast storage $SAFE 0xb104e0b93118902c651344349b610029d694cfdec91c589c91ebafbcd0289947
cast call $SAFE "isModuleEnabled(address)(bool)" $MODULE
# Caso negativo: una dirección que no es un módulo habilitado no puede ejecutar a través del Safe.
# Esperado mientras el módulo esté deshabilitado: un revert con GS104. Si devuelve un resultado, puede.
cast call $SAFE "execTransactionFromModule(address,uint256,bytes,uint8)(bool)" \
  $SAFE 0 0x 0 --from $MODULE

Ejecutamos los tres bloques tal como están escritos, contra dos Safes públicos de mainnet, de las versiones 1.3.0 y 1.4.1, y en un fork local en el que el módulo se habilitó y después se deshabilitó. La clasificación coincidió con los comentarios, y la llamada simulada devolvió true solo mientras el módulo estuvo habilitado. En el Safe 1.4.1, la lista de módulos mostró un módulo habilitado que la comprobación de una sola dirección no veía. En el fork, el borrador con los campos de reembolso de gas rellenos quedó clasificado como STOP; ejecutado de todos modos, ese mismo borrador pagó al receptor del reembolso con el saldo del Safe, además de la transferencia. Estas comprobaciones cubren las reglas que escribas; no sustituyen la revisión del código del módulo ni de las políticas del proveedor.

Mantener un responsable para cada cambio

Cada fila necesita una persona responsable, y cada cambio de la matriz necesita el mismo trato que un cambio de la cuenta. Tres hechos deberían provocar una revisión: una nueva versión de Safe, porque la cobertura de los módulos por parte del guard cambia entre la 1.4.1 y la 1.5.0, y el module guard hay que configurarlo expresamente; un cambio en las políticas del proveedor o en quién puede editarlas; y cualquier módulo, guard, fallback handler o destino de DELEGATECALL nuevo.

El registro debe mostrar quién aprobó cada cambio, qué casos negativos se ejecutaron y con qué resultado. Ese registro es lo que permite al equipo explicar después por qué el agente pudo hacer algo, o por qué no pudo.

Preguntas frecuentes

¿Qué no hace nunca el agente IA en esta matriz de permisos? El agente IA nunca firma ni envía transacciones, no tiene claves y no ejecuta cambios de administración en el Safe. Solo lee, decodifica, simula y redacta borradores.

¿Quién habilita un módulo en un Safe? Los owners del Safe, con una transacción firmada por el umbral, a través del canal de administración. Un módulo ya habilitado también podría hacerlo, y también la llamada de setup que desplegó el Safe, lo que es un motivo más para listar todos los módulos habilitados antes de dar acceso al agente. El agente puede analizar el módulo y redactar la transacción.

Convertir la matriz en un entregable

La matriz es un documento de trabajo: una fila por capacidad, recursos con nombre, un responsable, el punto de control de cada regla y los casos negativos que lo comprueban. Antes de que un agente IA toque un Safe, el equipo debería poder mostrar qué aprobación necesita cada acción y qué prueba falló cuando el agente intentó ir más allá.

Si quieres comentar los permisos de tu propio Safe, puedes escribirnos desde la página de contacto; en el mensaje, menciona este artículo y el Safe o el flujo que tienes en mente, sin claves ni material operativo confidencial.

Fuentes

Documentación consultada el 1 de octubre de 2026. Las fuentes describen mecanismos y configuraciones; la matriz, el escenario del módulo y las reglas de clasificación son propuestas de este artículo. Ejecutado, con la salida guardada: los tres bloques de código, con cast 1.8.3 de Foundry, contra dos Safes públicos de mainnet y en un fork local, incluido el caso de reembolso de gas; la prueba del guard y el módulo en un fork local de un Safe 1.4.1; y la prueba del module guard en un fork local de un Safe 1.5.0 nuevo. Contrastado con la documentación y no ejecutado: las descripciones de los motores de políticas de Turnkey y Fireblocks.

Lecturas relacionadas

¿Necesitas aplicar esto a tu lanzamiento o workflow?

Solicita una revisión técnica y mapeamos el primer paso seguro.

Posts relacionados