Una clave API de CaptchaAI expuesta se agota sola: basta con que aparezca un instante en un repositorio público de GitHub para que los bots la recojan y consuman tu saldo antes de que te enteres. Blindarla no requiere nada exótico, solo tres decisiones que se aplican una vez y protegen a partir de entonces:
- Sácala del código y cárgala desde variables de entorno.
- Restringe desde qué IPs puede usarse, con una lista blanca.
- Rótala con un plan claro, no solo cuando algo ya ha salido mal.
Esta guía recorre las tres con código listo para copiar, más la parte que casi todo el mundo olvida — no filtrar la clave en los logs ni en tu pipeline de CI/CD.
Lista blanca de IP: restringe desde dónde se usa la clave
Empecemos por la medida que da nombre a esta guía, porque es la que sigue protegiéndote aunque la clave se filtre. La idea es sencilla: autorizas solo las IPs de tus propios servidores, de modo que una clave copiada desde otra red no sirva para nada.
- Revisa en tu panel de CaptchaAI si ofrece opciones de restricción por IP.
- Si están disponibles, deja únicamente las IPs de salida de tu infraestructura.
- Con IP dinámica, enruta el tráfico de tus workers por un gateway o un NAT con IP fija, en lugar de abrir un rango entero.
Un caso habitual: una agencia en Madrid o Ciudad de México que automatiza flujos para varios clientes. Si todos comparten una misma clave, un .env filtrado en un proyecto compromete el saldo de todos. Con una clave por cliente y por entorno —desarrollo, staging y producción— el radio de impacto queda acotado a un único proyecto. Y como el saldo de CaptchaAI se factura en USD por threads, es dinero real que conviene aislar.
Por dónde se escapa una clave API
Antes de blindar nada, conviene saber por dónde se filtran las claves en la práctica. Casi siempre es una de estas cuatro rutas, y el resultado en todas es el mismo: alguien más termina gastando tu saldo.
Exposed API key:
├── Leaked in Git repository
├── Hardcoded in client-side code
├── Shared in documentation
└── Visible in logs
Impact:
├── Balance drained by unauthorized users
├── Usage spikes from abuse
└── Key disabled by service provider
Guarda la clave fuera del código fuente
El almacenamiento seguro se apoya en tres piezas que trabajan juntas:
- La clave se lee de una variable de entorno, nunca de una constante en el código.
- El valor real vive en un archivo
.envlocal que no se versiona. .gitignoremantiene ese.envfuera del repositorio.
Nunca escribas la clave en el código
La regla número uno: la clave no vive en el código fuente. Cárgala desde una variable de entorno o desde un archivo .env que jamás llegue a Git.
# BAD — key in source code
API_KEY = "abc123def456" # DO NOT DO THIS
# GOOD — environment variable
import os
API_KEY = os.environ["CAPTCHAAI_API_KEY"]
# GOOD — .env file (not committed to Git)
from dotenv import load_dotenv
load_dotenv()
API_KEY = os.environ["CAPTCHAAI_API_KEY"]
Archivo .env
Un archivo .env mantiene la clave fuera del repositorio, siempre que lo excluyas del control de versiones:
# .env (add to .gitignore!)
CAPTCHAAI_API_KEY=your_api_key_here
.gitignore
Añade el patrón a .gitignore antes del primer commit, nunca después de haberlo subido:
# Always ignore .env files
.env
.env.local
.env.production
Carga la configuración desde el entorno
Centraliza la lectura de la clave en una clase de configuración: que falle de inmediato si la variable no está definida y que valide la clave contra el endpoint res.php antes de empezar a trabajar.
import os
class CaptchaConfig:
"""Load CaptchaAI config from environment."""
def __init__(self):
self.api_key = os.environ.get("CAPTCHAAI_API_KEY")
if not self.api_key:
raise EnvironmentError(
"CAPTCHAAI_API_KEY not set. "
"Set it in your environment or .env file."
)
self.base_url = os.environ.get(
"CAPTCHAAI_URL", "https://ocr.captchaai.com"
)
def validate(self):
"""Verify the API key works."""
import requests
resp = requests.get(f"{self.base_url}/res.php", params={
"key": self.api_key,
"action": "getbalance",
"json": 1,
}, timeout=10)
data = resp.json()
if data.get("status") != 1:
raise RuntimeError(f"Invalid API key: {data.get('request')}")
return float(data["request"])
# Usage
config = CaptchaConfig()
balance = config.validate()
print(f"Key valid, balance: ${balance:.2f}")
Rota la clave cada cierto tiempo
Rotar la clave de forma periódica limita el daño si alguna vez se filtra. Mantén una clave secundaria lista y cambia a ella en cuanto la primaria deje de responder:
import os
import datetime
class KeyManager:
"""Manage API key rotation."""
def __init__(self):
self.primary_key = os.environ.get("CAPTCHAAI_API_KEY")
self.secondary_key = os.environ.get("CAPTCHAAI_API_KEY_BACKUP")
self.active_key = self.primary_key
def get_key(self):
return self.active_key
def rotate(self):
"""Switch to secondary key."""
if self.secondary_key:
self.active_key = self.secondary_key
print("Rotated to secondary key")
else:
print("No secondary key configured")
def test_key(self, key):
"""Verify a key is valid."""
import requests
resp = requests.get("https://ocr.captchaai.com/res.php", params={
"key": key, "action": "getbalance", "json": 1,
}, timeout=10)
return resp.json().get("status") == 1
# Usage
keys = KeyManager()
# If primary fails, rotate to secondary
if not keys.test_key(keys.get_key()):
keys.rotate()
Como referencia práctica, conviene rotar en estos momentos:
- De forma programada, cada 90 días.
- Cada vez que alguien con acceso al panel deja el equipo.
- Ante cualquier sospecha de filtración, sin esperar a confirmarla.
Valida las solicitudes antes de enviarlas
Comprobar las entradas antes de llamar a in.php evita errores tontos que terminan exponiendo la clave — por ejemplo, registrar una URL con la clave pegada o enviar un method inexistente:
import requests
import logging
logger = logging.getLogger(__name__)
class SecureSolver:
"""Solver with security best practices."""
def __init__(self, api_key):
self.api_key = api_key
self.base = "https://ocr.captchaai.com"
def solve(self, method, **params):
# Validate inputs
self._validate_params(method, params)
data = {"key": self.api_key, "method": method, "json": 1}
data.update(params)
# Log without exposing key
logger.info(
"Submitting %s solve for %s",
method, params.get("pageurl", "unknown"),
)
resp = requests.post(
f"{self.base}/in.php", data=data, timeout=30,
)
return resp.json()
def _validate_params(self, method, params):
"""Prevent common security mistakes."""
# Ensure pageurl is a valid URL
pageurl = params.get("pageurl", "")
if pageurl and not pageurl.startswith(("http://", "https://")):
raise ValueError(f"Invalid pageurl: {pageurl}")
# Ensure method is valid
valid_methods = {
"userrecaptcha", "turnstile", "geetest",
"base64", "post", "bls", "cloudflare_challenge",
}
if method not in valid_methods:
raise ValueError(f"Unknown method: {method}")
Registro de logs sin exponer la clave
El punto más olvidado son los logs. Una clave de 32 caracteres impresa en un log de producción es tan peligrosa como una filtrada en Git, y casi nadie la busca ahí. Redáctala automáticamente con un formateador:
import logging
import re
logger = logging.getLogger(__name__)
class SafeFormatter(logging.Formatter):
"""Redact API keys from log messages."""
KEY_PATTERN = re.compile(r'[a-f0-9]{32}', re.IGNORECASE)
def format(self, record):
msg = super().format(record)
return self.KEY_PATTERN.sub("[REDACTED]", msg)
# Configure safe logging
handler = logging.StreamHandler()
handler.setFormatter(SafeFormatter("%(levelname)s: %(message)s"))
logger.addHandler(handler)
logger.setLevel(logging.INFO)
# Key is automatically redacted in logs
logger.info(f"Using key: abc123def456ghi789jkl012mno345pq")
# Output: INFO: Using key: [REDACTED]
Secrets en Docker
En despliegues con contenedores, nunca incrustes la clave dentro de la imagen. Pásala por variable de entorno o, mejor todavía, con Docker secrets:
# Dockerfile — DO NOT embed keys here
FROM python:3.11-slim
WORKDIR /app
COPY . .
RUN pip install requests
CMD ["python", "solver.py"]
# docker-compose.yml
services:
solver:
build: .
environment:
- CAPTCHAAI_API_KEY=${CAPTCHAAI_API_KEY}
# Or use Docker secrets:
secrets:
- captchaai_key
secrets:
captchaai_key:
file: ./secrets/captchaai_key.txt
Seguridad en CI/CD
GitHub Actions
En integración continua, la clave vive en el gestor de secrets de la plataforma, nunca escrita en el YAML del workflow:
# .github/workflows/test.yml
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run tests
env:
CAPTCHAAI_API_KEY: ${{ secrets.CAPTCHAAI_API_KEY }}
run: python test_solver.py
Nunca imprimas ni hagas echo del secreto en la salida de CI: los logs de las ejecuciones suelen ser visibles para todo el equipo.
Resolución de problemas
| Problema | Causa | Solución |
|---|---|---|
ERROR_WRONG_USER_KEY |
Clave incorrecta o caducada | Verifica la clave desde el panel de CaptchaAI |
| Caída de saldo inesperada | Clave filtrada o compartida | Rota la clave de inmediato y audita los accesos |
| La clave funciona en local pero no en CI | Variable de entorno sin definir | Añádela a los secrets de CI/CD |
| Clave en el historial de Git | Se subió un archivo .env |
Rota la clave, añade .env a .gitignore y limpia el historial con git filter-branch |
Checklist de seguridad
| Práctica | Estado |
|---|---|
| Clave API en variable de entorno | ☑ |
.env añadido a .gitignore |
☑ |
| Sin claves en el código fuente | ☑ |
| Claves redactadas en los logs | ☑ |
| CI/CD usa el gestor de secrets | ☑ |
| Calendario de rotación de claves | ☑ |
| Monitoreo de saldo activo | ☑ |
Preguntas frecuentes
¿La lista blanca de IP sustituye a guardar la clave en variables de entorno?
No. Son capas distintas: las variables de entorno evitan que la clave se filtre, y la lista blanca de IP hace que una clave filtrada sea inútil fuera de tu red. Usa ambas — ninguna reemplaza a la otra.
¿Cómo sé si mi clave ya se ha filtrado?
Vigila el saldo: una caída que no corresponde a tu volumen habitual es la señal más clara. Revisa también los logs de uso en tu panel de CaptchaAI en busca de solicitudes desde IPs o a horas que no reconoces. Ante la duda, rota la clave: es una operación barata frente al coste de un saldo agotado.
¿Cada cuánto debería rotar la clave API?
Depende de tu exposición. Un buen punto de partida son 90 días de forma programada, más una rotación inmediata cada vez que alguien con acceso al panel deja el equipo o cuando sospechas de una filtración. La clase KeyManager de arriba te permite cambiar de clave sin downtime.
¿Necesito claves distintas para desarrollo y producción?
Sí. Usa claves separadas para desarrollo, staging y producción. Si se filtra la clave de desarrollo, el radio de impacto queda contenido y tu producción sigue intacta.
Guías relacionadas
- Cómo proteger las credenciales de CaptchaAI con variables de entorno
- Consulta de saldo y recarga automática
Protege tu inversión: asegura tu clave API de CaptchaAI hoy mismo.