Assessor_2.0/tests/server/group-policy-preference-par...

119 lines
6.5 KiB
TypeScript

import { createHash } from 'node:crypto';
import { describe, expect, it } from 'vitest';
import {
parseGroupPolicyPreferenceXml,
reduceGroupPolicyPreferences
} from '../../server/gpo/group-policy-preference-parser';
import type { VerifiedZipFile } from '../../server/gpo/zip-inventory';
import { ImportError } from '../../src/shared/errors';
function verifiedFile(content: string | Uint8Array, name = 'Registry.xml'): VerifiedZipFile {
const bytes = typeof content === 'string' ? new TextEncoder().encode(content) : content;
return {
entry: {
index: 1,
relativePath: `synthetic/DomainSysvol/GPO/Machine/Preferences/Registry/${name}`,
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' };
}
const rootClsid = '{A3CCFC41-DFDB-43a5-8D26-0FE8B954DA51}';
const itemClsid = '{9CD4B2F4-923D-47f5-A062-E897DD1DAD50}';
describe('Group Policy Preferences parser and Registry reducer', () => {
it('preserves Registry action, order, common options and opaque item-level targeting', () => {
const document = parseGroupPolicyPreferenceXml(verifiedFile([
`<?xml version="1.0"?><RegistrySettings clsid="${rootClsid}" disabled="0">`,
` <Registry clsid="${itemClsid}" uid="{11111111-1111-4111-8111-111111111111}" removePolicy="1" applyOnce="0">`,
' <Properties action="R" default="0" hive="HKEY_LOCAL_MACHINE" key="Software\\Synthetic" name="Mode" type="REG_SZ" value="Strict"/>',
' <Filters><FilterGroup bool="AND" not="0" name="Synthetic\\Operators" sid="S-1-5-21-1-2-3-1001"/></Filters>',
' </Registry>',
` <Registry clsid="${itemClsid}" uid="{22222222-2222-4222-8222-222222222222}">`,
' <Properties action="D" default="0" hive="HKEY_LOCAL_MACHINE" key="Software\\Synthetic" name="Legacy" type="REG_DWORD"/>',
' </Registry>',
'</RegistrySettings>'
].join('\r\n')), 'computer');
expect(document).toMatchObject({ status: 'complete', preferenceType: 'registry', secretIndicatorStatus: 'not-detected' });
const observations = reduceGroupPolicyPreferences(document, context());
expect(observations).toHaveLength(2);
expect(observations[0]).toMatchObject({
deliveryMechanism: 'group-policy-preference',
state: 'instruction',
identity: { namespace: 'group-policy-preference', section: 'registry' },
preference: {
cse: 'registry', operation: 'replace', sequence: 0, disabled: false, removePolicy: true, applyOnce: false,
targeting: { status: 'present', filterTypes: ['filtergroup'], evaluability: 'unknown' }
},
values: [{
kind: 'registry-preference', hive: 'HKEY_LOCAL_MACHINE', key: 'Software\\Synthetic', target: 'value',
valueName: 'Mode', valueType: 'REG_SZ', rawData: 'Strict'
}],
sourceLines: [2],
effectiveState: 'unknown',
diagnosticCodes: ['gpo-preference-targeting-unevaluated']
});
expect(observations[1]).toMatchObject({ preference: { operation: 'delete', sequence: 1 }, sourceLines: [6] });
});
it('recognizes but never returns cpassword values', () => {
const secret = 'synthetic-secret-must-not-leave-parser';
const document = parseGroupPolicyPreferenceXml(verifiedFile(
`<RegistrySettings clsid="${rootClsid}"><Registry clsid="${itemClsid}" uid="{33333333-3333-4333-8333-333333333333}" cpassword="${secret}"><Properties hive="HKEY_CURRENT_USER" key="Software\\Synthetic" name="Mode" type="REG_SZ" value="Safe"/></Registry></RegistrySettings>`
), 'user');
expect(document).toMatchObject({ status: 'complete', secretIndicatorStatus: 'present' });
expect(document.diagnosticCodes).toContain('gpo-secret-indicator-present');
expect(JSON.stringify(document)).not.toContain(secret);
expect(JSON.stringify(reduceGroupPolicyPreferences(document, context()))).not.toContain(secret);
});
it('keeps unsupported Preference CSEs partial without producing policy observations', () => {
const document = parseGroupPolicyPreferenceXml(verifiedFile(
'<Printers clsid="{1F577D12-3D1B-471e-A1B7-060317597B9C}"><SharedPrinter cpassword="masked"/></Printers>',
'Groups.xml'
), 'computer');
expect(document).toMatchObject({
preferenceType: 'unsupported', status: 'partial', secretIndicatorStatus: 'present', instructions: [],
diagnosticCodes: ['gpo-preference-type-unsupported', 'gpo-secret-indicator-present']
});
expect(reduceGroupPolicyPreferences(document, context())).toEqual([]);
});
it('fails closed for malformed supported actions, targets, common flags and targeting', () => {
const document = parseGroupPolicyPreferenceXml(verifiedFile(
`<RegistrySettings clsid="${rootClsid}"><Registry clsid="${itemClsid}" uid="{44444444-4444-4444-8444-444444444444}" removePolicy="1"><Properties action="D" hive="HKEY_LOCAL_MACHINE" key="Software\\Synthetic"/><Filters><FilterGroup bool="XOR" not="0"/></Filters></Registry></RegistrySettings>`
), 'computer');
expect(document.status).toBe('invalid');
expect(document.instructions).toEqual([]);
expect(document.diagnosticCodes).toEqual(expect.arrayContaining([
'gpo-preference-remove-policy-invalid', 'gpo-preference-targeting-invalid'
]));
});
it('keeps unsupported Registry subproperties partial instead of inventing bitfield semantics', () => {
const document = parseGroupPolicyPreferenceXml(verifiedFile(
'<RegistrySettings clsid="' + rootClsid + '"><Registry clsid="' + itemClsid + '" uid="{55555555-5555-4555-8555-555555555555}"><Properties action="U" hive="HKEY_LOCAL_MACHINE" key="Software\Synthetic" name="Flags" type="REG_DWORD" value="1"><SubProp id="synthetic" value="1" mask="1"/></Properties></Registry></RegistrySettings>'
), 'computer');
expect(document).toMatchObject({
status: 'partial', instructions: [], diagnosticCodes: ['gpo-preference-registry-item-unsupported']
});
expect(reduceGroupPolicyPreferences(document, context())).toEqual([]);
});
it('rejects active XML constructs before parsing', () => {
expect(() => parseGroupPolicyPreferenceXml(verifiedFile(
`<!DOCTYPE RegistrySettings [<!ENTITY x SYSTEM "file:///synthetic">]><RegistrySettings clsid="${rootClsid}">&x;</RegistrySettings>`
), 'computer')).toThrow(ImportError);
});
});