Kasse und Google-Anmeldung in der Inhaltsrichtlinie freigegeben

Fortsetzung des Musik-Fundes: PayPal und Google haetten an derselben
Stelle still versagt, sobald Dogi seine Zugangsdaten eintraegt -- leerer
Bereich statt Bezahlknopf, ohne Fehlermeldung, ohne Protokolleintrag.

Vorgehen bewusst gemessen statt geraten: Erst die Angaben der Anbieter
(PayPal "Best Practices", Google CSP-Abschnitt der Setup-Anleitung),
dann im echten Browser mit PayPals offizieller Testkennung
"client-id=test" nachgemessen und die Verstoesse ueber das Ereignis
securitypolicyviolation eingesammelt. Die Messung hat zwei Dinge
gefunden, die in keiner Anleitung standen:

- www.sandbox.paypal.com (frame-src + connect-src). Beim Einrichten
  testet man mit Sandbox-Zugangsdaten; ohne diesen Eintrag haette die
  Kasse in genau dieser Phase nicht abgeschlossen werden koennen.
- accounts.google.com/gsi/style (style-src). 'unsafe-inline' deckt das
  NICHT ab -- es erlaubt nur Stile im Dokument, keine nachgeladene
  Stilvorlage. Der Anmelde-Knopf waere unformatiert erschienen.

Bewusst einzelne Adressen statt PayPals vorgeschlagener Platzhalter
(*.paypal.com): Was die Messung nicht gebraucht hat, steht nicht drin.
Skripte bleiben ohne 'unsafe-inline' -- die Pruefsummen-Loesung fuer die
eigenen Inline-Bloecke bleibt unangetastet, und ein zusaetzliches
'unsafe-inline' waere neben Pruefsummen ohnehin wirkungslos.

Neuer Test pruef-kasse.mjs (15 Pruefungen): laedt das echte PayPal-SDK,
baut die Bezahlknoepfe wirklich auf, rendert den echten Google-Knopf und
verlangt NULL Verstoesse. Bestandstest pruef-inhaltsrichtlinie.mjs (22)
und pruef-musik.mjs (17) bleiben gruen -- inklusive der Gegenprobe, dass
eingeschleuste Skripte weiterhin blockiert werden.

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
2026-08-27 15:11:27 +02:00
co-authored by Claude Opus 5
parent 87db17704a
commit 8fa15c04f1
4 changed files with 353 additions and 6 deletions
+38
View File
@@ -0,0 +1,38 @@
import { chromium } from "playwright";
import { spawn } from "node:child_process";
const server = spawn(process.execPath, ["index.js"], { cwd: "server", env: { ...process.env, PORT: "4434" }, stdio: "ignore" });
const warte = async () => { for (let i=0;i<40;i++){ try { if ((await fetch("http://127.0.0.1:4434/index.html")).ok) return; } catch{} await new Promise(r=>setTimeout(r,250)); } throw new Error("kein Server"); };
await warte();
const b = await chromium.launch();
const s = await b.newPage();
await s.goto("http://127.0.0.1:4434/abonnieren.html", { waitUntil: "domcontentloaded" });
await s.evaluate(() => { window.__v = []; document.addEventListener("securitypolicyviolation", (e) => window.__v.push({ dir: e.violatedDirective, blockiert: e.blockedURI })); });
const paypal = await s.evaluate(async () => {
const geladen = await new Promise((res) => { const sk = document.createElement("script"); sk.src = "https://www.paypal.com/sdk/js?client-id=test&currency=EUR"; sk.onload = () => res(true); sk.onerror = () => res(false); document.body.appendChild(sk); setTimeout(() => res(false), 15000); });
if (!geladen || !window.paypal) return { geladen, buttons: false };
const ziel = document.createElement("div"); ziel.id = "pp-test"; ziel.style.width = "300px"; document.body.appendChild(ziel);
try { await window.paypal.Buttons({}).render("#pp-test"); } catch (e) { return { geladen, buttons: false, fehler: String(e).slice(0,150) }; }
const f = ziel.querySelector("iframe");
return { geladen, buttons: !!f, rahmenHoehe: f ? Math.round(f.getBoundingClientRect().height) : 0 };
});
console.log("PAYPAL:", JSON.stringify(paypal));
const google = await s.evaluate(async () => {
const geladen = await new Promise((res) => { const sk = document.createElement("script"); sk.src = "https://accounts.google.com/gsi/client"; sk.onload = () => res(true); sk.onerror = () => res(false); document.head.appendChild(sk); setTimeout(() => res(false), 15000); });
if (!geladen || !window.google) return { geladen, knopf: false };
const ziel = document.createElement("div"); ziel.id = "g-test"; document.body.appendChild(ziel);
try {
window.google.accounts.id.initialize({ client_id: "000000000000-testtesttesttesttesttest.apps.googleusercontent.com", callback: () => {} });
window.google.accounts.id.renderButton(ziel, { type: "standard" });
} catch (e) { return { geladen, knopf: false, fehler: String(e).slice(0,150) }; }
await new Promise(r => setTimeout(r, 4000));
return { geladen, knopf: ziel.children.length > 0, inhalt: ziel.innerHTML.slice(0, 60) };
});
console.log("GOOGLE:", JSON.stringify(google));
await s.waitForTimeout(3000);
const v = await s.evaluate(() => window.__v);
const zus = {};
for (const e of v) { const k = e.dir + " <- " + (e.blockiert || "(inline)"); zus[k] = (zus[k]||0)+1; }
console.log("\nVERSTOESSE:", Object.keys(zus).length === 0 ? "KEINE" : "");
Object.entries(zus).sort().forEach(([k,n]) => console.log(" " + n + "x " + k));
console.log("\nRahmen:"); s.frames().forEach(f => console.log(" " + f.url().slice(0,75)));
await b.close(); server.kill();
+185
View File
@@ -0,0 +1,185 @@
/* =====================================================================
Kasse & Anmeldung gegen die Inhaltsrichtlinie (27.08.2026)
WORUM ES GEHT
Auf abonnieren.html kommen zwei Dinge von fremden Servern: die
PayPal-Kasse und die Google-Anmeldung. Beide werden erst NACH einer
Nutzerhandlung nachgeladen (Klick bzw. Anmeldung) und beide sind
derzeit nicht scharf geschaltet -- googleClientId und paypalClientId
stehen auf null. Ein Fehler dort fällt deshalb heute NICHT auf. Er
fällt erst an dem Tag auf, an dem Dogi seine echten Zugangsdaten
einträgt: Dann stünde an der Kasse ein leerer Bereich, ohne
Fehlermeldung, ohne Eintrag im Server-Protokoll.
Genau dieses Muster hat schon einmal zugeschlagen (Musik-Knopf,
26.-27.08.2026). Deshalb wird hier vorgezogen geprüft, statt zu warten.
WIE ECHT DIESER TEST IST
- Er startet den ECHTEN server/index.js, damit die echte Kopfzeile
ausgeliefert wird. Ein Datei-Server erzeugt sie gar nicht.
- Er lädt das ECHTE PayPal-SDK mit deren offizieller Testkennung
("client-id=test") und baut die Knöpfe wirklich auf.
- Er lädt die ECHTE Google-Bibliothek und rendert den Knopf wirklich.
- Verstöße werden nicht aus Konsolentext geraten, sondern über das
Ereignis "securitypolicyviolation" gezählt -- dort stehen Richtlinie
und blockierte Adresse maschinenlesbar drin.
Kein echter Zugangscode und kein echtes Geld sind beteiligt.
===================================================================== */
import { chromium } from "playwright";
import { spawn } from "node:child_process";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
const HIER = dirname(fileURLToPath(import.meta.url));
const PORT = 4435;
const BASIS = `http://127.0.0.1:${PORT}`;
let ok = 0, fehl = 0;
const funde = [];
const pruefe = (name, gut, info) => {
if (gut) { ok++; console.log(" ok " + name + (info ? " -> " + info : "")); }
else { fehl++; console.log(" FEHL " + name + (info ? " -> " + info : "")); funde.push(name + " — " + info); }
};
const server = spawn(process.execPath, ["index.js"], {
cwd: join(HIER, "server"),
env: { ...process.env, PORT: String(PORT) },
stdio: "ignore",
});
const beenden = (code) => { try { server.kill(); } catch { /* schon weg */ } process.exit(code); };
/* Notbremse: PayPals render() wartet ohne echten Bezahlvorgang unbegrenzt.
Ohne diese Grenze bliebe der Test im Zweifel ewig hängen. */
const notbremse = setTimeout(() => { console.log("\nZEIT ABGELAUFEN — Testlauf abgebrochen."); beenden(1); }, 180000);
notbremse.unref();
async function warteAufServer() {
for (let i = 0; i < 40; i++) {
try {
const res = await fetch(BASIS + "/abonnieren.html");
if (res.ok) return res;
} catch { /* noch nicht oben */ }
await new Promise((r) => setTimeout(r, 250));
}
throw new Error("Server ist nicht hochgekommen");
}
(async () => {
const antwort = await warteAufServer();
const richtlinie = antwort.headers.get("content-security-policy") || "";
const regel = (name) => (richtlinie.split(";").find((t) => t.trim().startsWith(name + " ")) || "").trim();
console.log("\n== Was die Kopfzeile verspricht ==");
pruefe("script-src kennt PayPal", /www\.paypal\.com/.test(regel("script-src")), regel("script-src").slice(0, 120));
pruefe("script-src kennt Google", /accounts\.google\.com\/gsi\/client/.test(regel("script-src")), "");
pruefe("frame-src kennt PayPal (Betrieb + Übung)", /www\.paypal\.com/.test(regel("frame-src")) && /sandbox\.paypal\.com/.test(regel("frame-src")), regel("frame-src"));
pruefe("frame-src kennt Google", /accounts\.google\.com\/gsi\//.test(regel("frame-src")), "");
pruefe("style-src kennt Googles Stilvorlage", /accounts\.google\.com\/gsi\/style/.test(regel("style-src")), regel("style-src"));
pruefe("Musik bleibt erlaubt", /open\.spotify\.com/.test(regel("frame-src")), "");
pruefe("Kein Sternchen in frame-src", !/\*/.test(regel("frame-src")), regel("frame-src"));
pruefe("Kein 'unsafe-inline' bei Skripten", !/unsafe-inline/.test(regel("script-src")), "");
const browser = await chromium.launch();
const seite = await browser.newPage({ viewport: { width: 1280, height: 900 } });
await seite.goto(BASIS + "/abonnieren.html", { waitUntil: "domcontentloaded" });
await seite.evaluate(() => {
window.__verstoesse = [];
document.addEventListener("securitypolicyviolation", (e) =>
window.__verstoesse.push({ regel: e.violatedDirective, blockiert: e.blockedURI || "(inline)" })
);
});
/* ---------- PayPal ---------- */
console.log("\n== PayPal-Kasse (offizielle Testkennung) ==");
const paypal = await seite.evaluate(async () => {
const geladen = await new Promise((res) => {
const sk = document.createElement("script");
sk.src = "https://www.paypal.com/sdk/js?client-id=test&currency=EUR";
sk.onload = () => res(true);
sk.onerror = () => res(false);
document.body.appendChild(sk);
setTimeout(() => res(false), 20000);
});
if (!geladen || !window.paypal) return { geladen, knoepfe: false };
const ziel = document.createElement("div");
ziel.id = "kassen-test";
ziel.style.width = "320px";
document.body.appendChild(ziel);
/* Absichtlich NICHT abwarten: render() loest ohne echten Bezahlvorgang
nie auf. Es geht nur darum, ob der Rahmen entstehen darf. */
try { window.paypal.Buttons({}).render("#kassen-test").catch(() => {}); } catch { /* egal */ }
await new Promise((r) => setTimeout(r, 9000));
const rahmen = ziel.querySelector("iframe");
return { geladen, knoepfe: !!rahmen, hoehe: rahmen ? Math.round(rahmen.getBoundingClientRect().height) : 0 };
});
pruefe("PayPal-SDK wird geladen", paypal.geladen, paypal.geladen ? "geladen" : "BLOCKIERT");
pruefe("Bezahlknöpfe werden aufgebaut", paypal.knoepfe, paypal.knoepfe ? paypal.hoehe + "px hoch" : "kein Rahmen entstanden");
/* ---------- Google ---------- */
console.log("\n== Google-Anmeldung ==");
const google = await seite.evaluate(async () => {
const geladen = await new Promise((res) => {
const sk = document.createElement("script");
sk.src = "https://accounts.google.com/gsi/client";
sk.onload = () => res(true);
sk.onerror = () => res(false);
document.head.appendChild(sk);
setTimeout(() => res(false), 20000);
});
if (!geladen || !window.google) return { geladen, knopf: false };
const ziel = document.createElement("div");
ziel.id = "anmelde-test";
document.body.appendChild(ziel);
try {
window.google.accounts.id.initialize({
client_id: "000000000000-testtesttesttesttesttest.apps.googleusercontent.com",
callback: () => {},
});
window.google.accounts.id.renderButton(ziel, { type: "standard" });
} catch { return { geladen, knopf: false }; }
await new Promise((r) => setTimeout(r, 5000));
return { geladen, knopf: ziel.children.length > 0 };
});
pruefe("Google-Bibliothek wird geladen", google.geladen, google.geladen ? "geladen" : "BLOCKIERT");
pruefe("Anmelde-Knopf wird aufgebaut", google.knopf, google.knopf ? "gerendert" : "leer geblieben");
const googleRahmen = seite.frames().filter((f) => f.url().startsWith("https://accounts.google.com/gsi/")).length;
pruefe("Googles Rahmen darf laden", googleRahmen >= 1, googleRahmen + " Rahmen");
/* ---------- Das eigentliche Urteil ---------- */
console.log("\n== Verstöße gegen die Inhaltsrichtlinie ==");
const verstoesse = await seite.evaluate(() => window.__verstoesse);
const gezaehlt = {};
for (const v of verstoesse) {
const schluessel = v.regel + " <- " + v.blockiert.split("?")[0];
gezaehlt[schluessel] = (gezaehlt[schluessel] || 0) + 1;
}
Object.entries(gezaehlt).forEach(([k, n]) => console.log(" " + n + "x " + k));
pruefe("Kein einziger Verstoß", verstoesse.length === 0, verstoesse.length + " Verstöße");
const fehlerRahmen = seite.frames().filter((f) => f.url().startsWith("chrome-error://")).length;
pruefe("Kein Rahmen endet als Fehlerseite", fehlerRahmen === 0, fehlerRahmen + " Fehlerseiten");
/* Bilder von genau den beiden Testelementen.
WICHTIG: Die Elemente werden dafür NICHT im Seitenbaum verschoben.
PayPal baut seine Knöpfe wieder ab, sobald ihr Rahmen umgehängt wird
— ein Bild davon zeigte dann fälschlich eine leere Kasse, obwohl sie
kurz zuvor sauber stand. */
for (const [wahl, datei] of [["#kassen-test", "bild-kasse-paypal.png"], ["#anmelde-test", "bild-kasse-google.png"]]) {
try {
await seite.locator(wahl).scrollIntoViewIfNeeded();
await seite.waitForTimeout(600);
await seite.locator(wahl).screenshot({ path: datei });
} catch { /* Bild ist Beiwerk, kein Prüfkriterium */ }
}
await browser.close();
clearTimeout(notbremse);
console.log("\n===== " + ok + " ok, " + fehl + " Fehler =====");
if (funde.length) { console.log("\nOffene Punkte:"); funde.forEach((f) => console.log(" - " + f)); }
beenden(fehl ? 1 : 0);
})().catch((e) => {
console.error("Testlauf abgebrochen:", e.message);
beenden(1);
});
+67
View File
@@ -0,0 +1,67 @@
/* =====================================================================
Wie schwer und wie schnell sind die öffentlichen Seiten?
Misst je Seite die übertragene Datenmenge und die Ladezeit bis
"fertig geladen", und sammelt die größten Einzelressourcen über alle
Seiten. Ziel ist NICHT ein grüner Haken, sondern das Finden konkreter
Brocken, die sich lohnen zu verkleinern -- so wie am 27.08. das 2,5-MB-
PNG "Event des Jahres" gefunden wurde.
Gegen die echte Domain, mit leerem Cache (jeder Aufruf frische Seite).
===================================================================== */
import { chromium } from "playwright";
const BASIS = "https://dogfather-universe.com";
const SEITEN = [
"/", "/dogfather.html", "/hasidog.html", "/manager.html", "/casper.html",
"/vanvan.html", "/tt-muddi.html", "/zeitreise.html", "/streamer.html",
"/community.html", "/stimmen.html", "/streamplan.html", "/team-modis.html",
"/team-scouts.html", "/creator.html", "/werte.html", "/bewerben.html",
"/kontakt.html", "/links.html", "/supporter.html", "/abonnieren.html",
"/medien-dogfather.html", "/medien-hasidog.html", "/medien-casper.html",
];
const browser = await chromium.launch();
const groessteJeRessource = new Map(); // url -> bytes
const proSeite = [];
for (const pfad of SEITEN) {
const seite = await browser.newPage();
let bytes = 0;
seite.on("response", async (r) => {
try {
const h = r.headers();
const len = Number(h["content-length"] || 0);
let b = len;
if (!b) { try { b = (await r.body()).length; } catch { b = 0; } }
bytes += b;
const u = r.url();
if (b > (groessteJeRessource.get(u) || 0)) groessteJeRessource.set(u, b);
} catch { /* egal */ }
});
const start = Date.now();
try {
await seite.goto(BASIS + pfad, { waitUntil: "networkidle", timeout: 45000 });
} catch { /* trotzdem messen, was da war */ }
const dauer = Date.now() - start;
proSeite.push({ pfad, kb: Math.round(bytes / 1024), ms: dauer });
await seite.close();
}
await browser.close();
console.log("\n=== Pro Seite (übertragen / Ladezeit), schwerste zuerst ===");
for (const s of proSeite.sort((a, b) => b.kb - a.kb)) {
const warn = s.kb > 2000 ? " ⚠ schwer" : s.kb > 1000 ? " · mittel" : "";
console.log(` ${String(s.kb).padStart(5)} KB ${String(s.ms).padStart(5)} ms ${s.pfad}${warn}`);
}
const schnitt = Math.round(proSeite.reduce((a, s) => a + s.kb, 0) / proSeite.length);
console.log(` ---\n Schnitt: ${schnitt} KB / Seite`);
console.log("\n=== Die 12 größten Einzelressourcen (Kandidaten zum Verkleinern) ===");
const top = [...groessteJeRessource].sort((a, b) => b[1] - a[1]).slice(0, 12);
for (const [u, b] of top) {
const kb = Math.round(b / 1024);
const kurz = u.replace(BASIS, "").replace(/\?.*$/, "");
const warn = kb > 500 ? " ⚠" : "";
console.log(` ${String(kb).padStart(5)} KB ${kurz}${warn}`);
}
+63 -6
View File
@@ -50,6 +50,61 @@ import crypto from "crypto";
import { readFileSync, statSync } from "node:fs"; import { readFileSync, statSync } from "node:fs";
import { join, normalize } from "node:path"; import { join, normalize } from "node:path";
/* =====================================================================
FREMDE QUELLEN — hier stehen sie namentlich, nicht verstreut
Drei Dinge auf dieser Seite kommen nicht vom eigenen Server: die
Spotify-Player im Musik-Feld, die PayPal-Kasse und die
Google-Anmeldung auf abonnieren.html.
Warum sie hier zusammenstehen: Wer eine Quelle vergisst, merkt es
NICHT beim Deploy und nicht im Server-Protokoll. Der Browser wirft
das Element still weg -- genau so ist am 26.08.2026 der Musik-Knopf
ausgefallen und erst am 27.08. aufgefallen. Eine Liste an einer
Stelle, mit Begründung je Eintrag, ist die einzige Stelle, an der man
beim nächsten Mal nachsieht.
Ausgewählt wurde nach den Angaben der Anbieter (PayPal: "Best
Practices" der JS-SDK-Doku, Google: CSP-Abschnitt der Setup-Anleitung)
und danach mit pruef-kasse.mjs im echten Browser nachgemessen: Die
Kasse wird dort mit PayPals offizieller Testkennung wirklich
aufgebaut, die Anmeldung wirklich gerendert. Was dabei nicht gebraucht
wurde, steht hier auch nicht -- deshalb einzelne Adressen statt der
von PayPal vorgeschlagenen Platzhalter *.paypal.com.
===================================================================== */
/* Spotify: die zwei Player im Musik-Feld (main.js, jede Seite). */
const SPOTIFY = "https://open.spotify.com";
/* PayPal: das SDK selbst liegt auf www.paypal.com, Bilder/Schriften der
Knöpfe auf www.paypalobjects.com. Der Bezahlvorgang läuft anschließend
in einem Rahmen von www.paypal.com.
⚠️ Die Übungs-Umgebung (www.sandbox.paypal.com) steht bewusst MIT
drin, obwohl im Betrieb nur www.paypal.com gebraucht wird. Grund:
Beim Einrichten testet man zuerst mit Sandbox-Zugangsdaten -- das SDK
spricht dann ausschließlich mit der Sandbox-Adresse. Ohne diesen
Eintrag hätte Dogi beim ersten Testkauf eine Kasse gesehen, die sich
nicht abschließen lässt, ohne jeden Hinweis auf den Grund. Genau
dieser Verstoß ist bei der Messung mit PayPals Testkennung
aufgetreten -- geraten hätte man ihn nicht. */
const PAYPAL_SKRIPT = "https://www.paypal.com https://www.paypalobjects.com";
const PAYPAL_RAHMEN = "https://www.paypal.com https://www.sandbox.paypal.com";
const PAYPAL_BILDER = "https://www.paypal.com https://www.paypalobjects.com";
const PAYPAL_VERBINDUNG = "https://www.paypal.com https://www.paypalobjects.com https://www.sandbox.paypal.com";
/* Google-Anmeldung: Google nennt ausdrücklich die ELTERN-Adresse
.../gsi/ statt einzelner Endpunkte -- damit die Anmeldung nicht bricht,
wenn Google intern eine Adresse ändert. */
const GOOGLE_SKRIPT = "https://accounts.google.com/gsi/client";
const GOOGLE_RAHMEN = "https://accounts.google.com/gsi/";
const GOOGLE_VERBINDUNG = "https://accounts.google.com/gsi/";
/* Der Anmelde-Knopf holt sein Aussehen als eigene Stilvorlage. Wichtig:
'unsafe-inline' weiter unten deckt das NICHT ab -- das erlaubt nur
Stile IM Dokument, keine nachgeladene Datei von fremder Adresse. Ohne
diesen Eintrag erscheint der Knopf unfertig/unformatiert. */
const GOOGLE_STIL = "https://accounts.google.com/gsi/style";
/* Gemerkte Prüfsummen je Datei, zusammen mit der Änderungszeit, aus der /* Gemerkte Prüfsummen je Datei, zusammen mit der Änderungszeit, aus der
sie stammen. Ändert sich die Datei, wird neu gerechnet. */ sie stammen. Ändert sich die Datei, wird neu gerechnet. */
const merker = new Map(); const merker = new Map();
@@ -151,13 +206,13 @@ export function inhaltsrichtlinie(wurzel) {
wenn ohnehin etwas grundlegend nicht stimmt. */ wenn ohnehin etwas grundlegend nicht stimmt. */
if (!summen) return next(); if (!summen) return next();
const skript = ["'self'", ...summen].join(" "); const skript = ["'self'", ...summen, PAYPAL_SKRIPT, GOOGLE_SKRIPT].join(" ");
res.setHeader("Content-Security-Policy", [ res.setHeader("Content-Security-Policy", [
"default-src 'self'", "default-src 'self'",
`script-src ${skript}`, `script-src ${skript}`,
/* Siehe Kopf der Datei: bewusste Ausnahme, eigener Punkt auf der Liste. */ /* Siehe Kopf der Datei: bewusste Ausnahme, eigener Punkt auf der Liste. */
"style-src 'self' 'unsafe-inline'", `style-src 'self' 'unsafe-inline' ${GOOGLE_STIL}`,
/* data: für eingebettete Kleingrafiken, blob: für selbst erzeugte /* data: für eingebettete Kleingrafiken, blob: für selbst erzeugte
Vorschauen beim Hochladen. Vorschauen beim Hochladen.
@@ -169,12 +224,12 @@ export function inhaltsrichtlinie(wurzel) {
ihn: Der Deploy hätte Erfolg gemeldet, der Server wäre ihn: Der Deploy hätte Erfolg gemeldet, der Server wäre
gestartet, und der Schaden wäre erst jemandem aufgefallen, der gestartet, und der Schaden wäre erst jemandem aufgefallen, der
die Seite ansieht. */ die Seite ansieht. */
"img-src 'self' data: blob: https://postfach.dogfather-universe.com", `img-src 'self' data: blob: https://postfach.dogfather-universe.com ${PAYPAL_BILDER}`,
"font-src 'self'", "font-src 'self'",
/* Die Seite spricht mit dem eigenen Server und mit dem internen /* Die Seite spricht mit dem eigenen Server und mit dem internen
Bereich unter postfach. -- mehr nicht. Ein eingeschleustes Bereich unter postfach. -- mehr nicht. Ein eingeschleustes
Skript könnte damit keine Daten nach außen schicken. */ Skript könnte damit keine Daten nach außen schicken. */
"connect-src 'self' https://postfach.dogfather-universe.com", `connect-src 'self' https://postfach.dogfather-universe.com ${PAYPAL_VERBINDUNG} ${GOOGLE_VERBINDUNG}`,
/* Kein Flash, keine Java-Applets, keine eingebetteten Objekte. */ /* Kein Flash, keine Java-Applets, keine eingebetteten Objekte. */
"object-src 'none'", "object-src 'none'",
/* Verhindert, dass ein eingeschleustes <base> alle relativen /* Verhindert, dass ein eingeschleustes <base> alle relativen
@@ -209,8 +264,10 @@ export function inhaltsrichtlinie(wurzel) {
Google-Anmeldung auf abonnieren.html arbeiten ebenfalls mit Google-Anmeldung auf abonnieren.html arbeiten ebenfalls mit
Rahmen), muss er hier NAMENTLICH ergänzt werden -- sonst Rahmen), muss er hier NAMENTLICH ergänzt werden -- sonst
wiederholt sich exakt dieser stille Ausfall an der Kasse. wiederholt sich exakt dieser stille Ausfall an der Kasse.
pruef-musik.mjs prüft das im echten Browser mit. */ pruef-musik.mjs und pruef-kasse.mjs prüfen das im echten Browser
"frame-src https://open.spotify.com", mit -- inklusive der Kasse, die vorher genau hier gescheitert
wäre. */
`frame-src ${SPOTIFY} ${PAYPAL_RAHMEN} ${GOOGLE_RAHMEN}`,
"worker-src 'self'", "worker-src 'self'",
/* Hebt gemischte Inhalte auf HTTPS, statt sie zu blockieren. */ /* Hebt gemischte Inhalte auf HTTPS, statt sie zu blockieren. */
"upgrade-insecure-requests", "upgrade-insecure-requests",