API Tutorials

Белый список IP-адресов CaptchaAI и безопасность ключей API

Один закоммиченный .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 уже сегодня.

Комментарии для этой статьи отключены.