74 lines
5.4 KiB
TypeScript
74 lines
5.4 KiB
TypeScript
import { createElement } from 'react';
|
|
import { renderToStaticMarkup } from 'react-dom/server';
|
|
import { describe, expect, it } from 'vitest';
|
|
import { formatAppLockerXml } from '../../src/profiles/group-policy/xml-presentation';
|
|
import { formatPolicyComparisonValue } from '../../src/profiles/group-policy/presentation';
|
|
import { baselineRemediation } from '../../src/profiles/group-policy/guidance';
|
|
import { FindingReader } from '../../src/features/finding-reader/FindingReader';
|
|
import type { PolicyEvaluationResult } from '../../src/domain/policy-evaluation';
|
|
import type { FindingRecord } from '../../src/domain/finding';
|
|
|
|
const publisher = '<FilePublisherRule Id="synthetic-rule" Name="Synthetic application" Description="" UserOrGroupSid="S-1-1-0" Action="Deny"><Conditions><FilePublisherCondition PublisherName="O=SYNTHETIC" ProductName="DEMO" BinaryName="DEMO.EXE"><BinaryVersionRange LowSection="1.0.0.0" HighSection="*" /></FilePublisherCondition></Conditions></FilePublisherRule>';
|
|
|
|
describe('AppLocker value display', () => {
|
|
it('presents rule attributes and version bounds without XML syntax', () => {
|
|
const formatted = formatAppLockerXml(publisher)!;
|
|
for (const text of ['Publisher rule', 'Rule ID: synthetic-rule', 'Name: Synthetic application', 'Action: Deny', 'User/group SID: S-1-1-0', 'Publisher: O=SYNTHETIC', 'Product: DEMO', 'Binary: DEMO.EXE', 'Minimum: 1.0.0.0', 'Maximum: *']) expect(formatted).toContain(text);
|
|
expect(formatted).not.toContain('<');
|
|
expect(formatPolicyComparisonValue(JSON.stringify({ kind: 'text', value: publisher }))).toBe(formatted);
|
|
expect(formatPolicyComparisonValue(JSON.stringify([{ kind: 'registry-policy', data: { kind: 'string', value: publisher } }]))).toContain(' Publisher rule');
|
|
});
|
|
|
|
it('keeps path exceptions distinct from conditions and preserves collection mode', () => {
|
|
const formatted = formatAppLockerXml('<RuleCollection Type="Exe" EnforcementMode="AuditOnly"><FilePathRule Id="path" Action="Allow"><Conditions><FilePathCondition Path="%WINDIR%\\*" /></Conditions><Exceptions><FilePathCondition Path="%WINDIR%\\Temp\\*" /></Exceptions></FilePathRule></RuleCollection>')!;
|
|
expect(formatted).toContain('Enforcement mode: AuditOnly');
|
|
expect(formatted).toContain('Conditions\n Path condition\n Path: %WINDIR%\\*');
|
|
expect(formatted).toContain('Exceptions\n Path condition\n Path: %WINDIR%\\Temp\\*');
|
|
});
|
|
|
|
it('preserves hash attributes and unknown extensions instead of silently dropping them', () => {
|
|
const formatted = formatAppLockerXml('<FileHashRule Id="hash"><Conditions><FileHashCondition><FileHash Type="SHA256" Data="ABC123" SourceFileName="demo.exe" SourceFileLength="123" /></FileHashCondition></Conditions><Extension FutureSetting="true">Detail</Extension></FileHashRule>')!;
|
|
expect(formatted).toContain('Type: SHA256');
|
|
expect(formatted).toContain('Data: ABC123');
|
|
expect(formatted).toContain('Source name: demo.exe');
|
|
expect(formatted).toContain('Future Setting: true');
|
|
expect(formatted).toContain('Detail');
|
|
});
|
|
|
|
it('falls back unchanged for malformed, unrelated, namespaced and entity-bearing XML', () => {
|
|
for (const xml of ['<FilePublisherRule>', '<script>alert(1)</script>', '<FilePathRule xmlns="urn:unknown" />', '<!DOCTYPE FilePathRule [<!ENTITY secret SYSTEM "file:///synthetic">]><FilePathRule Name="&secret;"/>', '<?include href="https://example.invalid"?><FilePathRule/>']) {
|
|
expect(formatAppLockerXml(xml)).toBeUndefined();
|
|
expect(formatPolicyComparisonValue(xml)).toBe(xml);
|
|
}
|
|
});
|
|
|
|
it('bounds size, depth, attributes and node count', () => {
|
|
for (const xml of [
|
|
`<FilePathRule Name="${'x'.repeat(262_144)}"/>`,
|
|
`<FilePathRule>${'<Node>'.repeat(33)}${'</Node>'.repeat(33)}</FilePathRule>`,
|
|
`<FilePathRule ${Array.from({ length: 129 }, (_, i) => `a${i}="x"`).join(' ')}/>` ,
|
|
`<FilePathRule>${'<Node/>'.repeat(2001)}</FilePathRule>`,
|
|
]) expect(formatAppLockerXml(xml)).toBeUndefined();
|
|
});
|
|
|
|
it('uses the same readable baseline rule in remediation without changing evaluation', () => {
|
|
const result = { baselineState: 'explicit-recommendation', diagnosticCodes: [], comparator: { baseline: { kind: 'text', value: publisher }, comparator: { kind: 'equality' } }, identity: { scope: 'computer', target: { hive: 'HKLM', key: 'Software\\Synthetic', valueName: 'Value' } } } as unknown as PolicyEvaluationResult;
|
|
const original = JSON.stringify(result);
|
|
expect(baselineRemediation(result)).toContain(`Expected: ${formatAppLockerXml(publisher)}`);
|
|
expect(JSON.stringify(result)).toBe(original);
|
|
});
|
|
|
|
it('renders values as inert readable text, without code panels or active markup', () => {
|
|
const record = { id: 'synthetic', title: 'Synthetic policy', index: 0, filled: [], meta: [], fields: [
|
|
{ index: 0, label: 'Configured value', value: '<img src="https://example.invalid" onerror="alert(1)" />' },
|
|
{ index: 1, label: 'Baseline value', value: publisher },
|
|
] } as FindingRecord;
|
|
const html = renderToStaticMarkup(createElement(FindingReader, { record, title: record.title, linkedSheets: [], resultReferences: [], search: '', position: 0, recordCount: 1, toolId: 'group-policy', onPrevious() {}, onNext() {}, onReview() {} }));
|
|
expect(html).toContain('Publisher rule');
|
|
expect(html).toContain('Action: Deny');
|
|
expect(html).toContain('<img');
|
|
expect(html).not.toMatch(/<(?:img|pre|code)\b/);
|
|
expect(html).toContain('class="policy-guidance-text"');
|
|
});
|
|
});
|