import { describe, expect, it } from 'vitest'; import type { UnifiedResultProjection } from '../../src/domain/unified-result'; import { RELEASED_ASSESSMENT_CONTEXT_CATALOG as catalog } from '../../src/assessment-context/context-catalog'; import { buildAssessmentContext } from '../../src/assessment-context/context-engine'; // User decision 2026-10-01: every deviation receives context in a fixed, deterministic order of tiers; recognized and // estimated assignments count for their clusters and always carry confidence, method and evidence. const finding = (occurrence: string, tool: string, outcome: string, title: string, extra: Partial = {}) => ({ resultProjectionId: `scanner-finding:${occurrence}`, sourceKind: 'scanner-finding', sourceResultId: occurrence, scanRunId: 'run-a', sourceTool: tool, sourceRuleId: `RULE-${occurrence}`, outcomeNormalized: outcome, title, categories: [], ...extra }) as unknown as UnifiedResultProjection; const category = (label: string) => [{ namespace: 'x', categoryCode: label, categoryLabel: label, categoryType: 'source' as const, isPrimary: true }]; const reference = (id: string) => ({ framework: 'mitre-attack', id, title: id, kind: id.startsWith('TA') ? 'tactic' : 'technique', tactics: [], status: 'active', evidence: 'declared', matchedBy: 'id', catalogVersion: '19.2', catalogSha256: 'x', source: {} }); const build = (scannerFindings: UnifiedResultProjection[], referenceRuns: unknown[] = []) => buildAssessmentContext({ projectId: 'dummy-project', assessmentIds: ['dummy-assessment'], generatedAt: '2026-10-01T08:00:00Z', repositoryRevision: 1, observations: [], analyses: [], catalog, scannerFindings, referenceRuns: referenceRuns as never }); const attachment = (result: ReturnType, occurrence: string) => result.findings.find(item => item.occurrenceId === occurrence); const tier = (result: ReturnType, occurrence: string) => result.recognitions .filter(item => item.occurrenceId === occurrence).map(item => `${item.clusterId}:${item.confidence}:${item.method}`); describe('context engine assignment tiers', () => { const findings = [ finding('occ-title', 'purple-knight', 'fail', 'Users with SPN defined'), finding('occ-description', 'purple-knight', 'fail', 'Synthetic indicator A', { description: 'Accounts can be roasted offline.', recommendation: 'Use gMSA accounts and remove RC4 from service accounts.' }), finding('occ-anchor', 'cis-cat', 'fail', "Ensure 'Network security: LAN Manager authentication level' is reviewed"), finding('occ-category-0', 'pingcastle', 'fail', 'Kerberoast service accounts', { categories: category('Synthetic Category') }), finding('occ-category-1', 'pingcastle', 'fail', 'Users with SPN defined in forest', { categories: category('Synthetic Category') }), finding('occ-category-2', 'pingcastle', 'fail', 'Quarterly widget inventory', { categories: category('Synthetic Category') }), finding('occ-none', 'maester', 'fail', 'Unrelated widget check'), finding('occ-passed', 'purple-knight', 'pass', 'Users with SPN defined') ]; it('places each deviation in the first tier that yields a cluster and keeps only signal-free ones uncatalogued', () => { const result = build(findings); expect(tier(result, 'occ-title')).toEqual(['kerberoasting-exposure:recognized:title-term']); expect(tier(result, 'occ-description')).toEqual(['kerberoasting-exposure:recognized:description-terms']); expect(tier(result, 'occ-anchor')).toEqual(['credential-relay-exposure:recognized:title-term']); expect(tier(result, 'occ-category-2')).toEqual(['kerberoasting-exposure:estimated:scanner-category']); expect(tier(result, 'occ-none')).toEqual([]); expect(result.observations.filter(item => item.matchedBy === 'uncatalogued-deviation').map(item => item.occurrenceId)).toEqual(['occ-none']); // A passed result is no deviation and receives no automatic assignment. expect(tier(result, 'occ-passed')).toEqual([]); }); it('counts automatic assignments for their clusters with their confidence', () => { const result = build(findings); expect(attachment(result, 'occ-title')).toMatchObject({ clusterIds: ['kerberoasting-exposure'], clusterConfidence: { 'kerberoasting-exposure': 'recognized' } }); expect(attachment(result, 'occ-category-2')).toMatchObject({ clusterIds: ['kerberoasting-exposure'], clusterConfidence: { 'kerberoasting-exposure': 'estimated' } }); expect(attachment(result, 'occ-none')).toMatchObject({ clusterIds: ['other-deviations'] }); expect(attachment(result, 'occ-none')?.clusterConfidence).toBeUndefined(); expect(result.recognitions.find(item => item.occurrenceId === 'occ-category-2')).toMatchObject({ matchedText: 'Synthetic Category', assertion: 'automatically-estimated' }); expect(result.coverage).toMatchObject({ scannerRecognized: 5, scannerEstimated: 1, scannerUncatalogued: 1, uncataloguedWithoutRecognition: 1 }); }); it('yields identical assignments for identical input in any order', () => { const forward = build(findings); const reversed = build([...findings].reverse()); expect(reversed.recognitions).toEqual(forward.recognitions); expect(reversed.observations).toEqual(forward.observations); expect([...reversed.findings].sort((left, right) => left.occurrenceId.localeCompare(right.occurrenceId))) .toEqual([...forward.findings].sort((left, right) => left.occurrenceId.localeCompare(right.occurrenceId))); }); it('estimates from the most similar placed finding only when two informative words are shared', () => { const result = build([ finding('occ-known', 'purple-knight', 'fail', 'Users with SPN defined'), finding('occ-similar', 'pingcastle', 'fail', 'Service principal names registered on interactive users', { description: 'Users with SPN defined can be attacked.' }), finding('occ-one-word', 'maester', 'fail', 'Unrelated cloud widget') ]); expect(tier(result, 'occ-similar')).toEqual(['kerberoasting-exposure:estimated:nearest-finding']); expect(tier(result, 'occ-one-word')).toEqual([]); }); it('keeps tier 1 limits: a declared technique assigns only when unambiguous, a tactic never', () => { const run = [{ schemaVersion: 1, id: 'refs', projectId: 'dummy-project', assessmentId: 'dummy-assessment', scanRunId: 'run-a', recognizerVersion: '1.0.0', inputSha256: 'x', catalogHashes: [], createdAt: '2026-10-01T00:00:00Z', results: [['occ-relay', [reference('T1557.001')]], ['occ-tactic', [reference('TA0006')]], ['occ-ambiguous', [reference('T1078.004')]]] .map(([occurrenceId, references]) => ({ occurrenceId, importFileId: 'f', sourcePosition: {}, references })) }]; const result = build([finding('occ-relay', 'pingcastle', 'fail', 'Quarterly widget relay'), finding('occ-tactic', 'purple-knight', 'fail', 'Tactic declaration sample'), finding('occ-ambiguous', 'maester', 'fail', 'Ambiguous technique sample')], run); expect(attachment(result, 'occ-relay')?.clusterIds).toEqual(['credential-relay-exposure']); expect(attachment(result, 'occ-relay')?.clusterConfidence).toBeUndefined(); expect(result.techniqueAssignments.map(item => item.occurrenceId)).toEqual(['occ-relay']); expect(attachment(result, 'occ-tactic')?.clusterIds).toEqual(['other-deviations']); expect(attachment(result, 'occ-ambiguous')?.clusterIds).toEqual(['other-deviations']); }); }); describe('email cluster, vocabulary 1.1.0 and rule families (2026-10-01)', () => { it('recognizes sender authentication, phishing protection and forwarding as the email cluster', () => { const result = build([ finding('occ-dkim', 'maester', 'fail', '(L1) Ensure that DKIM is enabled for all Exchange Online Domains'), finding('occ-forward', 'maester', 'fail', 'Automatic forwarding to external domains SHALL be disabled'), finding('occ-safe-links', 'maester', 'fail', 'Safe Links SHOULD be enabled for email messages'), finding('occ-phishing-mfa', 'maester', 'fail', 'Phishing-resistant MFA SHALL be enforced for all users') ]); for (const occurrence of ['occ-dkim', 'occ-forward', 'occ-safe-links']) { expect(tier(result, occurrence)).toEqual(['email-spoofing-phishing-exposure:recognized:title-term']); } // "Phishing-resistant MFA" is a sign-in control, not a mail filter. expect(tier(result, 'occ-phishing-mfa')).toEqual(['cloud-sign-in-protection-gaps:recognized:title-term']); }); it('estimates from a declared rule family when nothing else places the deviation', () => { const result = build([ finding('occ-family', 'maester', 'fail', 'Synthetic quarterly widget', { sourceRuleId: 'CIS.M365.2.9.9' }), finding('occ-teams', 'maester', 'fail', 'Synthetic widget review', { sourceRuleId: 'MS.TEAMS.9.9' }), finding('occ-other-tool', 'pingcastle', 'fail', 'Synthetic gadget', { sourceRuleId: 'CIS.M365.2.9.9' }) ]); expect(tier(result, 'occ-family')).toEqual(['email-spoofing-phishing-exposure:estimated:scanner-category']); expect(result.recognitions.find(item => item.occurrenceId === 'occ-family')?.matchedText).toContain('CIS M365 section 2'); expect(tier(result, 'occ-teams')).toEqual(['cloud-external-access-exposure:estimated:scanner-category']); // A family belongs to its tool; the same ID from another tool means nothing. expect(tier(result, 'occ-other-tool')).toEqual([]); }); it('never contradicts a catalogued Maester rule with its rule family', async () => { const { CONTEXT_RULE_FAMILIES, ruleFamilyOf } = await import('../../src/assessment-context/context-recognition'); const clustersOf = (conditionId: string) => catalog.clusters.filter(cluster => cluster.conditionIds.includes(conditionId)).map(cluster => cluster.id); const contradictions = catalog.scannerRules.flatMap(rule => { const family = ruleFamilyOf(rule.tool, rule.ruleId, CONTEXT_RULE_FAMILIES); return family && !clustersOf(rule.conditionId).includes(family.clusterId) ? [`${rule.ruleId} -> ${family.clusterId}`] : []; }); expect(contradictions).toEqual([]); expect(CONTEXT_RULE_FAMILIES.every(family => catalog.clusters.some(cluster => cluster.id === family.clusterId))).toBe(true); }); }); describe('complete record and GPO technical signals (2026-10-01)', () => { it('reads every original field of a finding: one term in a structured field, two terms in long text', () => { const result = buildAssessmentContext({ projectId: 'dummy-project', assessmentIds: ['dummy-assessment'], generatedAt: '2026-10-01T08:00:00Z', repositoryRevision: 1, observations: [], analyses: [], catalog, scannerFindings: [ finding('occ-tag', 'maester', 'fail', 'Alpha sample entry'), finding('occ-script', 'maester', 'fail', 'Beta probe record'), finding('occ-one-term-in-script', 'maester', 'fail', 'Gamma trial item') ], recordFields: [ { occurrenceId: 'occ-tag', fields: [{ label: 'Block', value: 'Maester/Exchange' }, { label: 'Tag', value: 'Maester, Exchange, MyCustomApps' }] }, { occurrenceId: 'occ-script', fields: [{ label: 'TestScript', value: `${'x'.repeat(300)} Get-Mailbox | Where DKIM signing disabled; mail flow rule allows external forwarding` }] }, { occurrenceId: 'occ-one-term-in-script', fields: [{ label: 'TestScript', value: `${'x'.repeat(300)} checks the DKIM selector` }] } ] }); expect(tier(result, 'occ-tag')).toEqual(['cloud-app-consent-exposure:recognized:title-term']); expect(result.recognitions.find(item => item.occurrenceId === 'occ-tag')?.matchedText).toBe('Tag: MyCustomApps'); expect(tier(result, 'occ-script')).toEqual(['email-spoofing-phishing-exposure:recognized:description-terms']); // One term in a long script is not enough. expect(tier(result, 'occ-one-term-in-script')).toEqual([]); }); it('uses the registry path and Microsoft baseline template names of a GPO setting', () => { const evaluation = (identity: string, title: string) => ({ resultProjectionId: `policy-evaluation:${identity}`, sourceKind: 'policy-evaluation', sourceResultId: identity, policyScope: 'computer', policyIdentityKey: identity, isDeviation: true, outcomeNormalized: 'fail', title }) as unknown as UnifiedResultProjection; const result = buildAssessmentContext({ projectId: 'dummy-project', assessmentIds: ['dummy-assessment'], generatedAt: '2026-10-01T08:00:00Z', repositoryRevision: 1, observations: [], analyses: [], catalog, evaluations: [evaluation('identity-power', 'ACSettingIndex'), evaluation('identity-template', 'SyntheticValueName')], identityTargets: [ { id: 'identity-power', scope: 'computer', target: { kind: 'registry-value', hive: 'HKEY_LOCAL_MACHINE', key: 'SOFTWARE\\Policies\\Microsoft\\Power\\PowerSettings\\00000000-0000-0000-0000-000000000000', valueName: 'ACSettingIndex' } }, { id: 'identity-template', scope: 'computer', target: { kind: 'registry-value', hive: 'HKEY_LOCAL_MACHINE', key: 'SOFTWARE\\Policies\\Synthetic', valueName: 'SyntheticValueName' } } ], baselineTemplates: [{ identityKey: 'identity-template', names: ['MSFT Windows 11 - BitLocker'] }] }); const settingTier = (identity: string) => result.recognitions.filter(item => item.identityKey === identity).map(item => `${item.clusterId}:${item.confidence}:${item.method}`); expect(settingTier('identity-power')).toEqual(['physical-local-access-exposure:recognized:technical-anchor']); expect(settingTier('identity-template')).toEqual(['physical-local-access-exposure:recognized:technical-anchor']); expect(result.recognitions.find(item => item.identityKey === 'identity-template')?.matchedText).toBe('BitLocker'); expect(result.settings.find(item => item.identityKey === 'identity-power')).toMatchObject({ clusterIds: ['physical-local-access-exposure'] }); }); }); describe('everything Raw Data shows for a GPO setting (2026-10-01)', () => { it('reads the structured fields of the setting record when name and path carry no signal', () => { const evaluation = { resultProjectionId: 'policy-evaluation:identity-raw', sourceKind: 'policy-evaluation', sourceResultId: 'identity-raw', policyScope: 'computer', policyIdentityKey: 'identity-raw', isDeviation: true, outcomeNormalized: 'fail', title: 'SyntheticValueName' } as unknown as UnifiedResultProjection; const result = buildAssessmentContext({ projectId: 'dummy-project', assessmentIds: ['dummy-assessment'], generatedAt: '2026-10-01T08:00:00Z', repositoryRevision: 1, observations: [], analyses: [], catalog, evaluations: [evaluation], settingRecordFields: [{ identityKey: 'identity-raw', scope: 'computer', fields: [ { label: 'Title', value: 'SyntheticValueName' }, { label: 'Baseline', value: 'Synthetic Security Baseline — MSFT Synthetic - BitLocker' }] }] }); expect(result.recognitions.map(item => [item.clusterId, item.confidence, item.method, item.matchedText])).toEqual([ ['physical-local-access-exposure', 'recognized', 'title-term', 'Baseline: BitLocker']]); }); }); describe('Raw Data read in the backend run (2026-10-01)', () => { it('reads Raw Data only when needed and keeps the context when that read fails', async () => { const { createAssessmentContext } = await import('../../src/assessment-context/context-run'); const uncatalogued = finding('occ-raw', 'maester', 'fail', 'Alpha sample entry'); const port = (loadAssessmentModel: () => Promise) => ({ currentRevision: async () => 1, queryUnifiedResults: async (query?: { sourceKind?: string[] }) => { const items = query?.sourceKind?.includes('scanner-finding') ? [uncatalogued] : []; return { items, total: items.length, offset: 0, limit: 500 }; }, listGpoPolicyAnalyses: async () => [], listReferenceRuns: async () => [], listPolicyEvaluations: async () => [], getEnvironmentSnapshot: async () => ({ sections: [] }), listAssessments: async () => [{ id: 'dummy-assessment', projectId: 'dummy-project', name: 'Dummy' }], loadAssessmentModel }); const scope = { projectId: 'dummy-project', assessmentIds: ['dummy-assessment'], generatedAt: '2026-10-01T08:00:00Z' }; const failing = await createAssessmentContext(port(async () => { throw new Error('pool exhausted'); }) as never, scope); expect(failing.rawDataRead).toBe(false); expect(failing.findings.find(item => item.occurrenceId === 'occ-raw')?.clusterIds).toEqual(['other-deviations']); }); }); describe('registry policy areas of the Microsoft baselines (2026-10-01)', () => { const path = (...segments: string[]) => segments.join(String.fromCharCode(92)); it('recognizes Internet Explorer security zone actions as malicious code execution from the registry path', () => { const evaluation = { resultProjectionId: 'policy-evaluation:identity-zone', sourceKind: 'policy-evaluation', sourceResultId: 'identity-zone', policyScope: 'computer', policyIdentityKey: 'identity-zone', isDeviation: true, outcomeNormalized: 'fail', title: '1201' } as unknown as UnifiedResultProjection; const result = buildAssessmentContext({ projectId: 'dummy-project', assessmentIds: ['dummy-assessment'], generatedAt: '2026-10-01T08:00:00Z', repositoryRevision: 1, observations: [], analyses: [], catalog, evaluations: [evaluation], identityTargets: [{ id: 'identity-zone', scope: 'computer', target: { kind: 'registry-value', hive: 'HKEY_LOCAL_MACHINE', key: path('Software', 'Policies', 'Microsoft', 'Windows', 'CurrentVersion', 'Internet Settings', 'Zones', '2'), valueName: '1201' } }] }); expect(result.recognitions.map(item => [item.clusterId, item.confidence, item.method])).toEqual([ ['malicious-code-execution-exposure', 'recognized', 'technical-anchor']]); }); it('agrees with every registry target the catalogue assigns, and never maps a whole product tree', async () => { const { recognizeClusters } = await import('../../src/assessment-context/context-recognition'); const clustersOf = (conditionId: string) => catalog.clusters.filter(cluster => cluster.conditionIds.includes(conditionId)).map(cluster => cluster.id); const targets = catalog.gpoVerdictRules.flatMap(rule => { const target = rule.target as { mechanism: string; key?: string; valueName?: string }; return target.mechanism === 'registry-key' && target.key ? [{ path: [target.key, target.valueName].filter(Boolean).join(String.fromCharCode(92)), clusters: clustersOf(rule.conditionId) }] : []; }); expect(targets.length).toBeGreaterThan(5); const contradictions = targets.flatMap(item => recognizeClusters(item.path) .filter(match => !item.clusters.includes(match.clusterId)).map(match => `${item.path} -> ${match.clusterId}`)); expect(contradictions).toEqual([]); // The catalogue places an Office setting under host credentials; an Office path alone therefore says nothing. expect(recognizeClusters(path('Software', 'Policies', 'Microsoft', 'Office', '16.0', 'Common', 'SyntheticSetting'))).toEqual([]); }); });