import { createHash } from 'node:crypto'; import type { GpoPolicyObservation, GpoPolicyValue } from '../../src/domain/gpo-policy'; import { gpoPolicyObservationValidationErrors } from '../../src/domain/gpo-policy'; import type { GpoPolicyScope } from '../../src/domain/gpo-ingestion'; import { parseCsvLexically } from '../../src/importers/csv-importer'; import type { ImportResourcePolicy } from '../../src/importers/resource-policy'; import { resolveImportResourcePolicy, validateDecodedTextResources } from '../../src/importers/resource-policy'; import { ImportError } from '../../src/shared/errors'; import type { VerifiedZipFile } from './zip-inventory'; export const ADVANCED_AUDIT_PARSER = { id: 'gpo-advanced-audit-csv', version: '1.0.0' } as const; export const ADVANCED_AUDIT_REDUCER = { id: 'gpo-advanced-audit-reducer', version: '1.0.0' } as const; const maximumFileBytes = 4 * 1024 * 1024; const maximumRows = 20_000; const maximumColumns = 32; const maximumCellCharacters = 4_096; const guidPattern = /^\{?([a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12})\}?$/i; export interface AdvancedAuditInstruction { version: 1; subcategoryGuid: string; settingCode: 0 | 1 | 2 | 3; line: number; } export interface AdvancedAuditDocument { version: 1; parser: typeof ADVANCED_AUDIT_PARSER; sourcePath: string; sourceSha256: string; scope: GpoPolicyScope; status: 'complete' | 'partial' | 'invalid'; instructions: AdvancedAuditInstruction[]; diagnosticCodes: string[]; } function parserError(code: string, message: string, details: Record = {}): never { throw new ImportError(message, [{ severity: 'error', code, message, stage: 'format-decoding', details }]); } function decodeAuditCsv(bytes: Uint8Array): string { if (bytes.byteLength > maximumFileBytes) { parserError('gpo-advanced-audit-size-limit', 'Eine Advanced-Audit-Datei überschreitet das Größenlimit.', { sizeBytes: bytes.byteLength }); } let encoding = 'utf-8'; let content = bytes; if (bytes[0] === 0xff && bytes[1] === 0xfe) { encoding = 'utf-16le'; content = bytes.subarray(2); } else if (bytes[0] === 0xfe && bytes[1] === 0xff) { encoding = 'utf-16be'; content = bytes.subarray(2); } else if (bytes[0] === 0xef && bytes[1] === 0xbb && bytes[2] === 0xbf) content = bytes.subarray(3); try { return new TextDecoder(encoding, { fatal: true }).decode(content); } catch { parserError('gpo-advanced-audit-encoding-rejected', 'Die Advanced-Audit-Datei ist nicht eindeutig als UTF-8 oder BOM-markiertes UTF-16 dekodierbar.'); } } function canonicalHeader(value: string): string { return value.trim().toLocaleLowerCase('en-US').replace(/\s+/g, ' '); } function canonicalGuid(value: string): string | undefined { const guid = guidPattern.exec(value.trim())?.[1]?.toLocaleLowerCase('en-US'); return guid && guid !== '00000000-0000-0000-0000-000000000000' ? guid : undefined; } export function parseAdvancedAuditCsv( file: VerifiedZipFile, scope: GpoPolicyScope, resourceOverrides?: Partial ): AdvancedAuditDocument { const text = decodeAuditCsv(file.bytes); const policy = resolveImportResourcePolicy({ ...resourceOverrides, maximumDecodedCharacters: Math.min(resourceOverrides?.maximumDecodedCharacters ?? maximumFileBytes, maximumFileBytes) }); validateDecodedTextResources(text, 'csv', policy); const parsed = parseCsvLexically(text, ','); const diagnostics = new Set(); let invalid = parsed.unclosedQuote || parsed.rows.length === 0; if (parsed.unclosedQuote) diagnostics.add('gpo-advanced-audit-csv-unclosed-quote'); if (!parsed.rows.length) diagnostics.add('gpo-advanced-audit-header-missing'); if (parsed.rows.length > maximumRows) parserError('gpo-advanced-audit-row-limit', 'Die Advanced-Audit-Datei überschreitet das Zeilenlimit.', { rows: parsed.rows.length }); const header = parsed.rows[0] ?? []; if (header.length > maximumColumns) parserError('gpo-advanced-audit-column-limit', 'Die Advanced-Audit-Datei überschreitet das Spaltenlimit.', { columns: header.length }); const headerMap = new Map(); header.forEach((value, index) => { const key = canonicalHeader(value); if (!key || headerMap.has(key)) { diagnostics.add('gpo-advanced-audit-header-invalid'); invalid = true; } else headerMap.set(key, index); }); const guidColumn = headerMap.get('subcategory guid'); const settingColumn = headerMap.get('setting value'); const targetColumn = headerMap.get('policy target'); if (guidColumn === undefined || settingColumn === undefined || targetColumn === undefined) { diagnostics.add('gpo-advanced-audit-required-column-missing'); invalid = true; } const instructions: AdvancedAuditInstruction[] = []; let partial = scope !== 'computer'; if (scope !== 'computer') diagnostics.add('gpo-advanced-audit-scope-unsupported'); if (!invalid) { for (let rowIndex = 1; rowIndex < parsed.rows.length; rowIndex += 1) { const row = parsed.rows[rowIndex]; if (row.length !== header.length || row.length > maximumColumns || row.some(cell => cell.length > maximumCellCharacters || cell.includes('\n') || cell.includes('\r'))) { diagnostics.add('gpo-advanced-audit-row-invalid'); invalid = true; continue; } const target = row[targetColumn!].trim().toLocaleLowerCase('en-US'); if (target !== 'system') { diagnostics.add('gpo-advanced-audit-target-unsupported'); partial = true; continue; } const subcategoryGuid = canonicalGuid(row[guidColumn!]); const setting = row[settingColumn!].trim(); if (!subcategoryGuid || !/^[0-3]$/.test(setting)) { diagnostics.add('gpo-advanced-audit-instruction-invalid'); invalid = true; continue; } instructions.push({ version: 1, subcategoryGuid, settingCode: Number(setting) as 0 | 1 | 2 | 3, line: parsed.rowStartLines[rowIndex] }); } } return { version: 1, parser: ADVANCED_AUDIT_PARSER, sourcePath: file.entry.relativePath, sourceSha256: file.entry.sha256, scope, status: invalid ? 'invalid' : partial ? 'partial' : 'complete', instructions: invalid ? [] : instructions, diagnosticCodes: [...diagnostics].sort() }; } function auditValue(code: 0 | 1 | 2 | 3): GpoPolicyValue { return { kind: 'audit-flags', code, success: Boolean(code & 1), failure: Boolean(code & 2) }; } export function reduceAdvancedAuditPolicy( document: AdvancedAuditDocument, context: { backupInstanceId: string; sourceArtifactId: string; artifactId: string } ): GpoPolicyObservation[] { if (document.status === 'invalid' || document.scope !== 'computer') return []; const groups = new Map(); for (const instruction of document.instructions) { groups.set(instruction.subcategoryGuid, [...(groups.get(instruction.subcategoryGuid) ?? []), instruction]); } return [...groups.values()].map(instructions => { const first = instructions[0]; const distinctCodes = [...new Set(instructions.map(instruction => instruction.settingCode))].sort(); const conflicted = distinctCodes.length > 1; const id = `gpo-policy-${createHash('sha256').update([ context.backupInstanceId, document.sourceSha256, first.subcategoryGuid, ADVANCED_AUDIT_REDUCER.version ].join('\0')).digest('hex').slice(0, 32)}`; const observation: GpoPolicyObservation = { version: 1, id, backupInstanceId: context.backupInstanceId, sourceArtifactId: context.sourceArtifactId, artifactId: context.artifactId, sourcePath: document.sourcePath, sourceSha256: document.sourceSha256, scope: 'computer', deliveryMechanism: 'advanced-audit-policy', parser: document.parser, reducer: ADVANCED_AUDIT_REDUCER, identity: { status: 'unresolved', namespace: 'advanced-audit-policy', section: 'system audit subcategory', key: first.subcategoryGuid }, state: conflicted ? 'conflicted' : 'configured', values: distinctCodes.map(code => auditValue(code as 0 | 1 | 2 | 3)), sourceLines: instructions.map(instruction => instruction.line), effectiveState: 'unknown', diagnosticCodes: conflicted ? ['gpo-advanced-audit-value-conflict'] : [] }; const errors = gpoPolicyObservationValidationErrors(observation); if (errors.length) throw new Error(`Intern ungültige Advanced-Audit-Beobachtung: ${errors.join(' ')}`); return observation; }); }