import { describe, expect, it } from "vitest"; import { GESCHWAERZT, schwaerzen } from "@/lib/protokoll-schwaerzen"; const KEY = "-----BEGIN OPENSSH PRIVATE KEY-----\n" + "b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW\n" + "-----END OPENSSH PRIVATE KEY-----"; describe("schwaerzen", () => { it("ersetzt geheime Felder in verschachtelten Objekten und Arrays", () => { const eingabe = { name: "backup", privateKeyOpenssh: "irgendwas", privatVerschluesselt: Buffer.from("x"), einstellungen: { passwort: "hunter2", Password: "hunter2", sshPassphrase: "abc", client_secret: "s", geheimnisWert: { tief: 1 }, accessToken: "t", }, liste: [{ token: "t", erlaubt: "ja" }, "frei", 3], }; expect(schwaerzen(eingabe)).toEqual({ name: "backup", privateKeyOpenssh: GESCHWAERZT, privatVerschluesselt: GESCHWAERZT, einstellungen: { passwort: GESCHWAERZT, Password: GESCHWAERZT, sshPassphrase: GESCHWAERZT, client_secret: GESCHWAERZT, geheimnisWert: GESCHWAERZT, accessToken: GESCHWAERZT, }, liste: [{ token: GESCHWAERZT, erlaubt: "ja" }, "frei", 3], }); // Eingabe bleibt unberuehrt. expect(eingabe.einstellungen.passwort).toBe("hunter2"); }); it("findet Private Keys in beliebigen Zeichenketten", () => { const ergebnis = schwaerzen({ notiz: `vorher ${KEY} nachher`, tief: [[KEY]] }) as { notiz: string; tief: string[][]; }; expect(ergebnis.notiz).toBe(`vorher ${GESCHWAERZT} nachher`); expect(ergebnis.tief[0]![0]).toBe(GESCHWAERZT); expect(JSON.stringify(ergebnis)).not.toContain("b3BlbnNzaC1rZXkt"); }); it("schwärzt Keys in Fehlermeldungen und Stacktraces", () => { const fehler = new Error(`Parser scheiterte an ${KEY}`, { cause: { passphrase: "x", text: KEY } }); const ergebnis = schwaerzen({ fehler }) as { fehler: Record }; expect(ergebnis.fehler["name"]).toBe("Error"); expect(ergebnis.fehler["message"]).toBe(`Parser scheiterte an ${GESCHWAERZT}`); expect(JSON.stringify(ergebnis)).not.toContain("b3BlbnNzaC1rZXkt"); expect(JSON.stringify(ergebnis)).not.toContain("PRIVATE KEY"); expect(ergebnis.fehler["cause"]).toEqual({ passphrase: GESCHWAERZT, text: GESCHWAERZT }); }); it("schwärzt auch abgeschnittene Keys ohne END-Zeile", () => { const abgeschnitten = KEY.slice(0, 60); expect(schwaerzen(`log: ${abgeschnitten}`)).toBe(`log: ${GESCHWAERZT}`); }); it("schwärzt andere PEM-Private-Keys (PKCS#1, PKCS#8)", () => { for (const art of ["RSA PRIVATE KEY", "PRIVATE KEY", "ENCRYPTED PRIVATE KEY"]) { const pem = `-----BEGIN ${art}-----\nMIIEow\n-----END ${art}-----`; expect(schwaerzen(pem)).toBe(GESCHWAERZT); } }); it("lässt Public Keys und harmlose Werte stehen", () => { const wert = { publicKeyZeile: "ssh-ed25519 AAAAC3Nza x", fingerprint: "SHA256:abc", anzahl: 3, aktiv: true, leer: null, zeit: new Date(0), }; expect(schwaerzen(wert)).toEqual(wert); expect(schwaerzen("-----BEGIN CERTIFICATE-----")).toBe("-----BEGIN CERTIFICATE-----"); }); it("ersetzt rohe Bytes und übersteht Zyklen", () => { const zyklisch: Record = { daten: Buffer.from("geheim") }; zyklisch["selbst"] = zyklisch; expect(schwaerzen(zyklisch)).toEqual({ daten: "[Binärdaten]", selbst: "[zirkulär]" }); }); it("zeigt mehrfach referenzierte Teilbäume vollständig", () => { const geteilt = { a: 1 }; expect(schwaerzen({ x: geteilt, y: geteilt })).toEqual({ x: { a: 1 }, y: { a: 1 } }); }); }); describe("harmlose Felder mit privat im Namen (#005)", () => { it("bleiben lesbar", () => { expect( schwaerzen({ privateVerwendungen: 2, privatePfad: "~/.ssh/id_ed25519_x", privatGeloeschtAm: "2026-10-12", }), ).toEqual({ privateVerwendungen: 2, privatePfad: "~/.ssh/id_ed25519_x", privatGeloeschtAm: "2026-10-12", }); }); });