Threat Detection
Store Shield detects multiple threat types in real-time. This document covers the detection mechanisms for bots, spy tools, and IP-based threats.
Bot Detection
Bot detection identifies automated visitors attempting to scrape content or prices.
Detection Signals
| Signal | Method | Confidence |
|---|---|---|
| Selenium | WebDriver property detection | High |
| Headless | Missing browser features | High |
| Puppeteer | Puppeteer-specific properties | High |
| Honeypot | Interaction with hidden elements | Very High |
| Trap Links | Clicking invisible links | Very High |
| Behavioral | Mouse/scroll patterns | Medium |
Selenium Detection
// Client-side detection
function detectSelenium() {
const signals = [];
// WebDriver property
if (navigator.webdriver) {
signals.push('webdriver_present');
}
// Selenium-specific properties
if (window.document.__selenium_unwrapped) {
signals.push('selenium_unwrapped');
}
if (window._selenium || window.callSelenium) {
signals.push('selenium_globals');
}
// Chrome driver
if (window.cdc_adoQpoasnfa76pfcZLmcfl_Array) {
signals.push('chrome_driver');
}
return signals;
}
Headless Detection
function detectHeadless() {
const signals = [];
// Missing plugins
if (navigator.plugins.length === 0) {
signals.push('no_plugins');
}
// Missing languages
if (!navigator.languages || navigator.languages.length === 0) {
signals.push('no_languages');
}
// Headless user agent
if (/HeadlessChrome/.test(navigator.userAgent)) {
signals.push('headless_ua');
}
// WebGL vendor
const canvas = document.createElement('canvas');
const gl = canvas.getContext('webgl');
const debugInfo = gl?.getExtension('WEBGL_debug_renderer_info');
const vendor = gl?.getParameter(debugInfo?.UNMASKED_VENDOR_WEBGL);
if (vendor === 'Brian Paul' || vendor === 'Google Inc.') {
signals.push('suspicious_webgl');
}
return signals;
}
Honeypot Technique
Hidden elements that only bots interact with:
// Inject honeypot elements
function createHoneypot() {
const honeypot = document.createElement('a');
honeypot.href = '/trap-link-' + Math.random().toString(36);
honeypot.style.cssText = `
position: absolute;
left: -9999px;
opacity: 0;
pointer-events: auto;
`;
honeypot.textContent = 'Click here for discount';
honeypot.addEventListener('click', (e) => {
e.preventDefault();
reportBot('honeypot_click', { href: honeypot.href });
});
document.body.appendChild(honeypot);
}
Behavioral Analysis
// Track mouse/scroll behavior
class BehaviorAnalyzer {
constructor() {
this.mouseEvents = [];
this.scrollEvents = [];
}
trackMouse(e) {
this.mouseEvents.push({
x: e.clientX,
y: e.clientY,
t: Date.now()
});
}
analyze() {
// Bots often have perfect linear mouse movements
const linearity = this.calculateLinearity(this.mouseEvents);
// Bots often scroll at perfect intervals
const scrollRegularity = this.calculateScrollRegularity(this.scrollEvents);
// Human-like behavior has variability
const isHuman = linearity < 0.95 && scrollRegularity < 0.90;
return { isHuman, linearity, scrollRegularity };
}
}
Spy Tool Detection
Detects competitor spy tools that analyze stores.
Known Spy Extensions
| Tool | Detection Method |
|---|---|
| PPSPY | Extension ID, DOM modifications |
| Koala Inspector | Global objects, network patterns |
| Commerce Inspector | Extension markers |
| Shopify Theme Detector | Specific class names |
| BuiltWith | Script injection patterns |
Detection Implementation
function detectSpyTools() {
const detected = [];
// Check for extension-injected globals
const spyGlobals = [
'__ppspy__',
'__koala_inspector__',
'__commerce_inspector__'
];
for (const global of spyGlobals) {
if (window[global]) {
detected.push({ tool: global, method: 'global_object' });
}
}
// Check for known DOM modifications
const spySelectors = [
'[data-ppspy]',
'.koala-inspector-panel',
'#commerce-inspector-root'
];
for (const selector of spySelectors) {
if (document.querySelector(selector)) {
detected.push({ tool: selector, method: 'dom_element' });
}
}
// Check Chrome extension IDs (via resource timing)
detectExtensionResources(detected);
return detected;
}
Extension Resource Detection
async function detectExtensionResources(detected) {
const knownExtensions = {
'ppspy': 'chrome-extension://abcdef123456/icon.png',
'koala': 'chrome-extension://xyz789/manifest.json'
};
for (const [name, resource] of Object.entries(knownExtensions)) {
try {
const response = await fetch(resource, { mode: 'no-cors' });
// If no error, extension is present
detected.push({ tool: name, method: 'extension_resource' });
} catch (e) {
// Extension not present
}
}
}
IP Blocking
Block visitors by IP address, CIDR range, or country.
Configuration
interface IPBlockingConfig {
enabled: boolean;
blockedIPs: string[]; // Specific IPs
blockedCIDRs: string[]; // IP ranges (e.g., "192.168.0.0/24")
blockedCountries: string[]; // ISO country codes
allowedIPs: string[]; // Whitelist (overrides blocks)
blockVPN: boolean;
blockDatacenter: boolean;
blockTor: boolean;
}
Check Flow
┌─────────────────┐
│ Page Request │
└────────┬────────┘
│
▼
┌─────────────────┐
│ GET /api/ │
│ check-ip-access│
└────────┬────────┘
│
▼
┌─────────────────┐ ┌─────────────────┐
│ Load Config │────▶│ Memory → KV → │
│ │ │ D1 → Fly.io │
└────────┬────────┘ └─────────────────┘
│
▼
┌─────────────────┐
│ Check Rules │
│ 1. Whitelist? │
│ 2. Blocked IP? │
│ 3. Blocked CIDR?│
│ 4. Blocked Country?│
│ 5. VPN/Datacenter?│
└────────┬────────┘
│
▼
┌─────────────────┐
│ Allow / Block │
└─────────────────┘
Implementation
// ip-blocking-handler.js
export async function checkIPAccess(request, env) {
const ip = request.headers.get('CF-Connecting-IP');
const country = request.headers.get('CF-IPCountry');
const shop = new URL(request.url).searchParams.get('shop');
const config = await getIPBlockingConfig(shop, env);
if (!config.enabled) {
return { allowed: true };
}
// Check whitelist first
if (config.allowedIPs.includes(ip)) {
return { allowed: true, reason: 'whitelisted' };
}
// Check blocked IPs
if (config.blockedIPs.includes(ip)) {
return { allowed: false, reason: 'blocked_ip' };
}
// Check CIDR ranges
for (const cidr of config.blockedCIDRs) {
if (isIPInCIDR(ip, cidr)) {
return { allowed: false, reason: 'blocked_cidr' };
}
}
// Check country
if (config.blockedCountries.includes(country)) {
return { allowed: false, reason: 'blocked_country' };
}
// Check VPN/Datacenter (using Cloudflare data)
if (config.blockVPN || config.blockDatacenter) {
const ipInfo = await getIPReputation(ip, env);
if (config.blockVPN && ipInfo.isVPN) {
return { allowed: false, reason: 'vpn' };
}
if (config.blockDatacenter && ipInfo.isDatacenter) {
return { allowed: false, reason: 'datacenter' };
}
}
return { allowed: true };
}
Event Logging
export async function logBlockedAccess(shop, ip, country, reason, env) {
await env.DB.prepare(`
INSERT INTO IPBlockingEvent (id, shop, ip, country, reason, created_at)
VALUES (?, ?, ?, ?, ?, ?)
`).bind(generateId(), shop, ip, country, reason, Date.now()).run();
await incrementMetric(env.DB, shop, 'ip_blocking_events');
}
Risk Scoring
Combine signals into a visitor risk score:
async function calculateRiskScore(visitorId, signals, env) {
let score = 0;
// Weight each signal type
const weights = {
selenium: 50,
headless: 40,
honeypot: 80,
trap_link: 90,
spy_tool: 30,
behavioral_anomaly: 20,
vpn: 10,
datacenter: 15
};
for (const signal of signals) {
score += weights[signal.type] || 5;
}
// Cap at 100
score = Math.min(score, 100);
// Update visitor risk profile
await env.DB.prepare(`
INSERT OR REPLACE INTO VisitorRiskProfile (visitor_id, risk_score, signals, updated_at)
VALUES (?, ?, ?, ?)
`).bind(visitorId, score, JSON.stringify(signals), Date.now()).run();
return score;
}