April 20, 2026 • Versión: v1.2.0+

Compatibilidad con BYOK para Pasarelas en la Nube - Support BYOK (Bring Your Own Key) for Cloud Gateways

Guía de implementación para habilitar la gestión de claves API de proveedores de IA personalizados en puertas de enlace OpenClaw desplegadas en la nube con almacenamiento seguro y transparencia en la facturación.

🔍 Descripción general

Esta guía cubre los requisitos de implementación para agregar soporte de Bring Your Own Key (BYOK) a las puertas de enlace OpenClaw implementadas en la nube. Los usuarios deben poder proporcionar sus propias claves de API de proveedores de IA (OpenAI, Anthropic, Google AI, etc.) en lugar de depender de las credenciales incluidas en la aplicación.

Alcance de la función

El sistema BYOK para puertas de enlace en la nube requiere implementación en tres capas:

  • Interfaz de usuario del cliente: Interfaz de configuración para ingresar, visualizar y administrar claves API por proveedor
  • Almacenamiento seguro: Integración de keychain en clientes de escritorio, bóveda cifrada en dispositivos móviles
  • Configuración de puerta de enlace: Mecanismo de inyección de variables de entorno para puertas de enlace implementadas en la nube

Referencia de implementación existente

Las puertas de enlace locales ya implementan BYOK a través del asistente de configuración (ver PR #221). La implementación de BYOK para puertas de enlace en la nube debe alinearse con los patrones establecidos mientras aborda consideraciones específicas de la nube:


Arquitectura BYOK de puerta de enlace local:
┌─────────────┐     ┌──────────────┐     ┌─────────────┐
│ Setup Wizard│────▶│ Secure Storage│────▶│ .env.local  │
│   (UI)      │     │   (Keychain)  │     │   (File)    │
└─────────────┘     └──────────────┘     └─────────────┘

Arquitectura BYOK de puerta de enlace en la nube:
┌─────────────┐     ┌──────────────┐     ┌──────────────────┐     ┌─────────────┐
│ Settings UI │────▶│ Secure Vault │────▶│ Config API Patch │────▶│ Env Var     │
│   (Cloud)   │     │  (Encrypted) │     │   (Gateway)      │     │ Injection   │
└─────────────┘     └──────────────┘     └──────────────────┘     └─────────────┘

🧠 Requisitos técnicos

Componentes de arquitectura

1. Registro de proveedores de claves API

El sistema debe mantener un registro de proveedores de IA admitidos con sus requisitos de configuración:


// src/providers/registry.ts
export interface ProviderConfig {
  id: string;
  name: string;
  apiKeyEnvVar: string;
  endpoint?: string;
  requiresOrgId?: boolean;
  documentationUrl: string;
}

export const AI_PROVIDERS: Record<string, ProviderConfig> = {
  openai: {
    id: 'openai',
    name: 'OpenAI',
    apiKeyEnvVar: 'OPENAI_API_KEY',
    endpoint: 'https://api.openai.com/v1',
    documentationUrl: 'https://platform.openai.com/docs/api-keys'
  },
  anthropic: {
    id: 'anthropic',
    name: 'Anthropic',
    apiKeyEnvVar: 'ANTHROPIC_API_KEY',
    documentationUrl: 'https://docs.anthropic.com/en/api/getting-started'
  },
  google: {
    id: 'google',
    name: 'Google AI',
    apiKeyEnvVar: 'GOOGLE_API_KEY',
    requiresOrgId: true,
    documentationUrl: 'https://ai.google.dev/tutorials/setup'
  }
};

2. Especificación de almacenamiento seguro

Almacenamiento del lado del cliente (Keychain/Secure Enclave)

// src/storage/secure-keychain.ts
interface SecureKeyStorage {
  // Store API key with provider identifier
  setApiKey(provider: string, key: string): Promise<boolean>;
  
  // Retrieve API key (returns null if not found)
  getApiKey(provider: string): Promise<string | null>;
  
  // List configured providers (without exposing keys)
  listProviders(): Promise<string[]>;
  
  // Remove API key
  deleteApiKey(provider: string): Promise<boolean>;
  
  // Validate key format before storage
  validateKeyFormat(provider: string, key: string): ValidationResult;
}

interface ValidationResult {
  valid: boolean;
  error?: string;
  maskedKey?: string; // e.g., "sk-...xyz"
}
Patrones de validación de formato de clave

// Validation patterns by provider
const KEY_PATTERNS = {
  openai: /^sk-[A-Za-z0-9_-]{20,}$/,
  anthropic: /^sk-ant-[A-Za-z0-9_-]{20,}$/,
  google: /^[A-Za-z0-9_-]{39}$/,
  azure: /^[A-Za-z0-9]{32}$/
};

3. API de configuración de puerta de enlace

La puerta de enlace en la nube requiere un mecanismo seguro de parche de configuración:


// Gateway Config API Endpoint
// POST /api/v1/gateway/config/patch

interface ConfigPatchRequest {
  operation: 'set' | 'remove';
  target: 'env' | 'secret';
  key: string;
  value?: string; // Required for 'set' operation
  metadata?: {
    provider?: string;
    createdAt: string;
    expiresAt?: string;
  };
}

interface ConfigPatchResponse {
  success: boolean;
  appliedAt: string;
  restartRequired: boolean;
  error?: string;
}

🛠️ Pasos de implementación

Fase 1: Componentes de la interfaz de configuración

Paso 1.1: Crear panel de administración de claves API


// src/components/Settings/ApiKeyManager.tsx

import { useState } from 'react';
import { KeychainStorage } from '@/storage/secure-keychain';
import { AI_PROVIDERS } from '@/providers/registry';
import { BillingDisclaimer } from './BillingDisclaimer';

export function ApiKeyManager() {
  const [selectedProvider, setSelectedProvider] = useState<string | null>(null);
  const [apiKey, setApiKey] = useState('');
  const [isValidating, setIsValidating] = useState(false);
  const [configuredProviders, setConfiguredProviders] = useState<string[]>([]);
  
  // Load configured providers on mount
  useEffect(() => {
    KeychainStorage.listProviders().then(setConfiguredProviders);
  }, []);

  const handleSaveKey = async () => {
    if (!selectedProvider || !apiKey) return;
    
    setIsValidating(true);
    const validation = KeychainStorage.validateKeyFormat(selectedProvider, apiKey);
    
    if (!validation.valid) {
      showError(validation.error);
      setIsValidating(false);
      return;
    }

    // Store securely
    await KeychainStorage.setApiKey(selectedProvider, apiKey);
    
    // Sync to cloud gateway
    await syncKeyToGateway(selectedProvider, apiKey);
    
    // Clear input and refresh list
    setApiKey('');
    setConfiguredProviders(await KeychainStorage.listProviders());
    setIsValidating(false);
  };

  return (
    <div className="api-key-manager">
      <BillingDisclaimer />
      
      <div className="provider-grid">
        {Object.values(AI_PROVIDERS).map(provider => (
          <ProviderCard
            key={provider.id}
            provider={provider}
            isConfigured={configuredProviders.includes(provider.id)}
            onSelect={() => setSelectedProvider(provider.id)}
          />
        ))}
      </div>

      {selectedProvider && (
        <ApiKeyInputForm
          provider={AI_PROVIDERS[selectedProvider]}
          value={apiKey}
          onChange={setApiKey}
          onSubmit={handleSaveKey}
          isLoading={isValidating}
        />
      )}
    </div>
  );
}

Paso 1.2: Crear componente de aviso de facturación


// src/components/Settings/BillingDisclaimer.tsx

export function BillingDisclaimer() {
  return (
    <div className="billing-disclaimer">
      <div className="disclaimer-icon">💳</div>
      <div className="disclaimer-content">
        <h4>Responsabilidad de facturación</h4>
        <p>
          Cuando proporcionas tu propia clave API, todos los costos de uso se facturan directamente 
          a tu cuenta con el proveedor de IA. OpenClaw no procesa, aumenta ni tiene visibilidad sobre 
          tu uso de API o facturación.
        </p>
        <ul>
          <li>Eres responsable de los límites de uso y cuotas de tu proveedor</li>
          <li>Las claves API se transmiten de forma segura y nunca se almacenan en texto plano en servidores</li>
          <li>Puedes revocar el acceso en cualquier momento desde el panel de tu proveedor</li>
        </ul>
        <a href="../../../docs/byok/billing-faq" target="_blank">
          Más información sobre facturación BYOK →
        </a>
      </div>
    </div>
  );
}

Fase 2: Implementación de almacenamiento seguro

Paso 2.1: Servicio de almacenamiento Keychain


// src/storage/secure-keychain.ts

export class KeychainStorage implements SecureKeyStorage {
  private static readonly SERVICE_PREFIX = 'openclaw.byok';
  
  static async setApiKey(provider: string, key: string): Promise<boolean> {
    const service = `${this.SERVICE_PREFIX}.${provider}`;
    
    // Platform-specific implementation
    if (Platform.OS === 'ios' || Platform.OS === 'android') {
      return this.setSecureItem(service, key);
    }
    
    // Desktop: Use electron-store with encryption or native Keychain
    if (process.env.ELECTRON === 'true') {
      return this.setElectronKeychain(service, key);
    }
    
    throw new Error(`Platform ${Platform.OS} does not support secure storage`);
  }

  static async getApiKey(provider: string): Promise<string | null> {
    const service = `${this.SERVICE_PREFIX}.${provider}`;
    
    if (Platform.OS === 'ios') {
      return this.getIOSKeychain(service);
    }
    if (Platform.OS === 'android') {
      return this.getAndroidKeystore(service);
    }
    if (process.env.ELECTRON === 'true') {
      return this.getElectronKeychain(service);
    }
    
    return null;
  }

  static async listProviders(): Promise<string[]> {
    // Return list of providers with stored keys (without exposing the keys)
    const prefix = `${this.SERVICE_PREFIX}.`;
    const services = await this.listSecureServices(prefix);
    return services.map(s => s.replace(prefix, ''));
  }

  static validateKeyFormat(provider: string, key: string): ValidationResult {
    const pattern = KEY_PATTERNS[provider];
    
    if (!pattern) {
      return { valid: false, error: `Unknown provider: ${provider}` };
    }

    const trimmedKey = key.trim();
    
    if (!pattern.test(trimmedKey)) {
      return {
        valid: false,
        error: `Invalid key format for ${provider}. Expected format: ${pattern.description}`
      };
    }

    return {
      valid: true,
      maskedKey: this.maskKey(trimmedKey)
    };
  }

  private static maskKey(key: string): string {
    // Show first 3 and last 4 characters
    if (key.length <= 10) return '***';
    return `${key.slice(0, 3)}...${key.slice(-4)}`;
  }
}

Fase 3: Sincronización de configuración de puerta de enlace

Paso 3.1: Cliente API de puerta de enlace en la nube


// src/services/gateway-config-sync.ts

export class GatewayConfigSync {
  private readonly gatewayApiBase: string;

  constructor(gatewayId: string) {
    this.gatewayApiBase = `https://${gatewayId}.gateways.openclaw.io`;
  }

  async syncApiKey(provider: string, apiKey: string): Promise<ConfigPatchResponse> {
    const providerConfig = AI_PROVIDERS[provider];
    
    if (!providerConfig) {
      throw new Error(`Unknown provider: ${provider}`);
    }

    const response = await fetch(`${this.gatewayApiBase}/api/v1/config/patch`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${await this.getGatewayToken()}`
      },
      body: JSON.stringify({
        operation: 'set',
        target: 'secret', // Use secret, not env, for API keys
        key: providerConfig.apiKeyEnvVar,
        value: apiKey,
        metadata: {
          provider,
          createdAt: new Date().toISOString()
        }
      })
    });

    if (!response.ok) {
      const error = await response.json();
      throw new Error(`Failed to sync API key: ${error.message}`);
    }

    return response.json();
  }

  async removeApiKey(provider: string): Promise<ConfigPatchResponse> {
    const providerConfig = AI_PROVIDERS[provider];
    
    const response = await fetch(`${this.gatewayApiBase}/api/v1/config/patch`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${await this.getGatewayToken()}`
      },
      body: JSON.stringify({
        operation: 'remove',
        target: 'secret',
        key: providerConfig.apiKeyEnvVar
      })
    });

    return response.json();
  }
}

Paso 3.2: Gestión de secretos del lado de la puerta de enlace

En el lado de la puerta de enlace en la nube, implementar rotación de secretos e inyección segura:


# gateway/src/middleware/secrets-injector.ts

import { SecretManager } from '@gateway/secrets';

export class SecretsInjector {
  private secrets: Map<string, string> = new Map();
  private secretManager: SecretManager;

  constructor() {
    this.secretManager = new SecretManager();
  }

  async loadSecrets(): Promise<void> {
    // Load all BYOK secrets from secure storage
    const userSecrets = await this.secretManager.listUserSecrets();
    
    for (const secret of userSecrets) {
      this.secrets.set(secret.key, secret.value);
    }
  }

  getSecret(key: string): string | undefined {
    return this.secrets.get(key);
  }

  // Called when AI provider is invoked
  injectProviderCredentials(provider: string): Record<string, string> {
    const envVars: Record<string, string> = {};
    
    switch (provider) {
      case 'openai':
        envVars.OPENAI_API_KEY = this.getSecret('OPENAI_API_KEY') ?? '';
        break;
      case 'anthropic':
        envVars.ANTHROPIC_API_KEY = this.getSecret('ANTHROPIC_API_KEY') ?? '';
        break;
      case 'google':
        envVars.GOOGLE_API_KEY = this.getSecret('GOOGLE_API_KEY') ?? '';
        break;
    }

    return envVars;
  }
}

Fase 4: Migración de configuración

Antes (usando credenciales incluidas)


# gateway/.env (managed by OpenClaw)
AI_PROVIDER=openai
OPENAI_API_KEY=sk-org-managed-key-12345
ANTHROPIC_API_KEY=sk-ant-org-managed-key-67890

Después (BYOK del usuario con respaldo)


# gateway/.env (partial, non-sensitive)
AI_PROVIDER=openai
USE_BUNDLED_CREDENTIALS=false
BYOK_ENABLED=true

# Secrets stored separately (never committed to repo)
# Loaded from secure secret manager at runtime
# OPENAI_API_KEY=sk-user-provided-key (injected from secrets)

🧪 Verificación

Lista de verificación de verificación

Ejecutar las siguientes pruebas para validar la implementación de BYOK:

Prueba 1: Validación de almacenamiento de claves


// Unit test: Keychain storage operations
describe('KeychainStorage', () => {
  it('should store and retrieve API key', async () => {
    const testKey = 'sk-test-1234567890abcdefghijklmnop';
    await KeychainStorage.setApiKey('openai', testKey);
    const retrieved = await KeychainStorage.getApiKey('openai');
    expect(retrieved).toBe(testKey);
  });

  it('should reject invalid key format', async () => {
    const result = KeychainStorage.validateKeyFormat('openai', 'invalid-key');
    expect(result.valid).toBe(false);
    expect(result.error).toContain('Invalid key format');
  });

  it('should mask keys correctly', async () => {
    const result = KeychainStorage.validateKeyFormat(
      'openai', 
      'sk-1234567890abcdefghijklmnopqrstuvwxyz'
    );
    expect(result.maskedKey).toBe('sk-...qrst');
  });
});

Prueba 2: Sincronización de configuración de puerta de enlace


// Integration test: Gateway configuration sync
describe('GatewayConfigSync', () => {
  it('should sync API key to cloud gateway', async () => {
    const sync = new GatewayConfigSync('test-gateway-123');
    
    const response = await sync.syncApiKey('openai', 'sk-test-key');
    
    expect(response.success).toBe(true);
    expect(response.restartRequired).toBe(true);
    expect(response.appliedAt).toBeDefined();
  });

  it('should handle unauthorized gateway access', async () => {
    // Set up with invalid token
    const sync = new GatewayConfigSync('unauthorized-gateway');
    
    await expect(sync.syncApiKey('openai', 'sk-test'))
      .rejects.toThrow('Failed to sync API key');
  });
});

Prueba 3: Flujo BYOK de extremo a extremo


# E2E Test Script
#!/bin/bash

GATEWAY_ID="test-e2e-gateway"
PROVIDER="openai"
TEST_KEY="sk-test-$(date +%s)"

echo "=== BYOK End-to-End Test ==="

# 1. Store key locally
echo "1. Storing key in Keychain..."
# Client-side operation (pseudocode)
client.setApiKey --provider $PROVIDER --key $TEST_KEY

# 2. Sync to gateway
echo "2. Syncing to cloud gateway..."
curl -X POST "https://${GATEWAY_ID}.gateways.openclaw.io/api/v1/config/patch" \
  -H "Authorization: Bearer $GATEWAY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "operation": "set",
    "target": "secret",
    "key": "OPENAI_API_KEY",
    "value": "'"$TEST_KEY"'"
  }'

# 3. Verify gateway has key
echo "3. Verifying gateway configuration..."
RESPONSE=$(curl -s "https://${GATEWAY_ID}.gateways.openclaw.io/api/v1/config/status" \
  -H "Authorization: Bearer $GATEWAY_TOKEN")

echo $RESPONSE | jq '.secrets.OPENAI_API_KEY.configured'
# Expected: true

# 4. Test AI request uses user key
echo "4. Testing AI request with BYOK credentials..."
curl -X POST "https://${GATEWAY_ID}.gateways.openclaw.io/api/v1/chat/completions" \
  -H "Content-Type: application/json" \
  -d '{"model": "gpt-4", "messages": [{"role": "user", "content": "test"}]}'

# Verify X-Billing-Mode header indicates BYOK
# Expected: X-Billing-Mode: byok

echo "=== Test Complete ==="

Resultados esperados de las pruebas

PruebaResultado esperado
Almacenamiento de clavesClave recuperable, la visualización enmascarada muestra el formato correcto
Validación de formatoClaves inválidas rechazadas con error descriptivo
Sincronización de puerta de enlace200 OK, restartRequired: true
Solicitud de IASolicitud exitosa con clave proporcionada por el usuario

⚠️ Errores comunes

Errores de seguridad

  • Registro de secretos: Nunca registrar claves API, ni siquiera parcialmente. Asegurarse de que todas las declaraciones de registro redacten los valores sensibles.
    // ❌ Incorrecto
    logger.info(`Using API key: ${apiKey}`);
    

    // ✅ Correcto logger.debug(Using API key for provider: ${provider});

  • Fuga de mensajes de error: Los errores de validación pueden exponer la estructura de la clave. Enmascarar claves en todas las respuestas de error.
    // ❌ Incorrecto
    throw new Error(`Invalid key: ${providedKey}`);
    

    // ✅ Correcto throw new Error(Invalid key format. Key must match pattern for ${provider});

  • Retención en memoria: Las claves API en memoria deben borrarse después de su uso cuando sea posible. Considerar el uso de patrones de cadenas seguras.

Problemas específicos de la plataforma

PlataformaProblemaSolución
macOSAcceso a Keychain denegadoSolicitar derecho en el perfil de aprovisionamiento
WindowsRespaldo de Credential ManagerAsegurar que la API safeStorage de Electron esté disponible
Linuxlibsecret no disponibleImplementar respaldo con almacenamiento de archivos cifrados
Móvil (iOS)Limitaciones de Secure EnclaveUsar ASAuthorizationManager para acceso a keychain
Móvil (Android)Cifrado de KeystoreRequerir API 23+ para almacenamiento respaldado por hardware

Errores de experiencia de usuario

  • Responsabilidad de facturación poco clara: Los usuarios deben entender que se les factura directamente por el proveedor de IA. El aviso de facturación es obligatorio.
  • Sin advertencia de rotación de claves: Advertir a los usuarios que cambiar las claves API requiere reiniciar la puerta de enlace.
  • Sin manejo de expiración de claves: Algunos proveedores (Azure) tienen expiración de claves. Implementar advertencias proactivas.

Errores de configuración

// ❌ Error: Sobrescribir credenciales incluidas
// Si la puerta de enlace tiene credenciales incluidas Y BYOK, ¿cuál tiene prioridad?

// ✅ Solución: Prioridad explícita
const getEffectiveApiKey = (provider, byokKey, bundledKey) => {
  if (byokKey) {
    return { source: 'byok', key: byokKey };
  }
  if (bundledKey) {
    return { source: 'bundled', key: bundledKey };
  }
  throw new Error('No API key configured');
};

🔗 Funciones y errores relacionados

Implementación relacionada

  • #221 - BYOK de puerta de enlace local: Implementación existente de BYOK a través del asistente de configuración. El BYOK de puerta de enlace en la nube debe compartir componentes centrales mientras maneja los requisitos de seguridad específicos de la nube.
  • Sistema de rotación de secretos: Mejora planificada para rotación automatizada de claves API (ver roadmap).
  • Respaldo de múltiples proveedores: Permitir configurar múltiples proveedores con failover automático.

Opciones de configuración relacionadas

ConfiguraciónDescripciónValor predeterminado
AI_PROVIDERProveedor de IA activoopenai
USE_BUNDLED_CREDENTIALSRespaldo a claves incluidastrue
BYOK_ENABLEDHabilitar marca de función BYOKtrue
KEYCHAIN_SERVICEIdentificador de servicio keychainopenclaw.byok

Documentación relacionada

Consideraciones futuras

  • Claves con alcance: Soporte para claves con alcance del proveedor (por ejemplo, claves específicas del endpoint de Azure) con validación mejorada.
  • BYOK de equipo: Función empresarial para administración de claves API compartidas por equipo con registro de auditoría.
  • Estimación de costos: Integración con APIs de proveedores para mostrar estimaciones de uso antes de las solicitudes.

Evidencia y fuentes

Esta guía de solución de problemas fue sintetizada automáticamente por la tubería de inteligencia de FixClaw a partir de las discusiones de la comunidad.