- // ==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
- // ==/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 = '<div class="aptloading-spinner"></div><p style="text-align: center;">Scanning...</p>';
- auditWindow.style.display = "block";
- const cookies = getCookies();
- const trackers = await detectAllTrackers();
- const fingerprinting = await detectFingerprinting();
- windowContent.innerHTML = `
- <h2 class="aptTitle">Privacy Audit Results</h2>
- <div class="aptSectionTitle">Trackers & Fingerprinting</div>
- <ul>
- ${trackers.length > 0 ? trackers.map(tracker => `
- <li>${tracker.name} <span class="aptDangerLevel aptDangerLevel${capitalizeFirstLetter(tracker.danger)}">${capitalizeFirstLetter(tracker.danger)}</span> - ${tracker.description}</li>`).join('') : '<li>No trackers found.</li>'}
- </ul>
- <div class="aptSectionTitle">Cookies</div>
- <ul>
- ${!cookies.length ? '<li>No cookies found.</li>' : cookies.map(cookie => `<li>${cookie}</li>`).join('') }
- </ul>
- `;
- auditWindow.style.display = "block";
- }
-
- function capitalizeFirstLetter(string) {
- return string.charAt(0).toUpperCase() + string.slice(1);
- }
-
- scanButton.addEventListener("click", async function() {
- await showAuditResults();
- });
-
- })();