Assessor_2.0/tests/features/public-search.test.tsx

115 lines
7.4 KiB
TypeScript

import { createElement } from 'react';
import { renderToStaticMarkup } from 'react-dom/server';
import { describe, expect, it } from 'vitest';
import type { FindingRecord } from '../../src/domain/finding';
import { findingWebSearch } from '../../src/profiles/public-search/search';
import catalog from '../../src/profiles/public-search/catalog.json';
import { FindingReader } from '../../src/features/finding-reader/FindingReader';
function record(title: string, values: Record<string, string> = {}): FindingRecord {
return { id: 'synthetic', title, index: 0, filled: [], meta: [], fields: Object.entries(values).map(([label, value], index) => ({ label, value, index })) };
}
describe('tool-independent public AI search', () => {
it('resolves native policy identity, location and bounded benchmark target', () => {
const result = findingWebSearch(record('system access · minimumpasswordlength', { 'Baseline value': '{"kind":"number","value":14}', Scope: 'computer', Baseline: 'Windows Server 2022 Security Baseline', Status: 'Fail' }), 'group-policy');
expect(result.specificity).toBe('setting');
expect(result.query).toContain('MinimumPasswordLength');
expect(result.query).toContain('Account Policies');
expect(result.query).toContain('Selected benchmark target: 14');
expect(result.query).toContain('Windows Server 2022');
});
it('resolves public rights without sending assigned identities', () => {
const result = findingWebSearch(record('privilege rights · sebackupprivilege', { 'Baseline value': '["S-1-5-21-123-456-789-1001"]', 'Configured Value': 'SYNTHETIC\\PrivateUser' }), 'group-policy');
expect(result.query).toContain('SeBackupPrivilege');
expect(result.query).toContain('User Rights Assignment');
expect(result.query).not.toContain('S-1-5-21');
expect(result.query).not.toContain('PrivateUser');
});
it('matches full public registry targets and refuses private extensions', () => {
const path = 'HKEY_LOCAL_MACHINE\\SOFTWARE\\Policies\\Microsoft\\Windows Defender';
const result = findingWebSearch(record('PUAProtection', { 'Registry path': path, 'Baseline value': '1' }), 'group-policy');
expect(result.specificity).toBe('setting');
expect(result.query).toContain('Public registry target: SOFTWARE\\Policies\\Microsoft\\Windows Defender\\PUAProtection');
expect(result.query).toContain('not the selected SCT baseline');
const privateResult = findingWebSearch(record('PUAProtection', { 'Registry path': path + '\\PrivateTenant' }), 'group-policy');
expect(privateResult.query).not.toContain('Public registry target:');
expect(privateResult.query).not.toContain('PrivateTenant');
});
it('resolves ASR GUIDs only within the supported registry family', () => {
const id = '5beb7efe-fd9a-4556-801d-275e5ffc04cc';
const result = findingWebSearch(record(id, { 'Registry path': 'SOFTWARE\\Policies\\Microsoft\\Windows Defender\\Windows Defender Exploit Guard\\ASR\\Rules' }), 'group-policy');
expect(result.query).toContain(id);
expect(result.query).toContain('Attack Surface Reduction');
expect(findingWebSearch(record(id), 'group-policy').query).not.toContain(id);
});
it.each(['maester', 'pingcastle'])('uses verified public IDs for %s without dynamic instance content', tool => {
const id = tool === 'maester' ? 'MT.1006' : 'A-AdminSDHolder';
const result = findingWebSearch(record(`${id}: private-user@synthetic.invalid`, { Description: 'PrivateTenant' }), tool);
expect(result.specificity).toBe('rule');
expect(result.query).toContain(`Public rule ID: ${id}`);
expect(result.query).not.toContain('private-user');
expect(result.query).not.toContain('PrivateTenant');
});
it.each(['cis-cat', 'purple-knight', 'generic'])('adds useful fixed technical concepts for %s', tool => {
const result = findingWebSearch(record('SMB signing on private-host.synthetic.invalid', { Description: 'SMB signing is required for PrivateTenant', Benchmark: 'Windows Server 2022 PrivateTenant' }), tool);
expect(result.query).toContain('SMB signing requirements');
expect(result.query).toContain('Windows Server 2022');
expect(result.query).not.toContain('private-host');
expect(result.query).not.toContain('PrivateTenant');
});
it('supports a mixed-tool workspace and labels unresolved rules honestly', () => {
const mixed = record('MT.1006', { 'Source: Scan': 'Maester · Synthetic' });
expect(findingWebSearch(mixed, 'generic').query).toContain('Maester');
const result = findingWebSearch(record('Unknown private rule', { Description: 'person@example.invalid' }), 'cis-cat');
expect(result.specificity).toBe('topic');
expect(result.query).toContain('exact public rule is not resolved');
expect(result.query).not.toContain('person@');
});
it('never forwards free text, raw descriptions, source URLs, paths or customer values', () => {
const secret = 'Synthetic Person private@example.invalid 10.20.30.40 \\\\private\\share https://private.invalid';
const item = record(secret, Object.fromEntries(['Description', 'Remediation', 'Configured Value', 'Sources', 'Owner', 'Baseline value', 'Registry path', 'Rule ID', 'Search URL'].map(k => [k, secret])));
for (const tool of ['group-policy', 'cis-cat', 'maester', 'pingcastle', 'purple-knight', 'generic']) {
const result = findingWebSearch(item, tool);
expect(result.query).not.toMatch(/Synthetic Person|private@|10\.20|private\\|private\.invalid/);
expect(new URL(result.url).searchParams.get('q')).toBe(result.query);
expect(new URL(result.url).searchParams.get('udm')).toBe('50');
}
});
it('rejects malformed, string, unbounded or ambiguous benchmark values', () => {
for (const value of ['"private-person"', '99999999', '{"kind":"number","value":1,"secret":"private"}', '1 private']) {
const item = record('system access · MinimumPasswordLength', { 'Baseline value': value });
expect(findingWebSearch(item, 'group-policy').query).not.toContain('Selected benchmark target:');
}
const item = record('system access · MinimumPasswordLength', { 'Baseline value': '14' });
item.fields.push({ label: 'Baseline value', value: '12', index: 1 });
expect(findingWebSearch(item, 'group-policy').query).not.toContain('Selected benchmark target:');
});
it.each(['cis-cat', 'purple-knight', 'maester', 'pingcastle', 'group-policy', 'generic'])('renders an explicit protected search link for %s', toolId => {
const item = record('SMB signing');
const html = renderToStaticMarkup(createElement(FindingReader, { record: item, title: item.title, toolId, linkedSheets: [], resultReferences: [], search: '', position: 0, recordCount: 1, onPrevious() {}, onNext() {}, onReview() {} }));
expect(html).toContain('>AI search</a>');
expect(html).toContain('rel="noopener noreferrer"');
expect(html).toContain('referrerPolicy="no-referrer"');
expect(html).toContain('data-search-specificity=');
});
it('keeps a source-attributed public identifier catalog without report prose', () => {
expect(catalog.registryTargets.length).toBeGreaterThan(60);
expect(catalog.privilegeIds.length).toBe(45);
expect(catalog.maesterIds.length).toBeGreaterThan(150);
expect(catalog.pingcastleIds.length).toBeGreaterThan(40);
expect(catalog.asrIds).toContain('5beb7efe-fd9a-4556-801d-275e5ffc04cc');
for (const list of [catalog.registryTargets, catalog.privilegeIds, catalog.maesterIds, catalog.pingcastleIds, catalog.asrIds]) expect(new Set(list).size).toBe(list.length);
});
});