Satelit Shield
Stop bots and scrapers, not people
Shield recognises automation, antidetect browsers and scrapers, in the page and on the server; the most a person sees is a check that passes by itself in about a second.
The first 7 days are a trial, with 100,000 checks.
Checking your browser
The Shield sensor runs on this page, as on a customer's site.
A real browserscore …/100
No sign of automation or of a spoofed fingerprint. On a protected site, pages open as usual.
A few risk signsscore …/100
Weak signs, not certain ones. On a protected site you would see at most the check that passes by itself.
Automation foundscore …/100
Certain signs of automation or disguise. On a protected site, access would be stopped.
A search crawlerscore …/100
A declared crawler. Verified ones, such as Google and Bing, are never stopped.
Your browser passed
Shield asked for no check from this browser.
The test did not answer
A content blocker or a slow network stopped the script. Through a first-party proxy it comes from your own domain.
What the sensor saw
The signs appear after the sensor's first report.
These are the check IDs as the sensor sends them. What each one means is in the Shield dashboard.
The same script you put on your site. It sends check IDs only: no positions, keys or text.
- The check passes by itself in about a second. A person clicks nothing.
- Watch first: see what Shield would stop, then switch protection on.
- Verified search crawlers are never touched.
What it stops
Automation, false fingerprints, scrapers and abuse
Each card says what Shield looks for and the rules that decide.
-
Browser automation
Selenium, Puppeteer, Playwright, Cypress, TestCafe and headless Chrome, recognised by the traces they leave in the page.
automation -
Stealth plugins and “undetected” builds
Automation patched to look human. Shield looks for the patches themselves: replaced functions, detached windows, a Chromium posing as Chrome.
stealth -
Antidetect browsers
Spoofed fingerprints. Shield compares what the browser claims with what its engine does: the screen, the time zone, the language, the fonts, the canvas drawing.
antidetectbrowser-soft -
HTML scrapers
Programs that ask for pages without running them: no sensor report, too many pages, too fast.
no-proofratewalk -
Fake Googlebot
A crawler name is believed only when the official IP list and reverse DNS confirm it. Verified crawlers always get through.
fake-crawlerrobotstrap -
Address fleets and data centres
Browsers from hosting networks, User-Agents that lie, headers that do not match. IPv6 is counted per /64 network.
datacenterua-claimclient-hintsstale-browser -
Fake votes, comments and sign-ups
The form sends a sensor token and your server verifies it once, within 300 seconds. Verified with the visitor's address, a token copied to another address is refused.
POST /verify{ "success": true, "verdict": "human", "score": 3, "action": "allow", "age_s": 4 }
How it works
Two layers, one decision
The sensor sees the browser, your server sees the requests. Shield puts them together for each address and decides once.
-
In the page
The sensor
- One ES5 script on your pages, loaded async: it never holds the page back.
- It sends check IDs only: no positions, keys, text or fingerprint hash.
- It solves the check page: a 14-bit proof of work.
- It keeps the pass for 12 hours, in a cookie on your domain.
-
On the server
The requests
- Your server or edge tells Shield which requests arrive: one at a time to
/assess, in batches to/ingest. - Shield counts per address: IPv4, or the /64 network for IPv6.
- A crawler name is checked against the official IP list and by reverse DNS.
- The Sec-Fetch, Client Hints and User-Agent headers have to say the same thing.
- Your server or edge tells Shield which requests arrive: one at a time to
Three possible outcomes
- 200 Through People and verified crawlers. Nothing changes.
- 429 A one-second check Weak signs: a page that passes by itself. The person clicks nothing.
- 403 Stopped Certain signs: 24 hours, then 7 days, then 30 days on a repeat within 90 days.
Watch first, then protect
Every rule has a mode: off, shadow (watching: Shield only notes what it would do) or enforce (protection). A new site starts with every rule in shadow; you switch them to enforce one at a time.
Verified crawlers always get through
Google, Bing, Apple, Yandex, DuckDuckGo, Baidu, OpenAI, Perplexity, Anthropic and more: no decision, ever. The same for your allowlist.
Install
Three steps, the first is one line
The site key (shp_…) is public and sits in the page. The secret (shs_…) stays on your server; you see it once.
-
Put the sensor on your pages
Direct, one line in the page. Recommended: through a first-party proxy, from your own domain. No third-party request, it works under
script-src 'self', and ad blockers do not see it.<!-- in every page, direct --> <script src="https://api.satelit.md/shield/v1/sensor.js" data-site="shp_XXXXXXXXXXXXXXXXXXXXXXXX" async></script># Caddyfile: /_sh/* goes to Shield, with your secret and the visitor's address example.md { handle_path /_sh/* { rewrite * /shield/v1{uri} reverse_proxy https://api.satelit.md { header_up Host {upstream_hostport} header_up X-Shield-Secret {env.SHIELD_SECRET} header_up X-Shield-Client-IP {client_ip} header_up -Cookie header_up -Authorization } } # ... the rest of your site } # in every page: # <script src="/_sh/sensor.js" data-site="shp_XXXXXXXXXXXXXXXXXXXXXXXX" data-base="/_sh" async></script># nginx: /_sh/ goes to Shield, with your secret and the visitor's address location /_sh/ { proxy_pass https://api.satelit.md/shield/v1/; proxy_ssl_server_name on; proxy_set_header Host api.satelit.md; proxy_set_header X-Shield-Secret "shs_..."; # keep this file out of your repository proxy_set_header X-Shield-Client-IP $remote_addr; # behind Cloudflare: $http_cf_connecting_ip proxy_set_header Cookie ""; proxy_set_header Authorization ""; } # in every page: # <script src="/_sh/sensor.js" data-site="shp_XXXXXXXXXXXXXXXXXXXXXXXX" data-base="/_sh" async></script> -
Verify the token on forms
On a vote, a comment or a sign-up, the page asks for a token with
SatelitShield.token()and sends it with the form. Your server verifies it once, within 300 seconds.// a fresh token goes with the form, in a hidden field form.addEventListener("submit", function (e) { e.preventDefault(); SatelitShield.token(function (token) { form.elements.shield_token.value = token; form.submit(); }); });curl https://api.satelit.md/shield/v1/verify \ -H "Authorization: Bearer $SHIELD_SECRET" \ -H "Content-Type: application/json" \ -d '{ "token": "<the token from the form>", "ip": "203.0.113.9" }'// Node 18+: the token came with the form, ip is the visitor's address async function fromPerson(token, ip) { const res = await fetch("https://api.satelit.md/shield/v1/verify", { method: "POST", headers: { Authorization: `Bearer ${process.env.SHIELD_SECRET}`, "Content-Type": "application/json" }, body: JSON.stringify({ token, ip }), }); const v = await res.json(); return v.success === true && v.action !== "block"; }import json, os, urllib.request def from_person(token, ip): req = urllib.request.Request( "https://api.satelit.md/shield/v1/verify", data=json.dumps({"token": token, "ip": ip}).encode(), headers={"Authorization": f"Bearer {os.environ['SHIELD_SECRET']}", "Content-Type": "application/json"}, ) with urllib.request.urlopen(req, timeout=5) as res: v = json.load(res) return v.get("success") is True and v.get("action") != "block"<?php function from_person(string $token, string $ip): bool { $ch = curl_init("https://api.satelit.md/shield/v1/verify"); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_POST => true, CURLOPT_TIMEOUT => 5, CURLOPT_HTTPHEADER => ["Authorization: Bearer " . getenv("SHIELD_SECRET"), "Content-Type: application/json"], CURLOPT_POSTFIELDS => json_encode(["token" => $token, "ip" => $ip]), ]); $v = json_decode(curl_exec($ch), true); curl_close($ch); return ($v["success"] ?? false) === true && ($v["action"] ?? "") !== "block"; } -
Put the decision in your server
Optional, for the whole site. A middleware keeps the decisions in memory, reads them from
/decisionsevery 15–30 seconds and sends the requests to/ingestin batches. No visitor's request waits for the network.// Satelit Shield in a Node server: Express or any (req, res, next) stack. Node 18+, no packages. // The decisions stay in memory and are read again every 20 s, so a visitor's request never waits for the network. // Every request is sent to /ingest in batches. /_sh/ (the sensor) is served by the proxy in front, step 1. import { clientKey, passValid } from "./shield-pass.mjs"; const API = process.env.SHIELD_API || "https://api.satelit.md/shield/v1"; const SECRET = process.env.SHIELD_SECRET; // shs_..., never in a page const SITE = process.env.SHIELD_SITE; // shp_..., the public site key const HEADERS = { Authorization: `Bearer ${SECRET}`, "Content-Type": "application/json" }; const decisions = new Map(); // client key -> { a: "b" | "g", rule, until } const lines = []; let cursor = "0"; export async function poll() { const res = await fetch(`${API}/decisions?since=${cursor}`, { headers: HEADERS }); if (!res.ok) return; const body = await res.json(); if (body.full) decisions.clear(); for (const d of body.items) { if (d.a === "-") decisions.delete(d.key); else decisions.set(d.key, d); } cursor = body.cursor; } export async function flush() { while (lines.length) { const batch = lines.splice(0, 500); await fetch(`${API}/ingest`, { method: "POST", headers: HEADERS, body: JSON.stringify({ lines: batch }) }).catch(() => {}); } } export function start() { const quiet = (job) => () => job().catch(() => {}); quiet(poll)(); setInterval(quiet(poll), 20_000).unref(); setInterval(quiet(flush), 5_000).unref(); } const cookie = (req, name) => new RegExp(`(?:^|;\\s*)${name}=([^;]*)`).exec(req.headers.cookie ?? "")?.[1] ?? ""; const page = (mode, text) => `<!doctype html><html data-shield="${mode}"><head><meta charset="utf-8">` + `<meta content="noindex" name="robots"><meta name="viewport" content="width=device-width">` + `<script src="/_sh/sensor.js" data-site="${SITE}" data-base="/_sh" async></script></head><body><p>${text}</p></body></html>`; export function shield(req, res, next) { const ip = req.ip ?? req.socket.remoteAddress; // behind a proxy: Express's "trust proxy" gives the visitor's address const ua = req.headers["user-agent"] ?? ""; const path = (req.originalUrl ?? req.url).split("?")[0]; const shp = cookie(req, "__shp"); const pass = !shp ? 0 : passValid(shp, SECRET, ip, ua) ? 1 : 2; const d = decisions.get(clientKey(ip)); const on = d && (!d.until || Date.parse(d.until) > Date.now()) ? d.a : ""; // a ban is a 403; a check is a 429 unless the browser holds a valid pass (a pass never opens a ban) const gate = on === "b" ? 403 : on === "g" && pass !== 1 ? 429 : 0; res.on("finish", () => { const type = String(res.getHeader("content-type") ?? ""); const kind = /\.(css|js|mjs|png|jpe?g|gif|svg|webp|avif|ico|woff2?)$/i.test(path) ? "asset" : /json/.test(type) ? "api" : "html"; const h = (name) => req.headers[name] ?? ""; lines.push({ t: Date.now(), ip, ua, m: req.method, p: path, s: res.statusCode, k: kind, h: { sfs: h("sec-fetch-site"), chua: h("sec-ch-ua"), chm: h("sec-ch-ua-mobile"), chp: h("sec-ch-ua-platform"), al: h("accept-language"), ref: h("referer") }, pass, seen: cookie(req, "__shs") === "1" ? 1 : 0, g: gate === 429 ? 1 : 0, }); }); if (!gate) return next(); res.statusCode = gate; res.setHeader("Content-Type", "text/html; charset=utf-8"); res.setHeader("Cache-Control", "no-store"); res.end(gate === 429 ? page("check", "Checking your browser.") : page("ban", "Access blocked.")); }// Satelit Shield: check a pass (the __shp cookie) on your own server, with no call to Shield. Node 18+. // A pass is p1.{exp}.{token}.{sig}; sig is the first 22 characters of // base64url(HMAC-SHA256(pass key, "p1|{exp}|{token}|{client key}|{User-Agent}")). import { createHash, createHmac, timingSafeEqual } from "node:crypto"; // The pass key: HMAC-SHA256 with the key sha256_hex(secret) over "satelit-shield-pass-v1", in hex. export function passKey(secret) { const key = createHash("sha256").update(secret).digest("hex"); return createHmac("sha256", key).update("satelit-shield-pass-v1").digest("hex"); } // The client key: an IPv4 address as it is, an IPv6 address as its /64 (the first four groups and "::"). export function clientKey(ip) { const v4 = /^(?:::ffff:)?(\d{1,3}(?:\.\d{1,3}){3})$/i.exec(ip); if (v4) return v4[1]; const [head, tail = ""] = ip.toLowerCase().split("%")[0].split("::"); const a = head ? head.split(":") : []; const b = tail ? tail.split(":") : []; const groups = [...a, ...Array(Math.max(0, 8 - a.length - b.length)).fill("0"), ...b]; return groups.slice(0, 4).map((g) => parseInt(g, 16).toString(16)).join(":") + "::"; } // True when the pass has not expired and was signed for this address and this browser. export function passValid(pass, secret, ip, userAgent, now = Date.now() / 1000) { const m = /^p1\.(\d+)\.([0-9a-f]{16})\.([A-Za-z0-9_-]{22})$/.exec(pass ?? ""); if (!m || Number(m[1]) <= now) return false; const message = `p1|${m[1]}|${m[2]}|${clientKey(ip)}|${userAgent}`; const sig = createHmac("sha256", passKey(secret)).update(message).digest("base64url").slice(0, 22); return timingSafeEqual(Buffer.from(sig), Buffer.from(m[3])); }
Rules
15 rules, each with its own mode
Hard rules stop an unprotected address. Soft ones only show the check. A rule that goes past 50 stops an hour, or a site past 200, falls back to shadow by itself, and you get an event.
-
automationWebDriver, ChromeDriver, Selenium, PhantomJS, Nightmare, Playwright, Puppeteer, Cypress, TestCafe, HeadlessChrome.
-
stealthPatched automation: a replaced Function.toString, a window from a detached iframe, a redefined webdriver, Chromium posing as Chrome, a worker that says HeadlessChrome.
-
antidetectA spoofed fingerprint: a JS engine older than the claimed browser, canvas noise, a screen that differs from CSS, time zone, language, missing OS fonts, a worker or Client Hints that disagree.
-
fake-crawlerA crawler name refuted by two independent sources: the official IP list and reverse DNS.
-
robotsA self-declared bot asking for paths its own robots.txt group disallows.
-
trapThe site's hidden trap path, disallowed in robots.txt.
9 more rules
-
challenge-ignoredThe check page served 6 or more times to one address, with no answer.
-
browser-softTwo or more weak hints: no plugins on desktop Chrome, no window.chrome, empty languages, software WebGL, the DevTools protocol.
-
no-proof12 or more pages in 10 minutes, with no sensor report and no __shs cookie.
-
rate60 or more pages a minute.
-
walk40 or more different pages in 10 minutes.
-
stale-browserDesktop Chrome 8 or more versions behind, with no pass and no __shs.
-
ua-claimChrome 101+ with a full build number (real Chrome writes N.0.0.0), or no Sec-Fetch-Site on Chrome 80+.
-
client-hintsThe Sec-CH-UA headers contradict the User-Agent: mobile, platform, version, or missing on Chrome 90+.
-
datacenterA browser User-Agent from a hosting or cloud network (ASN), with no pass.
- Never judged
- Verified crawlers and your allowlist.
- A check only, never a stop
- Tor exits, shared addresses (3 or more real browsers on one address within 24 hours) and the addresses you trust.
Privacy
IDs, not fingerprints
Shield sees what it needs to tell a browser from a program. Nothing more.
- Check IDs only. The sensor sends the check IDs, interaction counts (no positions or keys) and the browser's first language. No fingerprint hash.
- Cookies on your domain only.
__shp, the pass after a check, and__shs, the sign that the browser ran the sensor. Satelit sets no cookie of its own. - Short retention. The client key (the address or its /64 network), the User-Agent, the rule and its evidence: 30 days. Hourly counters: 90 days.
- The secret stays on your server. The site key is public. You see the secret once, and Satelit keeps only its hash.
Pricing
You pay for checks, not for traffic
A check is one sensor report or one /assess call. Lines sent to /ingest are free, up to 10 for each check.
-
Shield Start
One site: a shop, a blog, a small newsroom.
19 € / month
381.72 lei
- Sites
- 1
- Checks a month
- 250,000
-
Shield Pro
RecommendedA few sites with forms, votes or accounts.
79 € / month
1,587.15 lei
- Sites
- 5
- Checks a month
- 2,500,000
-
Shield Business
Networks of sites and platforms with heavy traffic.
249 € / month
5,002.53 lei
- Sites
- 25
- Checks a month
- 15,000,000
-
Enterprise
Large volumes, terms and invoicing by contract.
from 990 € / month
from 19,889.59 lei
- Sites
- by contract
- Checks a month
- from 50,000,000
Every new site has a 7-day trial, with 100,000 checks. Lei at the BNM rate of 2 November 2026: 1 euro = 20.0905 lei.
Questions about Shield
Does Shield hurt my Google ranking?
No. Verified crawlers (Google, Bing, Apple, Yandex, DuckDuckGo, Baidu, OpenAI, Perplexity, Anthropic and more) get no decision: their name is confirmed by the official IP list and by reverse DNS. The sensor loads async and never holds the page back.
Does it work with Cloudflare?
Yes. Shield does not take the place of Cloudflare's rules; it adds the layer that sees the browser. If your site is behind Cloudflare, your proxy sends the address from CF-Connecting-IP in X-Shield-Client-IP, so Shield judges the visitor, not Cloudflare.
What happens to a person taken for a bot?
Weak signs only show the check, which passes by itself in about a second. Tor exits, shared addresses and the addresses you trust are never stopped. A rule with too many stops in an hour falls back to shadow by itself, and you can lift any decision from the dashboard.
How do I start without risk?
A new site starts with every rule in shadow: Shield only notes what it would stop. You read the events in the dashboard and switch to enforce the rules you trust, one at a time.
Does it slow my site down?
The sensor loads async, after the page. A middleware keeps the decisions in memory and reads them every 15–30 seconds, so a request never waits for the network. Only /assess, if you use it, is a call per request.
What data does Shield collect?
Check IDs, interaction counts without positions or keys, and the browser's first language. Shield keeps the client key, the User-Agent, the rule and its evidence for 30 days, and hourly counters for 90 days. No fingerprint hash and no Satelit cookies: you can describe it exactly in your privacy policy.
What does Shield do if the script is blocked?
The server layer works without it: a client that asks for many pages with no sensor report gets the check. Through a first-party proxy, the script comes from your own domain and ad blockers do not see it.
How long does a stop last?
24 hours, then 7 days, then 30 days, if the address repeats within 90 days. A valid pass opens a check, never a stop.
See what Shield would stop on your site
A new site starts in shadow: Shield only takes notes. You switch protection on when you are sure, rule by rule.