Files
certvia/prisma/import-managed.ts
T
msolarczekandClaude Opus 4.8 36c2903a43 GAP WP1: Neue VAs 14–19 integriert + Register + Baseline + Eltern-Wiring
ISB-Volltexte der sechs neuen Verfahrensanweisungen eingepflegt (VA-14..VA-19)
und die im GAP-Report identifizierten Inhalts-GAPs geschlossen.

- 6 neue VAs unter verfahren/ (VA-14 Personalsicherheit, VA-15 Interne Audits,
  VA-16 Sichere Beschaffung/Entwicklung, VA-17 Zutritts-/Besuchermanagement,
  VA-18 Datenschutz-Pflege, VA-19 Informationssicherheit in Projekten) mit
  FULFILLS-Headern; mapping.json: verfahren[] + Reverse-Links (anforderungen[].verfahren).
- 4 referenzierte Register als Dokumente (import-managed.ts): REG-PROJECTS,
  REG-SENS-ROLES, REG-AUDIT-PLAN, REG-EXT-SERVICES (Pflichtspalten dokumentiert;
  editierbares Datenmodell = offenes WP3.0).
- Baseline: BL-GOV-01 (Audit-/Prüfzyklus) und BL-PROJ-01 (Projekt-Kriterien).
- Eltern-Richtlinien verdrahtet (IMPL-Ersatztexte aus Report §8): R01 1.2.3 (A-N1,
  löst „bzw. ISMS-Tool" auf), R05 2.1.1 (A-G-B1) + 2.1.2 (N2→VA-14), R03 1.5.1
  (A-F12→VA-15), R11 5.3.1 (A-F13→VA-16), R07 3.1.1 (N3→VA-17), R14 7.1.2 (A-N8→VA-18).

Verifiziert: _verify.py Render 0 / Mapping sauber (nur vorbestehendes IMPL 3.1.3);
Re-Import in Demo → 10 neue Dokumente, Coverage-Gaps geschlossen (1.2.3→VA-19,
2.1.1→VA-14, 1.5.1→VA-15, 5.3.1→VA-16, 3.1.1→VA-17, 7.1.2→VA-18); Browser: R01
rendert klickbare Deep-Links auf VA-19 und REG-PROJECTS. tsc grün.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-22 20:53:29 +02:00

178 lines
15 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import type { PrismaClient } from "@prisma/client";
/**
* Seed der verwalteten Register-Tabellen (§7b) und des Anwender-Handbuchs (§9.7).
* Defaults aus den Auftraggeber-Vorgaben FB-80-04 (Risikomatrix) und
* AA-80-20 (Klassifizierung/Handhabung). Idempotent pro Mandant.
*/
const NOW = "2026-07-07T00:00:00.000Z";
const daysFrom = (base: string, d: number) => new Date(new Date(base).getTime() + d * 86400000);
// Klassifizierungs-Handhabungsmatrix (AA-80-20) — Reihenfolge: Offen · Intern · Vertraulich · Streng vertraulich
const CLASSES = [
{ name: "Offen", description: "Öffentlich; keine Schutzanforderungen." },
{ name: "Intern", description: "Nur für Mitarbeitende; geringer Schutzbedarf." },
{ name: "Vertraulich", description: "Begrenzter Personenkreis; hoher Schutzbedarf." },
{ name: "Streng vertraulich", description: "Namentlich Berechtigte; sehr hoher Schutzbedarf." },
];
// [Offen, Intern, Vertraulich, Streng vertraulich]
const ASPECTS: { name: string; category: string; rules: [string, string, string, string] }[] = [
{ name: "Kennzeichnung (Papier/elektronisch)", category: "Kennzeichnung", rules: ["nicht nötig", "„Intern“ empfohlen", "„Vertraulich“ verpflichtend", "„Streng vertraulich“ + Bearbeiter"] },
{ name: "Vervielfältigung", category: "Handhabung", rules: ["frei", "im Bedarfsfall", "nur mit Freigabe", "nur mit Freigabe, protokolliert"] },
{ name: "Weitergabe intern", category: "Weitergabe", rules: ["frei", "an Mitarbeitende", "an Berechtigte, need-to-know", "namentlich Berechtigte, dokumentiert"] },
{ name: "Postweg / Versand", category: "Weitergabe", rules: ["Standard", "Standard", "verschlossen, persönlich/Einschreiben", "Einschreiben eigenhändig, Empfangsbestätigung"] },
{ name: "E-Mail", category: "Weitergabe", rules: ["ohne Auflagen", "intern ohne Auflagen", "extern verschlüsselt (BL-CRY-04)", "immer verschlüsselt, nur an Berechtigte"] },
{ name: "Fax intern/extern", category: "Weitergabe", rules: ["zulässig", "zulässig", "nur mit Ankündigung/Abholung", "unzulässig"] },
{ name: "Mobile Datenträger", category: "Speicherung", rules: ["zulässig", "nur freigegebene", "nur verschlüsselt (BL-CRY-03)", "nur verschlüsselt, freigegeben, protokolliert"] },
{ name: "Internet / Blogs / Social Media", category: "Weitergabe", rules: ["zulässig", "kein interner Bezug", "unzulässig", "unzulässig"] },
{ name: "Entsorgung Papier", category: "Entsorgung", rules: ["Altpapier", "Datentonne", "Aktenvernichter ≥ P-4", "Aktenvernichter ≥ P-5, protokolliert"] },
{ name: "Löschung elektronisch / Hardware", category: "Entsorgung", rules: ["normal löschen", "normal löschen", "sicheres Löschen (BL-DEL-01)", "sicheres Löschen/Vernichten, Nachweis"] },
{ name: "Speicherung (IT-Systeme)", category: "Speicherung", rules: ["beliebig", "interne Systeme", "zugriffsbeschränkte Ablagen", "verschlüsselt, streng zugriffsbeschränkt"] },
{ name: "Homeoffice / mobiles Arbeiten", category: "Nutzung", rules: ["zulässig", "zulässig", "nur mit Sichtschutz, gesperrt bei Abwesenheit", "nur nach Freigabe, keine Papierform"] },
{ name: "Öffentlicher Bereich / Dienstreise", category: "Nutzung", rules: ["zulässig", "diskret", "Sichtschutz, nicht unbeaufsichtigt", "unzulässig (kritische Länder)"] },
{ name: "Verbale Weitergabe", category: "Weitergabe", rules: ["frei", "unter Kollegen", "nur in geschützter Umgebung", "nur namentlich Berechtigte, abhörsicher"] },
];
// Risiko-Bewertungsmatrix (FB-80-04): Schadensausmaß(14) × EW(14) = 116
const RISK_CLASSES = [
{ name: "Niedrig", maxScore: 3, acceptance: "wird akzeptiert", tone: "ok" },
{ name: "Mittel", maxScore: 6, acceptance: "Maßnahmen; Akzeptanz durch Risk Owner", tone: "warn" },
{ name: "Hoch", maxScore: 9, acceptance: "Akzeptanz durch Risikomanagement-Gremium", tone: "orange" },
{ name: "Kritisch", maxScore: 16, acceptance: "Akzeptanz durch Geschäftsführung", tone: "risk" },
];
const EW_LEVELS = [
{ level: 1, label: "Unwahrscheinlich", definition: "Ereignis in absehbarer Zeit kaum zu erwarten (seltener als alle 5 Jahre)." },
{ level: 2, label: "Gelegentlich", definition: "Ereignis denkbar, aber nicht häufig (ca. alle 15 Jahre)." },
{ level: 3, label: "Wahrscheinlich", definition: "Ereignis tritt voraussichtlich ein (mehrmals pro Jahr)." },
{ level: 4, label: "Sehr wahrscheinlich", definition: "Ereignis tritt regelmäßig/häufig ein (monatlich oder öfter)." },
];
const DAMAGE_DIMS: { name: string; levels: Record<string, string> }[] = [
{ name: "Gesetzes-/Vertragsverstöße", levels: { "1": "unerheblich", "2": "geringfügige Verstöße", "3": "erhebliche Verstöße/Bußgelder", "4": "gravierende Rechtsfolgen/Straftatbestand" } },
{ name: "Datenschutz", levels: { "1": "kein Personenbezug", "2": "geringe Betroffenheit", "3": "sensible Daten betroffen", "4": "massenhaft sensible Daten / hohe Betroffenheit" } },
{ name: "Persönliche Unversehrtheit", levels: { "1": "keine", "2": "leichte Beeinträchtigung", "3": "Gesundheitsgefahr", "4": "Gefahr für Leib und Leben" } },
{ name: "Aufgabenerfüllung", levels: { "1": "keine Einschränkung", "2": "geringe Einschränkung", "3": "erhebliche Einschränkung", "4": "Kernaufgaben nicht erfüllbar" } },
{ name: "Innen-/Außenwirkung (Reputation)", levels: { "1": "keine", "2": "intern begrenzt", "3": "öffentlich wahrnehmbar", "4": "nachhaltiger Reputationsschaden" } },
{ name: "Störungs-/Ausfallzeit", levels: { "1": "< 1 Std.", "2": "bis 1 Tag", "3": "bis 1 Woche", "4": "> 1 Woche" } },
{ name: "Finanzielle Auswirkungen", levels: { "1": "bis 10 T€", "2": "bis 50 T€", "3": "bis 200 T€", "4": "über 500 T€" } },
];
// Krypto-Register (VA-07) — Demo-Datensätze inkl. Ablaufüberwachung
const CRYPTO = [
{ dienst: "TLS Web-Portal (portal.gefim.example)", schluessel: "RSA 3072 · Let's Encrypt", algorithmus: "TLS 1.3 / AES-256", ablaufOffset: 45, verantwortlich: "IT-Leitung", speicherort: "Reverse Proxy", baselineRef: "BL-CRY-01" },
{ dienst: "VPN-Gateway", schluessel: "ECDSA P-256 · interne CA", algorithmus: "IKEv2 / AES-256-GCM", ablaufOffset: 12, verantwortlich: "IT-Leitung", speicherort: "Firewall/HSM", baselineRef: "BL-CRY-02" },
{ dienst: "Festplattenverschlüsselung Notebooks", schluessel: "BitLocker · TPM 2.0", algorithmus: "AES-256-XTS", ablaufOffset: null, verantwortlich: "IT-Betrieb", speicherort: "MDM-Recovery", baselineRef: "BL-CRY-03" },
{ dienst: "S/MIME Zertifikate Geschäftsführung", schluessel: "RSA 2048 · öffentliche CA", algorithmus: "S/MIME", ablaufOffset: -8, verantwortlich: "IT-Leitung", speicherort: "Smartcard", baselineRef: "BL-CRY-04" },
];
// Anwender-Handbuch (§9.7) — kuratierte Themen, Werte via {{VARIABLE}} synchron zur Baseline
const HANDBOOK: { category: string; title: string; bodyMd: string; sourceRefs: string[] }[] = [
{
category: "Zugang & Passwörter", title: "Passwörter & Mehr-Faktor-Anmeldung",
bodyMd: "**Das Wichtigste kurz:**\n\n- Nutze Passwörter mit **mindestens {{PW_MIN_LENGTH}} Zeichen** ({{PW_COMPLEXITY}}).\n- Aktiviere **Mehr-Faktor-Authentifizierung (MFA)** über {{TECH_MFA}} für {{MFA_SCOPE}}.\n- Gib Passwörter **nie** weiter und nutze für jeden Dienst ein eigenes.\n- Bei Verdacht auf Kompromittierung: Passwort sofort ändern und melden.",
sourceRefs: ["R08#4.1.2"],
},
{
category: "Zugang & Passwörter", title: "Zugriffsrechte nur was du brauchst",
bodyMd: "- Zugriff erhältst du **nach dem Minimalprinzip** (need-to-know) über {{TOOL_TICKET}}.\n- Nicht mehr benötigte Rechte werden entzogen; deine Rechte werden regelmäßig **überprüft (Rezertifizierung, {{RECERT_FREQ}})**.\n- Sperre deinen Bildschirm bei jedem Verlassen des Arbeitsplatzes.",
sourceRefs: ["R08#4.2.1"],
},
{
category: "Umgang mit Informationen", title: "Daten richtig klassifizieren & handhaben",
bodyMd: "- Behandle Informationen gemäß ihrer **Schutzklasse** (Offen · Intern · Vertraulich · Streng vertraulich).\n- **Vertrauliche** Inhalte extern nur **verschlüsselt** versenden.\n- Dokumente nicht offen liegen lassen (Clean Desk); vertrauliche Ausdrucke sicher vernichten.",
sourceRefs: ["R02"],
},
{
category: "Sicheres Arbeiten", title: "Mobiles Arbeiten & Homeoffice",
bodyMd: "- Nutze nur **freigegebene Geräte** (verwaltet über {{TECH_MDM}}).\n- Achte auf **Sichtschutz** in der Öffentlichkeit; keine vertraulichen Gespräche in Bahn/Café.\n- Verbinde dich extern nur über {{TECH_VPN}} mit MFA.",
sourceRefs: ["R06"],
},
{
category: "Sicheres Arbeiten", title: "E-Mail, Internet & Schadsoftware",
bodyMd: "- Öffne **keine unerwarteten Anhänge/Links**; im Zweifel nachfragen.\n- Melde verdächtige E-Mails (Phishing) über den Meldeweg.\n- {{TECH_MALWARE}} schützt deine Geräte deaktiviere den Schutz nie.",
sourceRefs: ["R10", "R09"],
},
{
category: "Vorfälle", title: "Sicherheitsvorfall? So meldest du ihn",
bodyMd: "**Sofort melden** bei Verdacht auf Vorfall, Datenverlust oder verlorenem Gerät:\n\n- an die {{ROLE_ISB}} bzw. den definierten Meldeweg.\n- Lieber einmal zu viel melden schnelle Meldung begrenzt den Schaden.\n- Nichts vertuschen, nichts eigenmächtig „reparieren“.",
sourceRefs: ["R04"],
},
];
export async function importManaged(prisma: PrismaClient, tenantId: string) {
// Idempotent: alte Datensätze pro Mandant entfernen
await prisma.handlingRule.deleteMany({ where: { tenantId } });
await prisma.handlingAspect.deleteMany({ where: { tenantId } });
await prisma.classificationClass.deleteMany({ where: { tenantId } });
await prisma.cryptoEntry.deleteMany({ where: { tenantId } });
await prisma.riskMatrixClass.deleteMany({ where: { tenantId } });
await prisma.riskEwLevel.deleteMany({ where: { tenantId } });
await prisma.riskDamageDimension.deleteMany({ where: { tenantId } });
await prisma.handbookTopic.deleteMany({ where: { tenantId } });
// Register-/Handbuch-Dokumente in der Bibliothek (interaktiv gerendert)
const registerDocs = [
{ code: "CRYPTO", type: "REGISTER" as const, title: "Verschlüsselungsmechanismen-Register", orderIdx: 902, intro: "Verwaltetes Register der eingesetzten Verschlüsselung (VA-07). Ablaufüberwachung aktiv." },
{ code: "RISKMATRIX", type: "REGISTER" as const, title: "Risiko-Bewertungsmatrix", orderIdx: 903, intro: "Zentrale Bewertungsmatrix Schadensausmaß × Eintrittswahrscheinlichkeit (R03 / VA-09), Defaults nach FB-80-04." },
{ code: "CLASSIFICATION", type: "REGISTER" as const, title: "Klassifizierung & Handhabungsmatrix", orderIdx: 904, intro: "Schutzklassen und Handhabungsregeln (R02 / VA-08), Defaults nach AA-80-20." },
{ code: "HANDBUCH", type: "HANDBUCH" as const, title: "Anwender-Handbuch Informationssicherheit", orderIdx: 950, intro: "Kompakte Zusammenfassung der wichtigsten Regeln für Mitarbeitende mit Deep-Links in die Quelldokumente." },
// Verwaltete Register (GAP-Report §5.2). Vorerst als benannte Register-Dokumente mit
// dokumentierten Pflichtspalten; die editierbare Datenpflege (Attribute/Cross-Links) folgt (WP3.0).
{ code: "REG-PROJECTS", type: "REGISTER" as const, title: "Register Informationssicherheit in Projekten", orderIdx: 905, intro: "Projekte mit IS-Klassifizierung, Risikobewertung, abgeleiteten Maßnahmen/Status und ISB-Einbindung (R01 / VA-19). Pflichtspalten: Projekt, IS-Klassifizierung, Risikobewertung, Maßnahmen/Status, ISB-Einbindung." },
{ code: "REG-SENS-ROLES", type: "REGISTER" as const, title: "Register sensibler Tätigkeiten", orderIdx: 906, intro: "Sensible Tätigkeitsbereiche/Rollen mit geforderten Eignungsnachweisen und Prüftiefe (R05 / VA-14). Pflichtspalten: Rolle/Bereich, geforderte Eignungsnachweise, Prüftiefe, Verantwortlich, Review." },
{ code: "REG-AUDIT-PLAN", type: "REGISTER" as const, title: "Audit-Programm-Register", orderIdx: 907, intro: "Auditprogramm mit Zeitplan, Umfang, geprüften Controls, Prüfern und Ergebnissen (R03 / VA-15). Pflichtspalten: Zeitplan, Umfang, geprüfte Controls, Prüfer, Ergebnisse." },
{ code: "REG-EXT-SERVICES", type: "REGISTER" as const, title: "Register externe IT-/Cloud-/KI-Dienste", orderIdx: 908, intro: "Externe/Cloud-/KI-Dienste mit Schutzbedarf, Datenlokation (EU), Verschlüsselung, Exit-Strategie, Quelle/Lieferant und Freigabestatus (R13/VA-10 + R02/VA-08). Cross-Link an Lieferantensteuerung und Asset-Inventar." },
];
for (const d of registerDocs) {
await prisma.policyDocument.upsert({
where: { tenantId_code: { tenantId, code: d.code } },
update: { title: d.title, type: d.type, orderIdx: d.orderIdx, rawMarkdown: `# ${d.title}\n\n${d.intro}` },
create: { tenantId, code: d.code, type: d.type, title: d.title, version: "1.0", status: "FREIGEGEBEN", orderIdx: d.orderIdx, rawMarkdown: `# ${d.title}\n\n${d.intro}` },
});
}
// Krypto-Register
for (let i = 0; i < CRYPTO.length; i++) {
const c = CRYPTO[i];
await prisma.cryptoEntry.create({
data: {
tenantId, dienst: c.dienst, schluessel: c.schluessel, algorithmus: c.algorithmus,
ablaufdatum: c.ablaufOffset === null ? null : daysFrom(NOW, c.ablaufOffset),
verantwortlich: c.verantwortlich, speicherort: c.speicherort, baselineRef: c.baselineRef, orderIdx: i,
},
});
}
// Klassifizierung + Handhabungsmatrix
const classIds: string[] = [];
for (let i = 0; i < CLASSES.length; i++) {
const cl = await prisma.classificationClass.create({ data: { tenantId, name: CLASSES[i].name, description: CLASSES[i].description, orderIdx: i } });
classIds.push(cl.id);
}
for (let a = 0; a < ASPECTS.length; a++) {
const asp = await prisma.handlingAspect.create({ data: { tenantId, name: ASPECTS[a].name, category: ASPECTS[a].category, orderIdx: a } });
for (let c = 0; c < classIds.length; c++) {
await prisma.handlingRule.create({ data: { tenantId, classId: classIds[c], aspectId: asp.id, text: ASPECTS[a].rules[c] } });
}
}
// Risiko-Bewertungsmatrix
for (let i = 0; i < RISK_CLASSES.length; i++) {
const r = RISK_CLASSES[i];
await prisma.riskMatrixClass.create({ data: { tenantId, name: r.name, maxScore: r.maxScore, acceptance: r.acceptance, tone: r.tone, orderIdx: i } });
}
for (const ew of EW_LEVELS) await prisma.riskEwLevel.create({ data: { tenantId, level: ew.level, label: ew.label, definition: ew.definition } });
for (let i = 0; i < DAMAGE_DIMS.length; i++) {
await prisma.riskDamageDimension.create({ data: { tenantId, name: DAMAGE_DIMS[i].name, levels: DAMAGE_DIMS[i].levels, orderIdx: i } });
}
// Anwender-Handbuch
for (let i = 0; i < HANDBOOK.length; i++) {
const h = HANDBOOK[i];
await prisma.handbookTopic.create({ data: { tenantId, category: h.category, title: h.title, bodyMd: h.bodyMd, sourceRefs: h.sourceRefs, orderIdx: i } });
}
return { crypto: CRYPTO.length, classes: CLASSES.length, aspects: ASPECTS.length, damage: DAMAGE_DIMS.length, handbook: HANDBOOK.length };
}