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;
}