Один закоммиченный .env-файл — и баланс на аккаунте CaptchaAI может обнулиться за несколько часов: ключ API по умолчанию не привязан ни к какому IP-адресу, поэтому любой, кто его увидел, отправляет запросы наравне с вами. Ниже — без теории, сразу по делу: где хранить ключ, как его вращать, что делать при утечке и можно ли ограничить доступ по IP-адресу. Примеры даны на Python, но принципы одинаковы для Node.js, PHP, Go и любого другого стека, который вызывает in.php/res.php.
Откуда берутся утечки ключа
Ключ API почти никогда не «крадут» в киношном смысле — его теряют по рассеянности. Самые частые пути утечки на практике:
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
Разница между этими сценариями большая. Хардкод в клиентском коде виден любому, кто откроет DevTools в браузере. Ключ в старом коммите 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"]
Ключ в .env-файле
Для локальной разработки удобнее держать переменные в .env-файле, который процесс подхватывает через python-dotenv или аналог. Главное условие — файл никогда не должен попасть в Git:
# .env (add to .gitignore!)
CAPTCHAAI_API_KEY=your_api_key_here
.gitignore — обязательная строка
Добавьте .env в .gitignore ещё до первого коммита, а не после того, как ключ туда уже попал:
# Always ignore .env files
.env
.env.local
.env.production
Если файл всё же закоммичен, одного удаления и нового коммита недостаточно — он останется в истории репозитория. В этом случае ключ нужно считать скомпрометированным и сгенерировать новый, а старую запись из истории вычищать отдельно (git filter-branch или git filter-repo).
Загрузка ключа из переменных окружения
Явная проверка на старте приложения экономит часы отладки: вместо непонятной ошибки 401 где-то в середине пайплайна вы получаете понятное исключение сразу при запуске, если ключ не задан.
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}")
Такой класс-обёртка также удобен тем, что он один раз проверяет ключ через getbalance — если ключ невалиден или истёк, вы узнаете об этом до того, как отправите первую реальную задачу на решение CAPTCHA, а не после того, как накопится очередь из ошибок ERROR_WRONG_USER_KEY.
Ротация ключей: как и когда менять
Плановая ротация — это не реакция на утечку, а профилактика. Меняйте рабочий ключ по расписанию (например, раз в квартал) и держите резервный ключ наготове на случай, если основной внезапно перестал работать:
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()
Практическая схема для команды из нескольких человек: один продакшен-ключ, который знает только CI/CD, и отдельные dev-ключи у каждого разработчика. Это обычная практика для распределённых команд — например, если часть подрядчиков работает из России, Беларуси или Казахстана в разных часовых поясах, вы не завязываете продакшен-доступ на конкретного человека и можете отозвать один dev-ключ, не трогая остальных.
Проверка запросов перед отправкой
Опечатка в pageurl или неверное имя метода — банальная причина половины «случайных» списаний баланса. Валидация на своей стороне отсекает это до отправки запроса на in.php:
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", "turnstile",
}
if method not in valid_methods:
raise ValueError(f"Unknown method: {method}")
Обратите внимание на logger.info в примере — в лог попадает pageurl, но не сам ключ. Это осознанное решение, а не случайность: следующий раздел объясняет, почему.
Логирование без утечки ключа
Ключ API — это учётные данные, и относиться к нему в логах нужно так же, как к паролю: не печатать в открытом виде, даже во «временных» debug-логах. Простой Formatter, который вырезает похожие на ключ строки регулярным выражением, закрывает большинство случайных утечек:
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]
Это особенно актуально, если ваши access-логи и так подпадают под требования к обработке персональных данных (для российской аудитории — 152-ФЗ, для международных команд — аналогичная дисциплина в духе GDPR): собирайте и храните в логах только то, что действительно нужно для отладки, и не дублируйте туда секреты по умолчанию.
Ключ в Docker: секреты вместо переменных окружения в образе
В Dockerfile ключ не должен появляться никогда — ни как ENV, ни как аргумент сборки, который остаётся в слоях образа. Передавайте его во время запуска контейнера:
# Dockerfile — DO NOT embed keys here
FROM python:3.11-slim
WORKDIR /app
COPY . .
RUN pip install requests
CMD ["python", "solver.py"]
Для docker-compose тот же принцип: значение приходит извне (${CAPTCHAAI_API_KEY} из окружения хоста или CI) либо через встроенный механизм Docker secrets, а не зашито в docker-compose.yml:
# 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
Безопасность в CI/CD
GitHub Actions
В пайплайне ключ должен жить только в секретах CI (Settings → Secrets and variables → Actions для GitHub) и подставляться в переменную окружения на этапе запуска задачи:
# .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
Не выводите секрет в лог задания и не передавайте его как обычный аргумент командной строки — некоторые CI-системы всё равно маскируют такие значения в выводе, но полагаться на маскировку как на единственную защиту не стоит.
Диагностика: типичные проблемы с ключом
| Проблема | Причина | Что делать |
|---|---|---|
ERROR_WRONG_USER_KEY |
Ключ введён с опечаткой или срок его действия истёк | Сверьте ключ в панели управления CaptchaAI |
| Баланс списывается быстрее ожидаемого | Ключ утёк или им поделились | Немедленно сгенерируйте новый ключ и проверьте, кто и откуда обращался к API |
| Ключ работает локально, но не в CI | Переменная окружения не задана в CI/CD | Добавьте ключ в секреты CI/CD-системы |
| Ключ в истории Git | .env-файл был закоммичен |
Сгенерируйте новый ключ, добавьте .env в .gitignore, вычистите историю (git filter-branch/filter-repo) |
Чек-лист безопасности API-ключа
| Пункт | Статус |
|---|---|
| Ключ API хранится в переменной окружения, не в коде | ☐ |
.env добавлен в .gitignore до первого коммита |
☐ |
| В логах ключ маскируется, а не пишется в открытом виде | ☐ |
| CI/CD использует встроенный менеджер секретов | ☐ |
| Есть график плановой ротации ключа | ☐ |
| Отдельные ключи для dev, staging и продакшена | ☐ |
| Настроен мониторинг баланса и алерт на аномальный расход | ☐ |
Часто задаваемые вопросы
Как быстро понять, что API-ключ CaptchaAI утёк?
Первый сигнал — баланс тает быстрее, чем позволяет ваш обычный объём задач, или в панели управления видны запросы в часы, когда ваши сервисы не работают. Настройте алерт на резкое падение баланса за короткий период — это дешевле, чем разбирать инцидент постфактум.
Что делать, если ключ всё-таки скомпрометирован?
Сгенерируйте новый ключ в панели управления CaptchaAI и обновите его во всех сервисах, которые используют старый. Проверьте баланс и логи на предмет запросов, которые вы не отправляли, и только после этого разбирайтесь, откуда произошла утечка.
Можно ли ограничить API-ключ CaptchaAI по IP-адресу?
Проверьте раздел с настройками доступа в своей панели управления CaptchaAI — если ограничение по IP там доступно для вашего аккаунта, внесите в список только адреса своих продакшен-серверов. Если функции пока нет, компенсируйте это ротацией ключа и строгим контролем логов.
Нужно ли менять ключ по графику, если утечки не было?
Да, это стандартная гигиена, а не признак проблемы. Плановая ротация раз в квартал ограничивает «время жизни» ключа, который мог утечь незаметно — например, через старый бэкап или логи стороннего сервиса, о которых вы не знаете.
Как хранить ключ, если CI/CD разворачивает несколько сред?
Заведите отдельный секрет CI на каждую среду (dev, staging, production) с собственным ключом CaptchaAI и правами доступа, а не один общий ключ на весь пайплайн. Так утечка из dev-окружения не затронет продакшен.
Связанные руководства
Защитите свои вложения — настройте безопасное хранение API-ключа CaptchaAI уже сегодня.