Assessor_2.0/tests/features/gpo-source-evidence.test.tsx

148 lines
11 KiB
TypeScript

import { renderToStaticMarkup } from 'react-dom/server';
import { describe, expect, it } from 'vitest';
import type { UnifiedResultProjection } from '../../src/domain/unified-result';
import type { JsonValue } from '../../src/domain/canonical-model';
import type { GpoPolicyValue } from '../../src/domain/gpo-policy';
import type { PolicyBaselineVariantAssessment } from '../../src/domain/policy-evaluation';
import { policySourceEvidence, policySourceGroups, policySourceValueLines } from '../../src/profiles/group-policy/source-evidence';
import { PolicySourceEvidence } from '../../src/features/finding-reader/PolicySourceEvidence';
function source(id: string, backupId: string | undefined, sequence?: number): UnifiedResultProjection {
return { sourceResultId: id, projectId: 'synthetic', assessmentId: 'slot', policyBackupInstanceId: backupId,
policyGpoId: backupId ? `gpo-${backupId}` : undefined, policyNativeBackupId: backupId ? `native-${backupId}` : undefined,
sourceLocator: { objectPath: 'C:/Private/{1667E9C3-865B-4ABA-A288-71C8A18AD5E0}/DomainSysvol/GPO/Machine/registry.pol#records=' + sequence },
policySourceSequence: sequence,
policyValues: [{ kind: 'registry-policy', operation: 'set-value', key: 'Software\\Policies\\Synthetic',
valueName: 'Mode', typeCode: 4, data: { kind: 'uint32', value: 1 } }] } as unknown as UnifiedResultProjection;
}
describe('Original GPO evidence attribution', () => {
it('counts backup identities, not observations; retains source order only within each GPO file', () => {
const original = [source('later', 'a', 9), source('other', 'b', 0), source('earlier', 'a', 0)];
const before = JSON.stringify(original);
const evidence = policySourceEvidence(original);
const groups = policySourceGroups(evidence);
expect(groups).toHaveLength(2);
expect(groups[0].observations.map(item => item.id)).toEqual([evidence[2].id, evidence[0].id]);
expect(groups[0]).toMatchObject({ label: 'GPO gpo-a', backupLabel: 'Backup: native-a' });
expect(policySourceGroups(evidence)).toBe(groups);
expect(policySourceGroups(policySourceEvidence(original.slice(0, 1)))).toHaveLength(1);
expect(JSON.stringify(original)).toBe(before);
const html = renderToStaticMarkup(<PolicySourceEvidence sources={evidence} search="gpo-a"/>);
expect(html).toContain('2 GPO backup sources');
expect(html).toContain('<div class="policy-source-groups">');
expect((html.match(/<article class="policy-source-group/g) ?? [])).toHaveLength(2);
expect(html).not.toContain('<table');
expect(html).toContain('Source sequence: 0');
expect(html).toContain('<mark');
expect(html).not.toMatch(/C:|Private/);
});
it('does not merge backups with equal GPO names/IDs across timeslots or unknown backup identities', () => {
const first = source('one', 'a');
const otherSlot = { ...source('two', 'a'), assessmentId: 'other-slot' };
const evidence = policySourceEvidence([first, first, otherSlot, source('three', undefined), source('four', undefined)]);
const groups = policySourceGroups(evidence);
expect(groups).toHaveLength(4);
expect(groups[0].observations).toHaveLength(1);
expect(groups.filter(group => !group.identified)).toHaveLength(2);
const html = renderToStaticMarkup(<PolicySourceEvidence sources={evidence} search=""/>);
expect(html).toContain('2 GPO backup sources · 2 unassigned');
});
it('distinguishes delete, typed empty strings, whitespace, zero and unsupported instructions as inert original values', () => {
const values: JsonValue[] = [
{ kind: 'registry-policy', operation: 'delete-value', key: 'Software\\Policies\\Synthetic', valueName: 'Mode' },
{ kind: 'registry-policy', operation: 'set-value', key: 'Software\\Policies\\Synthetic', typeCode: 1, data: { kind: 'string', value: '' } },
{ kind: 'registry-policy', operation: 'set-value', typeCode: 1, data: { kind: 'string', value: ' ' } },
{ kind: 'registry-policy', operation: 'set-value', typeCode: 4, data: { kind: 'uint32', value: 0 } },
{ kind: 'registry-policy', operation: 'unsupported-special', targets: ['<img src="https://private.invalid">'] }
];
const before = JSON.stringify(values);
expect(policySourceValueLines(values[0]).filter(line => line.emphasized)).toEqual([{ text: 'Operation: delete-value', emphasized: true }]);
expect(policySourceValueLines(values[1])).toContainEqual({ text: 'Data: ""', emphasized: true });
expect(policySourceValueLines(values[2])).toContainEqual({ text: 'Data: " "', emphasized: true });
expect(policySourceValueLines(values[3])).toContainEqual({ text: 'Data: 0', emphasized: true });
const evidence = policySourceEvidence([{ ...source('unsafe', 'a'), policyValues: values }]);
const html = renderToStaticMarkup(<PolicySourceEvidence sources={evidence} search=""/>);
expect(html).toContain('&lt;img');
expect(html).not.toContain('<img');
expect(html).not.toContain('href=');
expect(html).toContain('<strong class="policy-value-emphasis">Data: 0</strong>');
expect(html).not.toContain('Not set');
expect(JSON.stringify(values)).toBe(before);
});
it('marks a source only when the retained current baseline variants prove its individual relation', () => {
const set = source('set', 'set-backup', 1);
const deleted = { ...source('delete', 'delete-backup', 2), policyValues: [{ kind: 'registry-policy',
operation: 'delete-value', key: 'Software\\Policies\\Synthetic', valueName: 'Mode' }] } as UnifiedResultProjection;
const assessment = { version: 1, scope: 'backup-configuration', decision: 'all-fail',
actualValues: [...set.policyValues!, ...deleted.policyValues!] as GpoPolicyValue[], variants: [{
settingId: 'baseline', baselineValues: [{ kind: 'registry-policy', operation: 'set-value',
key: 'Software\\Policies\\Synthetic', valueName: 'Mode', typeCode: 4, data: { kind: 'uint32', value: 1 } }],
comparator: { version: 1, comparator: { kind: 'equality' }, relation: 'different',
baseline: { kind: 'integer', value: '1' }, diagnosticCodes: [] }
}], diagnosticCodes: [] } as PolicyBaselineVariantAssessment;
const evidence = policySourceEvidence([set, deleted], assessment);
const html = renderToStaticMarkup(<PolicySourceEvidence sources={evidence} search=""/>);
expect(evidence.map(item => item.baselineRelation)).toEqual(['aligned', 'conflicting']);
expect(html).toContain('data-source-relation="aligned"');
expect(html).toContain('data-source-relation="conflicting"');
expect(html).toContain('class="policy-source-group is-conflicting"');
expect(evidence[1].deviationFields).toEqual([['operation']]);
expect(html).toContain('class="policy-source-deviation-value"><strong class="policy-value-emphasis">Operation: delete-value</strong>');
expect((html.match(/policy-source-deviation-value/g) ?? [])).toHaveLength(1);
expect(html).not.toContain('Conflicting contribution');
expect(html).toContain('Type code: 4');
expect(html).toContain('Operation: delete-value');
const wrongType = { ...source('wrong-type', 'wrong-type-backup', 3), policyValues: [{ kind: 'registry-policy',
operation: 'set-value', key: 'Software\\Policies\\Synthetic', valueName: 'Mode', typeCode: 1,
data: { kind: 'uint32', value: 1 } }] } as UnifiedResultProjection;
const typeEvidence = policySourceEvidence([wrongType], assessment);
expect(typeEvidence[0].deviationFields).toEqual([['typeCode']]);
expect(renderToStaticMarkup(<PolicySourceEvidence sources={typeEvidence} search=""/>))
.toContain('class="policy-source-deviation-value">Type code: 1<span class="windows-code-meaning"> (REG_SZ)</span></span>');
const wrongDataAndType = { ...source('wrong-data-type', 'wrong-data-type-backup', 4), policyValues: [{ kind: 'registry-policy',
operation: 'set-value', key: 'Software\\Policies\\Synthetic', valueName: 'Mode', typeCode: 1,
data: { kind: 'uint32', value: 0 } }] } as UnifiedResultProjection;
const combinedEvidence = policySourceEvidence([wrongDataAndType], assessment);
expect(combinedEvidence[0].deviationFields).toEqual([['data', 'typeCode']]);
const combinedHtml = renderToStaticMarkup(<PolicySourceEvidence sources={combinedEvidence} search=""/>);
expect(combinedHtml).toContain('class="policy-source-deviation-value"><strong class="policy-value-emphasis">Data: 0</strong>');
expect(combinedHtml).toContain('class="policy-source-deviation-value">Type code: 1<span class="windows-code-meaning"> (REG_SZ)</span></span>');
const scalarSource = { ...source('scalar', 'scalar-backup', 5), policyValues: [{ kind: 'scalar', raw: 'disabled' }] } as UnifiedResultProjection;
const scalarAssessment = { ...assessment, actualValues: scalarSource.policyValues,
variants: [{ ...assessment.variants[0], baselineValues: [{ kind: 'scalar', raw: 'enabled' }] }] } as PolicyBaselineVariantAssessment;
const scalarEvidence = policySourceEvidence([scalarSource], scalarAssessment);
expect(scalarEvidence[0].deviationFields).toEqual([['value']]);
expect(renderToStaticMarkup(<PolicySourceEvidence sources={scalarEvidence} search=""/>))
.toContain('class="policy-source-deviation-value">Raw: disabled</span>');
const principalSource = { ...source('principals', 'principal-backup', 6), policyValues: [{ kind: 'principal-set',
principals: ['principal-extra-a', 'principal-extra-b', 'principal-required'] }] } as UnifiedResultProjection;
const principalAssessment = { ...assessment, actualValues: principalSource.policyValues,
variants: [{ ...assessment.variants[0], baselineValues: [{ kind: 'principal-set', principals: ['principal-required'] }] }] } as PolicyBaselineVariantAssessment;
const principalEvidence = policySourceEvidence([principalSource], principalAssessment);
expect(principalEvidence[0].deviationFields).toBeUndefined();
expect(principalEvidence[0].deviationItems).toEqual([['principal-extra-a', 'principal-extra-b']]);
const principalHtml = renderToStaticMarkup(<PolicySourceEvidence sources={principalEvidence} search=""/>);
expect((principalHtml.match(/policy-source-deviation-value/g) ?? [])).toHaveLength(2);
expect(principalHtml).toContain('class="policy-source-deviation-value"> principal-extra-a</span>');
expect(principalHtml).toContain('class="policy-source-deviation-value"> principal-extra-b</span>');
expect(principalHtml).not.toContain('class="policy-source-deviation-value"> principal-required</span>');
const missingOnlyAssessment = { ...principalAssessment,
variants: [{ ...principalAssessment.variants[0], baselineValues: [{ kind: 'principal-set',
principals: ['principal-extra-a', 'principal-extra-b', 'principal-required', 'principal-missing'] }] }] } as PolicyBaselineVariantAssessment;
const missingOnlyEvidence = policySourceEvidence([principalSource], missingOnlyAssessment);
expect(missingOnlyEvidence[0].deviationFields).toBeUndefined();
expect(missingOnlyEvidence[0].deviationItems).toBeUndefined();
expect(renderToStaticMarkup(<PolicySourceEvidence sources={missingOnlyEvidence} search=""/>))
.not.toContain('policy-source-deviation-value');
expect(policySourceEvidence([set], undefined)[0].baselineRelation).toBeUndefined();
});
});