كابتشا توقف البوتات،
لا البشر.

يتحقق VeilGuard من الزوّار عبر إشارات غير مرئية. لا أحاجيز صور ولا اتصالات بأطراف خارجية. كل شيء يعمل على خادمك أنت.

بلا تسجيل، بلا بطاقة ائتمانية. الإعداد يستغرق نحو دقيقتين.

example.com/signup

أنشئ حسابك

Verified!
VeilGuard

جرّبه بنفسك

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

نموذج تسجيل تجريبي
example.com/signup — veilguard-token

يرسل رمزًا مزيّفًا يحمل بصمات الأتمتة.

قرار الخادم
POST verify.php — تُدقّق الرموز محليًا عبر HMAC-SHA256. البشر يمرون، والبوتات لا.
أرسل النموذج لترى ما الذي سيقرره خادمك.
ما الذي يفحصه VeilGuard
  • أطر الأتمتة (WebDriver وPuppeteer وPlaywright)
  • المتصفحات بلا واجهة ومحرّكات العرض البرمجية
  • User-Agent للبوتات والزواحف المعروفة
  • إنتروبيا السلوك: إشارات الفأرة ولوحة المفاتيح واللمس
  • إثبات عمل مرتبط بتحدٍّ أحادي الاستخدام
  • تحليل التوقيت وإعادة التشغيل على خادمك

ما الذي يفعله VeilGuard

ستة أشياء يتقنها.

معدل حجب يقارب 100%

محرك متعدد الإشارات يجمع بصمات الأتمتة وإنتروبيا السلوك وإثبات العمل وتحليل التوقيت على الخادم.

يوقف بوتات AI أيضًا

يأخذ البصمات على Chrome بلا واجهة وأتمتة CDP والوكلاء المبرمَجة ويحجبها، وليس الزواحف الساذجة فقط.

تكامل في دقيقتين

مقتطف واحد للأداة، ونداء واحد على خادمك. انسخ والصق لـPHP وNode.js وPython والمواقع الثابتة.

يعمل 100% على خادمك

التحقق مجرد فحص HMAC-SHA256 محلي. حتى لو تعطل veilguard.online، ستبقى حمايتك تعمل.

غير مرئية بالتصميم

لا إشارات مرور ولا حنفيات حريق. يضغط البشر مرة واحدة ويرون «تم التحقق!».

الخصوصية أولًا ومتوافقة مع GDPR

لا ملفات تعريف ارتباط، ولا بكسلات تتبع، ولا طلبات لأطراف خارجية. الإشارات المؤقتة تعيش داخل الرمز فقط.

كيف تعمل

ثلاث خطوات، بلا حاجة إلى حساب.

  1. 1.

    أنشئ مفاتيح مجانية

    بلا تسجيل وبلا بريد إلكتروني. تحصل فورًا على مفتاح الموقع ومفتاح سرّي.

  2. 2.

    الصق مقتطفين

    سكربت الأداة في نموذجك، ونداء التحقق في خادمك الخلفي.

  3. 3.

    انتهى

    تُدقّق الرموز محليًا عبر HMAC-SHA256. البشر يمرون، والبوتات لا.

تكامل في دقيقتين

أنشئ مفاتيحك المجانية، واختر تقنيتك، والصق الكود. لا شيء يغادر خادمك.

1. أنشئ مفاتيح مجانية
بلا تسجيل وبلا بريد إلكتروني. تحصل فورًا على مفتاح الموقع ومفتاح سرّي.
2. الصق مقتطفين
سكربت الأداة في نموذجك، ونداء التحقق في خادمك الخلفي.

1 · الأداة: الصقها في نموذجك

veilguard-widget.js
/*! VeilGuard v1.0.0 - self-hosted bot protection - https://veilguard.online */
(function () {
  'use strict';
  var LINK = 'https://veilguard.online';
  var STR = {
    en: { nb: "I'm not a bot", vd: 'Verified!', ck: 'Verifying…', er: 'Verification failed — click to retry' },
    de: { nb: 'Ich bin kein Bot', vd: 'Verifiziert!', ck: 'Prüfung…', er: 'Fehlgeschlagen — zum Wiederholen klicken' },
    es: { nb: 'No soy un bot', vd: '¡Verificado!', ck: 'Verificando…', er: 'Error — haz clic para reintentar' },
    fr: { nb: "Je ne suis pas un robot", vd: 'Vérifié !', ck: 'Vérification…', er: 'Échec — cliquez pour réessayer' },
    pt: { nb: 'Não sou um bot', vd: 'Verificado!', ck: 'Verificando…', er: 'Falhou — clique para tentar de novo' },
    ru: { nb: 'Я не робот', vd: 'Проверено!', ck: 'Проверка…', er: 'Ошибка — нажмите для повтора' },
    zh: { nb: '我不是机器人', vd: '已验证!', ck: '验证中…', er: '验证失败 — 点击重试' },
    ja: { nb: 'ロボットではありません', vd: '認証済み!', ck: '確認中…', er: '失敗 — クリックで再試行' },
    ar: { nb: 'لست روبوتًا', vd: 'تم التحقق!', ck: 'جارٍ التحقق…', er: 'فشل — انقر للمحاولة مجددًا' }
  };
  var BOT_UA = /bot|crawl|spider|slurp|headless|phantom|selenium|puppeteer|playwright|webdriver|curl|wget|python|scrapy|axios|go-http|httpclient|okhttp|libwww|lighthouse|facebookexternalhit|telegram|whatsapp|discord/i;

  function sha256(ascii) {
    function rightRotate(value, amount) { return (value >>> amount) | (value << (32 - amount)); }
    var mathPow = Math.pow, maxWord = mathPow(2, 32), result = '';
    var words = [], asciiBitLength = ascii.length * 8;
    var hash = sha256.h = sha256.h || [], k = sha256.k = sha256.k || [], primeCounter = k.length;
    var isComposite = {};
    for (var candidate = 2; primeCounter < 64; candidate++) {
      if (!isComposite[candidate]) {
        for (var i = 0; i < 313; i += candidate) { isComposite[i] = candidate; }
        hash[primeCounter] = (mathPow(candidate, 0.5) * maxWord) | 0;
        k[primeCounter++] = (mathPow(candidate, 1 / 3) * maxWord) | 0;
      }
    }
    ascii += '\x80';
    while (ascii.length % 64 - 56) ascii += '\x00';
    for (i = 0; i < ascii.length; i++) {
      var j = ascii.charCodeAt(i);
      if (j >> 8) return '';
      words[i >> 2] |= j << ((3 - i) % 4) * 8;
    }
    words[words.length] = ((asciiBitLength / maxWord) | 0);
    words[words.length] = (asciiBitLength);
    for (j = 0; j < words.length;) {
      var w = words.slice(j, j += 16), oldHash = hash;
      hash = hash.slice(0, 8);
      for (i = 0; i < 64; i++) {
        var w15 = w[i - 15], w2 = w[i - 2];
        var a = hash[0], e = hash[4];
        var temp1 = hash[7]
          + (rightRotate(e, 6) ^ rightRotate(e, 11) ^ rightRotate(e, 25))
          + ((e & hash[5]) ^ ((~e) & hash[6]))
          + k[i]
          + (w[i] = (i < 16) ? w[i] : (
            w[i - 16]
            + (rightRotate(w15, 7) ^ rightRotate(w15, 18) ^ (w15 >>> 3))
            + w[i - 7]
            + (rightRotate(w2, 17) ^ rightRotate(w2, 19) ^ (w2 >>> 10))
          ) | 0);
        var temp2 = (rightRotate(a, 2) ^ rightRotate(a, 13) ^ rightRotate(a, 22))
          + ((a & hash[1]) ^ (a & hash[2]) ^ (hash[1] & hash[2]));
        hash = [(temp1 + temp2) | 0].concat(hash);
        hash[4] = (hash[4] + temp1) | 0;
      }
      for (i = 0; i < 8; i++) { hash[i] = (hash[i] + oldHash[i]) | 0; }
    }
    for (i = 0; i < 8; i++) {
      for (j = 3; j + 1; j--) {
        var b = (hash[i] >> (j * 8)) & 255;
        result += ((b < 16) ? 0 : '') + b.toString(16);
      }
    }
    return result;
  }

  function b64u(s) {
    try {
      return btoa(unescape(encodeURIComponent(s)))
        .replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
    } catch (e) { return ''; }
  }
  function unb64u(s) {
    try { return decodeURIComponent(escape(atob(s.replace(/-/g, '+').replace(/_/g, '/')))); }
    catch (e) { return ''; }
  }
  function rand(n) {
    var s = '', chars = '0123456789abcdef';
    try {
      var a = new Uint8Array(n);
      (window.crypto || window.msCrypto).getRandomValues(a);
      for (var i = 0; i < n; i++) s += chars[a[i] & 15];
    } catch (e) {
      for (var j = 0; j < n; j++) s += chars[Math.floor(Math.random() * 16)];
    }
    return s;
  }
  function resolveLang(lang) {
    if (lang && lang !== 'auto' && STR[lang]) return lang;
    var nav = ((navigator.languages && navigator.languages[0]) || navigator.language || 'en').slice(0, 2).toLowerCase();
    return STR[nav] ? nav : 'en';
  }
  function resolveTheme(theme) {
    if (theme === 'light' || theme === 'dark') return theme;
    try { return (window.matchMedia && matchMedia('(prefers-color-scheme: dark)').matches) ? 'dark' : 'light'; }
    catch (e) { return 'light'; }
  }
  function powBits(challenge) {
    try {
      var dot = challenge.lastIndexOf('.');
      var p = JSON.parse(unb64u(challenge.slice(0, dot)));
      var d = p && typeof p.d === 'number' ? p.d : 12;
      if (d <= 0 || d > 24) d = 12;
      return Math.round(d / 4) * 4;
    } catch (e) { return 12; }
  }
  function solvePow(challenge, bits) {
    var zeros = Math.max(1, Math.round(bits / 4));
    var prefix = new Array(zeros + 1).join('0');
    for (var p = 0; p < 2000000; p++) {
      var h = sha256(challenge + '.' + p);
      if (h && h.slice(0, zeros) === prefix) return { p: p, h: h };
    }
    return { p: p, h: '' };
  }

  var STYLE = '.vg-root{display:inline-block;width:100%;max-width:340px;font-family:system-ui,-apple-system,Segoe UI,Roboto,sans-serif;direction:ltr}'
    + '.vg-box{border:1px solid #d4d4d8;border-radius:10px;background:#fff;overflow:hidden;box-shadow:0 1px 3px rgba(0,0,0,.07);cursor:pointer;transition:border-color .2s,box-shadow .2s;-webkit-user-select:none;user-select:none}'
    + '.vg-box:focus-visible{outline:2px solid #10b981;outline-offset:2px}'
    + '.vg-dark{background:#18181b;border-color:#3f3f46}'
    + '.vg-main{display:flex;align-items:center;gap:11px;padding:15px 16px;min-height:52px}'
    + '.vg-cb{width:24px;height:24px;border:2px solid #a1a1aa;border-radius:5px;background:#fff;flex:none;display:flex;align-items:center;justify-content:center;position:relative;transition:border-color .15s,background .15s}'
    + '.vg-dark .vg-cb{background:transparent;border-color:#52525b}'
    + '.vg-box:hover .vg-cb{border-color:#10b981}'
    + '.vg-cb svg{width:15px;height:15px;opacity:0;transform:scale(.3);transition:opacity .18s ease,transform .18s ease}'
    + '.vg-ok .vg-cb{background:#10b981;border-color:#10b981}'
    + '.vg-ok .vg-cb svg{opacity:1;transform:scale(1)}'
    + '.vg-label{font-size:14px;color:#3f3f46;font-weight:500;flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}'
    + '.vg-dark .vg-label{color:#e4e4e7}'
    + '.vg-ok .vg-label{color:#059669;font-weight:600}'
    + '.vg-err .vg-label{color:#dc2626;font-weight:500}'
    + '.vg-brand{display:flex;align-items:center;gap:5px;color:#71717a;font-size:11.5px;font-weight:700;letter-spacing:.01em;flex:none}'
    + '.vg-brand svg{width:18px;height:18px;display:block}'
    + '.vg-foot{display:flex;justify-content:flex-end;align-items:center;gap:5px;padding:4px 12px 6px;border-top:1px solid #f4f4f5;background:#fafafa}'
    + '.vg-dark .vg-foot{border-top-color:#27272a;background:#131316}'
    + '.vg-foot svg{width:10px;height:10px;display:block;color:#a1a1aa}'
    + '.vg-foot a{font-size:10.5px;color:#a1a1aa;text-decoration:none;font-weight:500}'
    + '.vg-foot a:hover{color:#10b981;text-decoration:underline}'
    + '.vg-spinner{position:absolute;inset:2px;border-radius:50%;border:2px solid #e4e4e7;border-top-color:#10b981;opacity:0;transition:opacity .15s;animation:vg-spin .8s linear infinite}'
    + '.vg-dark .vg-spinner{border-color:#3f3f46}'
    + '.vg-checking .vg-spinner{opacity:1}'
    + '.vg-ok{border-color:#10b981 !important;box-shadow:0 0 0 3px rgba(16,185,129,.14)}'
    + '.vg-err{border-color:#f87171 !important}'
    + '@keyframes vg-spin{to{transform:rotate(360deg)}}'
    + '@media (prefers-reduced-motion:reduce){.vg-spinner{animation-duration:2s}}';

  var LOGO = '<svg viewBox="0 0 24 24" fill="none"><path d="M12 2.5 20 5.5v6c0 4.8-3.3 9-8 10.7C7.3 20.5 4 16.3 4 11.5v-6L12 2.5Z" fill="#10b981" fill-opacity="0.14" stroke="#10b981" stroke-width="1.5" stroke-linejoin="round"/><path d="m8.6 12 2.4 2.4 4.4-4.8" stroke="#10b981" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/></svg>';
  var CHECK = '<svg viewBox="0 0 24 24" fill="none"><path d="m5 13 4.2 4.2L19 7" stroke="#fff" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/></svg>';
  var MINI = '<svg viewBox="0 0 24 24" fill="none"><path d="M12 2.5 20 5.5v6c0 4.8-3.3 9-8 10.7C7.3 20.5 4 16.3 4 11.5v-6L12 2.5Z" stroke="currentColor" stroke-width="2" stroke-linejoin="round"/></svg>';

  function ensureStyle() {
    if (document.getElementById('vg-style')) return;
    var st = document.createElement('style');
    st.id = 'vg-style';
    st.textContent = STYLE;
    document.head.appendChild(st);
  }

  function render(target, opts) {
    opts = opts || {};
    var el = typeof target === 'string' ? document.querySelector(target) : target;
    if (!el || !el.appendChild) return null;
    ensureStyle();
    var lang = resolveLang(opts.lang);
    var t = STR[lang];
    var theme = resolveTheme(opts.theme);
    var sitekey = opts.sitekey || '';
    var inputName = opts.inputName || 'veilguard-token';
    var mode = opts.mode === 'static' ? 'static' : 'server';
    var challengeUrl = opts.challengeUrl || ('/veilguard-challenge?sitekey=' + encodeURIComponent(sitekey));

    var st = { mm: 0, ds: 0, kd: 0, td: 0, pd: 0, scc: 0, load: Date.now(), token: '', done: false, busy: false };
    var lastXY = null;

    function track(evt, fn) { document.addEventListener(evt, fn, { passive: true, capture: true }); }
    track('mousemove', function (e) {
      st.mm++;
      if (lastXY) { var dx = e.clientX - lastXY[0], dy = e.clientY - lastXY[1]; st.ds += Math.sqrt(dx * dx + dy * dy); }
      lastXY = [e.clientX, e.clientY];
    });
    track('keydown', function () { st.kd++; });
    track('touchstart', function () { st.td++; });
    track('pointerdown', function () { st.pd++; });
    track('scroll', function () { st.scc++; });

    el.innerHTML = '';
    el.className += ' vg-root';
    var box = document.createElement('div');
    box.className = 'vg-box' + (theme === 'dark' ? ' vg-dark' : '');
    box.setAttribute('role', 'checkbox');
    box.setAttribute('aria-checked', 'false');
    box.setAttribute('aria-label', t.nb);
    box.tabIndex = 0;
    box.innerHTML = '<div class="vg-main">'
      + '<div class="vg-cb"><span class="vg-spinner"></span>' + CHECK + '</div>'
      + '<span class="vg-label" aria-live="polite">' + t.nb + '</span>'
      + '<div class="vg-brand">' + LOGO + '<span>VeilGuard</span></div>'
      + '</div>'
      + '<div class="vg-foot">' + MINI + '<a href="' + LINK + '" target="_blank" rel="noopener noreferrer">veilguard.online</a></div>';
    var input = document.createElement('input');
    input.type = 'hidden';
    input.name = inputName;
    el.appendChild(box);
    el.appendChild(input);

    var label = box.querySelector('.vg-label');

    function setState(s) {
      var cls = 'vg-box' + (theme === 'dark' ? ' vg-dark' : '');
      if (s === 'checking') cls += ' vg-checking';
      if (s === 'ok') cls += ' vg-ok';
      if (s === 'err') cls += ' vg-err';
      box.className = cls;
      box.setAttribute('aria-checked', s === 'ok' ? 'true' : 'false');
      label.textContent = s === 'checking' ? t.ck : s === 'ok' ? t.vd : s === 'err' ? t.er : t.nb;
    }

    function detect() {
      var flags = 0;
      try { if (navigator.webdriver === true || !!window.webdriver) flags |= 1; } catch (e) {}
      if (BOT_UA.test(navigator.userAgent)) flags |= 2;
      try {
        if (window._phantom || window.callPhantom || window.__nightmare || window.domAutomation || window.__selenium_unwrapped) flags |= 4;
      } catch (e) {}
      try { if ((window.outerWidth === 0 || window.outerHeight === 0) && window.innerWidth > 0) flags |= 8; } catch (e) {}
      try {
        var c = document.createElement('canvas');
        var gl = c.getContext && (c.getContext('webgl') || c.getContext('experimental-webgl'));
        if (gl) {
          var dbg = gl.getExtension('WEBGL_debug_renderer_info');
          var r = dbg ? gl.getParameter(dbg.UNMASKED_RENDERER_WEBGL) : '';
          if (typeof r === 'string' && /swiftshader|llvmpipe|software|basic render/i.test(r)) flags |= 16;
        }
      } catch (e) {}
      try { if (!navigator.languages || !navigator.languages.length) flags |= 32; } catch (e) {}
      var isChromeUA = navigator.userAgent.indexOf('Chrome/') !== -1 && !/Edg|OPR|SamsungBrowser/i.test(navigator.userAgent);
      if (isChromeUA && !window.chrome) flags |= 64;
      if (isChromeUA && (!navigator.plugins || navigator.plugins.length === 0)) flags |= 128;

      var score = 100;
      if (flags & 31) { score = 0; }
      else {
        if (flags & 32) score -= 20;
        if (flags & 64) score -= 15;
        if (flags & 128) score -= 10;
        var dt = Date.now() - st.load;
        if (dt < 350) score -= 30;
        if (st.mm === 0 && st.td === 0 && st.kd === 0 && st.pd <= 1) score -= 25;
        else if (st.mm > 0 && st.ds < 40) score -= 10;
        if (score < 0) score = 0;
        if (score > 100) score = 100;
      }
      return { flags: flags, score: score };
    }

    function staticChallenge() {
      var now = Date.now();
      return b64u(JSON.stringify({ n: rand(16), i: now, e: now + 300000, k: sitekey, d: 12 }));
    }

    function activate() {
      if (st.done || st.busy) return;
      st.busy = true;
      setState('checking');
      var started = Date.now();
      var finish = function (challenge) {
        if (!challenge) { setState('err'); st.busy = false; return; }
        try {
          var det = detect();
          var pow = solvePow(challenge, powBits(challenge));
          var att = {
            v: 1, ch: challenge, ts: Date.now(), sc: det.score, hl: det.flags,
            lg: lang, ua: navigator.userAgent.slice(0, 180),
            ev: { mm: st.mm, ds: Math.round(st.ds), kd: st.kd, td: st.td, pd: st.pd, sc: st.scc, dt: started - st.load },
            pw: pow
          };
          st.token = 'v1.' + b64u(JSON.stringify(att));
          input.value = st.token;
        } catch (e) { st.token = ''; }
        var wait = Math.max(0, 550 + Math.random() * 350 - (Date.now() - started));
        setTimeout(function () {
          st.busy = false;
          if (st.token) {
            st.done = true;
            setState('ok');
            if (typeof opts.callback === 'function') { try { opts.callback(st.token); } catch (e2) {} }
          } else { setState('err'); }
        }, wait);
      };
      if (mode === 'static') { finish(staticChallenge()); return; }
      try {
        fetch(challengeUrl, { credentials: 'omit' })
          .then(function (r) { if (!r.ok) throw new Error('http ' + r.status); return r.json(); })
          .then(function (d) { finish(d && d.challenge ? String(d.challenge) : ''); })
          .catch(function () { setState('err'); st.busy = false; });
      } catch (e) { setState('err'); st.busy = false; }
    }

    box.addEventListener('click', activate);
    box.addEventListener('keydown', function (e) {
      if (e.key === 'Enter' || e.key === ' ' || e.key === 'Spacebar') { e.preventDefault(); activate(); }
    });

    return {
      reset: function () { st.done = false; st.busy = false; st.token = ''; input.value = ''; setState('idle'); },
      getResponse: function () { return st.token; },
      getSiteKey: function () { return sitekey; }
    };
  }

  function auto() {
    var nodes = document.querySelectorAll('.veilguard[data-sitekey]');
    for (var i = 0; i < nodes.length; i++) {
      var n = nodes[i];
      if (n.getAttribute('data-vg-init') === '1') continue;
      n.setAttribute('data-vg-init', '1');
      render(n, {
        sitekey: n.getAttribute('data-sitekey') || '',
        lang: n.getAttribute('data-lang') || 'auto',
        theme: n.getAttribute('data-theme') || 'auto',
        mode: n.getAttribute('data-mode') || 'server',
        challengeUrl: n.getAttribute('data-challenge-url') || undefined,
        inputName: n.getAttribute('data-input-name') || 'veilguard-token'
      });
    }
  }

  if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', auto); }
  else { auto(); }

  window.VeilGuard = { render: render, auto: auto, version: '1.0.0' };
})();
form.html
<!-- VeilGuard widget — place inside your <form> -->
<div class="veilguard" data-sitekey="vg_YOUR_SITE_KEY" data-challenge-url="veilguard-challenge.php"></div>

<!-- Load the widget once per page (before </body>) -->
<script src="veilguard-widget.js"></script>

2 · التحقق: يعمل على خادمك

veilguard.php
<?php
/**
 * VeilGuard v1.0.0 — self-hosted verification library for PHP
 * Docs: https://veilguard.online
 */

define('VG_MIN_SCORE', 55);
define('VG_TTL', 300);        // seconds — challenge lifetime
define('VG_GRACE', 60);       // seconds — grace after expiry
define('VG_MIN_SOLVE_MS', 250);

function vg_b64u_decode(string $s): string {
    return base64_decode(strtr($s, '-_', '+/'));
}

/** Issue a signed single-use challenge (used by your challenge endpoint). */
function vg_issue_challenge(string $site_key, string $secret): string {
    $now = (int) (microtime(true) * 1000);
    $payload = json_encode(array(
        'n' => bin2hex(random_bytes(8)),
        'i' => $now,
        'e' => $now + VG_TTL * 1000,
        'k' => $site_key,
        'd' => 12,
    ));
    $body = rtrim(strtr(base64_encode($payload), '+/', '-_'), '=');
    return $body . '.' . hash_hmac('sha256', $body, $secret);
}

/**
 * Verify a token submitted by the visitor.
 * Returns array('success' => bool, 'score' => int, 'reason' => string)
 */
function vg_verify_token(string $token, string $secret, string $site_key, int $min_score = VG_MIN_SCORE): array {
    $fail = function (string $reason): array { return array('success' => false, 'reason' => $reason); };

    if (strlen($token) > 4096 || strpos($token, 'v1.') !== 0) {
        return $fail('Malformed token');
    }
    $json = json_decode(vg_b64u_decode(substr($token, 3)), true);
    if (!is_array($json) || !isset($json['ch'])) {
        return $fail('Malformed payload');
    }

    // 1. Cryptographic challenge validation (HMAC-SHA256)
    $ch = $json['ch'];
    $dot = strrpos($ch, '.');
    if ($dot === false) { return $fail('Malformed challenge'); }
    $body = substr($ch, 0, $dot);
    $sig  = substr($ch, $dot + 1);
    if (!hash_equals(hash_hmac('sha256', $body, $secret), $sig)) {
        return $fail('Invalid challenge signature');
    }
    $p = json_decode(vg_b64u_decode($body), true);
    if (!is_array($p)) { return $fail('Malformed challenge payload'); }
    if (($p['k'] ?? '') !== $site_key) { return $fail('Site key mismatch'); }

    // 2. Timing window — server clock only
    $now = (int) (microtime(true) * 1000);
    if ($now > $p['e'] + VG_GRACE * 1000) { return $fail('Challenge expired'); }
    if ($now < $p['i']) { return $fail('Challenge issued in the future'); }
    if ($now - $p['i'] < VG_MIN_SOLVE_MS) { return $fail('Solved impossibly fast'); }

    // 3. Proof-of-work bound to this challenge
    $pow = $json['pw'] ?? null;
    if (!is_array($pow) || !isset($pow['p'])) { return $fail('Missing proof of work'); }
    $hash = hash('sha256', $ch . '.' . $pow['p']);
    $zeros = (int) ceil($p['d'] / 4);
    if (substr($hash, 0, $zeros) !== str_repeat('0', $zeros)) {
        return $fail('Invalid proof of work');
    }

    // 4. Automation fingerprints reported by the widget (bitmask)
    $hl = (int) ($json['hl'] ?? 255);
    if ($hl & 1)  { return $fail('Automation framework detected'); }
    if ($hl & 2)  { return $fail('Known bot user agent'); }
    if ($hl & 4)  { return $fail('Automation framework detected'); }
    if ($hl & 8)  { return $fail('Headless browser detected'); }
    if ($hl & 16) { return $fail('Software renderer detected'); }

    // 5. Behavioral score threshold
    $score = (int) ($json['sc'] ?? 0);
    if ($score < $min_score) { return $fail('Score below threshold'); }

    // 6. Single-use nonce — replay protection
    if (!vg_nonce_fresh($p['n'], $p['e'])) { return $fail('Token replay detected'); }

    return array('success' => true, 'score' => $score);
}

/** File-based single-use nonce store (shared-hosting friendly). */
function vg_nonce_fresh(string $nonce, int $expires_ms): bool {
    $file = sys_get_temp_dir() . '/veilguard-nonces.json';
    $used = array();
    if (is_file($file)) {
        $used = json_decode((string) @file_get_contents($file), true) ?: array();
    }
    $now = (int) (microtime(true) * 1000);
    foreach ($used as $k => $exp) {
        if ((int) $exp < $now) { unset($used[$k]); }
    }
    if (isset($used[$nonce])) { return false; }
    $used[$nonce] = $expires_ms + 60000;
    @file_put_contents($file, json_encode($used), LOCK_EX);
    return true;
}
veilguard-challenge.php
<?php
/**
 * VeilGuard challenge endpoint — serves single-use challenges to the widget.
 * File: veilguard-challenge.php
 */
require __DIR__ . '/veilguard.php';

$siteKey = 'vg_YOUR_SITE_KEY';   // public
$secret  = 'vgs_YOUR_SECRET_KEY'; // private — never expose in HTML

header('Content-Type: application/json');
header('Cache-Control: no-store');
echo json_encode(array('challenge' => vg_issue_challenge($siteKey, $secret)));
signup.php
<?php
/**
 * VeilGuard example — a bot-proof signup form.
 * File: signup.php
 */
require __DIR__ . '/veilguard.php';

$siteKey = 'vg_YOUR_SITE_KEY';
$secret  = 'vgs_YOUR_SECRET_KEY';
$error = null;
$success = null;

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $token = $_POST['veilguard-token'] ?? '';
    $result = vg_verify_token($token, $secret, $siteKey);
    if (!$result['success']) {
        http_response_code(403);
        $error = 'Bot detected: ' . $result['reason'];
    } else {
        // Human confirmed — continue with your logic here.
        $success = 'Welcome, human! Trust score: ' . $result['score'];
    }
}
?>
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Signup — protected by VeilGuard</title>
</head>
<body>
  <?php if ($success): ?><p style="color:green"><?= htmlspecialchars($success) ?></p><?php endif; ?>
  <?php if ($error): ?><p style="color:red"><?= htmlspecialchars($error) ?></p><?php endif; ?>

  <form method="post">
    <input type="email" name="email" placeholder="Email" required>

    <!-- VeilGuard widget -->
    <div class="veilguard" data-sitekey="<?= htmlspecialchars($siteKey) ?>"
         data-challenge-url="veilguard-challenge.php"></div>

    <button type="submit">Sign up</button>
  </form>

  <script src="veilguard-widget.js"></script>
</body>
</html>

يمكن أيضًا إدارة المفاتيح من لوحة الإدارة.

إحصائياتك الخاصة

VeilGuard لا يرسل أي شيء إلى الخارج. إذا أردت معرفة عدد مرات استخدام الكابتشا خاصتك، سجّل النتائج بنفسك - مقطعان صغيران وملف JSON واحد، دون قاعدة بيانات.

مقطعان برمجيان
بدون إطار عمل وبدون قاعدة بيانات - PHP خالٍ يعمل على أي استضافة. النمط نفسه يعمل في Node و Python.

1 · في معالج النموذج

form-handler.php
<?php
// form-handler.php — your existing form handler.
require __DIR__ . '/veilguard.php';

// Your key pair from the integration section.
$siteKey = 'vg_YOUR_SITE_KEY';
$secret  = 'vgs_YOUR_SECRET_KEY';

$result = vg_verify_token($_POST['veilguard-token'] ?? '', $secret, $siteKey);

// --- Statistics: one row per attempt, stored only on your server. ---
$file = __DIR__ . '/data/stats.json';
$rows = is_file($file) ? (json_decode((string) file_get_contents($file), true) ?: []) : [];
$rows[] = array(
    't' => gmdate('c'),                           // when
    'v' => $result['success'] ? 'human' : 'bot',  // verdict
    's' => (int) ($result['score'] ?? 0),         // score
);
@mkdir(dirname($file), 0755, true);
file_put_contents($file, json_encode($rows), LOCK_EX);

if (!$result['success']) {
    http_response_code(403);
    exit($result['reason'] ?? 'Bot detected');
}

// Human confirmed — continue with your logic here.

كيف يمكن أن يبدو

VeilGuard
إحصائيات مستضافة ذاتيًا
بيانات تجريبية

1248

عمليات التحقق

1173

بشر

75

بوتات

14

اليوم

بشر بوتات

بيانات تجريبية - بطاقتك ستعرض أرقامك الحقيقية.

ملف الإحصائيات يبقى على خادمك - وككل شيء في VeilGuard، لا يُرسل أي شيء. امنع الوصول عبر الويب إلى مجلد data/ (مثلًا عبر .htaccess) كما هو موضح في الحزمة.

الأسعار

VeilGuard مجاني، بما في ذلك الاستخدام التجاري. لا توجد خطط مدفوعة.

احصل على مفاتيحك
$0 للأبد
  • تحققات غير محدودة
  • مواقع غير محدودة
  • كل اللغات التسع
  • تحقق ذاتي الاستضافة
  • بلا تتبّع ولا ملفات تعريف ارتباط
  • الاستخدام التجاري مسموح

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

تواصل معنا

أسئلة عن التكامل أو ملاحظات أو بوت تسلل عبر الحماية؟ راسلنا - هذا النموذج يطبّق ما يبشّر به VeilGuard: فهو محمي بكابتشا VeilGuard نفسها.

  • نرد عادة خلال 48 ساعة
  • تُخزَّن رسالتك على هذا الخادم فقط
  • لا تتبّع ولا رسائل بريدية
أرسل رسالة
محمي بـ VeilGuard - لا يصل البوت إلى صندوق الوارد أبدًا.