fix: tabs ohne seiten-rahmen, scan aller unterseiten, button-radius bei hover (v1.5.3)
- Admin-tabs: kein rahmen/box/outline in irgendeinem zustand mehr - nur die untere unterstreichung markiert den aktiven tab (WP-default-borders ueber- schrieben, auch :focus/:active). - Scan deckt jetzt ALLE veroeffentlichten seiten/beitraege (alle public post types) ab statt nur 4. Backend: limit 10 -> 60 URLs und PARALLELE abfrage (concurrency 12, 8s/seite), plugin-AJAX-timeout 45 -> 90s. - Platzhalter-button: border-radius in hover/focus/active festgenagelt (3px) -> theme kann die ecken beim hover nicht mehr veraendern. + version 1.5.3. (Backend-redeploy noetig fuer den scan.) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -17,9 +17,10 @@ import {
|
||||
} from './util.js';
|
||||
import { extractResources, analyze, isPublicHost, isPrivateIp } from './scan.js';
|
||||
|
||||
const MAX_SCAN_URLS = 10;
|
||||
const MAX_SCAN_URLS = 60; // pages scanned per request (covers whole small/medium sites)
|
||||
const MAX_SCAN_BYTES = 2_000_000;
|
||||
const SCAN_TIMEOUT_MS = 10_000;
|
||||
const SCAN_TIMEOUT_MS = 8_000;
|
||||
const SCAN_CONCURRENCY = 12; // fetch pages in parallel so "all pages" stays fast
|
||||
|
||||
const DATA_DIR = process.env.DATA_DIR || '/data';
|
||||
const RELEASES_DIR = join(DATA_DIR, 'releases');
|
||||
@@ -366,43 +367,47 @@ app.post('/api/v1/scan', async (req, res) => {
|
||||
}
|
||||
if (!targets.length) return fail(res, 400, 'no valid target URLs for this domain');
|
||||
|
||||
const pages = [];
|
||||
for (const t of targets) {
|
||||
// Fetch one page (SSRF-guarded, IP-pinned, no redirects). Returns a page row.
|
||||
async function scanOne(t) {
|
||||
try {
|
||||
// SSRF hardening: resolve the host and refuse private/link-local IPs
|
||||
// (e.g. a public hostname pointed at 169.254.169.254 cloud metadata).
|
||||
const host = new URL(t).hostname;
|
||||
let address, family;
|
||||
try {
|
||||
({ address, family } = await lookup(host));
|
||||
} catch {
|
||||
pages.push({ url: t, error: 'dns lookup failed', resources: [] });
|
||||
continue;
|
||||
return { url: t, error: 'dns lookup failed', resources: [] };
|
||||
}
|
||||
if (isPrivateIp(address)) {
|
||||
pages.push({ url: t, error: 'blocked: resolves to a private address', resources: [] });
|
||||
continue;
|
||||
return { url: t, error: 'blocked: resolves to a private address', resources: [] };
|
||||
}
|
||||
|
||||
// Connect to the validated IP (no re-resolution); do not follow redirects.
|
||||
const r = await pinnedRequest(t, address, family, {
|
||||
maxBytes: MAX_SCAN_BYTES,
|
||||
timeoutMs: SCAN_TIMEOUT_MS,
|
||||
headers: { 'User-Agent': 'ContentBlockerScanner/1.0', Accept: 'text/html' },
|
||||
});
|
||||
if (r.status >= 300 && r.status < 400) {
|
||||
pages.push({ url: t, error: `redirect (${r.status}) not followed`, resources: [] });
|
||||
continue;
|
||||
return { url: t, error: `redirect (${r.status}) not followed`, resources: [] };
|
||||
}
|
||||
pages.push({
|
||||
url: t,
|
||||
resources: extractResources(r.buffer.toString('utf8').slice(0, MAX_SCAN_BYTES), t),
|
||||
});
|
||||
return { url: t, resources: extractResources(r.buffer.toString('utf8').slice(0, MAX_SCAN_BYTES), t) };
|
||||
} catch (e) {
|
||||
pages.push({ url: t, error: String(e?.message || e), resources: [] });
|
||||
return { url: t, error: String(e?.message || e), resources: [] };
|
||||
}
|
||||
}
|
||||
|
||||
// Run with bounded concurrency so scanning all pages stays fast but doesn't
|
||||
// hammer the target site.
|
||||
const pages = new Array(targets.length);
|
||||
let next = 0;
|
||||
async function worker() {
|
||||
while (next < targets.length) {
|
||||
const idx = next++;
|
||||
pages[idx] = await scanOne(targets[idx]);
|
||||
}
|
||||
}
|
||||
await Promise.all(
|
||||
Array.from({ length: Math.min(SCAN_CONCURRENCY, targets.length) }, worker)
|
||||
);
|
||||
|
||||
const findings = analyze(pages, domain);
|
||||
return res.json({
|
||||
ok: true,
|
||||
|
||||
Reference in New Issue
Block a user