Assessor_2.0/src/reporting/management-business-impact.ts

99 lines
7.6 KiB
TypeScript

import type { ReportContextPublication } from './report-context';
import type { FindingRecord } from '../domain/finding';
/** The same Work identities for report exports and the Client Info extension. */
export function managementWorkspaceDeviationKeys(records: readonly FindingRecord[], isDeviation: (record: FindingRecord) => boolean): string[] {
const field = (record: FindingRecord, label: string) => record.fields.find(item => item.label.toLowerCase() === label)?.value?.trim() ?? '';
return records.filter(isDeviation).map(record => record.findingOccurrenceId ? `scanner:${record.findingOccurrenceId}`
: field(record, 'category') === 'GPOs' ? `gpo:${field(record, 'scope').toLowerCase()}\u0000${field(record, 'technical identity')}`
: `unmapped:${record.id}`);
}
/** Report-only interpretation of published categories; no new finding assignment or impact rating. */
export const MANAGEMENT_BUSINESS_IMPACT_VERSION = '2.1.0';
type Cluster = ReportContextPublication['clusters'][number];
interface ManagementTheme {
id: string;
label: string;
question: string;
impact: string;
action: string;
limitation: string;
clusterIds: readonly string[];
}
/**
* Four disjoint themes (user decision 2026-10-04): every problem category belongs to exactly one theme, the
* consequence an organisation loses first. Financial and compliance consequences can follow from every theme;
* they are stated in the report prose, never counted as a theme of their own.
*/
export const managementThemes: readonly ManagementTheme[] = [
{ id: 'control', label: 'Kontrolle & Vertrauen', question: 'Kann die Kontrolle über Systeme und Identitäten verloren gehen?',
impact: 'Die Übernahme oder der Missbrauch von Identitäten könnte unberechtigte Kontrolle über Systeme, Berechtigungen und Vertrauensbeziehungen ermöglichen. Betroffen wäre dann nicht ein einzelnes System, sondern alles, was dieselben Identitätsdienste nutzt.',
action: 'Privilegierte Berechtigungen, Delegation und Authentifizierung anhand der Originalempfehlungen prüfen und administrative Sicherheitsgrenzen fachlich überprüfen.',
limitation: 'Gemeinsame Themen belegen weder eine durchgängige Angriffskette noch eine erfolgreiche Übernahme.',
clusterIds: ['cloud-privileged-access-exposure', 'ad-privileged-account-exposure', 'kerberos-delegation-abuse', 'adcs-escalation',
'ad-persistence-indicators', 'credential-relay-exposure', 'cloud-sign-in-protection-gaps', 'kerberoasting-exposure',
'host-credential-privilege-exposure', 'password-guessing-exposure', 'physical-local-access-exposure'] },
{ id: 'information', label: 'Informationen & Daten', question: 'Sind Unternehmensinformationen gefährdet?',
impact: 'Unberechtigte Zugriffe könnten die Vertraulichkeit von Unternehmensinformationen beeinträchtigen. Die Problemfelder dieses Themas betreffen Wege, auf denen Daten den vorgesehenen Kreis verlassen oder Außenstehende Einblick erhalten.',
action: 'Zugriffsrechte, Freigaben und externe Zugänge anhand der Originalempfehlungen prüfen. Relevante Datenbestände und deren Schutzbedarf fachlich zuordnen.',
limitation: 'Ein tatsächlicher Datenzugriff oder Datenverlust sowie die Sensibilität betroffener Informationen sind damit nicht nachgewiesen.',
clusterIds: ['cloud-external-access-exposure', 'data-sharing-exposure', 'cloud-app-consent-exposure', 'email-spoofing-phishing-exposure', 'ad-reconnaissance-exposure'] },
{ id: 'continuity', label: 'Betriebsfähigkeit', question: 'Kann das Unternehmen weiterarbeiten?',
impact: 'Die Ausbreitung eines Angriffs oder die Ausführung schädlichen Codes könnte die Nutzung abhängiger Systeme und damit Geschäftsabläufe beeinträchtigen oder unterbrechen. Je leichter sich ein Angriff von System zu System bewegt, desto mehr Systeme könnten gleichzeitig betroffen sein.',
action: 'Begrenzung der Schadensausbreitung und Schutz vor schädlichem Code anhand der Originalempfehlungen prüfen. Abhängige geschäftskritische Dienste mit den Fachverantwortlichen bestimmen.',
limitation: 'Betroffene Geschäftsprozesse und mögliche Ausfallzeiten sind aus diesen Befunden nicht bestimmt.',
clusterIds: ['lateral-movement-exposure', 'malicious-code-execution-exposure'] },
{ id: 'resilience', label: 'Widerstandsfähigkeit & Wiederanlauf', question: 'Kann das Unternehmen Auswirkungen begrenzen und sich erholen?',
impact: 'Lücken bei Erkennung, Aktualität oder Wiederherstellung könnten die Reaktion auf einen Vorfall verzögern und die Rückkehr zu einem verlässlichen Betrieb erschweren. Dieses Thema bestimmt weniger, ob ein Vorfall eintritt, als wie früh er bemerkt und wie vollständig er behoben werden kann.',
action: 'Die Originalempfehlungen zu Erkennung, Patch-Stand und Wiederherstellung prüfen. Vorhandene Wiederanlaufverfahren durch geeignete Tests bestätigen.',
limitation: 'Die tatsächliche Wiederherstellbarkeit und Wiederanlaufzeiten sind durch diese Befunde nicht bestätigt.',
clusterIds: ['detection-blind-spots', 'ad-recovery-gaps', 'patch-configuration-drift'] },
];
/** Consequences that can follow from every theme; stated once, never counted. */
export const MANAGEMENT_DOWNSTREAM_CONSEQUENCES = 'Aus jedem Thema können finanzielle Folgen (Aufwand für Untersuchung, Eindämmung und Wiederherstellung, Folgen einer Betriebsunterbrechung) und Compliance-Folgen (Schutz- und Nachweisanforderungen) entstehen. Sie werden nicht als eigenes Thema gezählt.';
const themeOfCluster = new Map(managementThemes.flatMap(theme => theme.clusterIds.map(clusterId => [clusterId, theme.id] as const)));
export function managementBusinessImpacts(context: ReportContextPublication): Array<ManagementTheme & { clusters: Cluster[]; findingCount: number }> {
return managementThemes.map(theme => {
const clusters = context.clusters.filter(item => item.hasEvidence && item.scanner + item.gpo > 0 && theme.clusterIds.includes(item.id));
return { ...theme, clusters, findingCount: new Set(clusters.flatMap(item => item.findingKeys ?? item.resultIds)).size };
});
}
/** A partition, never a sum of possible effects: one finding, one theme. Findings without a category stay unassigned. */
export function managementFindingThemeAssignments(context: ReportContextPublication): Map<string, string> {
const assignments = new Map<string, Set<string>>();
for (const cluster of context.clusters) {
if (!cluster.hasEvidence || cluster.scanner + cluster.gpo === 0) continue;
for (const key of cluster.findingKeys ?? cluster.resultIds) {
const themes = assignments.get(key) ?? new Set<string>();
const theme = themeOfCluster.get(cluster.id);
if (theme) themes.add(theme);
assignments.set(key, themes);
}
}
const primary = new Map<string, string>();
for (const [key, themes] of assignments) {
if (themes.size !== 1) continue;
const theme = [...themes][0];
primary.set(key, theme);
}
return primary;
}
export function managementFindingDistribution(context: ReportContextPublication, total: number, workspaceKeys?: readonly string[]) {
const allowed = workspaceKeys ? new Set(workspaceKeys) : undefined;
const counts = new Map<string, number>();
for (const [key, theme] of managementFindingThemeAssignments(context)) {
if (!allowed || allowed.has(key)) counts.set(theme, (counts.get(theme) ?? 0) + 1);
}
const assigned = [...counts.values()].reduce((sum, count) => sum + count, 0);
if (assigned > total) throw new Error('Theme distribution exceeds the authoritative finding population.');
return { themes: managementThemes.map(theme => ({ id: theme.id, label: theme.label, count: counts.get(theme.id) ?? 0 })),
unassigned: total - assigned, total };
}