دروس API

تأمين مفتاح CaptchaAI API والقائمة البيضاء لعناوين IP

مفتاح CaptchaAI API هو بيانات اعتمادك الوحيدة أمام الخدمة؛ من يحصل عليه يستهلك رصيدك دون قيد. تأمينه يقوم على خمس ممارسات مترابطة يشرحها هذا الدليل بأمثلة Python جاهزة للنسخ، بحيث ينتقل فريقك من مفتاح مكشوف إلى إعداد يمكن مراجعته والاعتماد عليه في الإنتاج:

  • إبقاء المفتاح خارج الشيفرة المصدرية تماماً.
  • تحميله من متغيرات البيئة أو ملف .env.
  • تدويره بشكل دوري لتقليص عمر أي تسريب.
  • تنقيحه آلياً من السجلات ومخرجات التشغيل.
  • تقييد الوصول عبر القائمة البيضاء لعناوين IP حيثما أتاحت لوحة التحكم ذلك.

لماذا يُعدّ مفتاح الـ API هدفاً سهلاً

معظم حوادث تسريب المفاتيح لا تنتج عن هجوم متقدم، بل عن خطأ يومي متكرر:

  • مفتاح مكتوب مباشرة في الشيفرة ثم رُفع إلى Git.
  • مفتاح مضمّن في كود يعمل على المتصفح ويصل إليه المستخدم.
  • مفتاح ظاهر في سطر سجل أو في مخرجات أداة تصحيح.

بمجرد أن يصبح المفتاح علنياً، يمكن لأي طرف إرسال طلبات باسمك حتى يجف الرصيد. المخطط التالي يلخّص مسارات التسريب الشائعة وأثرها المباشر:

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

استيعاب هذه المسارات هو الخطوة الأولى؛ فكل ممارسة في هذا الدليل تُغلق واحداً منها تحديداً.


خزّن المفتاح بأمان بعيداً عن الشيفرة

لا تُضمّن المفاتيح داخل الشيفرة مطلقاً

القاعدة الأولى: يجب ألا يظهر المفتاح كنص ثابت في أي ملف مصدري. الطريقة الموثوقة هي قراءته من متغير بيئة، أو من ملف .env غير مُدرَج ضمن مستودع 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 قيمة المفتاح عن الشيفرة، ويسمح لكل بيئة (تطوير، تجربة، إنتاج) بقيمة خاصة بها دون تعديل الكود:

# .env (add to .gitignore!)
CAPTCHAAI_API_KEY=your_api_key_here

ملف .gitignore

خطوة واحدة منسية هنا تلغي كل ما سبق. أضِف أنماط ملفات البيئة إلى .gitignore قبل أول commit حتى لا يصل المفتاح إلى تاريخ المستودع أصلاً:

# Always ignore .env files
.env
.env.local
.env.production

حمّل الإعداد من متغيرات البيئة

بدل قراءة المتغير في كل موضع من الشيفرة، اجمع الإعداد في فئة واحدة تتحقق من وجود المفتاح وتفشل مبكراً برسالة واضحة إذا غاب. هذا يمنع الأخطاء الغامضة أثناء التشغيل، ويوفّر مكاناً منطقياً للتحقق من صحة المفتاح عبر استعلام الرصيد قبل بدء العمل الفعلي:

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}")

دوّر مفاتيح الـ API بشكل دوري

تدوير المفاتيح يحدّ من مدة صلاحية أي مفتاح مسرّب؛ فحتى لو تسرّب مفتاح، ينتهي أثره عند التدوير التالي. الأسلوب العملي هو الاحتفاظ بمفتاح أساسي وآخر احتياطي، والتبديل تلقائياً إلى الاحتياطي عند فشل الأساسي. صمّم الكود بحيث يختبر المفتاح النشط قبل الاعتماد عليه:

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()

اعتمد جدول تدوير واضحاً يوازن بين الأمان وكلفة التشغيل:

  • تدوير مجدول كل تسعين يوماً كحد أقصى.
  • تدوير فوري عند أي اشتباه في التسريب.
  • تدوير عند مغادرة عضو كان يملك صلاحية الوصول إلى المفتاح.

تحقّق من الطلبات قبل إرسالها

كثير من حوادث الكشف تحدث لأن طلباً خاطئاً يُرسَل إلى نقطة نهاية غير متوقعة فيسرّب المفتاح ضمنه. أضِف طبقة تحقق تفحص أن pageurl رابط صالح، وأن method من القائمة المدعومة، وتسجّل ما يجري دون كتابة المفتاح نفسه في السجل:

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}")

تشمل قائمة valid_methods الأنواع التي تحلّها CaptchaAI فعلياً مثل userrecaptcha وturnstile وgeetest (الإصدار الثالث) وcloudflare_challenge والصور عبر post، فيمنع ضبطها إرسال طلبات لأنواع غير مدعومة.


سجّل الأحداث دون كشف المفاتيح

السجلات مصدر تسريب متكرر لأنها تُخزَّن وتُشارَك بسهولة. بدل الاعتماد على الانضباط اليدوي، افرض التنقيح آلياً عبر مُنسّق سجلات يستبدل أي سلسلة تشبه المفتاح بعلامة [REDACTED]، فيصبح الكشف مستحيلاً حتى لو سجّل مطوّر المفتاح سهواً:

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]

القائمة البيضاء لعناوين IP كطبقة دفاع إضافية

تقييد الوصول إلى مجموعة عناوين IP معروفة يجعل المفتاح المسرّب أقل نفعاً بكثير للمهاجم، لأن الطلبات القادمة من خارج القائمة تُرفض. تحقّق من لوحة تحكم CaptchaAI لمعرفة ما إذا كانت تتيح إعدادات تقييد IP؛ فإن توفّرت، أضِف عناوين خوادمك فقط. هذا النهج يناسب على وجه الخصوص:

  • أحمال العمل التي تنطلق من خوادم ذات عناوين IP ثابتة.
  • النشر ضمن نطاق IP مخصّص لدى مزوّد سحابي.
  • الفرق التي تريد تقليل الاعتماد الوحيد على سرية المفتاح.

اعتبر القائمة البيضاء طبقة إضافية فوق التخزين الآمن والتدوير، لا بديلاً عنهما.


أدِر الأسرار داخل حاويات Docker

في عمليات النشر بالحاويات، لا تُضمّن المفتاح داخل Dockerfile إطلاقاً، لأنه يبقى محفوظاً في طبقات الصورة ويمكن استخراجه. مرّر المفتاح وقت التشغيل عبر متغير بيئة أو عبر 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

أمّن مفتاح الـ API في خطوط CI/CD

GitHub Actions

خطوط التكامل والنشر المستمر بيئة حسّاسة لأن سجلاتها كثيراً ما تكون مرئية للفريق كله. خزّن المفتاح في أسرار المستودع (Repository secrets) ومرّره كمتغير بيئة فقط داخل الخطوة التي تحتاجه:

# .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 مطلقاً، حتى في رسائل التصحيح المؤقتة.


سيناريو تطبيقي من السوق العربي

لنفترض فريقاً في الرياض يبني تكاملاً مع CaptchaAI لأتمتة اختبارات الجودة على بوابة تسجيل. في البداية كان المفتاح مكتوباً داخل سكربت الاختبار، وبعد الانتقال إلى GitHub Actions ظهر في سجلات التشغيل أمام كل عضو في الفريق. الحل المتوافق مع متطلبات حماية البيانات في المنطقة كان مباشراً: نقل المفتاح إلى أسرار المستودع، وتفعيل تنقيح السجلات، وفصل مفتاح الاختبار عن مفتاح الإنتاج، وتقييد عناوين IP حيثما أتاحت لوحة التحكم ذلك. النتيجة: بقي سير العمل كما هو، لكن سطح التعرّض تقلّص إلى الحد الأدنى دون أي تعديل في منطق التطبيق.


استكشاف الأخطاء وإصلاحها

المشكلة السبب المحتمل الإجراء الموصى به
ERROR_WRONG_USER_KEY المفتاح غير صحيح أو منتهي الصلاحية تحقّق من المفتاح من لوحة تحكم CaptchaAI
استنزاف مفاجئ للرصيد تسريب المفتاح أو مشاركته دوّر المفتاح فوراً وراجع سجل الوصول
المفتاح يعمل محلياً لا في CI متغير البيئة غير مضبوط أضِفه إلى أسرار CI/CD
المفتاح موجود في تاريخ Git التزام ملف .env بالخطأ دوّر المفتاح، أضِف .env إلى .gitignore، ونظّف التاريخ عبر git filter-branch

قائمة تحقق أمان مفتاح الـ API

الممارسة الحالة
المفتاح مخزّن في متغير بيئة
.env مُضاف إلى .gitignore
لا مفاتيح داخل الشيفرة المصدرية
المفاتيح مُنقّحة في السجلات
CI/CD يستخدم مدير أسرار
جدول دوري لتدوير المفاتيح
مراقبة الرصيد مفعّلة

الأسئلة الشائعة

كيف أُزيل مفتاحاً سرّبته بالفعل داخل مستودع Git؟

اعتبر المفتاح محروقاً فوراً: أنشئ مفتاحاً جديداً من لوحة تحكم CaptchaAI وحدّث كل التطبيقات، ثم نظّف تاريخ المستودع عبر git filter-branch أو أداة مكافئة. حذف الملف في commit جديد لا يكفي، لأن القيمة تبقى في التاريخ.

هل تحمي القائمة البيضاء لعناوين IP مفتاحي إذا تسرّب؟

تقلّل الخطر لكنها لا تلغيه. عندما تكون متاحة في لوحة التحكم، فإنها ترفض الطلبات القادمة من خارج نطاقك، لكنها تبقى طبقة تُضاف فوق التخزين الآمن والتدوير، لا بديلاً عنهما.

كل كم يجب أن أُدوّر مفاتيح الـ API؟

جدول معقول هو التدوير كل تسعين يوماً كحد أقصى، مع تدوير فوري عند أي اشتباه في التسريب أو مغادرة عضو من الفريق كان يملك صلاحية الوصول.

كيف أؤمّن المفتاح في البيئات عديمة الخادم مثل Lambda؟

مرّر المفتاح عبر متغيرات البيئة المشفّرة الخاصة بالدالة، أو عبر مدير أسرار سحابي، ولا تضعه داخل حزمة النشر. الفكرة نفسها المطبَّقة في الحاويات: فصل السرّ عن الشيفرة والصورة.

ما الفرق بين تخزين المفتاح في ملف .env ومدير الأسرار؟

ملف .env مناسب للتطوير المحلي وبسيط، لكنه نص عادي على القرص. مدير الأسرار يوفّر تشفيراً وتحكماً في الوصول وتدقيقاً للاستخدام، وهو الخيار المفضّل في الإنتاج وبيئات CI/CD.


أدلة ذات صلة


احمِ رصيدك اليوم — أمّن مفتاح CaptchaAI API الخاص بك.

التعليقات غير مفعّلة لهذا المقال.