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:
s4luorth
2026-06-08 18:19:57 +02:00
parent ef84c43ded
commit 3228965c60
4 changed files with 59 additions and 40 deletions

View File

@@ -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,