README.md
Rendering markdown...
// Based on: Ox Security
// https://www.ox.security/blog/cve-2025-65717-live-server-vscode-vulnerability/
// Modified by natsu
let origin = '';
const visited = new Set();
function joinPath(basePath, relative) {
if (relative.startsWith('http')) return relative;
if (relative.startsWith('/')) return origin + relative;
if (basePath.endsWith('/')) return basePath + relative;
return basePath + '/' + relative;
}
function report(path) {
const container = document.getElementById('results');
const details = document.createElement('details');
const summary = document.createElement('summary');
summary.textContent = decodeURI(path);
details.appendChild(summary);
details.addEventListener('toggle', async () => {
const res = await fetch(path);
const contentType = res.headers.get('content-type') || '';
const pre = document.createElement('pre');
if (contentType.startsWith('text/')) {
pre.textContent = await res.text();
} else {
pre.textContent = `(binary file: ${contentType || 'unknown'})`;
}
details.appendChild(pre);
}, { once: true });
container.appendChild(details);
}
async function crawl(path) {
if (visited.has(path)) return;
visited.add(path);
try {
const res = await fetch(path);
const text = await res.text();
report(path);
if (res.headers.get('content-type')?.includes('text/html')) {
const parser = new DOMParser();
const doc = parser.parseFromString(text, 'text/html');
const links = Array.from(doc.querySelectorAll('a'))
.map(a => a.getAttribute('href'))
.filter(h => h && h !== '#');
for (const link of links) {
const nextPath = joinPath(path, link);
await crawl(nextPath);
}
}
} catch (e) {
// fail silently
}
}
async function scanPorts() {
const container = document.getElementById('scan-results');
const status = document.getElementById('scan-status');
const minPort = parseInt(document.getElementById('scan-min').value) || 5000;
const maxPort = parseInt(document.getElementById('scan-max').value) || 6000;
const total = maxPort - minPort + 1;
let scanned = 0;
let found = 0;
// Remove previous scan results
container.querySelectorAll('.port-item').forEach(el => el.remove());
async function probe(port) {
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 12000);
try {
await fetch(`http://localhost:${port}/`, {
signal: controller.signal,
mode: 'no-cors',
});
const portItem = document.createElement('li');
portItem.className = 'port-item';
portItem.textContent = port + ' ';
const btn = document.createElement('button');
btn.textContent = 'crawl';
btn.addEventListener('click', () => startCrawl(port));
portItem.appendChild(btn);
container.appendChild(portItem);
found++;
} catch (e) {
// port closed or timed out
} finally {
clearTimeout(timeout);
scanned++;
status.textContent = `Scanning... ${scanned}/${total}`;
}
}
const ports = Array.from({ length: total }, (_, i) => minPort + i);
const batchSize = 100;
for (let i = 0; i < ports.length; i += batchSize) {
await Promise.all(ports.slice(i, i + batchSize).map(p => probe(p)));
}
status.textContent = `Done (${found} port${found !== 1 ? 's' : ''} found)`;
}
document.getElementById('scan-btn').addEventListener('click', () => scanPorts());
async function startCrawl(port) {
visited.clear();
const results = document.getElementById('results');
results.innerHTML = '';
origin = `http://localhost:${port}`;
await crawl(origin + '/');
if (!results.childElementCount) {
results.textContent = 'No results found.';
}
}
document.getElementById('crawl-btn').addEventListener('click', () => {
const port = document.getElementById('port-input').value;
if (!port) return;
startCrawl(port);
});