Tutorials

Защита учетных данных CaptchaAI в переменных среды

API-ключ CaptchaAI не должен встречаться в коде ни одной строкой — ни в scraper.py, ни в Dockerfile, ни в тестовом скрипте, который «всё равно никто не увидит». Правильное место для него — переменная окружения: её читает процесс, но она не попадает ни в Git, ни в слои образа, ни в скриншот со стек-трейсом.

Ниже — рабочая схема по средам: .env для локальной разработки, переменные ОС для сервера, Docker secrets для контейнеров, секреты GitHub Actions и GitLab CI для пайплайна и проверка ключа при старте, чтобы сервис падал сразу, а не на первой задаче CAPTCHA.

Почему на это стоит потратить полчаса: ключ привязан к тарифу с оплатой по потокам — например, BASIC ($15/мес, 5 потоков) или ADVANCE ($90/мес, 50 потоков). Утёкший ключ — это не абстрактный риск, а чужие задачи в ваших потоках и очередь у ваших собственных воркеров.


Типичные ошибки

Ошибка Риск Как исправить
Коммит .env в Git Ключ навсегда остаётся в истории репозитория Добавьте .env в .gitignore до первого коммита
Печать ключа в логах Ключ виден в агрегаторе логов и в выводе CI Не логируйте ключ целиком — маскируйте или опускайте
Ключ в Dockerfile Ключ запечён в слои образа Передавайте через ENV во время выполнения, не на сборке
Пересылка ключа в чате или почте Ключ утекает вместе с историей переписки Используйте менеджер секретов или защищённый канал

Каждой из четырёх ошибок соответствует свой способ хранения. Короткая карта, куда класть ключ в зависимости от того, где выполняется код:

Где выполняется код Где хранить ключ Чем плох соседний вариант
Ноутбук разработчика .env + .gitignore Переменная в ~/.bashrc видна всем процессам пользователя
Одиночный сервер Переменная окружения юнита systemd Файл .env придётся деплоить и синхронизировать вручную
Контейнеры, Swarm Docker secret в /run/secrets/ -e KEY=... виден в docker inspect и в списке процессов
Пайплайн CI Маскированный секрет репозитория Значение в .gitlab-ci.yml попадает в историю Git

Разные ключи под разные среды

Практика, которая экономит больше всего времени в командах: заведите отдельный ключ на каждую среду — local, staging, production.

Имя переменной остаётся одним и тем же, меняется только значение в конкретном окружении.

Что это даёт:

  1. Утёкший ключ разработчика ротируется без остановки продакшена.
  2. По расходу потоков видно, какая среда их съедает: QA-прогон на staging не смешивается с боевым трафиком.
  3. Подрядчику или временному участнику команды выдаётся ключ, который отзывается отдельно от остальных.

Для распределённых команд — типичный расклад, когда разработчики в Москве, Минске, Алматы и Тбилиси работают в одном репозитории — это ещё и способ обойтись без пересылки общего ключа в мессенджере. Тариф остаётся один, ключи разные.

И отдельно про данные: если пайплайн параллельно собирает или логирует персональные данные, требования 152-ФЗ и аналогичных норм касаются именно вашей стороны — храните только то, что вы вправе обрабатывать, и никогда не пишите в лог сам ключ.


Шаг 1: вынесите ключ в файл .env

Создайте .env в корне проекта:

CAPTCHAAI_API_KEY=your_actual_api_key_here

И сразу — до первого коммита — закройте его в .gitignore:

# .gitignore
.env
.env.local
.env.production

Порядок здесь важнее содержимого.

Файл, попавший в индекс Git хотя бы один раз, остаётся в истории репозитория, и удаление его следующим коммитом ничего не меняет.

Python (python-dotenv)

pip install python-dotenv
import os
from dotenv import load_dotenv

load_dotenv()

API_KEY = os.environ["CAPTCHAAI_API_KEY"]

# Use in API calls
import requests
resp = requests.post("https://ocr.captchaai.com/in.php", data={
    "key": API_KEY,
    "method": "userrecaptcha",
    "googlekey": "6Le-SITEKEY",
    "pageurl": "https://example.com",
    "json": "1",
})
print(resp.json())

Обратите внимание на os.environ["CAPTCHAAI_API_KEY"] вместо os.environ.get(...).

При отсутствующей переменной процесс упадёт с KeyError на старте, а не отправит в in.php пустой ключ и не получит невнятную ошибку через полминуты.

JavaScript (dotenv)

npm install dotenv
require('dotenv').config();

const API_KEY = process.env.CAPTCHAAI_API_KEY;

if (!API_KEY) {
  console.error('CAPTCHAAI_API_KEY not set');
  process.exit(1);
}

// Use in API calls
const axios = require('axios');
const resp = await axios.post('https://ocr.captchaai.com/in.php', null, {
  params: {
    key: API_KEY,
    method: 'userrecaptcha',
    googlekey: '6Le-SITEKEY',
    pageurl: 'https://example.com',
    json: 1,
  },
});
console.log(resp.data);

Шаг 2: переменные окружения на уровне ОС

На сервере файл .env часто лишний.

Переменную задают средствами системы, и тогда её не нужно ни деплоить, ни синхронизировать.

Linux / macOS

export CAPTCHAAI_API_KEY="your_actual_api_key_here"

# Persist across sessions — add to ~/.bashrc or ~/.zshrc
echo 'export CAPTCHAAI_API_KEY="your_actual_api_key_here"' >> ~/.bashrc

Windows (PowerShell)

На Windows переменную задают в текущей сессии, а затем закрепляют на уровне пользователя:

$env:CAPTCHAAI_API_KEY = "your_actual_api_key_here"

# Persist permanently
[System.Environment]::SetEnvironmentVariable("CAPTCHAAI_API_KEY", "your_actual_api_key_here", "User")

Отдельный нюанс для локальной машины: значение, дописанное в ~/.bashrc, видно любому процессу пользователя, включая сторонние CLI-утилиты и расширения редактора.

Для рабочих машин разработчиков это приемлемо. Для общих или staging-серверов лучше сразу переходить к секретам оркестратора.


Шаг 3: Docker без ключа внутри образа

Переменная при docker run

docker run -e CAPTCHAAI_API_KEY="your_key" my-scraper

Docker Compose

В compose-файле переменная берётся из окружения хоста:

# docker-compose.yml
services:
  scraper:
    image: my-scraper
    environment:

      - CAPTCHAAI_API_KEY=${CAPTCHAAI_API_KEY}

Запись ${CAPTCHAAI_API_KEY} подставляет переменную хоста.

Сам ключ в compose-файл не попадает, поэтому файл можно спокойно держать в репозитории.

Docker secrets (Swarm)

echo "your_actual_api_key_here" | docker secret create captchaai_key -
# docker-compose.yml (Swarm mode)
services:
  scraper:
    image: my-scraper
    secrets:

      - captchaai_key
secrets:
  captchaai_key:
    external: true

Читаем в коде:

with open("/run/secrets/captchaai_key") as f:
    API_KEY = f.read().strip()

Разница принципиальная.

Переменная окружения видна в docker inspect и в списке процессов, а секрет монтируется в /run/secrets/ и не светится в метаданных контейнера.


Шаг 4: секреты в CI/CD

GitHub Actions

# .github/workflows/scrape.yml
jobs:
  scrape:
    runs-on: ubuntu-latest
    steps:

      - uses: actions/checkout@v4
      - run: python scraper.py
        env:
          CAPTCHAAI_API_KEY: ${{ secrets.CAPTCHAAI_API_KEY }}

Секрет добавляется в Settings → Secrets and variables → Actions → New repository secret.

GitLab CI

# .gitlab-ci.yml
scrape:
  script:

    - python scraper.py
  variables:
    CAPTCHAAI_API_KEY: $CAPTCHAAI_API_KEY

Переменная добавляется в Settings → CI/CD → Variables с включённой опцией «Masked».

Тогда значение маскируется в логах джобы.

Отдельно проверьте форки и merge request от внешних участников.

Если секрет отдаётся пайплайну форка, достаточно одного PR со строкой echo $CAPTCHAAI_API_KEY.


Шаг 5: проверка ключа при старте

Пусть сервис проверяет ключ до того, как примет первую задачу:

import os
import sys
import requests

API_KEY = os.environ.get("CAPTCHAAI_API_KEY")
if not API_KEY:
    print("ERROR: CAPTCHAAI_API_KEY environment variable not set")
    sys.exit(1)

# Verify key works
resp = requests.get("https://ocr.captchaai.com/res.php", params={
    "key": API_KEY, "action": "getbalance", "json": "1"
}).json()

if resp["status"] != 1:
    print(f"ERROR: Invalid API key — {resp['request']}")
    sys.exit(1)

print(f"API key valid — balance: ${float(resp['request']):.2f}")

Запрос getbalance к res.php отвечает сразу на два вопроса: ключ валиден и на балансе есть средства.

Ставьте эту проверку в readiness-пробу контейнера или в первые строки воркера — тогда сломанный деплой отсекается на старте, а не превращается в поток ошибок в мониторинге.


Чек-лист перед деплоем

Минимальный набор действий, который закрывает основную часть риска:

  • .env в .gitignore до первого коммита, а не после.
  • Разные значения ключа для local, staging и production.
  • Проверка getbalance при старте сервиса.
  • Маскирование секрета в логах CI и запрет на его выдачу пайплайнам форков.

Частые вопросы

Что выбрать для продакшена: .env или менеджер секретов?

Для продакшена — менеджер секретов.

Шифровать сам .env смысла мало: ключ расшифровки всё равно надо где-то хранить. Подойдут AWS Secrets Manager, Google Secret Manager, Azure Key Vault или HashiCorp Vault, а .gitignore остаётся достаточным только для локальной разработки.

Ключ уже попал в публичный репозиторий — что делать в первую очередь?

Сначала ротируйте ключ в панели управления CaptchaAI, потом чистите историю.

Порядок именно такой: пока старый ключ действителен, он остаётся рабочим у всех, кто успел его скопировать, независимо от того, что вы сделали с репозиторием.

Можно ли держать несколько ключей в одном .env?

Да, через список значений или пронумерованные переменные:

CAPTCHAAI_KEYS=key1,key2,key3
keys = os.environ["CAPTCHAAI_KEYS"].split(",")

Учтите, что несколько ключей не суммируют потоки одного тарифа — это способ разделить среды или проекты, а не увеличить пропускную способность.

Где ключ виден на сервере, даже если он лежит в переменной окружения?

В /proc/<pid>/environ, в выводе docker inspect, в дампах падений и в отладочных эндпоинтах, которые печатают окружение целиком.

Поэтому продакшен-нагрузки лучше переводить на Docker secrets или менеджер секретов, а отладочные ручки с выводом окружения закрывать.

Отличается ли схема хранения для разных типов CAPTCHA?

Нет. Один и тот же ключ работает для reCAPTCHA v2 и v3, Cloudflare Turnstile, Cloudflare Challenge, GeeTest v3, image/OCR и grid-задач, а также для CaptchaFox (beta), Friendly Captcha (beta) и Lemin (beta). Меняется только параметр method в запросе к in.php — хранение ключа одинаковое.


Подключите ключ правильно с первого дня

Получите API-ключ на captchaai.com и заведите отдельное значение под каждую среду.


Что почитать дальше

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