diff --git a/extension/manifest.json b/extension/manifest.json index d07d557..f2e95bc 100644 --- a/extension/manifest.json +++ b/extension/manifest.json @@ -14,9 +14,7 @@ "permissions": [ "webNavigation", "storage", - "", - "http://*/*", - "https://*/*" + "" ], "browser_action": { diff --git a/extension/popup/js/background.js b/extension/popup/js/background.js index fe73008..e535bda 100644 --- a/extension/popup/js/background.js +++ b/extension/popup/js/background.js @@ -1,72 +1,23 @@ -// 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); + if (!browser.tabs.onUpdated.hasListener(tabUpdateListener)) { + browser.tabs.onUpdated.addListener(tabUpdateListener); } } function disableListener() { - if (browser.tabs.onUpdated.hasListener(onTabUpdate)) { - browser.tabs.onUpdated.removeListener(onTabUpdate); + if (browser.tabs.onUpdated.hasListener(tabUpdateListener)) { + browser.tabs.onUpdated.removeListener(tabUpdateListener); } } +function tabUpdateListener(tabId, changeInfo, tab) { + if (changeInfo.status === "complete" && tab.active) { + runSingleScan(tab); + } +} + +// Add and remove listeners based on message sent by frontend toggle browser.runtime.onMessage.addListener((msg) => { if (msg.type === "toggleListener") { if (msg.enabled) enableListener(); @@ -75,55 +26,115 @@ browser.runtime.onMessage.addListener((msg) => { }); // Singe run, can be merged with onTabUpdate function +async function runSingleScan(tab) { + + console.log("Checking site...") + + //const tabs = await browser.tabs.query({ active: true, currentWindow: true }); + + //if (!tabs.length) return; + //const tab = tabs[0]; -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; + + console.log(`Running scan on ${url}`) + async function tryFetch(path) { const target = url.origin + path; let response; try { - response = await fetch(target, { redirect: "manual" }); + response = await fetch(target); } catch { return null; } - return response.status === 200 ? target : null; + return [200, 301].includes(response.status) ? response : 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"), + git: await tryFetch("/.git") }; const stored = await browser.storage.local.get("entries"); const entries = stored.entries || []; - for (const [key, foundPath] of Object.entries(results)) { - if (!foundPath) continue; + for (const [key, response] of Object.entries(results)) { + if (!response) continue; + + const validated = await validatePathResponse(response.url, response); + + if (!validated.ok) { + console.log(`Rejected ${key}: ${validated.reason}`); + continue; + } + + console.log(`Hit! A hidden file was found: ${key}`) entries.push({ domainField: hostname, - pathField: foundPath, + pathField: response.url, type: key }); - } await browser.storage.local.set({ entries }); + } browser.runtime.onMessage.addListener((msg) => { if (msg.type === "runOnce") { - runSingleScan(); + browser.tabs.query({ active: true, currentWindow: true }).then(([tab]) => { + if (tab) runSingleScan(tab); + }); } }); + + +// Check if returned value is a file/path based on heuristics +async function validatePathResponse(url, response) { + + const text = await response.text().catch(() => ""); + const contentType = response.headers.get("content-type")?.toLowerCase() || ""; + const status = response.status; + + const fileIndicators = [ + "application/octet-stream", + "application/x-git", + "text/plain", + "application/env", + ]; + + const isLikelyFile = fileIndicators.some(type => contentType.includes(type)); + if (isLikelyFile && status === 200) { + return { ok: true, type: "file", reason: "content-type indicates file" }; + } + + const directoryKeywords = /(Index of|Directory listing|Parent Directory)/i; + const errorKeywords = /(oops|error|not found|forbidden|unauthorized|denied)/i; + + const looksLikeDirectory = directoryKeywords.test(text); + const looksLikeErrorPage = errorKeywords.test(text); + + if (looksLikeDirectory && !looksLikeErrorPage) { + return { ok: true, type: "directory", reason: "directory listing heuristics" }; + } + + if (url.endsWith("/.git")) { + const head = await tryFetchInternal(url + "/HEAD"); + if (head?.status === 200) { + const headText = await head.text().catch(() => ""); + if (/ref:/.test(headText)) { + return { ok: true, type: "directory", reason: ".git HEAD file found" }; + } + } + } + + if (status === 404 || looksLikeErrorPage) { + return { ok: false, reason: "soft 404 or error page" }; + } + + return { ok: false, reason: "did not match any valid directory/file heuristics" }; + +}