Risiko-Minderung dezimal + editierbar, Bedrohungs-/Schwachstellen-Kataloge

- Minderung je Maßnahmen-Verknüpfung jetzt dezimal (0,00–4,00 statt
  ganzer Punkte): mehrere Maßnahmen summieren sich, bis eine Dimension
  einen vollen Punkt sinkt; Rest-Risiko entsprechend dezimal (z. B.
  2,5 × 4 = 10), gerundet auf 2 Nachkommastellen, lokalisierte Anzeige
- Minderung bereits verknüpfter Maßnahmen direkt im Risiko-Bearbeiten
  editierbar (Eingabefelder je Verknüpfung + Speichern-Button);
  Komma-Eingaben werden akzeptiert
- Globale Kataloge Bedrohungen/Schwachstellen (je 15 Einträge, Seed):
  Eingabefelder schlagen per datalist Katalogwerte vor, Freitext bleibt
  weiterhin möglich (SPEC §5 Threat/Vulnerability)
- Schema: reduction*/residual* auf Float, Threat-/Vulnerability-Modelle

Verifiziert: −1 + −0,5 Minderung → Rest 2,5 × 4 = 10 · Erhöht (DB +
Anzeige), Neuberechnung auch beim Entfernen einer Verknüpfung.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Martin
2026-07-02 16:55:27 +02:00
co-authored by Claude Fable 5
parent 668e587881
commit dfe27de51f
10 changed files with 274 additions and 70 deletions
@@ -0,0 +1,32 @@
-- AlterTable
ALTER TABLE "risk_measures" ALTER COLUMN "reduction_likelihood" SET DEFAULT 0,
ALTER COLUMN "reduction_likelihood" SET DATA TYPE DOUBLE PRECISION,
ALTER COLUMN "reduction_impact" SET DEFAULT 0,
ALTER COLUMN "reduction_impact" SET DATA TYPE DOUBLE PRECISION;
-- AlterTable
ALTER TABLE "risks" ALTER COLUMN "residual_likelihood" SET DATA TYPE DOUBLE PRECISION,
ALTER COLUMN "residual_impact" SET DATA TYPE DOUBLE PRECISION,
ALTER COLUMN "residual_score" SET DATA TYPE DOUBLE PRECISION;
-- CreateTable
CREATE TABLE "threats" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
CONSTRAINT "threats_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "vulnerabilities" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
CONSTRAINT "vulnerabilities_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "threats_name_key" ON "threats"("name");
-- CreateIndex
CREATE UNIQUE INDEX "vulnerabilities_name_key" ON "vulnerabilities"("name");
+25 -7
View File
@@ -279,10 +279,10 @@ model Risk {
impact Int @default(1)
score Int @default(1) // redundant für Listen/Heatmap
// Rest-Risiko nach Maßnahmen
residualLikelihood Int? @map("residual_likelihood")
residualImpact Int? @map("residual_impact")
residualScore Int? @map("residual_score")
// Rest-Risiko nach Maßnahmen (berechnet, dezimal wegen Teil-Minderungen)
residualLikelihood Float? @map("residual_likelihood")
residualImpact Float? @map("residual_impact")
residualScore Float? @map("residual_score")
treatment RiskTreatment @default(MITIGATE)
status RiskStatus @default(OPEN)
@@ -366,9 +366,10 @@ model RiskMeasure {
riskId String @map("risk_id")
measureId String @map("measure_id")
// Minderung in Stufen (04) je Dimension
reductionLikelihood Int @default(0) @map("reduction_likelihood")
reductionImpact Int @default(0) @map("reduction_impact")
// Minderung je Dimension (0,004,00) — mehrere Maßnahmen summieren sich,
// bis ein voller Punkt (oder mehr) erreicht ist
reductionLikelihood Float @default(0) @map("reduction_likelihood")
reductionImpact Float @default(0) @map("reduction_impact")
risk Risk @relation(fields: [riskId], references: [id], onDelete: Cascade)
measure Measure @relation(fields: [measureId], references: [id], onDelete: Cascade)
@@ -378,6 +379,23 @@ model RiskMeasure {
@@map("risk_measures")
}
// Globale Kataloge (SPEC §5: Threat/Vulnerability, mandantenübergreifend).
// Risiken speichern weiterhin Freitext — der Katalog liefert Vorschläge (datalist),
// eigene Formulierungen bleiben möglich.
model Threat {
id String @id @default(cuid())
name String @unique
@@map("threats")
}
model Vulnerability {
id String @id @default(cuid())
name String @unique
@@map("vulnerabilities")
}
model AuditLog {
id String @id @default(cuid())
tenantId String @map("tenant_id")
+45
View File
@@ -15,7 +15,52 @@ const prisma = new PrismaClient({
const DEMO_PASSWORD = "Demo1234!";
const THREATS = [
"Schadsoftware / Ransomware",
"Phishing / Social Engineering",
"Innentäter / Missbrauch von Berechtigungen",
"Diebstahl oder Verlust von Geräten",
"Ausfall von IT-Systemen oder Diensten",
"Ausfall eines Dienstleisters / Lieferanten",
"Stromausfall / Infrastrukturausfall",
"Feuer / Wasser / Elementarschäden",
"Unbefugter physischer Zutritt",
"Denial-of-Service-Angriff",
"Datenabfluss / Industriespionage",
"Fehlbedienung durch Mitarbeitende",
"Softwarefehler / fehlerhafte Updates",
"Kompromittierte Zugangsdaten",
"Rechtliche / regulatorische Verstöße",
];
const VULNERABILITIES = [
"Fehlende oder veraltete Backups",
"Fehlende Netzwerksegmentierung",
"Unzureichendes Patch-Management",
"Schwache oder wiederverwendete Passwörter",
"Fehlende Multi-Faktor-Authentifizierung",
"Übermäßige Berechtigungen / fehlendes Least-Privilege",
"Fehlende Awareness / Schulungen",
"Unverschlüsselte Datenträger oder Übertragungen",
"Kein Notfallkonzept / ungetesteter Wiederanlauf",
"Single Point of Failure (Technik oder Person)",
"Unzureichende Protokollierung / Überwachung",
"Veraltete oder nicht mehr unterstützte Software",
"Fehlende Vertrags-/AV-Regelungen mit Dienstleistern",
"Offene, ungenutzte Dienste und Ports",
"Unklare Verantwortlichkeiten",
];
async function main() {
// 0. Globale Kataloge Bedrohungen/Schwachstellen (Vorschläge, Freitext bleibt möglich)
for (const name of THREATS) {
await prisma.threat.upsert({ where: { name }, update: {}, create: { name } });
}
for (const name of VULNERABILITIES) {
await prisma.vulnerability.upsert({ where: { name }, update: {}, create: { name } });
}
console.log(`✔ Kataloge: ${THREATS.length} Bedrohungen, ${VULNERABILITIES.length} Schwachstellen`);
// 1. Global permission catalog
for (const key of PERMISSIONS) {
await prisma.permission.upsert({ where: { key }, update: {}, create: { key } });