Skip to main content

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

SignalMethodConfidence
SeleniumWebDriver property detectionHigh
HeadlessMissing browser featuresHigh
PuppeteerPuppeteer-specific propertiesHigh
HoneypotInteraction with hidden elementsVery High
Trap LinksClicking invisible linksVery High
BehavioralMouse/scroll patternsMedium

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

ToolDetection Method
PPSPYExtension ID, DOM modifications
Koala InspectorGlobal objects, network patterns
Commerce InspectorExtension markers
Shopify Theme DetectorSpecific class names
BuiltWithScript 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;
}