commit 654efe34169ca6173435c8bcb39e5d2aa7ae3efa Author: Dominik Date: Thu Nov 13 19:03:30 2025 +0100 Proof of concept diff --git a/README.md b/README.md new file mode 100644 index 0000000..e69de29 diff --git a/makefile b/makefile new file mode 100644 index 0000000..a982db8 --- /dev/null +++ b/makefile @@ -0,0 +1,4 @@ +.PHONY: run + +run: + npm run start:firefox -- diff --git a/manifest.json b/manifest.json new file mode 100644 index 0000000..df30a99 --- /dev/null +++ b/manifest.json @@ -0,0 +1,39 @@ +{ + "manifest_version": 2, + "name": "File-Leak", + "version": "1.0", + + "description": "Find hidden files on websites you visit which are not supposed to be there", + "homepage_url": "https://agres.online", + + "icons": { + "32": "static/icons/icon32.png", + "48": "static/icons/icon48.png" + }, + + "permissions": [ + "webNavigation", + "storage", + "", + "http://*/*", + "https://*/*" + ], + + "browser_action": { + "default_icon": "static/icons/icon48.png", + "default_title": "File-leak", + "default_popup": "popup/html/main.html" + }, + + "background": { + "scripts": ["popup/js/background.js"], + "persistent": false + }, + + "content_scripts": [ + { + "matches": [""], + "js": ["popup/js/visual.js"] + } + ] +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..43f438e --- /dev/null +++ b/package.json @@ -0,0 +1,8 @@ +{ + "dependencies": { + "web-ext": "^9.1.0" + }, + "scripts": { + "start:firefox": "web-ext run --source-dir ./" + } +} diff --git a/popup/css/styles.css b/popup/css/styles.css new file mode 100644 index 0000000..78a25bb --- /dev/null +++ b/popup/css/styles.css @@ -0,0 +1,71 @@ +body { + margin: 0; + padding: 0; + font-family: sans-serif; + background: #1e2430; + color: #e4e7ec; +} + +#popup-content { + padding: 12px; + width: 260px; +} + +h1 { + margin-top: 0; + font-size: 18px; + color: #f0f3f8; +} + +label, button { + display: block; + margin: 8px 0; +} + +button { + padding: 6px 10px; + background: #2d3441; + border: 1px solid #3a4253; + color: #e4e7ec; + border-radius: 4px; + cursor: pointer; +} + +button:hover { + background: #3a4253; +} + +#entry-box { + margin-top: 10px; + background: #252b36; + border: 1px solid #333b49; + border-radius: 4px; + max-height: 250px; + overflow-y: auto; + padding: 6px; +} + +.entry { + display: grid; + grid-template-columns: 1.2fr 1fr auto auto; + align-items: center; + gap: 6px; + padding: 6px; + background: #2b3240; + border-radius: 3px; + margin-bottom: 6px; + font-size: 12px; +} + +.entry span { + font-size: 12px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + min-width: 0; +} + +.entry button { + padding: 4px 6px; + font-size: 12px; +} diff --git a/popup/html/main.html b/popup/html/main.html new file mode 100644 index 0000000..87fa62b --- /dev/null +++ b/popup/html/main.html @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + diff --git a/popup/js/background.js b/popup/js/background.js new file mode 100644 index 0000000..fe73008 --- /dev/null +++ b/popup/js/background.js @@ -0,0 +1,129 @@ +// function to scan the url the tab is on for hidden files + +async function onTabUpdate(tabId, changeInfo, tab) { + if (changeInfo.status !== "complete") return; + + const url = new URL(tab.url); + const hostname = url.hostname; + + const stored = await browser.storage.local.get("entries"); + const existingEntries = stored.entries || []; + + const alreadyDone = existingEntries.some(e => e.domainField === hostname); + if (alreadyDone) return; + + async function tryFetch(pathname) { + const target = url.origin + pathname; + let response; + try { + response = await fetch(target, { redirect: "manual" }); + } catch { + return null; + } + return response.status === 200 ? target : null; + } + + const results = { + env: await tryFetch("/.env"), + git: await tryFetch("/.git"), + dsstore: await tryFetch("/.DS_Store"), + config: await tryFetch("/.config"), + svn: await tryFetch("/.svn"), + npm: await tryFetch("/.npm"), + hg: await tryFetch("/.hg"), + docker: await tryFetch("/.docker"), + }; + + const newEntries = [...existingEntries]; + + for (const [key, foundPath] of Object.entries(results)) { + if (!foundPath) continue; + + const entry = { + domainField: hostname, + pathField: foundPath, + type: key + }; + + newEntries.push(entry); + + } + + await browser.storage.local.set({ entries: newEntries }); + +} + +// Enable, Idsable automatic listener and the message listener for it + +function enableListener() { + if (!browser.tabs.onUpdated.hasListener(onTabUpdate)) { + browser.tabs.onUpdated.addListener(onTabUpdate); + } +} + +function disableListener() { + if (browser.tabs.onUpdated.hasListener(onTabUpdate)) { + browser.tabs.onUpdated.removeListener(onTabUpdate); + } +} + +browser.runtime.onMessage.addListener((msg) => { + if (msg.type === "toggleListener") { + if (msg.enabled) enableListener(); + else disableListener(); + } +}); + +// Singe run, can be merged with onTabUpdate function + +async function runSingleScan() { + const tabs = await browser.tabs.query({ active: true, currentWindow: true }); + if (!tabs.length) return; + const tab = tabs[0]; + const url = new URL(tab.url); + const hostname = url.hostname; + + async function tryFetch(path) { + const target = url.origin + path; + let response; + try { + response = await fetch(target, { redirect: "manual" }); + } catch { + return null; + } + return response.status === 200 ? target : null; + } + + const results = { + env: await tryFetch("/.env"), + git: await tryFetch("/.git"), + dsstore: await tryFetch("/.DS_Store"), + config: await tryFetch("/.config"), + svn: await tryFetch("/.svn"), + npm: await tryFetch("/.npm"), + hg: await tryFetch("/.hg"), + docker: await tryFetch("/.docker"), + }; + + const stored = await browser.storage.local.get("entries"); + const entries = stored.entries || []; + + for (const [key, foundPath] of Object.entries(results)) { + if (!foundPath) continue; + + entries.push({ + domainField: hostname, + pathField: foundPath, + type: key + }); + + } + + await browser.storage.local.set({ entries }); +} + +browser.runtime.onMessage.addListener((msg) => { + if (msg.type === "runOnce") { + runSingleScan(); + } +}); diff --git a/popup/js/visual.js b/popup/js/visual.js new file mode 100644 index 0000000..fb0bf3d --- /dev/null +++ b/popup/js/visual.js @@ -0,0 +1,89 @@ +// Handling of adding saving and loading list entries + +function addEntry(domain, path) { + + const list = document.getElementById("entry-list"); + + const entry = document.createElement("div"); + entry.className = "entry"; + + const domainField = document.createElement("span"); + domainField.textContent = domain; + + const pathField = document.createElement("span"); + pathField.textContent = path; + + const deleteButton = document.createElement("button"); + deleteButton.textContent = "Delete"; + deleteButton.addEventListener("click", function () { + entry.remove(); + saveAllEntries(); + }) + + const openButton = document.createElement("button"); + openButton.textContent = "Open"; + openButton.addEventListener("click", function () { + const url = pathField.textContent; + if (url) { + window.open(url, "_blank"); + } + }); + + entry.appendChild(domainField); + entry.appendChild(pathField); + entry.appendChild(deleteButton); + entry.appendChild(openButton); + + list.appendChild(entry); + + saveAllEntries(); +} + +function saveAllEntries() { + const entries = []; + + document.querySelectorAll(".entry").forEach(entry => { + const spans = entry.querySelectorAll("span"); + entries.push({ + domainField: spans[0].textContent, + pathField: spans[1].textContent + }); + }); + + browser.storage.local.set({ entries }); +} + +async function loadEntries() { + const stored = await browser.storage.local.get("entries"); + if (!stored.entries) return; + + for (const data of stored.entries) { + addEntry(data.domainField, data.pathField); + } +} + +// Load entries when popup starts + +document.addEventListener("DOMContentLoaded", () => { + loadEntries(); +}); + +// Handle persistance for toggle button + +const toggle = document.getElementById("listenerToggle"); + +browser.storage.local.get("listenerEnabled").then(({ listenerEnabled }) => { + toggle.checked = !!listenerEnabled; +}); + +toggle.addEventListener("change", (e) => { + const enabled = e.target.checked; + browser.storage.local.set({ listenerEnabled: enabled }); + browser.runtime.sendMessage({ type: "toggleListener", enabled }); +}); + +// Run one scan on button press + +document.getElementById("runOnceBtn").addEventListener("click", () => { + browser.runtime.sendMessage({ type: "runOnce" }); +}); diff --git a/static/icons/icon32.png b/static/icons/icon32.png new file mode 100644 index 0000000..3094d6a Binary files /dev/null and b/static/icons/icon32.png differ diff --git a/static/icons/icon48.png b/static/icons/icon48.png new file mode 100644 index 0000000..bde8f43 Binary files /dev/null and b/static/icons/icon48.png differ