/* ===================================================================== WER DIE KAMERA DARF — und wer sie NICHT darf (28.09.2026) In index.js steht eine Sperre: Mikrofon und Kamera sind auf JEDER Seite verboten, ausser auf wenigen benannten. Das ist richtig und soll so bleiben -- ohne sie koennte ein eingeschleustes Skript ueberall das Mikrofon anschalten. Die Sperre hat aber eine unangenehme Eigenschaft: Wenn sie zu weit geht, merkt es niemand beim Bauen. Die Seite laedt, die Knoepfe sind da, und erst wenn jemand auf „Kamera an" drueckt, kommt eine Meldung, die auf die falsche Faehrte fuehrt -- man sucht dann in den Browsereinstellungen. Genau so ist es am 18.09.2026 beim Anruf passiert und am 28.09.2026 bei der Reaction. --------------------------------------------------------------------- DESHALB WIRD HIER NICHT EINE LISTE GEPRUEFT, SONDERN ABGELEITET Gesucht wird im Quelltext: Welche Seite laedt ein Skript, das `getUserMedia` aufruft? Genau diese Seiten brauchen die Erlaubnis. Eine Liste, die gegen den Quelltext gehalten wird, kann nicht veralten -- wer morgen eine dritte Seite mit Kamera baut, bekommt hier einen roten Punkt statt in drei Wochen einen Anruf. UND DIE GEGENRICHTUNG WIRD MITGEPRUEFT. Eine Seite, die die Erlaubnis hat und sie nicht braucht, ist eine offene Tuer ohne Grund. Auch das faellt hier auf. Aufrufen mit: node server/pruef-kamera-richtlinie.mjs ===================================================================== */ import { readFileSync, readdirSync } from "node:fs"; import { dirname, join } from "node:path"; import { fileURLToPath } from "node:url"; const HIER = dirname(fileURLToPath(import.meta.url)); const WURZEL = join(HIER, ".."); const SEITEN_ORDNER = join(WURZEL, "workspace"); const JS_ORDNER = join(SEITEN_ORDNER, "assets", "js"); let fehler = 0, geprueft = 0; const ok = (b, t) => { geprueft++; console.log((b ? " ok " : " FEHL ") + t); if (!b) fehler++; }; const melde = (t) => console.log(t); /* ---------- 1. Welche Skripte fassen die Kamera an? --------------------- */ melde("=== 1. Welche Skripte die Kamera anfassen ==="); const jsDateien = readdirSync(JS_ORDNER).filter((n) => n.endsWith(".js")); const mitKamera = new Set(); for (const name of jsDateien) { const text = readFileSync(join(JS_ORDNER, name), "utf8"); /* Der Aufruf selbst, nicht das Wort in einem Kommentar: Es muss eine Klammer folgen. Sonst zaehlte jede Erklaerung mit, in der `getUserMedia` erwaehnt wird -- und davon gibt es hier viele. */ if (/getUserMedia\s*\(/.test(text.replace(/\/\*[\s\S]*?\*\//g, ""))) { mitKamera.add(name); } } ok(mitKamera.size > 0, `${mitKamera.size} Skript(e) rufen getUserMedia auf: ${[...mitKamera].join(", ")}`); /* ---------- 2. Welche Seiten laden diese Skripte? ----------------------- */ melde(""); melde("=== 2. Welche Seiten sie laden ==="); const seiten = readdirSync(SEITEN_ORDNER).filter((n) => n.endsWith(".html")); const brauchen = new Set(); for (const seite of seiten) { const html = readFileSync(join(SEITEN_ORDNER, seite), "utf8"); for (const js of mitKamera) { if (html.includes(`assets/js/${js}`)) { brauchen.add(seite); break; } } } ok(brauchen.size > 0, `${brauchen.size} Seite(n) brauchen die Erlaubnis: ${[...brauchen].sort().join(", ")}`); /* ---------- 3. Und was steht in index.js? ------------------------------- */ melde(""); melde("=== 3. Der Abgleich ==="); const indexText = readFileSync(join(HIER, "index.js"), "utf8"); const zeile = indexText.match(/const MIT_KAMERA = \[([^\]]*)\]/); ok(!!zeile, "die Menge MIT_KAMERA steht in index.js"); const erlaubt = new Set(); if (zeile) { for (const m of zeile[1].matchAll(/workspace\\\/([a-z0-9-]+)\\\.html/g)) { erlaubt.add(m[1] + ".html"); } } ok(erlaubt.size > 0, `sie nennt ${erlaubt.size} Seite(n): ${[...erlaubt].sort().join(", ")}`); const fehlend = [...brauchen].filter((s) => !erlaubt.has(s)); ok(fehlend.length === 0, fehlend.length ? `FEHLT: ${fehlend.join(", ")} laedt ein Kamera-Skript, steht aber nicht in MIT_KAMERA ` + "-- dort geht die Kamera nicht an, ohne dass jemand einen Fehler sieht" : "jede Seite, die die Kamera braucht, steht drin"); const zuviel = [...erlaubt].filter((s) => !brauchen.has(s)); ok(zuviel.length === 0, zuviel.length ? `ZU VIEL: ${zuviel.join(", ")} hat die Erlaubnis, braucht sie aber nicht ` + "-- eine offene Tuer ohne Grund" : "und keine Seite hat sie, ohne sie zu brauchen"); /* ---------- 4. Die Gegenprobe ------------------------------------------ Eine Pruefung, die nur bestaetigen kann, bestaetigt nichts. Hier wird erfunden, was passiert waere, haette jemand die Reaction vergessen. */ melde(""); melde("=== 4. Gegenprobe ==="); { const erfunden = new Set([...erlaubt].filter((s) => s !== "reaktion.html")); const haetteGefehlt = [...brauchen].filter((s) => !erfunden.has(s)); ok(haetteGefehlt.includes("reaktion.html"), "ohne reaktion.html in der Menge wuerde die Pruefung rot " + `(${haetteGefehlt.join(", ") || "sie wuerde NICHT anschlagen"})`); } /* ---------- 5. Und die Sperre steht ueberhaupt --------------------------- Wenn jemand die Zeile ganz entfernt, waeren Kamera und Mikrofon ueberall offen -- und keine der Pruefungen oben wuerde das sehen, denn beide Mengen waeren dann leer und stimmten wunderbar ueberein. */ melde(""); melde("=== 5. Die Sperre selbst ==="); { ok(/microphone=\(\), camera=\(\)/.test(indexText), "die Vorgabe verbietet Mikrofon und Kamera"); ok(/microphone=\(self\), camera=\(self\)/.test(indexText), "und oeffnet sie nur fuer die eigene Seite, nicht fuer fremde Rahmen"); ok(/Permissions-Policy/.test(indexText), "der Kopf heisst Permissions-Policy"); } melde(""); melde(`${geprueft} Pruefungen, ${fehler} Fehler`); melde(fehler === 0 ? "ALLES IN ORDNUNG" : "NICHT IN ORDNUNG"); process.exit(fehler === 0 ? 0 : 1);