126 lines
6.4 KiB
TypeScript
126 lines
6.4 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
|
import { buildFindingPathItems, defaultMitreGraphData, extractFindingMitreFocus, sharedEntityIds, validateMitreMatrixData, type MitreEntity, type MitreGraphData } from '../../src/features/mitre-graph/mitre-graph';
|
|
import { convertEnterpriseStix } from '../../src/features/mitre-graph/mitre-stix';
|
|
import type { FindingRecord } from '../../src/domain/finding';
|
|
|
|
const record = (value: string): FindingRecord => ({
|
|
id: value,
|
|
index: 0,
|
|
title: 'Finding',
|
|
fields: [{ label: 'MITRE ATT&CK', value, index: 0 }],
|
|
filled: [],
|
|
meta: []
|
|
});
|
|
|
|
describe('MITRE graph', () => {
|
|
it('ships the current Enterprise catalog with the new Stealth tactic', () => {
|
|
expect(defaultMitreGraphData.version).toBe('19.2');
|
|
expect(defaultMitreGraphData.tacticOrder).toContain('Stealth');
|
|
expect(defaultMitreGraphData.techniques.find(technique => technique.id === 'T1055.011')?.tactics).toContain('Stealth');
|
|
});
|
|
|
|
it('converts an official-style STIX bundle, excluding revoked techniques and relations', () => {
|
|
const raw = { type: 'bundle', objects: [
|
|
{ type: 'x-mitre-collection', name: 'Enterprise ATT&CK', x_mitre_version: '19.2' },
|
|
{ id: 'matrix', type: 'x-mitre-matrix', name: 'Enterprise ATT&CK', tactic_refs: ['tactic'] },
|
|
{ id: 'tactic', type: 'x-mitre-tactic', name: 'Stealth', x_mitre_shortname: 'stealth' },
|
|
{ id: 'pattern', type: 'attack-pattern', name: 'Synthetic technique', external_references: [{ source_name: 'mitre-attack', external_id: 'T9999', url: 'https://attack.mitre.org/techniques/T9999' }], kill_chain_phases: [{ kill_chain_name: 'mitre-attack', phase_name: 'stealth' }] },
|
|
{ id: 'old', type: 'attack-pattern', name: 'Old', revoked: true, external_references: [{ source_name: 'mitre-attack', external_id: 'T9998' }], kill_chain_phases: [{ kill_chain_name: 'mitre-attack', phase_name: 'stealth' }] },
|
|
{ id: 'group', type: 'intrusion-set', name: 'Synthetic group', external_references: [{ source_name: 'mitre-attack', external_id: 'G9999' }] },
|
|
{ type: 'relationship', relationship_type: 'uses', source_ref: 'group', target_ref: 'pattern' },
|
|
{ type: 'relationship', relationship_type: 'uses', source_ref: 'group', target_ref: 'old' }
|
|
] };
|
|
const converted = convertEnterpriseStix(raw);
|
|
expect(converted).toEqual({
|
|
version: '19.2', tacticOrder: ['Stealth'],
|
|
techniques: [{ id: 'T9999', name: 'Synthetic technique', tactics: ['Stealth'], platforms: [], parent: null, url: 'https://attack.mitre.org/techniques/T9999', sourceCount: 1 }],
|
|
entities: [{ id: 'G9999', name: 'Synthetic group', type: 'group', techniques: ['T9999'] }]
|
|
});
|
|
expect(validateMitreMatrixData(raw)).toEqual(converted);
|
|
});
|
|
|
|
it('rejects unrelated STIX bundles and incomplete compact files', () => {
|
|
expect(() => convertEnterpriseStix({ type: 'bundle', objects: [] })).toThrow('Enterprise ATT&CK');
|
|
expect(() => validateMitreMatrixData({ version: '19.2', tacticOrder: ['Stealth'], techniques: [] })).toThrow('techniques');
|
|
});
|
|
it('erkennt Technik-IDs und Taktiknamen aus Findings', () => {
|
|
const focus = extractFindingMitreFocus(
|
|
[record('T1059.001; Persistence / Privilege Escalation')],
|
|
['Persistence', 'Privilege Escalation']
|
|
);
|
|
|
|
expect([...focus.techniqueIds]).toEqual(['T1059.001']);
|
|
expect([...focus.tactics]).toEqual(['Persistence', 'Privilege Escalation']);
|
|
expect(focus.findingCount).toBe(1);
|
|
});
|
|
|
|
it('ordnet Findings als Pfadschritte einer primären Taktik zu', () => {
|
|
const data: MitreGraphData = {
|
|
version: 'test',
|
|
tacticOrder: ['Execution', 'Persistence'],
|
|
techniques: [{ id: 'T1059.001', name: 'PowerShell', tactics: ['Execution'], platforms: ['Windows'], parent: 'T1059', url: '', sourceCount: 1 }],
|
|
entities: []
|
|
};
|
|
const item = buildFindingPathItems([record('T1059.001, Persistence')], data)[0];
|
|
|
|
expect(item.primaryTactic).toBe('Execution');
|
|
expect(item.tactics).toEqual(['Execution', 'Persistence']);
|
|
expect(item.techniqueIds).toEqual(['T1059.001']);
|
|
});
|
|
|
|
it('bewahrt mehrere Kategorien und D3FEND-Beziehungen für den Netzwerkgraphen', () => {
|
|
const source = record('Execution / Persistence');
|
|
source.fields.push(
|
|
{ label: 'Category', value: 'Identity | Lateral Movement', index: 1 },
|
|
{ label: 'MITRE D3FEND', value: 'Harden - Credential Hardening; Detect - Account Monitoring', index: 2 },
|
|
{ label: 'Target', value: 'DC-01 | Tier 0', index: 3 }
|
|
);
|
|
const data: MitreGraphData = {
|
|
version: 'test',
|
|
tacticOrder: ['Execution', 'Persistence'],
|
|
techniques: [],
|
|
entities: []
|
|
};
|
|
|
|
const item = buildFindingPathItems([source], data)[0];
|
|
|
|
expect(item.categories).toEqual(['Identity', 'Lateral Movement']);
|
|
// Only exact catalog entries form relations; the old unchecked Account Monitoring text remains in the source field.
|
|
expect(item.defenses).toEqual(['D3-CH · Credential Hardening']);
|
|
expect(source.fields[2].value).toContain('Account Monitoring');
|
|
expect(item.targets).toEqual(['DC-01', 'Tier 0']);
|
|
expect(item.tactics).toEqual(['Execution', 'Persistence']);
|
|
});
|
|
|
|
it('erzeugt keine Kategorie aus dem Quellblatt oder All-Records-Kontext', () => {
|
|
const source = { ...record('Execution'), sourceSheet: 'Dummy · All Records' };
|
|
const data: MitreGraphData = { version: 'test', tacticOrder: ['Execution'], techniques: [], entities: [] };
|
|
|
|
const item = buildFindingPathItems([source], data)[0];
|
|
|
|
expect(item.category).toBe('');
|
|
expect(item.categories).toEqual([]);
|
|
expect(item.targets).toEqual([]);
|
|
});
|
|
|
|
it('leitet CIS-Kategorien aus allen Eltern einer Benchmark-Regelnummer ab', () => {
|
|
const source = record('');
|
|
source.fields.push({ label: 'Rule ID', value: '17.2.4', index: 1 });
|
|
const data: MitreGraphData = { version: 'test', tacticOrder: [], techniques: [], entities: [] };
|
|
|
|
const item = buildFindingPathItems([source], data, '', 'cis-cat')[0];
|
|
|
|
expect(item.categories).toEqual(['CIS 17', 'CIS 17.2']);
|
|
});
|
|
|
|
it('ermittelt gemeinsame MITRE-Kontexte nach Typ', () => {
|
|
const entities: MitreEntity[] = [
|
|
{ id: 'G1', name: 'Group', type: 'group', techniques: ['T1', 'T2'] },
|
|
{ id: 'S1', name: 'Software', type: 'software', techniques: ['T1', 'T2'] }
|
|
];
|
|
|
|
expect(sharedEntityIds('T1', 'T2', entities, 'all')).toEqual(['G1', 'S1']);
|
|
expect(sharedEntityIds('T1', 'T2', entities, 'group')).toEqual(['G1']);
|
|
});
|
|
});
|