SilentShield watches 12 invisible detection layers β mouse entropy, biometrics, fingerprinting, font enumeration, proof-of-work β and blocks bots before they touch your backend.
Unlike reCAPTCHA or hCaptcha, SilentShield works completely anonymously β no registration, no dashboard, no approval process required.
<!-- 1. Add script to <head> --> <script src="https://cdn.jsdelivr.net/npm/silentshield@latest/dist/silentshield.min.js" ></script> <!-- 2. Tag your form (no key needed) --> <form data-silentshield action="/submit" method="POST"> <input type="email" name="email" /> <button type="submit">Submit</button> </form> <!-- 3. Verify on your server --> // POST https://sh.krl.kr/api/verify // body: { "token": req.body._ss_token } // response: { "valid": true, "score": 91 }
Each layer adds independent signal. Combined score 0β100. β₯70 = human, 45β69 = suspicious, <45 = bot.
Browser solves SHA256(challenge+nonce).startsWith("000") in background. Real browsers: ~100β500ms. Pre-computed/spoofed: <5ms = -25pts. Missing PoW = -35pts. Valid = +15pts.
Measures angle-change variance across all mouse positions. Humans move in organic curves (variance >0.01 rad/event). Bots move linearly or not at all. Linear path with >10 events = -18pts.
Calculates pixel/ms speed between consecutive mouse samples and measures its variance. Humans constantly accelerate and decelerate. Scripted mouse movement has near-constant speed.
Tracks inter-keystroke intervals (up to 120). Variance >5000 = very human. Variance <10 with >3 keys = definitely scripted (-45pts). Form fill <300ms = -55pts.
Renders gradient + multi-font text + arc to a canvas, reads the last 80 PNG bytes. Headless Chromium and SwiftShader produce known artifact patterns. Match = -25pts. Unique render = +10pts.
Reads UNMASKED_RENDERER_WEBGL. Software renderers (SwiftShader, llvmpipe, Mesa, ANGLE) = -35pts. Also reads MAX_TEXTURE_SIZE for additional GPU fingerprinting.
Creates an AudioContext, routes triangle oscillator through AnalyserNode, captures 50-point FFT. Headless lacks real audio pipeline βreturns errors or flat zero. Error = -8pts. Valid = +8pts.
Renders 28 known fonts via canvas, detects which are installed by comparing pixel widths. Real desktops have 15β25 fonts. Headless on a bare VPS has 0β. fontCount = 0 β -8pts, >15 β+10pts.
speechSynthesis.getVoices().length βreal desktop browsers return 5β0 OS TTS voices. Headless Chrome on a Linux VPS returns 0. Zero voices = -5pts. >5 voices = +10pts.
Checks 20+ API signals: navigator.webdriver (-70), plugin count, window.chrome (+8), localStorage, IndexedDB, Web Workers, Notification API, Service Worker, Battery API, Bluetooth, innerWidth/screenWidth ratio.
Injects a CSS-invisible field with one of 25 rotating names (website, company_url, phone2β. Name rotates every page load to defeat bot learning. Field filled = -100pts βautomatic bot.
Content filter checks 25+ spam keywords. UA matching blocks 23+ known bot clients (curl, wget, Selenium, Scrapy, etc.) and learned patterns from the 10-minute bot learning job.
| Feature | SilentShield | reCAPTCHA v3 | hCaptcha | Turnstile | ALTCHA |
|---|---|---|---|---|---|
| Completely free | β Always | 1M/mo | 1M/mo | Free tier | β |
| No API key needed | β | β | β | β | β |
| Invisible to users | β | β | π² (checkbox) | β | β |
| Open source / MIT | β | β | β | β | β |
| Self-hostable | β | β | β | β | β |
| Font enumeration | β | β | β | β | β |
| Keyboard biometrics | β | β | β | β | β |
| Speech voice detection | β | β | β | β | β |
| Mouse entropy scoring | β | β | β | ~ | β |
| Bot auto-learning | β every 10min | Google ML | Proprietary | Proprietary | β |
| GDPR / no user tracking | β | β | β | β | β |
| Fake Success mode | β | β | β | β | β |
Everything you need to integrate, configure, and self-host SilentShield.
SilentShield has two parts: a JavaScript SDK that runs in the browser, and a server that verifies tokens. You can use the hosted server at sh.krl.kr or self-host.
<script src="https://cdn.jsdelivr.net/npm/silentshield@latest/dist/silentshield.min.js" ></script>
Served globally via jsDelivr CDN. Cached with immutable headers. ~13KB minified, zero external dependencies.
npm install silentshield
import SilentShield from 'silentshield';
The fastest way to add SilentShield to any HTML form.
<script src="https://cdn.jsdelivr.net/npm/silentshield@latest/dist/silentshield.min.js"></script>
<form data-silentshield action="/submit" method="POST"> <input type="text" name="name" /> <input type="email" name="email" /> <button type="submit">Submit</button> </form> <!-- SilentShield auto-inits all [data-silentshield] forms -->
On form submit, SilentShield injects a hidden _ss_token field. POST it to /api/verify:
const r = await fetch('https://sh.krl.kr/api/verify', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ token: req.body._ss_token }) }); const { valid, score, verdict } = await r.json(); if (!valid) return res.status(403).send('Bot');
All options are optional. SilentShield works with zero configuration.
| Option | Default | Description |
|---|---|---|
| apiUrl | 'https://sh.krl.kr' | API endpoint. Set to your self-hosted URL to use your own server. |
| publicKey | null | Optional site identifier for analytics grouping. No security value βpurely for dashboard. |
| threshold | 45 | Score below which to trigger bot handling. Range 0β100. Recommended: 45 (balanced), 60 (strict). |
| fakeSuccess | true | Show fake "Sent successfully!" to blocked bots. Silently discards their submission. |
| onBot | null | Callback function called when a bot is detected. Receives {score, verdict}. |
const shield = new SilentShield({ apiUrl: 'https://sh.krl.kr', threshold: 45, fakeSuccess: true, onBot: (r) => console.log('Blocked bot, score:', r.score) }); shield.protect('#contact-form'); // Or use the static auto-init (protects all [data-silentshield] forms): SilentShield.init({ apiUrl: 'https://sh.krl.kr' });
POST/api/signal
Called automatically by the JS SDK when a form is submitted. Accepts all collected behavioral signals and returns a one-time token.
| Field | Type | Description |
|---|---|---|
| mouseEvents | number | Number of mousemove events recorded |
| mouseEntropy | number | Angle-change variance of mouse path |
| mouseSpeedVariance | number | Speed variance along mouse path |
| keystrokeIntervals | number[] | Array of inter-keystroke delays (ms) |
| formFillMs | number | Total form fill duration |
| powChallenge | string | PoW challenge string |
| powNonce | number | Solved PoW nonce |
| powHash | string | Resulting SHA-256 hash |
| canvasFingerprint | string | Last 80 bytes of canvas PNG (base64) |
| webglRenderer | string | GPU renderer string |
| fontCount | number | Number of detected installed fonts |
| speechVoiceCount | number | Number of speech synthesis voices |
| siteId | string|null | Optional public key for analytics |
POST/api/verify
Your backend calls this to validate a token before processing a form submission.
$data = json_decode(file_get_contents('https://sh.krl.kr/api/verify', false, stream_context_create(['http' => [ 'method' => 'POST', 'header' => 'Content-Type: application/json', 'content' => json_encode(['token' => $_POST['_ss_token']]) ]]) ), true); if (!$data['valid']) { http_response_code(403); exit; }
POST/api/register
Register your domain to get analytics keys. Completely optional βSilentShield works without any registration.
All analytics endpoints require header: x-secret-key: sk_...
Returns 7-day traffic stats, bot rate, detection breakdown by layer.
Returns learned bot patterns with confidence scores.
Returns the 50 most recent submissions with scores and verdicts.
Returns all sites, system stats. Requires x-admin-secret header matching ADMIN_SECRET env var.
Every submission gets a score from 0 to 100. Score starts at 50 and is adjusted by each detection layer.
| Score Range | Verdict | Meaning |
|---|---|---|
| 70β100 | human | Strong signals of real human browser. Process normally. |
| 45β69 | suspicious | Some bot-like signals but not conclusive. You can require additional verification or silently log for review. |
| 0β44 | bot | Strong bot indicators. Reject, show fake success, or challenge. |
| Layer | Max Positive | Max Negative |
|---|---|---|
| Proof of Work | +15 | -35 |
| Mouse path entropy | +12 | -18 |
| Keyboard biometrics + fill time | +18 | -55 |
| Canvas fingerprint | +10 | -25 |
| WebGL renderer | +12 | -35 |
| Audio fingerprint | +8 | -8 |
| Font enumeration | +10 | -8 |
| Speech voices | +10 | -5 |
| Environment flags | +45 | -70 |
| Honeypot | π― | -100 |
| Spam content | π« | -80 |
| User agent | +10 | -50 |
The final score is clamped to 0β100. navigator.webdriver = true is an immediate -70 (nearly always bot verdict).
Complete list of signals collected by the SDK v2.1:
| Signal | Type | Description |
|---|---|---|
| mouseEvents | number | Total mousemove events (capped at 300 samples) |
| scrollEvents | number | Scroll events |
| clickEvents | number | Click events |
| dblClickEvents | number | Double-click events |
| contextMenuEvents | number | Right-click (contextmenu) events |
| mouseDownEvents | number | Mousedown events |
| mouseEntropy | number | Angle-change variance of mouse path |
| mouseSpeedVariance | number | Speed variance (px/ms) along path |
| keystrokeIntervals | number[] | Last 120 inter-keystroke delays |
| backspaceCount | number | Backspace key presses |
| pasteCount | number | Paste events |
| formFillMs | number | Duration from first keystroke to submit |
| Signal | Type | Description |
|---|---|---|
| webdriver | boolean | navigator.webdriver (true = definitive bot) |
| pluginCount | number | navigator.plugins.length |
| hardwareConcurrency | number | CPU core count |
| deviceMemory | number | RAM in GB (rounded) |
| hasChrome | boolean | window.chrome defined |
| fontCount | number | Installed fonts detected via canvas |
| speechVoiceCount | number | speechSynthesis.getVoices().length |
| hasLocalStorage | boolean | localStorage available and writable |
| hasIndexedDB | boolean | IndexedDB available |
| hasWebWorker | boolean | Web Workers available |
| hasBattery | boolean | navigator.getBattery available |
| hasWebBluetooth | boolean | navigator.bluetooth available |
| pointerFine | boolean | (pointer: fine) CSS media match |
| hoverCapable | boolean | (hover: hover) CSS media match |
| perfPrecision | number | Minimum performance.now() increment |
| innerWidth / innerHeight | number | Window inner dimensions |
| screenWidth / screenHeight | number | Screen dimensions |
SilentShield is designed to be self-hosted. One command on any Ubuntu 22.04 VPS.
curl -fsSL \ https://raw.githubusercontent.com/3289david/silentshield/main/deploy.sh \ | bash
This installs Node.js 20, PM2, clones the repo, builds the SDK, and starts the server.
apt install nginx certbot python3-certbot-nginx certbot --nginx -d your-domain.com cp nginx.conf /etc/nginx/sites-available/silentshield ln -s /etc/nginx/sites-available/silentshield /etc/nginx/sites-enabled/ nginx -t && systemctl reload nginx
new SilentShield({ apiUrl: 'https://your-domain.com' }).protect('#my-form');
Configure via server/.env (auto-created by deploy script).
| Variable | Default | Description |
|---|---|---|
| PORT | 3000 | Server listen port |
| DB_PATH | ./silentshield.db | SQLite database file path |
| ADMIN_SECRET | (random 32-byte hex) | Admin dashboard password |
| CORS_ORIGINS | * | Allowed CORS origins (comma-separated) |
| RATE_LIMIT_MAX | 200 | Max requests per window |
| RATE_LIMIT_WINDOW_MS | 60000 | Rate limit window in ms |
| NODE_ENV | development | Set to production on VPS |
import { useEffect, useRef } from 'react'; import SilentShield from 'silentshield'; export function ContactForm() { const formRef = useRef(); useEffect(() => { const shield = new SilentShield({ apiUrl: 'https://sh.krl.kr' }); shield.protect(formRef.current); }, []); return <form ref={formRef} onSubmit={handleSubmit}>...</form>; }
export async function POST(req) { const { token, ...formData } = await req.json(); const r = await fetch('https://sh.krl.kr/api/verify', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ token }) }); const { valid } = await r.json(); if (!valid) return Response.json({ error: 'Bot' }, { status: 403 }); // process form... }
// In functions.php or plugin: function ss_verify($token) { $r = wp_remote_post('https://sh.krl.kr/api/verify', [ 'body' => json_encode(['token' => $token]), 'headers' => ['Content-Type' => 'application/json'] ]); $data = json_decode(wp_remote_retrieve_body($r), true); return !empty($data['valid']); }
Deploy to any Linux VPS in 5 minutes. One command. Full control over your data.
# Paste on your VPS as root: curl -fsSL \ https://raw.githubusercontent.com/3289david/silentshield/main/deploy.sh \ | bash β SilentShield is running! URL: http://YOUR_IP:3000 # Add SSL: apt install nginx certbot python3-certbot-nginx certbot --nginx -d your-domain.com # Use your own server: new SilentShield({ apiUrl: 'https://your-domain.com' }).protect('#my-form');
Built and maintained by one developer. If SilentShield saves you money on reCAPTCHA fees, consider a coffee.
100% goes toward server costs and dev time. No VCs, no investors.
β Buy Me a CoffeeRegister your domain for free to unlock the analytics dashboard. No account, no email β instant keys.