import { deflateRawSync } from 'node:zlib'; import { describe, expect, it } from 'vitest'; import { ingestGpoBackupContainer } from '../../server/gpo/gpo-backup-ingestion'; import { gpoBackupCompleteness } from '../../src/domain/gpo-ingestion'; import { buildGpoAnalysisSnapshots } from '../../src/gpo/snapshot-builder'; import { ImportError } from '../../src/shared/errors'; import { createGpoFolderTransport } from '../../src/gpo/gpo-folder-transport'; interface Entry { path: string; content: string | Uint8Array; } function crc32(bytes: Uint8Array): number { let value = 0xffffffff; for (const byte of bytes) { value ^= byte; for (let bit = 0; bit < 8; bit += 1) value = (value >>> 1) ^ (0xedb88320 & -(value & 1)); } return (value ^ 0xffffffff) >>> 0; } function zip(entries: Entry[]): Uint8Array { const localParts: Buffer[] = []; const centralParts: Buffer[] = []; let localOffset = 0; for (const entry of entries) { const name = Buffer.from(entry.path); const content = typeof entry.content === 'string' ? Buffer.from(entry.content) : Buffer.from(entry.content); const compressed = deflateRawSync(content); const checksum = crc32(content); const local = Buffer.alloc(30 + name.length); local.writeUInt32LE(0x04034b50, 0); local.writeUInt16LE(20, 4); local.writeUInt16LE(0x0800, 6); local.writeUInt16LE(8, 8); local.writeUInt32LE(checksum, 14); local.writeUInt32LE(compressed.length, 18); local.writeUInt32LE(content.length, 22); local.writeUInt16LE(name.length, 26); name.copy(local, 30); localParts.push(local, compressed); const central = Buffer.alloc(46 + name.length); central.writeUInt32LE(0x02014b50, 0); central.writeUInt16LE(20, 4); central.writeUInt16LE(20, 6); central.writeUInt16LE(0x0800, 8); central.writeUInt16LE(8, 10); central.writeUInt32LE(checksum, 16); central.writeUInt32LE(compressed.length, 20); central.writeUInt32LE(content.length, 24); central.writeUInt16LE(name.length, 28); central.writeUInt32LE(localOffset, 42); name.copy(central, 46); centralParts.push(central); localOffset += local.length + compressed.length; } const directory = Buffer.concat(centralParts); const end = Buffer.alloc(22); end.writeUInt32LE(0x06054b50, 0); end.writeUInt16LE(entries.length, 8); end.writeUInt16LE(entries.length, 10); end.writeUInt32LE(directory.length, 12); end.writeUInt32LE(localOffset, 16); return Buffer.concat([...localParts, directory, end]); } const discoveredAt = '2026-07-20T12:00:00.000Z'; const gpoId = '11111111-1111-4111-8111-111111111111'; const backupId = '22222222-2222-4222-8222-222222222222'; function metadata(gpo = gpoId, backup = backupId, extra = ''): string { return `{${backup}}{${gpo}}synthetic.example2026-07-19T10:30:00Z${extra}`; } function ingest(bytes: Uint8Array) { return ingestGpoBackupContainer(bytes, { projectId: 'project-synthetic', sourceArtifactId: 'artifact-synthetic', discoveredAt }); } describe('GPO backup metadata ingestion', () => { it('processes a 2300-GPO folder and ZIP including metadata larger than 2 MiB', async () => { let seed = 31; const noise = Array.from({ length: 8192 }, () => { seed = (Math.imul(seed, 1664525) + 1013904223) >>> 0; return String.fromCharCode(65 + (seed >>> 16) % 26); }).join(''); const entries: Entry[] = []; for (let index = 1; index <= 2300; index++) { const suffix = index.toString(16).padStart(12, '0'); entries.push({ path: `backup-${index}/Backup.xml`, content: metadata( `00000000-0000-4000-8000-${suffix}`, `11111111-1111-4111-8111-${suffix}`, `
${index === 1 ? noise.repeat(270) : noise}
` ) }); entries.push({ path: `backup-${index}/DomainSysvol/GPO/Machine/Registry.pol`, content: new Uint8Array([80, 82, 101, 103, 1, 0, 0, 0]) }); } // Folder transport preserves the same bytes and goes through the ZIP ingestion boundary. const sources = entries.map(entry => { const bytes = typeof entry.content === 'string' ? new TextEncoder().encode(entry.content) : entry.content; return { relativePath: entry.path, sizeBytes: bytes.length, readBytes: async () => bytes }; }); const folder = await createGpoFolderTransport(sources); expect(folder.sourceBytes).toBeGreaterThan(16 * 1024 * 1024); for (const bytes of [folder.bytes, zip(entries)]) { const result = ingestGpoBackupContainer(bytes, { projectId: 'synthetic-load', sourceArtifactId: 'synthetic-load-artifact', discoveredAt, retainIntermediateDocuments: false }); expect(result.backupInstances).toHaveLength(2300); expect(result.backupInstances.every(instance => instance.identity.status === 'resolved')).toBe(true); } }, 60_000); it('derives technical identity from XML content and keeps set/effectiveness unknown', () => { const root = 'folder-name-is-not-an-identity'; const result = ingest(zip([ { path: `${root}/Backup.xml`, content: metadata() }, { path: `${root}/DomainSysvol/GPO/Machine/Registry.pol`, content: new Uint8Array([80, 82, 101, 103, 1, 0, 0, 0]) } ])); expect(result.backupInstances).toHaveLength(1); expect(result.metadataDocuments[0]).toMatchObject({ status: 'complete', secretIndicatorStatus: 'not-detected' }); expect(result.backupInstances[0]).toMatchObject({ identity: { status: 'resolved', gpoId, backupId, evidence: 'backup-metadata' }, domain: { status: 'resolved', key: 'dns:synthetic.example', evidence: 'backup-metadata' }, collection: { status: 'observed', collectedAt: discoveredAt, evidence: 'backend-receipt' }, backupSet: { id: 'gpo-backup-set:artifact-synthetic', scope: 'selected-gpos', evidence: 'user-selection' }, backupCreatedAt: '2026-07-19T10:30:00.000Z', metadataStatus: 'complete', sections: { computer: 'present', user: 'unknown' } }); expect(gpoBackupCompleteness(result.backupInstances[0])).toBe('unknown'); }); it('keeps GPMC local backup times unresolved without discarding technical identity', () => { const localTimeMetadata = metadata().replace('2026-07-19T10:30:00Z', '2026-07-19T10:30:00'); const result = ingest(zip([ { path: 'one/bkupInfo.xml', content: localTimeMetadata }, { path: 'one/DomainSysvol/GPO/Machine/Registry.pol', content: new Uint8Array([80, 82, 101, 103, 1, 0, 0, 0]) } ])); expect(result.metadataDocuments[0]).toMatchObject({ status: 'partial', gpoId, backupId, domainKey: 'dns:synthetic.example', backupCreatedAt: undefined }); expect(result.metadataDocuments[0].diagnosticCodes).toContain('gpo-metadata-backup-time-zone-missing'); expect(result.backupInstances[0]).toMatchObject({ identity: { status: 'resolved', gpoId, backupId }, domain: { status: 'resolved', key: 'dns:synthetic.example' }, backupCreatedAt: undefined, metadataStatus: 'partial', inventory: { status: 'complete' } }); }); it('parses Security Templates into versioned observations without claiming effective application', () => { const path = 'one/DomainSysvol/GPO/Machine/Microsoft/Windows NT/SecEdit/GptTmpl.inf'; const result = ingest(zip([ { path: 'one/Backup.xml', content: metadata() }, { path, content: '[Unicode]\r\nUnicode=yes\r\n[System Access]\r\nMinimumPasswordLength=14\r\n[Version]\r\nsignature="$CHICAGO$"' } ])); expect(result.securityTemplateDocuments).toMatchObject([{ status: 'complete', sourcePath: path }]); expect(result.backupInstances[0].inventory.entries).toEqual(expect.arrayContaining([ expect.objectContaining({ relativePath: path, role: 'security-template', disposition: 'supported', secretIndicatorStatus: 'not-inspected' }) ])); expect(result.backupInstances[0].policyObservations).toEqual([ expect.objectContaining({ deliveryMechanism: 'security-template', state: 'configured', identity: expect.objectContaining({ section: 'System Access', key: 'MinimumPasswordLength' }), values: [{ kind: 'scalar', raw: '14' }], effectiveState: 'unknown' }) ]); }); it('keeps identical policy files at different paths as distinct observations', () => { const content = '[System Access]\r\nMinimumPasswordLength=14'; const result = ingest(zip([ { path: 'one/Backup.xml', content: metadata() }, { path: 'one/DomainSysvol/GPO/Machine/Microsoft/Windows NT/SecEdit/GptTmpl.inf', content }, { path: 'one/DomainSysvol/GPO/User/Microsoft/Windows NT/SecEdit/GptTmpl.inf', content } ])); const observations = result.backupInstances[0].policyObservations ?? []; expect(observations).toHaveLength(2); expect(new Set(observations.map(item => item.id))).toHaveLength(2); expect(new Set(observations.map(item => item.artifactId))).toHaveLength(2); }); it('releases parser intermediates on the persistence worker path', () => { const path = 'one/DomainSysvol/GPO/Machine/Microsoft/Windows NT/SecEdit/GptTmpl.inf'; const result = ingestGpoBackupContainer(zip([ { path: 'one/Backup.xml', content: metadata() }, { path, content: '[System Access]\r\nMinimumPasswordLength=14' } ]), { projectId: 'project-synthetic', sourceArtifactId: 'artifact-synthetic', discoveredAt, retainIntermediateDocuments: false }); expect(result).toMatchObject({ metadataDocuments: [], securityTemplateDocuments: [], advancedAuditDocuments: [], preferenceDocuments: [] }); expect(result.backupInstances[0].policyObservations).toEqual([ expect.objectContaining({ deliveryMechanism: 'security-template', sourcePath: path }) ]); }); it('retains valid Security Template observations with visible partial inventory diagnostics', () => { const path = 'one/DomainSysvol/GPO/Machine/Microsoft/Windows NT/SecEdit/GptTmpl.inf'; const result = ingest(zip([ { path: 'one/Backup.xml', content: metadata() }, { path, content: '[System Access]\r\nMinimumPasswordLength=14\r\n[Registry Values]\r\nMACHINE\\Synthetic=not-a-type' } ])); expect(result.securityTemplateDocuments).toMatchObject([{ status: 'partial', instructions: [expect.objectContaining({ key: 'MinimumPasswordLength' })], diagnosticCodes: ['gpo-security-template-registry-value-invalid'] }]); expect(result.backupInstances[0]).toMatchObject({ inventory: { status: 'complete', diagnosticCodes: expect.arrayContaining(['gpo-security-template-registry-value-invalid']), entries: expect.arrayContaining([expect.objectContaining({ relativePath: path, disposition: 'inventory-only' })]) }, policyObservations: [expect.objectContaining({ sourceLines: [2], values: [{ kind: 'scalar', raw: '14' }] })] }); expect(gpoBackupCompleteness(result.backupInstances[0])).toBe('partial'); }); it('blocks malformed Advanced Audit Policy without salvaging valid-looking rows', () => { const path = 'one/DomainSysvol/GPO/Machine/Microsoft/Windows NT/Audit/audit.csv'; const content = [ 'Machine Name,Policy Target,Subcategory,Subcategory GUID,Inclusion Setting,Exclusion Setting,Setting Value', ',System,ignored,{0cce9210-69ae-11d9-bed3-505054503030},Success,,1', ',System,ignored,not-a-guid,Failure,,2' ].join('\r\n'); const result = ingest(zip([ { path: 'one/Backup.xml', content: metadata() }, { path, content } ])); expect(result.advancedAuditDocuments).toMatchObject([{ status: 'invalid', instructions: [], diagnosticCodes: ['gpo-advanced-audit-instruction-invalid'] }]); expect(result.backupInstances[0]).toMatchObject({ sections: { computer: 'unreadable' }, inventory: { status: 'blocked' }, policyObservations: [] }); }); it('separates multiple backup roots by metadata content rather than folder GUIDs', () => { const secondGpo = '33333333-3333-4333-8333-333333333333'; const secondBackup = '44444444-4444-4444-8444-444444444444'; const result = ingest(zip([ { path: 'first/Backup.xml', content: metadata() }, { path: 'first/DomainSysvol/GPO/gpt.ini', content: '[General]' }, { path: 'second/Backup.xml', content: metadata(secondGpo, secondBackup) }, { path: 'second/DomainSysvol/GPO/gpt.ini', content: '[General]' } ])); expect(result.backupInstances).toHaveLength(2); expect(result.backupInstances.map(instance => instance.identity)).toEqual(expect.arrayContaining([ expect.objectContaining({ gpoId, backupId }), expect.objectContaining({ gpoId: secondGpo, backupId: secondBackup }) ])); expect(new Set(result.backupInstances.map(instance => instance.backupSet.id))).toEqual(new Set(['gpo-backup-set:artifact-synthetic'])); expect(result.backupInstances.every(instance => instance.backupSet.scope === 'selected-gpos' && instance.backupSet.evidence === 'user-selection')).toBe(true); const snapshots = buildGpoAnalysisSnapshots(result.backupInstances, { createdAt: discoveredAt }); expect(snapshots).toHaveLength(1); expect(snapshots[0]).toMatchObject({ backupSetId: 'gpo-backup-set:artifact-synthetic', completeness: 'selected-subset', instanceIds: expect.arrayContaining(result.backupInstances.map(instance => instance.id)), permitsSetAbsenceConclusions: false }); }); it('blocks conflicting technical metadata instead of choosing one value', () => { const conflictingGpo = '33333333-3333-4333-8333-333333333333'; const result = ingest(zip([ { path: 'one/Backup.xml', content: metadata() }, { path: 'one/bkupInfo.xml', content: metadata(conflictingGpo) }, { path: 'one/DomainSysvol/GPO/gpt.ini', content: '[General]' } ])); expect(result.backupInstances[0]).toMatchObject({ identity: { status: 'unresolved', reasonCode: 'metadata-conflict' }, metadataStatus: 'unreadable', inventory: { status: 'blocked' } }); expect(result.backupInstances[0].inventory.diagnosticCodes).toContain('gpo-metadata-gpoId-conflict'); }); it('detects but never returns a GPP-style secret value from metadata XML', () => { const result = ingest(zip([ { path: 'one/Backup.xml', content: metadata(gpoId, backupId, '') }, { path: 'one/DomainSysvol/GPO/gpt.ini', content: '[General]' } ])); expect(result.metadataDocuments[0].secretIndicatorStatus).toBe('present'); expect(result.metadataDocuments[0].diagnosticCodes).toContain('gpo-secret-indicator-present'); expect(JSON.stringify(result)).not.toContain('synthetic-secret'); }); it('does not combine metadata and SYSVOL evidence from different backup roots', () => { const result = ingest(zip([ { path: 'metadata-only/Backup.xml', content: metadata() }, { path: 'different-root/DomainSysvol/GPO/gpt.ini', content: '[General]' } ])); expect(result.container.recognition.status).toBe('unrecognized-zip'); expect(result.backupInstances).toEqual([]); }); it('blocks ambiguous domain and non-ISO backup time fields', () => { const ambiguous = `{${backupId}}{${gpoId}}invalid_domain07/19/2026 10:30`; const result = ingest(zip([ { path: 'one/Backup.xml', content: ambiguous }, { path: 'one/DomainSysvol/GPO/gpt.ini', content: '[General]' } ])); expect(result.metadataDocuments[0]).toMatchObject({ status: 'invalid', domainKey: undefined, backupCreatedAt: undefined }); expect(result.backupInstances[0]).toMatchObject({ metadataStatus: 'unreadable', inventory: { status: 'blocked' } }); }); it('does not salvage valid identity, domain or time values beside malformed recognized values', () => { const mixed = `{${backupId}}not-a-guid{${gpoId}}not-a-guidsynthetic.exampleinvalid_domain2026-07-19T10:30:00Zambiguous`; const result = ingest(zip([ { path: 'one/Backup.xml', content: mixed }, { path: 'one/DomainSysvol/GPO/gpt.ini', content: '[General]' } ])); expect(result.metadataDocuments[0]).toMatchObject({ status: 'invalid', gpoId: undefined, backupId: undefined, domainKey: undefined, backupCreatedAt: undefined }); expect(result.backupInstances[0]).toMatchObject({ identity: { status: 'unresolved', reasonCode: 'metadata-conflict' }, domain: { status: 'unknown' }, backupCreatedAt: undefined, metadataStatus: 'unreadable', inventory: { status: 'blocked' } }); }); it('rejects XML document types and external-reference mechanisms before DOM processing', () => { const unsafe = ']>&e;'; try { ingest(zip([ { path: 'one/Backup.xml', content: unsafe }, { path: 'one/DomainSysvol/GPO/gpt.ini', content: '[General]' } ])); throw new Error('Erwartete Ablehnung blieb aus.'); } catch (error) { expect(error).toBeInstanceOf(ImportError); expect((error as ImportError).diagnostics[0]?.code).toBe('xml-doctype-rejected'); } }); }); describe('GPO backup link facts', () => { it('retains per-backup link counts from its own GPReport and leaves backups without a report unknown', () => { const second = '33333333-3333-4333-8333-333333333333'; const secondBackup = '44444444-4444-4444-8444-444444444444'; const report = (links: string) => `Synthetic${links}`; const template = (root: string) => ({ path: `${root}/DomainSysvol/GPO/Machine/Microsoft/Windows NT/SecEdit/GptTmpl.inf`, content: '[System Access]\r\nMinimumPasswordLength=14' }); const result = ingest(zip([ { path: `{${backupId}}/bkupInfo.xml`, content: metadata() }, { path: `{${backupId}}/gpreport.xml`, content: report('') }, template(`{${backupId}}`), { path: `{${secondBackup}}/bkupInfo.xml`, content: metadata(second, secondBackup) }, template(`{${secondBackup}}`) ])); const byBackup = new Map(result.backupInstances.map(instance => [instance.identity.status === 'resolved' ? instance.identity.backupId : '', instance])); expect(byBackup.get(backupId)?.links).toEqual({ status: 'not-linked', linkCount: 0, enabledLinkCount: 0, evidence: 'gp-report' }); expect(byBackup.get(secondBackup)?.links).toBeUndefined(); const linked = ingest(zip([ { path: `{${backupId}}/bkupInfo.xml`, content: metadata() }, { path: `{${backupId}}/gpreport.xml`, content: report('synthetic.example/Synthetictrue') }, template(`{${backupId}}`) ])); expect(linked.backupInstances[0].links).toEqual({ status: 'linked', linkCount: 1, enabledLinkCount: 1, evidence: 'gp-report' }); expect(JSON.stringify(linked.backupInstances[0])).not.toContain('synthetic.example/Synthetic'); }); });