import { createHash } from 'node:crypto'; import { describe, expect, it } from 'vitest'; import { parseRegistryPolicyFile, reduceRegistryPolicy } from '../../server/gpo/registry-policy-parser'; import type { VerifiedZipFile } from '../../server/gpo/zip-inventory'; type RecordInput = { key: string; valueName: string; typeCode: number; data: Uint8Array }; function utf16(value: string): Buffer { return Buffer.from(value, 'utf16le'); } function stringData(value: string): Buffer { return Buffer.concat([utf16(value), Buffer.alloc(2)]); } function multiStringData(values: string[]): Buffer { return Buffer.concat([utf16(values.join('\0')), Buffer.alloc(4)]); } function dword(value: number, bigEndian = false): Buffer { const bytes = Buffer.alloc(4); if (bigEndian) bytes.writeUInt32BE(value); else bytes.writeUInt32LE(value); return bytes; } function qword(value: bigint): Buffer { const bytes = Buffer.alloc(8); bytes.writeBigUInt64LE(value); return bytes; } function policyFile(records: RecordInput[], version = 1): Uint8Array { const header = Buffer.alloc(8); header.write('PReg', 0, 'ascii'); header.writeUInt32LE(version, 4); const encoded = records.map(record => { const type = Buffer.alloc(4); type.writeUInt32LE(record.typeCode); const size = Buffer.alloc(4); size.writeUInt32LE(record.data.byteLength); return Buffer.concat([ utf16('['), utf16(record.key), Buffer.alloc(2), utf16(';'), utf16(record.valueName), Buffer.alloc(2), utf16(';'), type, utf16(';'), size, utf16(';'), record.data, utf16(']') ]); }); return Buffer.concat([header, ...encoded]); } function verifiedFile(bytes: Uint8Array): VerifiedZipFile { return { entry: { index: 1, relativePath: 'synthetic/DomainSysvol/GPO/Machine/Registry.pol', kind: 'file', compressionMethod: 8, compressedBytes: bytes.byteLength, uncompressedBytes: bytes.byteLength, crc32: '00000000', sha256: createHash('sha256').update(bytes).digest('hex') }, bytes }; } function context() { return { backupInstanceId: 'backup-synthetic', sourceArtifactId: 'source-synthetic', artifactId: 'entry-synthetic' }; } describe('Registry.pol parser and instruction reducer', () => { it('accepts REG_NONE only as an empty key-creation instruction', () => { const document = parseRegistryPolicyFile(verifiedFile(policyFile([{ key: 'Software\\Policies\\Synthetic\\Child', valueName: '', typeCode: 0, data: Buffer.alloc(0) }])), 'computer'); expect(document).toMatchObject({ status: 'complete' }); expect(reduceRegistryPolicy(document, context())).toMatchObject([{ registryPolicy: { sequence: 0, operation: 'create-key' }, values: [{ kind: 'registry-policy', operation: 'create-key', key: 'Software\\Policies\\Synthetic\\Child', typeCode: 0, data: { kind: 'none' } }] }]); expect(parseRegistryPolicyFile(verifiedFile(policyFile([{ key: 'Software\\Policies\\Synthetic\\Child', valueName: 'unexpected', typeCode: 0, data: Buffer.alloc(0) }])), 'computer')).toMatchObject({ status: 'invalid', instructions: [], diagnosticCodes: ['gpo-registry-policy-none-invalid'] }); }); it('decodes the documented Registry.pol types without losing record order', () => { const document = parseRegistryPolicyFile(verifiedFile(policyFile([ { key: 'Software\\Policies\\Synthetic', valueName: 'Text', typeCode: 1, data: stringData('strict') }, { key: 'Software\\Policies\\Synthetic', valueName: 'Expanded', typeCode: 2, data: stringData('%SYSTEMROOT%') }, { key: 'Software\\Policies\\Synthetic', valueName: 'Bytes', typeCode: 3, data: Buffer.from([0, 127, 255]) }, { key: 'Software\\Policies\\Synthetic', valueName: 'Little', typeCode: 4, data: dword(42) }, { key: 'Software\\Policies\\Synthetic', valueName: 'Big', typeCode: 5, data: dword(43, true) }, { key: 'Software\\Policies\\Synthetic', valueName: 'List', typeCode: 7, data: multiStringData(['one', 'two']) }, { key: 'Software\\Policies\\Synthetic', valueName: 'Wide', typeCode: 11, data: qword(9_007_199_254_740_993n) } ])), 'computer'); expect(document).toMatchObject({ status: 'complete', instructions: [ { sequence: 0, data: { kind: 'string', value: 'strict' } }, { sequence: 1, data: { kind: 'string', value: '%SYSTEMROOT%' } }, { sequence: 2, data: { kind: 'binary', hexadecimal: '007fff' } }, { sequence: 3, data: { kind: 'uint32', value: 42 } }, { sequence: 4, data: { kind: 'uint32', value: 43 } }, { sequence: 5, data: { kind: 'multi-string', values: ['one', 'two'] } }, { sequence: 6, data: { kind: 'uint64', value: '9007199254740993' } } ] }); }); it('preserves documented delete, secure and soft operations as ordered instructions', () => { const document = parseRegistryPolicyFile(verifiedFile(policyFile([ { key: 'Software\\Policies\\Synthetic', valueName: '**DeleteValues', typeCode: 1, data: stringData('One;Two') }, { key: 'Software\\Policies\\Synthetic', valueName: '**Del.Three', typeCode: 1, data: stringData(' ') }, { key: 'Software\\Policies\\Synthetic', valueName: '**DelVals.', typeCode: 1, data: stringData(' ') }, { key: 'Software\\Policies\\Synthetic', valueName: '**DeleteKeys', typeCode: 1, data: stringData('ChildA;ChildB') }, { key: 'Software\\Policies\\Synthetic', valueName: '**SecureKey', typeCode: 4, data: dword(1) }, { key: 'Software\\Policies\\Synthetic', valueName: '**soft.Mode', typeCode: 4, data: dword(1) } ])), 'computer'); const observations = reduceRegistryPolicy(document, context()); expect(observations.map(observation => observation.registryPolicy)).toEqual([ { sequence: 0, operation: 'delete-values' }, { sequence: 1, operation: 'delete-value' }, { sequence: 2, operation: 'delete-all-values' }, { sequence: 3, operation: 'delete-keys' }, { sequence: 4, operation: 'secure-key' }, { sequence: 5, operation: 'set-value-if-absent' } ]); expect(observations[0]).toMatchObject({ state: 'instruction', values: [{ kind: 'registry-policy', operation: 'delete-values', targets: ['One', 'Two'] }], sourceRecords: [{ index: 0, byteOffset: 8 }], effectiveState: 'unknown' }); expect(observations[4]).toMatchObject({ values: [{ operation: 'secure-key', secure: true }] }); }); it('keeps unknown reserved operations partial and does not silently reinterpret them', () => { const document = parseRegistryPolicyFile(verifiedFile(policyFile([ { key: 'Software\\Policies\\Synthetic', valueName: '**FutureOperation', typeCode: 1, data: stringData('opaque') } ])), 'computer'); expect(document).toMatchObject({ status: 'partial', diagnosticCodes: ['gpo-registry-policy-special-value-unsupported'] }); expect(reduceRegistryPolicy(document, context())).toMatchObject([{ values: [{ operation: 'unsupported-special' }], diagnosticCodes: ['gpo-registry-policy-special-value-unsupported'] }]); }); it('fails closed on an invalid header, version, truncation or special-operation shape', () => { const validRecord = { key: 'Software\\Policies\\Synthetic', valueName: 'Mode', typeCode: 4, data: dword(1) }; const invalidSignature = policyFile([validRecord]); invalidSignature[0] = 0; expect(parseRegistryPolicyFile(verifiedFile(invalidSignature), 'computer')).toMatchObject({ status: 'invalid', instructions: [], diagnosticCodes: ['gpo-registry-policy-signature-invalid'] }); expect(parseRegistryPolicyFile(verifiedFile(policyFile([validRecord], 2)), 'computer')).toMatchObject({ status: 'invalid', instructions: [], diagnosticCodes: ['gpo-registry-policy-version-unsupported'] }); const truncated = policyFile([validRecord]).slice(0, -1); expect(parseRegistryPolicyFile(verifiedFile(truncated), 'computer')).toMatchObject({ status: 'invalid', instructions: [], diagnosticCodes: ['gpo-registry-policy-record-close-invalid'] }); const invalidSpecial = parseRegistryPolicyFile(verifiedFile(policyFile([ { key: 'Software\\Policies\\Synthetic', valueName: '**SecureKey', typeCode: 1, data: stringData('1') } ])), 'computer'); expect(invalidSpecial).toMatchObject({ status: 'invalid', instructions: [], diagnosticCodes: ['gpo-registry-policy-special-value-invalid'] }); }); it('does not reduce a Registry.pol file whose scope is not technically known', () => { const document = parseRegistryPolicyFile(verifiedFile(policyFile([ { key: 'Software\\Policies\\Synthetic', valueName: 'Mode', typeCode: 4, data: dword(1) } ])), 'unknown'); expect(document).toMatchObject({ status: 'partial', diagnosticCodes: ['gpo-registry-policy-scope-unsupported'] }); expect(reduceRegistryPolicy(document, context())).toEqual([]); }); });