Assessor_2.0/src/exports/result-db-copy.ts

119 lines
4.5 KiB
TypeScript

import type { UnifiedResultProjection } from '../domain/unified-result';
const SAFE_ENVIRONMENT_PROPERTIES = new Set([
'domain.controller_count',
'domain.functional_level',
'domain.recycle_bin_enabled',
'forest.functional_level',
'logging.wef.configuration_count',
'operating_system.count',
'operating_system.name'
]);
export function privacySafeResultJsonFileName(generatedAt: string): string {
const timestamp = generatedAt.replace(/\.\d{3}Z$/, 'Z').replaceAll(':', '-');
return `assessor-result-db-privacy-safe-${timestamp}.json`;
}
export function resultsToPrivacySafeExportJson(items: UnifiedResultProjection[]): string {
const findings = items.filter(item => item.sourceKind !== 'environment-observation');
const environment = items.filter(item => item.sourceKind === 'environment-observation');
const actionable = findings.filter(item =>
item.isDeviation || item.isIoe || ['fail', 'warning', 'execution_error'].includes(item.outcomeNormalized));
const notRun = findings.filter(item => item.outcomeNormalized === 'not_run');
return JSON.stringify({
schemaVersion: 1,
summary: {
totalResults: items.length,
findingResults: findings.length,
environmentResults: environment.length,
actionableFindings: actionable.length,
notRunChecks: notRun.length,
findingOutcomes: countBy(findings, item => item.outcomeNormalized),
findingSeverities: countBy(findings, item => item.severityNormalized),
findingTools: countBy(findings, item => item.sourceTool ?? 'unknown'),
environmentStates: countBy(environment, item => item.environmentState ?? 'unknown')
},
findings: actionable.map(item => {
const description = copiedText(item.description);
const recommendation = copiedText(item.recommendation);
return {
id: item.sourceRuleId ?? null,
title: privacySafeText(item.title),
...(description ? { description } : {}),
...(recommendation ? { recommendation } : {}),
severity: item.severityNormalized,
outcome: item.outcomeNormalized,
resultType: item.resultType,
isIoe: item.isIoe,
deviation: item.isDeviation,
tool: item.sourceTool ?? 'unknown'
};
}),
notRunChecks: notRun.map(item => ({
id: item.sourceRuleId ?? null,
title: privacySafeText(item.title),
severity: item.severityNormalized,
resultType: item.resultType,
tool: item.sourceTool ?? 'unknown'
})),
environmentFacts: groupedEnvironmentFacts(environment)
}, null, 2);
}
function groupedEnvironmentFacts(items: UnifiedResultProjection[]) {
const grouped = new Map<string, {
title: string;
property: string;
value: unknown;
state: string;
tool: string;
count: number;
}>();
items.forEach(item => {
const property = copiedEnvironmentProperty(item);
if (!property) return;
const fact = {
title: privacySafeText(item.title),
property,
value: item.environmentNormalizedValue ?? null,
state: item.environmentState ?? 'unknown',
tool: item.sourceTool ?? 'unknown'
};
const key = JSON.stringify(fact);
const existing = grouped.get(key);
if (existing) existing.count += 1;
else grouped.set(key, { ...fact, count: 1 });
});
return [...grouped.values()];
}
function countBy(items: UnifiedResultProjection[], valueOf: (item: UnifiedResultProjection) => string): Record<string, number> {
const counts: Record<string, number> = {};
items.forEach(item => {
const value = valueOf(item);
counts[value] = (counts[value] ?? 0) + 1;
});
return counts;
}
function copiedText(value: string | undefined): string | undefined {
const trimmed = value?.trim();
return !trimmed || trimmed.toLocaleLowerCase('en-US') === 'none' ? undefined : privacySafeText(trimmed);
}
function privacySafeText(value: string): string {
return value.replace(/\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/gi, '[redacted-email]');
}
function copiedEnvironmentProperty(item: UnifiedResultProjection): string | undefined {
if (item.sourceKind !== 'environment-observation' || !item.environmentProperty) return undefined;
if (SAFE_ENVIRONMENT_PROPERTIES.has(item.environmentProperty) || item.environmentProperty.endsWith('.row_count')) {
return item.environmentProperty;
}
const aggregateAccountValue = item.environmentValueType === 'number'
&& ['user-information.account-analysis', 'computer-information.account-analysis'].includes(item.environmentSubjectType ?? '');
return aggregateAccountValue ? item.environmentProperty : undefined;
}