Back

Autenticación y Autorización de Agentes IA: Cómo Asegurar Tool Calls, Scopes OAuth y Permisos en Producción

Tu agente de IA acaba de mandar un mensaje de Slack a 14.000 clientes. Un agente de soporte en producción, diseñado para consultar estados de pedidos, fue prompt-injected para acceder a la API de mensajería masiva. Tenía las credenciales. Tenía los permisos. Nadie lo aprobó. El agente actuó dentro de su autorización técnica: simplemente no debería haber hecho eso.

Esta es la nueva frontera de la seguridad de IA. Pasamos años hardening aplicaciones web contra SQL injection, XSS y CSRF. Ahora estamos deployando sistemas autónomos que manejan API keys, tokens OAuth y credenciales de base de datos, sistemas que toman sus propias decisiones sobre qué herramientas invocar y a qué datos acceder. La superficie de ataque no son los pesos del modelo. Es la capa de ejecución: las credenciales, permisos y accesos a herramientas otorgados a agentes que pueden ser manipulados mediante prompt injection, goal hijacking o simple mala configuración.

Esta guía cubre la arquitectura de seguridad completa para agentes de IA en producción: gestión de identidad, autorización delegada con OAuth 2.1, permisos granulares de herramientas, enforcement de gateway MCP, patrones human-in-the-loop y las estrategias de defensa en profundidad que separan un deploy seguro de un incidente esperando a pasar.

Por qué la autenticación tradicional no funciona para agentes de IA

No le darías las credenciales root de AWS a un pasante diciendo "usá tu mejor criterio". Pero eso es esencialmente lo que muchos equipos hacen con sus agentes de IA. Veamos por qué el modelo de service account tradicional se rompe:

Los agentes son actores no determinísticos

Un microservicio tradicional hace las mismas llamadas API cada vez. Podés auditar su comportamiento leyendo su código fuente. Un agente de IA es fundamentalmente diferente:

Servicio Tradicional:
  Input: "Obtener orden #12345"
  → Siempre llama: GET /api/orders/12345
  → Predecible, auditable

Agente IA:
  Input: "Ayudá a este cliente con su pedido"
  → Podría llamar: GET /api/orders/12345
  → Podría llamar: POST /api/refunds
  → Podría llamar: PUT /api/customer/email
  → Podría llamar: DELETE /api/orders/12345
  → No determinístico, dependiente del contexto

El agente decide qué herramientas invocar basándose en su razonamiento en tiempo de ejecución. Las políticas RBAC estáticas que funcionaban para microservicios no pueden manejar esto: necesitás autorización dinámica y consciente del contexto.

El problema del radio de explosión

Cuando un servicio tradicional es comprometido, el daño está acotado a su funcionalidad fija. Cuando un agente de IA es comprometido (o manipulado), el radio de explosión equivale a todo su conjunto de permisos:

FactorMicroservicioAgente IA
AccionesFijas, predefinidasDinámicas, decididas por el modelo
Vector de ataqueExploits de códigoPrompt injection, goal hijacking
Radio de explosiónFunción únicaTodos los permisos otorgados
AuditoríaLogs determinísticosRequiere trazas de razonamiento
Patrón de accesoPredecibleDependiente del contexto

Un agente con permisos amplios se convierte en una superficie de ataque universal. Cada herramienta a la que puede acceder es una herramienta que un atacante podría invocar a través del agente.

El desajuste del ciclo de vida de credenciales

La mayoría de las service accounts usan credenciales de larga duración: API keys que rotan trimestralmente, tokens de servicio sin expiración. Para un servicio determinístico, esto es (algo) aceptable. Para un agente que puede ser manipulado en tiempo real:

// ❌ Cómo la mayoría de los equipos deployean agentes hoy const agent = new Agent({ openaiKey: process.env.OPENAI_API_KEY, stripeKey: process.env.STRIPE_SECRET_KEY, // Acceso completo dbConnection: process.env.DATABASE_URL, // Lectura + Escritura slackToken: process.env.SLACK_BOT_TOKEN, // Todos los canales awsCredentials: { accessKeyId: process.env.AWS_ACCESS_KEY, // ¿IAM admin?? secretAccessKey: process.env.AWS_SECRET_KEY, }, }); // Este agente tiene las llaves del reino

Si el agente sufre prompt injection, cada una de estas credenciales está en juego.

El modelo de identidad para agentes

El primer paso para asegurar agentes es tratarlos como Identidades No Humanas (NHIs): no como extensiones de cuentas de usuario, no como service accounts compartidas, sino como entidades de identidad de primera clase.

Identidad única por agente

Cada instancia de agente debería tener su propia identidad, sin compartir credenciales con otros agentes o servicios:

interface AgentIdentity { agentId: string; // Identificador único agentType: string; // ej: 'customer-support', 'data-analyst' version: string; // Versión del agente para auditoría deploymentEnv: string; // 'production' | 'staging' | 'development' owner: string; // Equipo responsable createdAt: Date; expiresAt: Date; // Expiración obligatoria maxConcurrentSessions: number; allowedTools: string[]; // Whitelist de herramientas permitidas deniedTools: string[]; // Blacklist explícita } // Registrar identidad del agente en el deploy const identity = await identityProvider.register({ agentType: 'customer-support', version: '2.4.1', owner: 'support-team', expiresAt: new Date(Date.now() + 24 * 60 * 60 * 1000), // 24h allowedTools: [ 'lookup_order', 'check_shipping_status', 'create_support_ticket', ], deniedTools: [ 'issue_refund', // Requiere aprobación humana 'delete_account', // Nunca automatizado 'bulk_message', // Nunca automatizado ], });

Credenciales de corta duración y alcance limitado

Eliminá las API keys estáticas. Cada sesión de agente debería usar credenciales que sean:

  1. Limitadas en tiempo: Expiran en minutos u horas, no meses
  2. Limitadas en alcance: Solo acceso a las herramientas específicas necesarias
  3. Vinculadas a la sesión: Atadas a una sesión específica del agente, no al tipo de agente
class AgentCredentialManager { async getSessionCredentials( agentIdentity: AgentIdentity, sessionContext: SessionContext ): Promise<ScopedCredentials> { // Solicitar token de corta duración al identity provider const token = await this.idp.issueToken({ subject: agentIdentity.agentId, audience: 'tool-gateway', scopes: this.resolveScopes(agentIdentity, sessionContext), expiresIn: '15m', // Sesiones de 15 minutos sessionId: sessionContext.id, constraints: { maxToolCalls: 50, // Límite duro por sesión allowedIPs: ['10.0.0.0/8'], // Restricciones de red rateLimit: '100/minute', }, }); return { token, refreshToken: null, // Sin refresh — obtener nueva sesión expiresAt: token.expiresAt, }; } private resolveScopes( identity: AgentIdentity, context: SessionContext ): string[] { // Resolución dinámica de scopes basada en contexto const baseScopes = identity.allowedTools.map( (t) => `tool:${t}:execute` ); // Elevar o restringir según contexto del usuario if (context.userTier === 'enterprise') { baseScopes.push('tool:priority_support:execute'); } // Restricciones basadas en horario const hour = new Date().getHours(); if (hour < 6 || hour > 22) { // Fuera de horario: modo solo lectura return baseScopes.filter((s) => !s.includes('write')); } return baseScopes; } }

OAuth 2.1 para autorización delegada de agentes

Cuando un agente de IA actúa en nombre de un usuario, necesita autorización delegada: el usuario otorga explícitamente al agente acceso limitado y temporal a sus recursos. Esto es exactamente para lo que se diseñó OAuth 2.1.

El flujo OAuth para agentes

┌──────────┐    ┌──────────┐    ┌──────────┐    ┌──────────┐
│ Usuario  │    │  Gateway │    │  Servidor│    │ Servidor │
│          │    │  Agente  │    │   Auth   │    │ Recurso  │
└────┬─────┘    └────┬─────┘    └────┬─────┘    └────┬─────┘
     │               │               │               │
     │ "Ayudame con  │               │               │
     │  mi pedido"   │               │               │
     │──────────────>│               │               │
     │               │               │               │
     │  Auth necesario│              │               │
     │<──────────────│               │               │
     │               │               │               │
     │ Login + otorgar│              │               │
     │ acceso acotado│               │               │
     │──────────────────────────────>│               │
     │               │               │               │
     │               │  Token con    │               │
     │               │  scopes       │               │
     │               │<──────────────│               │
     │               │               │               │
     │               │  API call     │               │
     │               │  con token    │               │
     │               │──────────────────────────────>│
     │               │               │               │
     │               │  Respuesta    │               │
     │               │<──────────────────────────────│
     │               │               │               │
     │  El agente    │               │               │
     │  usa el       │               │               │
     │  resultado    │               │               │
     │<──────────────│               │               │

Implementación: OAuth 2.1 + PKCE para agentes

import { AuthorizationCode } from 'simple-oauth2'; class AgentOAuthManager { private client: AuthorizationCode; constructor() { this.client = new AuthorizationCode({ client: { id: process.env.AGENT_CLIENT_ID!, secret: '', // Cliente público — sin secret }, auth: { tokenHost: process.env.AUTH_SERVER_URL!, authorizePath: '/authorize', tokenPath: '/token', }, }); } async initiateUserConsent( userId: string, requiredScopes: string[] ): Promise<ConsentRequest> { // Generar challenge PKCE const codeVerifier = crypto.randomBytes(32) .toString('base64url'); const codeChallenge = crypto .createHash('sha256') .update(codeVerifier) .digest('base64url'); const authorizationUrl = this.client.authorizeURL({ redirect_uri: process.env.AGENT_CALLBACK_URL, scope: requiredScopes.join(' '), state: crypto.randomUUID(), code_challenge: codeChallenge, code_challenge_method: 'S256', }); // Guardar verifier para el intercambio de token await this.storeSession(userId, { codeVerifier }); return { consentUrl: authorizationUrl, scopes: requiredScopes, expiresIn: 300, // 5 minutos para completar }; } async exchangeCode( userId: string, authorizationCode: string ): Promise<AgentToken> { const session = await this.getSession(userId); const tokenResponse = await this.client.getToken({ code: authorizationCode, redirect_uri: process.env.AGENT_CALLBACK_URL, code_verifier: session.codeVerifier, }); return { accessToken: tokenResponse.token.access_token, expiresAt: new Date(tokenResponse.token.expires_at), scopes: tokenResponse.token.scope.split(' '), // Sin refresh token — el agente debe re-solicitar consentimiento }; } }

Diseño de scopes granulares

Diseñá scopes lo suficientemente estrechos para aplicar el principio de mínimo privilegio:

// ❌ MAL: Scopes demasiado amplios const scopes = ['orders:full', 'customers:full', 'payments:full']; // ✅ BIEN: Scopes granulares y específicos por acción const scopes = [ 'orders:read', // Solo consultar pedidos 'orders:status:read', // Solo verificar estado de envío 'tickets:create', // Solo crear tickets de soporte // NO incluido: // 'orders:write' // No puede modificar pedidos // 'refunds:create' // No puede emitir reembolsos // 'customers:delete' // No puede eliminar cuentas ]; // Aún mejor: scopes específicos por recurso const scopes = [ 'orders:read:user:usr_abc123', // Solo pedidos de este usuario 'tickets:create:org:org_xyz789', // Solo tickets de esta org ];

El Tool Gateway: interceptando cada acción del agente

La capa de seguridad más crítica es la que se interpone entre el agente y cada herramienta que puede invocar. Pensalo como un firewall para acciones de agentes.

Arquitectura

┌─────────────┐    ┌─────────────────────────────────────┐
│             │    │          Tool Gateway                │
│   Agente    │    │                                     │
│   Runtime   │───>│  ┌──────────┐  ┌───────────────┐   │
│             │    │  │  Motor   │  │  Rate Limiter │   │
│             │    │  │  AuthZ   │  │               │   │
└─────────────┘    │  └────┬─────┘  └───────┬───────┘   │
                   │       │                │            │
                   │  ┌────▼────────────────▼───────┐   │
                   │  │     Punto de Enforcement      │   │
                   │  │     de Políticas (PEP)        │   │
                   │  └────┬─────────────────────────┘   │
                   │       │                             │
                   │  ┌────▼─────────────────────────┐   │
                   │  │     Logger de Auditoría        │   │
                   │  └────┬─────────────────────────┘   │
                   └───────┼─────────────────────────────┘
                           │
              ┌────────────┼────────────────┐
              │            │                │
        ┌─────▼────┐ ┌────▼─────┐  ┌──────▼──────┐
        │ Stripe   │ │ Database │  │ Slack API   │
        │ API      │ │          │  │             │
        └──────────┘ └──────────┘  └─────────────┘

Implementación

interface ToolCallRequest { agentId: string; sessionId: string; toolName: string; parameters: Record<string, unknown>; reasoning: string; // Por qué el agente quiere llamar esta herramienta traceId: string; } interface PolicyDecision { allowed: boolean; reason: string; requiresApproval: boolean; modifiedParams?: Record<string, unknown>; } class ToolGateway { private authzEngine: AuthorizationEngine; private rateLimiter: RateLimiter; private auditLog: AuditLogger; private approvalQueue: ApprovalQueue; async executeToolCall( request: ToolCallRequest ): Promise<ToolCallResult> { // Paso 1: Verificar identidad y sesión del agente const session = await this.verifySession(request); if (!session.valid) { throw new UnauthorizedError('Sesión inválida o expirada'); } // Paso 2: Verificar rate limits const rateLimitOk = await this.rateLimiter.check( request.agentId, request.toolName ); if (!rateLimitOk) { await this.auditLog.log({ event: 'RATE_LIMIT_EXCEEDED', ...request, }); throw new RateLimitError('Rate limit de tool call excedido'); } // Paso 3: Evaluar política de autorización const decision = await this.authzEngine.evaluate({ subject: request.agentId, action: request.toolName, resource: request.parameters, context: { sessionId: request.sessionId, time: new Date(), reasoning: request.reasoning, }, }); // Paso 4: Manejar decisión de política if (!decision.allowed) { await this.auditLog.log({ event: 'TOOL_CALL_DENIED', reason: decision.reason, ...request, }); throw new ForbiddenError(decision.reason); } // Paso 5: Aprobación humana si es necesaria if (decision.requiresApproval) { const approved = await this.requestHumanApproval(request); if (!approved) { throw new ForbiddenError('Aprobación humana denegada'); } } // Paso 6: Ejecutar con sanitización de parámetros const sanitizedParams = decision.modifiedParams || this.sanitizeParams(request.parameters); // Paso 7: Ejecutar y auditar const result = await this.executeWithAudit( request.toolName, sanitizedParams, request ); return result; } private sanitizeParams( params: Record<string, unknown> ): Record<string, unknown> { const sanitized = { ...params }; // Remover patrones potenciales de inyección for (const [key, value] of Object.entries(sanitized)) { if (typeof value === 'string') { sanitized[key] = value .replace(/ignore previous instructions/gi, '') .replace(/system:/gi, '') .replace(/\bsudo\b/gi, '') .replace(/;\s*(rm|drop|delete|truncate)\b/gi, ''); } } return sanitized; } }

MCP como gateway estándar

El Model Context Protocol (MCP) se convirtió en el estándar de facto para la comunicación agente-herramienta. Usá servidores MCP como tu frontera de seguridad:

import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { z } from 'zod'; const server = new McpServer({ name: 'secure-tools', version: '1.0.0', }); // Registrar herramientas con seguridad integrada y validación Zod server.registerTool( 'lookup_order', { description: 'Consultar detalles de pedido por ID', inputSchema: z.object({ orderId: z.string() .regex(/^ORD-[A-Z0-9]{8}$/) .describe('El ID del pedido a consultar'), }), }, async ({ orderId }, { meta }) => { const hasScope = await verifyScope( meta.authToken, 'orders:read' ); if (!hasScope) { return { content: [ { type: 'text', text: 'Error: Permisos insuficientes para consulta de pedido', }, ], isError: true, }; } const order = await db.orders.findByIdAndUser( orderId, meta.userId ); if (!order) { return { content: [ { type: 'text', text: 'Pedido no encontrado o acceso denegado', }, ], isError: true, }; } return { content: [ { type: 'text', text: JSON.stringify(sanitizeOrder(order)), }, ], }; } );

Flujos de aprobación Human-in-the-Loop (HITL)

No toda acción debería bloquearse con aprobación humana: eso anula el propósito de la automatización. Pero las operaciones de alto riesgo lo exigen.

Matrix de clasificación de riesgo

enum RiskLevel { LOW = 'low', // Auto-aprobar MEDIUM = 'medium', // Loguear y proceder, revisar async HIGH = 'high', // Aprobación humana sincrónica requerida CRITICAL = 'critical', // Bloquear completamente } const TOOL_RISK_CLASSIFICATION: Record<string, RiskLevel> = { // Bajo riesgo — auto-aprobar 'lookup_order': RiskLevel.LOW, 'check_shipping': RiskLevel.LOW, 'search_faq': RiskLevel.LOW, // Riesgo medio — proceder pero flaggear para review 'create_ticket': RiskLevel.MEDIUM, 'update_customer_preferences': RiskLevel.MEDIUM, 'send_notification': RiskLevel.MEDIUM, // Alto riesgo — aprobación en tiempo real requerida 'issue_refund': RiskLevel.HIGH, 'modify_subscription': RiskLevel.HIGH, 'access_pii': RiskLevel.HIGH, 'escalate_to_human': RiskLevel.HIGH, // Crítico — nunca permitir ejecución automatizada 'delete_account': RiskLevel.CRITICAL, 'bulk_data_export': RiskLevel.CRITICAL, 'modify_permissions': RiskLevel.CRITICAL, 'execute_code': RiskLevel.CRITICAL, };

Implementación de aprobación en tiempo real

class ApprovalQueue { async requestApproval( request: ToolCallRequest ): Promise<boolean> { const risk = TOOL_RISK_CLASSIFICATION[request.toolName]; if (risk === RiskLevel.CRITICAL) { return false; // Siempre denegar } if (risk === RiskLevel.LOW) { return true; // Siempre permitir } if (risk === RiskLevel.MEDIUM) { // Auto-aprobar pero flaggear para revisión async await this.flagForReview(request); return true; } // Riesgo ALTO: aprobación sincrónica requerida const approval = await this.createApprovalRequest({ toolName: request.toolName, parameters: request.parameters, reasoning: request.reasoning, agentId: request.agentId, timeout: 300000, // 5 minutos }); // Notificar revisores vía Slack/Teams/PagerDuty await this.notifyReviewers(approval); // Esperar decisión const result = await this.waitForDecision( approval.id, approval.timeout ); // Timeout = denegado (fail-closed) return result?.approved ?? false; } }

Defensa en profundidad: arquitectura de seguridad por capas

Ninguna capa de seguridad es suficiente sola. Los deploys de agentes en producción necesitan defensa en profundidad:

Capa 1: Filtrado de input (antes del agente)

class AgentInputFilter { private readonly INJECTION_PATTERNS = [ /ignore\s+(all\s+)?previous\s+instructions/i, /you\s+are\s+now\s+a/i, /system\s*:\s*/i, /\bact\s+as\b/i, /forget\s+(everything|all|your)/i, /new\s+instructions?\s*:/i, /admin\s+(mode|access|override)/i, ]; async filterInput(input: string): Promise<FilterResult> { for (const pattern of this.INJECTION_PATTERNS) { if (pattern.test(input)) { return { safe: false, reason: `Potencial inyección detectada: ${pattern.source}`, sanitized: null, }; } } const classification = await this.classifyIntent(input); if (classification.maliciousScore > 0.7) { return { safe: false, reason: `Contenido clasificado como potencialmente malicioso (score: ${classification.maliciousScore})`, sanitized: null, }; } return { safe: true, reason: null, sanitized: input }; } }

Capa 2: Validación de tool calls (el gateway)

Ya cubierta arriba: el Tool Gateway con AuthZ, rate limiting y flujos de aprobación.

Capa 3: Filtrado de output (después del agente)

class AgentOutputFilter { async filterOutput( output: string, context: SessionContext ): Promise<FilterResult> { // Detección y redacción de PII const piiCheck = await this.detectPII(output); if (piiCheck.found) { output = this.redactPII(output, piiCheck.entities); } // Prevención de fuga de datos sensibles const secrets = this.detectSecretsInOutput(output); if (secrets.length > 0) { output = '[REDACTADO: El output contenía datos sensibles]'; await this.alertSecurityTeam({ type: 'SECRET_LEAK_PREVENTED', context, }); } return { safe: true, reason: null, sanitized: output }; } }

Capa 4: Detección de anomalías de comportamiento

class AgentBehaviorMonitor { private baselines: Map<string, BehaviorBaseline> = new Map(); async monitorAction( action: AgentAction ): Promise<AnomalyResult> { const baseline = this.baselines.get(action.agentType); if (!baseline) return { anomalous: false }; const anomalies: string[] = []; // Anomalía en frecuencia de uso de herramientas const toolFreq = await this.getToolFrequency( action.agentId, action.toolName, '1h' ); if (toolFreq > baseline.toolFrequency[action.toolName] * 3) { anomalies.push( `Tool "${action.toolName}" llamada ${toolFreq}x (baseline: ${baseline.toolFrequency[action.toolName]}x)` ); } // Patrón de acceso a herramientas nuevas const previousTools = await this.getHistoricalTools( action.agentId, '30d' ); if (!previousTools.includes(action.toolName)) { anomalies.push( `Primer acceso a herramienta: "${action.toolName}"` ); } if (anomalies.length > 0) { await this.triggerAlert({ agentId: action.agentId, anomalies, severity: anomalies.length > 2 ? 'critical' : 'warning', }); } return { anomalous: anomalies.length > 0, details: anomalies, }; } }

Logging de auditoría: la columna vertebral forense

Cada acción del agente debe loguearse de forma inmutable con suficiente contexto para reconstruir la cadena de decisiones completa:

interface AgentAuditEntry { // Identidad timestamp: Date; traceId: string; agentId: string; agentType: string; sessionId: string; // Contexto del actor triggerUserId: string | null; triggerSource: 'user' | 'schedule' | 'event' | 'agent'; // Acción event: 'TOOL_CALL' | 'TOOL_DENIED' | 'APPROVAL_REQUESTED' | 'APPROVAL_GRANTED' | 'APPROVAL_DENIED' | 'RATE_LIMITED' | 'ANOMALY_DETECTED' | 'SESSION_CREATED' | 'SESSION_EXPIRED'; // Detalles toolName: string; parameters: Record<string, unknown>; // Sanitizados reasoning: string; policyDecision: PolicyDecision; result: 'success' | 'failure' | 'denied' | 'timeout'; // Costo tokensConsumed: number; estimatedCost: number; // Metadata modelUsed: string; latencyMs: number; } // CRÍTICO: Distinguir acciones de agente vs. humano class AuditLogger { async log(entry: AgentAuditEntry): Promise<void> { // Store append-only e inmutable await this.immutableStore.append({ ...entry, actorType: 'AI_AGENT', hash: this.computeHash(entry), }); // Streaming en tiempo real para monitoreo await this.eventStream.publish('agent.audit', entry); } }

Anti-patrones de producción

Anti-patrón 1: API Keys compartidas

// ❌ NUNCA: Múltiples agentes compartiendo una credencial const agentA = new Agent({ apiKey: SHARED_KEY }); const agentB = new Agent({ apiKey: SHARED_KEY }); // No podés distinguir agente A de B en los logs // Revocar una key mata todos los agentes // ✅ SIEMPRE: Credenciales por agente, por sesión const agentA = new Agent({ credential: await issueCredential({ agentId: 'agent-a', sessionId: 'sess-123', scopes: ['orders:read'], expiresIn: '15m', }), });

Anti-patrón 2: Permisos "God Mode"

// ❌ NUNCA: Agente con acceso completo a la base de datos const agent = new Agent({ db: new PrismaClient(), // Acceso a todo el schema tools: ALL_TOOLS, // Todas las herramientas disponibles }); // ✅ SIEMPRE: Acceso mínimo basado en whitelist const agent = new Agent({ db: new ReadOnlyClient({ allowedTables: ['orders', 'products'], allowedOperations: ['SELECT'], rowLimit: 100, }), tools: SUPPORT_AGENT_TOOLS, // Subconjunto curado });

Anti-patrón 3: Confiar en el razonamiento del agente

// ❌ NUNCA: Dejar que el agente decida escalar sus propios permisos if (agent.reasoning.includes('Necesito acceso admin')) { grantAdminAccess(agent); // ¡El agente pidió amablemente! } // ✅ SIEMPRE: Cambios de permisos requieren aprobación fuera de banda // Los permisos se definen en el deploy, no en runtime // Los agentes no pueden solicitar ni otorgarse nuevos permisos

Anti-patrón 4: Sin kill switch de emergencia

// ❌ NUNCA: Sin forma de frenar un agente comprometido agent.run(); // Y rezamos // ✅ SIEMPRE: Circuit breaker + kill switch const controller = new AbortController(); const breaker = new CircuitBreaker({ maxFailures: 5, maxCost: 100, // $100 máximo timeout: 60000, signal: controller.signal, }); // Kill switch externo adminApi.on('kill-agent', (agentId) => { if (agentId === agent.id) { controller.abort('Shutdown de emergencia por admin'); revokeAllCredentials(agent.id); notifySecurityTeam(agent.id, 'EMERGENCY_KILL'); } });

Checklist de seguridad

Antes de deployar cualquier agente de IA a producción:

Identidad y Autenticación:

  • El agente tiene identidad única (no credenciales compartidas)
  • Credenciales de corta duración (minutos/horas, no días/meses)
  • OAuth 2.1 con PKCE para autorización delegada del usuario
  • Sin API keys estáticas en la configuración del agente
  • Rotación de credenciales automatizada

Autorización y Permisos:

  • Acceso a herramientas basado en whitelist (allow explícito, deny por defecto)
  • Scopes granulares y específicos por acción
  • Autorización dinámica considera contexto (hora, riesgo, tier del usuario)
  • Los agentes no pueden auto-escalar permisos
  • Operaciones de alto riesgo requieren aprobación human-in-the-loop

Tool Gateway:

  • Todas las tool calls pasan por un gateway centralizado
  • Parámetros de input son validados y sanitizados
  • Rate limiting aplicado por agente y por herramienta
  • MCP o protocolo equivalente estandariza la comunicación de herramientas
  • Outputs filtrados para PII y datos sensibles

Monitoreo y Respuesta:

  • Toda acción del agente se loguea de forma inmutable
  • Acciones del agente se distinguen de acciones humanas en los logs
  • Detección de anomalías de comportamiento activa
  • Kill switch de emergencia existe para cada agente
  • El playbook de respuesta a incidentes incluye escenarios de agente comprometido

Los agentes de IA son el patrón de software más poderoso y más peligroso de 2026. Toman decisiones autónomas con credenciales reales contra sistemas reales. Si tratás la seguridad del agente como seguridad de API tradicional, estás construyendo sobre supuestos falsos. Los agentes necesitan autorización dinámica, credenciales acotadas, acceso a herramientas controlado por gateway, supervisión humana para acciones de alto impacto y monitoreo comportamental continuo. Los patrones de esta guía fueron diseñados para la realidad de que los agentes son no determinísticos, manipulables y capaces de causar daño real cuando sus permisos exceden su confiabilidad. Asegurá la capa de ejecución, y tus agentes se convierten en un multiplicador de fuerza. Ignoralo, y estás a una prompt injection de tu peor incidente.

AI agentsauthenticationauthorizationOAuthsecurityMCPtool callingproductionidentityzero trust

Explora herramientas relacionadas

Prueba estas herramientas gratuitas de Pockit