// ==UserScript== // @name SpyScan // @namespace https://greasyfork.org/fr/users/1451802 // @version 1.0 // @description Uncover tracking scripts, fingerprinting, and surveillance tactics lurking on the websites you visit // @description:de Untersuche Websites auf Tracking-Skripte, Fingerprinting und Überwachungsmethoden. // @description:es Descubre scripts de seguimiento, técnicas de huellas digitales y tácticas de vigilancia en las páginas web que visitas. // @description:fr Détecte les scripts de suivi, le fingerprinting et les techniques de surveillance cachées sur les sites que vous visitez. // @description:it Scopri script di tracciamento, tecniche di fingerprinting e metodi di sorveglianza sui siti web che visiti. // @description:ru Раскрывает трекинговые скрипты, отпечатки браузера и методы слежки на посещаемых сайтах. // @description:zh-CN 发现网站上的跟踪脚本、指纹识别和监控技术。 // @description:zh-TW 發現網站上的追蹤腳本、指紋辨識和監控技術。 // @description:ja 訪問したサイトに潜むトラッキングスクリプト、フィンガープリント、監視技術を検出。 // @description:ko 방문한 웹사이트에서 추적 스크립트, 브라우저 지문, 감시 기술을 찾아냅니다. // @author NormalRandomPeople (https://github.com/NormalRandomPeople) // @match *://*/* // @grant GM_addStyle // @license MIT // @icon https://www.svgrepo.com/show/360090/analyse.svg // @compatible chrome // @compatible firefox // @compatible opera // @compatible edge // @compatible brave // @run-at document-end // @noframes // @downloadURL https://update.greasyfork.icu/scripts/531352/SpyScan.user.js // @updateURL https://update.greasyfork.icu/scripts/531352/SpyScan.meta.js // ==/UserScript== /* jshint esversion: 8 */ (function() { 'use strict'; // Global arrays to hold detected network responses let detectedETags = []; let detectedIPGeolocationRequests = []; let detectedWebRTCLeaks = []; // List of known IP Geolocation service domains const ipGeoServices = [ "ipinfo.io", "ip-api.com", "ipgeolocation.io", "geoip-db.com", "freegeoip.app", "ip2location.com", "extreme-ip-lookup.com", "ip-geolocation.whoisxmlapi.com", "ipligence.com", "bigdatacloud.com", "maxmind.com", "db-ip.com", "ipinfodb.com", "ipdata.co", "abstractapi.com", "ipapi.com", "ipstack.com", "geo.ipify.org", "ipwhois.io", "ipregistry.co", "telize.com", "geoplugin.com" ]; // Patch fetch to capture responses with ETag headers and IP Geolocation requests const originalFetch = window.fetch; window.fetch = async function(...args) { let reqUrl = ""; if (typeof args[0] === "string") { reqUrl = args[0]; } else if (args[0] instanceof Request) { reqUrl = args[0].url; } if (ipGeoServices.some(domain => reqUrl.includes(domain))) { detectedIPGeolocationRequests.push({ url: reqUrl }); } const response = await originalFetch.apply(this, args); const responseClone = response.clone(); try { const etag = responseClone.headers.get("ETag"); if (etag) { detectedETags.push({ url: responseClone.url, etag: etag }); } } catch (err) { console.warn("ETag header could not be read:", err); } return response; }; // Patch XMLHttpRequest to capture responses with ETag headers and IP Geolocation requests const originalXHROpen = XMLHttpRequest.prototype.open; XMLHttpRequest.prototype.open = function(...args) { let reqUrl = args[1]; if (ipGeoServices.some(domain => reqUrl.includes(domain))) { detectedIPGeolocationRequests.push({ url: reqUrl }); } this.addEventListener("readystatechange", function() { if (this.readyState === 4) { try { const etag = this.getResponseHeader("ETag"); if (etag) { detectedETags.push({ url: this.responseURL, etag: etag }); } } catch (err) { console.warn("ETag header could not be read from XHR:", err); } } }); return originalXHROpen.apply(this, args); }; let scanButton = document.createElement("button"); scanButton.id = "aptScanButton"; scanButton.textContent = ""; let svgImg = document.createElement("img"); svgImg.src = "https://www.svgrepo.com/show/360090/analyse.svg"; svgImg.style.width = "32px"; svgImg.style.height = "32px"; svgImg.style.display = "block"; svgImg.style.margin = "0 auto"; scanButton.appendChild(svgImg); scanButton.style.position = "fixed"; scanButton.style.bottom = "10px"; scanButton.style.left = "10px"; scanButton.style.padding = "15px 20px"; scanButton.style.border = "none"; scanButton.style.backgroundColor = "black"; scanButton.style.color = "#fff"; scanButton.style.borderRadius = "10px"; scanButton.style.cursor = "pointer"; scanButton.style.zIndex = "9999999999"; scanButton.style.boxShadow = "0 4px 8px rgba(0, 0, 0, 0.3)"; scanButton.style.transition = "background-color 0.3s, transform 0.3s"; scanButton.addEventListener("mouseover", function() { scanButton.style.backgroundColor = "#333"; scanButton.style.transform = "scale(1.05)"; }); scanButton.addEventListener("mouseout", function() { scanButton.style.backgroundColor = "black"; scanButton.style.transform = "scale(1)"; }); document.body.appendChild(scanButton); let auditWindow = document.createElement("div"); auditWindow.id = "aptAuditWindow"; let windowContent = document.createElement("div"); windowContent.className = "aptWindowContent"; auditWindow.appendChild(windowContent); document.body.appendChild(auditWindow); auditWindow.addEventListener("click", function(event) { if (event.target === auditWindow) { auditWindow.style.display = "none"; } }); GM_addStyle(` #aptScanButton { font-family: Arial, sans-serif; background-color: black; color: #fff; border: none; padding: 15px 20px; font-size: 18px; border-radius: 10px; cursor: pointer; transition: background-color 0.3s, transform 0.3s; } #aptScanButton:hover { background-color: #333; } #aptAuditWindow { display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.7); color: #fff; font-family: Arial, sans-serif; overflow: auto; padding: 20px; z-index: 99999999999; box-sizing: border-box; } .aptWindowContent { max-width: 800px; margin: 50px auto; background-color: #333; border-radius: 8px; padding: 20px; box-shadow: 0 0 20px rgba(0, 0, 0, 0.5); overflow-y: auto; max-height: 80%; } .aptWindowContent h2 { text-align: center; margin-bottom: 10px; font-size: 1.8em; } .aptWindowContent p { font-size: 1em; line-height: 1.5; } .aptWindowContent ul { list-style-type: none; padding: 0; } .aptWindowContent li { background-color: #444; padding: 10px; margin: 5px 0; border-radius: 5px; word-wrap: break-word; position: relative; } .aptTitle { font-weight: bold; font-family: Arial; color: grey; } .aptSectionTitle { font-size: 1.3em; font-weight: bold; margin-bottom: 10px; padding-bottom: 5px; border-bottom: 2px solid #666; margin-top: 20px; } .aptDangerLevel { font-weight: bold; font-size: 1.1em; } .aptDangerLevelLow { color: #28A745; } .aptDangerLevelMedium { color: #FFA500; } .aptDangerLevelHigh { color: #FF4C4C; } .aptloading-spinner { border: 4px solid rgba(255, 255, 255, 0.3); border-top: 4px solid #fff; border-radius: 50%; width: 40px; height: 40px; animation: spin 1s linear infinite; margin: 20px auto; } @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } `); function getCookies() { return document.cookie.split(';').map(cookie => cookie.trim()).filter(cookie => cookie); } async function detectWebRTCLeak() { return new Promise(resolve => { const rtcPeerConnection = new RTCPeerConnection({ iceServers: [{ urls: "stun:stun.l.google.com:19302" }] }); rtcPeerConnection.createDataChannel(""); rtcPeerConnection.createOffer().then(offer => rtcPeerConnection.setLocalDescription(offer)); rtcPeerConnection.onicecandidate = event => { if (event.candidate) { const ipRegex = /([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})/; const match = ipRegex.exec(event.candidate.candidate); if (match && !match[1].startsWith("192.168") && !match[1].startsWith("10.") && !match[1].startsWith("172.")) { detectedWebRTCLeaks.push({ name: "WebRTC Leak", danger: "high", description: `Your real IP is exposed via WebRTC: ${match[1]}` }); } } }; setTimeout(() => { rtcPeerConnection.close(); resolve(detectedWebRTCLeaks); }, 5000); }); } function detectWebBeacons() { let beacons = []; let images = document.getElementsByTagName("img"); for (let img of images) { let width = img.getAttribute("width") || img.width; let height = img.getAttribute("height") || img.height; let computedStyle = window.getComputedStyle(img); if ((parseInt(width) === 1 && parseInt(height) === 1) || (img.naturalWidth === 1 && img.naturalHeight === 1) || (computedStyle.width === "1px" && computedStyle.height === "1px")) { beacons.push({ name: "Web Beacon", src: img.src, danger: "medium", description: "Detected a 1x1 pixel image that could be used as a web beacon." }); } } return beacons; } function detectEtagTracking() { let etagTrackers = []; detectedETags.forEach(item => { etagTrackers.push({ name: "Etag Tracking", danger: "medium", description: `ETag detected from ${item.url} with value ${item.etag}` }); }); return etagTrackers; } function detectIPGeolocation() { let ipGeoTrackers = []; detectedIPGeolocationRequests.forEach(item => { ipGeoTrackers.push({ name: "IP Geolocation", danger: "high", description: `IP Geolocation request detected to ${item.url}` }); }); return ipGeoTrackers; } function detectTrackersSync() { const trackers = []; const knownTrackers = [ { name: 'Google Analytics', regex: /analytics\.js/, danger: 'medium', description: 'Tracks user behavior for analytics and advertising purposes.' }, { name: 'Facebook Pixel', regex: /facebook\.com\/tr\.js/, danger: 'medium', description: 'Tracks user activity for targeted ads on Facebook.' }, { name: 'Hotjar', regex: /hotjar\.com/, danger: 'medium', description: 'Records user behavior such as clicks and scrolling for website optimization.' }, { name: 'AdSense', regex: /pagead2\.googlesyndication\.com/, danger: 'medium', description: 'Google\'s ad network, tracks user activity for ads.' }, { name: 'Google Tag Manager', regex: /googletagmanager\.com/, danger: 'medium', description: 'Manages JavaScript and HTML tags for tracking purposes.' }, { name: 'Amazon Tracking', regex: /amazon\.com\/at\/tag/, danger: 'low', description: 'Tracks activity for Amazon ads and recommendations.' }, { name: 'Twitter', regex: /twitter\.com\/widgets\.js/, danger: 'low', description: 'Tracks activity for Twitter widgets and ads.' }, { name: 'Local Storage', regex: /localStorage/, danger: 'low', description: 'Stores data in the browser that can be used for persistent tracking.' }, { name: 'Session Storage', regex: /sessionStorage/, danger: 'low', description: 'Stores data temporarily in the browser during a session.' }, ]; knownTrackers.forEach(tracker => { if (document.body.innerHTML.match(tracker.regex)) { trackers.push({ name: tracker.name, danger: tracker.danger, description: tracker.description }); } }); let webBeacons = detectWebBeacons(); webBeacons.forEach(beacon => trackers.push(beacon)); let etagTrackers = detectEtagTracking(); etagTrackers.forEach(etag => trackers.push(etag)); let ipGeoTrackers = detectIPGeolocation(); ipGeoTrackers.forEach(ipgeo => trackers.push(ipgeo)); return trackers; } function detectZombieCookies() { return new Promise(resolve => { const testName = "aptZombieTest"; document.cookie = `${testName}=test; path=/;`; document.cookie = `${testName}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;`; setTimeout(() => { if (document.cookie.includes(testName + "=")) { resolve([{ name: "Zombie Cookies", danger: "high", description: "Test cookie was recreated, indicating persistent zombie cookie behavior." }]); } else { resolve([]); } }, 1000); }); } async function detectAllTrackers() { const trackersSync = detectTrackersSync(); const zombieTrackers = await detectZombieCookies(); const webrtcLeaks = await detectWebRTCLeak(); return trackersSync.concat(zombieTrackers, webrtcLeaks); } async function detectFingerprinting() { let fingerprintingMethods = []; try { // Canvas Fingerprinting const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); ctx.textBaseline = "top"; ctx.font = "14px 'Arial'"; ctx.fillText('test', 2, 2); const data = canvas.toDataURL(); if (data !== 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUA') { fingerprintingMethods.push({ name: 'Canvas Fingerprinting', danger: 'high', description: 'Uses HTML5 canvas to uniquely identify users based on their rendering properties.' }); } } catch (e) {} try { // WebGL Fingerprinting const glCanvas = document.createElement('canvas'); const gl = glCanvas.getContext('webgl'); if (gl) { const fingerprint = gl.getParameter(gl.VERSION); if (fingerprint) { fingerprintingMethods.push({ name: 'WebGL Fingerprinting', danger: 'high', description: 'Uses WebGL rendering data to track users.' }); } } } catch (e) {} try { // Font Fingerprinting const fontFingerprint = document.createElement('div'); fontFingerprint.style.fontFamily = "'Arial', 'sans-serif'"; fontFingerprint.innerText = "test"; document.body.appendChild(fontFingerprint); const fontFingerprintData = window.getComputedStyle(fontFingerprint).fontFamily; document.body.removeChild(fontFingerprint); if (fontFingerprintData.includes('Arial')) { fingerprintingMethods.push({ name: 'Font Fingerprinting', danger: 'medium', description: 'Uses unique system fonts to track users across sessions.' }); } } catch (e) {} try { // AudioContext Fingerprinting using AudioWorkletNode if available const audioContext = new (window.AudioContext || window.webkitAudioContext)(); let audioFingerprintValue = 0; if (audioContext.audioWorklet) { // Define an inline AudioWorkletProcessor as a string const workletCode = ` class FingerprintProcessor extends AudioWorkletProcessor { process(inputs, outputs, parameters) { let sum = 0; if (inputs[0] && inputs[0][0]) { const input = inputs[0][0]; for (let i = 0; i < input.length; i++) { sum += input[i]; } } this.port.postMessage(sum); return false; } } registerProcessor('fingerprint-processor', FingerprintProcessor); `; const blob = new Blob([workletCode], { type: 'application/javascript' }); const moduleUrl = URL.createObjectURL(blob); await audioContext.audioWorklet.addModule(moduleUrl); const workletNode = new AudioWorkletNode(audioContext, 'fingerprint-processor'); audioFingerprintValue = await new Promise(resolve => { workletNode.port.onmessage = (event) => { resolve(event.data); }; workletNode.connect(audioContext.destination); }); workletNode.disconnect(); } else { // Fallback to ScriptProcessorNode if AudioWorklet is not available const buffer = audioContext.createBuffer(1, 1, 22050); const processor = audioContext.createScriptProcessor(0, 1, 1); processor.connect(audioContext.destination); audioFingerprintValue = buffer.getChannelData(0)[0]; processor.disconnect(); } if (audioFingerprintValue !== 0) { fingerprintingMethods.push({ name: 'AudioContext Fingerprinting', danger: 'high', description: 'Uses audio hardware properties to uniquely identify users. Value: ' + audioFingerprintValue }); } } catch (e) {} return fingerprintingMethods; } async function showAuditResults() { windowContent.innerHTML = '
Scanning...
'; auditWindow.style.display = "block"; const cookies = getCookies(); const trackers = await detectAllTrackers(); const fingerprinting = await detectFingerprinting(); windowContent.innerHTML = `