211 lines
10 KiB
TypeScript
211 lines
10 KiB
TypeScript
import { describe, expect, it, vi } from 'vitest';
|
|
import type { UnifiedResultProjection } from '../../src/domain/unified-result';
|
|
import {
|
|
buildUnifiedResultJsonl,
|
|
queryAllUnifiedResults,
|
|
RESULT_DB_EXPORT_HANDLING,
|
|
RESULT_DB_JSONL_FORMAT,
|
|
RESULT_DB_JSONL_SCHEMA_VERSION,
|
|
unifiedResultJsonlFileName
|
|
} from '../../src/exports/unified-result-jsonl';
|
|
|
|
function result(id: string): UnifiedResultProjection {
|
|
return {
|
|
resultProjectionId: id,
|
|
sourceKind: 'scanner-finding',
|
|
sourceResultId: `source-${id}`,
|
|
projectId: 'project-1',
|
|
assessmentId: 'assessment-1',
|
|
collectionRunId: 'scan-1',
|
|
scanRunId: 'scan-1',
|
|
title: `Synthetic result ${id}`,
|
|
severityOriginal: 'high',
|
|
severityNormalized: 'high',
|
|
outcomeOriginal: 'fail',
|
|
outcomeNormalized: 'fail',
|
|
technicalExecutionState: 'completed',
|
|
resultType: 'compliance_check',
|
|
isIoe: false,
|
|
isDeviation: true,
|
|
lifecycleState: 'unknown',
|
|
mappingConfidence: 1,
|
|
reviewStatus: 'unreviewed',
|
|
importedAt: '2026-09-03T10:00:00.000Z',
|
|
createdAt: '2026-09-03T10:00:00.000Z',
|
|
frameworkReferences: [],
|
|
categories: []
|
|
};
|
|
}
|
|
|
|
describe('filtered Result DB JSONL export', () => {
|
|
it('writes an explicit manifest followed by one complete JSON object per filtered result', () => {
|
|
const text = buildUnifiedResultJsonl({
|
|
projectId: 'project-1', scanRunIds: ['scan-1'], deviation: true,
|
|
severity: ['critical', 'high'], search: 'synthetic', offset: 50, limit: 50
|
|
}, [result('result-1'), result('result-2')], '2026-09-03T12:34:56.789Z');
|
|
const lines = text.trimEnd().split('\n').map(line => JSON.parse(line));
|
|
|
|
expect(lines[0]).toEqual({
|
|
recordType: 'manifest',
|
|
format: RESULT_DB_JSONL_FORMAT,
|
|
schemaVersion: RESULT_DB_JSONL_SCHEMA_VERSION,
|
|
disclosurePolicyVersion: '3.6.0',
|
|
handling: RESULT_DB_EXPORT_HANDLING,
|
|
generatedOn: '2026-09-03',
|
|
resultCount: 2,
|
|
scope: { projectScoped: true, assessmentScopeCount: 0, scanScopeCount: 1, evaluationScoped: false },
|
|
filters: { deviation: true, severity: { active: true, selectionCount: 2 } }
|
|
});
|
|
expect(lines.slice(1)).toHaveLength(2);
|
|
expect(lines[1]).toMatchObject({ recordType: 'result', position: 1,
|
|
result: { title: 'Security assessment finding', severityNormalized: 'high', outcomeNormalized: 'fail' } });
|
|
expect(text).not.toMatch(/project-1|assessment-1|scan-1|source-result-1|synthetic/i);
|
|
expect(text.endsWith('\n')).toBe(true);
|
|
expect(unifiedResultJsonlFileName('2026-09-03T12:34:56.789Z'))
|
|
.toBe('assessor-result-db-filtered-2026-09-03.jsonl');
|
|
expect(text).not.toContain('12:34');
|
|
});
|
|
|
|
it('loads every filtered page with the same scope and rejects a changing result set', async () => {
|
|
const all = [result('result-1'), result('result-2'), result('result-3')];
|
|
const reader = vi.fn(async query => ({
|
|
total: all.length,
|
|
items: all.slice(query.offset ?? 0, (query.offset ?? 0) + (query.limit ?? all.length))
|
|
}));
|
|
|
|
await expect(queryAllUnifiedResults(reader, {
|
|
projectId: 'project-1', scanRunIds: ['scan-1'], deviation: true, offset: 99, limit: 1
|
|
}, 2)).resolves.toEqual(all);
|
|
expect(reader).toHaveBeenNthCalledWith(1, {
|
|
projectId: 'project-1', scanRunIds: ['scan-1'], deviation: true, offset: 0, limit: 2
|
|
});
|
|
expect(reader).toHaveBeenNthCalledWith(2, {
|
|
projectId: 'project-1', scanRunIds: ['scan-1'], deviation: true, offset: 2, limit: 2
|
|
});
|
|
|
|
let call = 0;
|
|
await expect(queryAllUnifiedResults(async query => {
|
|
call += 1;
|
|
return { total: call === 1 ? 2 : 3, items: [result(`result-${query.offset ?? 0}`)] };
|
|
}, { deviation: true }, 1)).rejects.toThrow('changed during export');
|
|
});
|
|
|
|
it('exports only the already disclosed supplemental projection', () => {
|
|
const supplemental: UnifiedResultProjection = {
|
|
...result('safe-environment'),
|
|
sourceKind: 'environment-observation',
|
|
sourceTool: 'pingcastle',
|
|
title: 'Domain functional level',
|
|
environmentProperty: 'domain.functional_level',
|
|
environmentSubjectType: 'active-directory-environment',
|
|
environmentNormalizedValue: 2016,
|
|
supplementalDisclosurePolicyVersion: '1.0.0',
|
|
supplementalDisclosureClass: 'technical-metadata',
|
|
affectedAsset: 'Private Customer',
|
|
environmentOriginalValue: 'Private Customer',
|
|
environmentSubjectIdentity: 'private.synthetic.example',
|
|
environmentAttributes: { customer: 'Private Customer' },
|
|
sourceLocator: { objectPath: 'private/customer/path' }
|
|
};
|
|
const text = buildUnifiedResultJsonl({}, [supplemental], '2026-09-03T12:34:56.789Z');
|
|
|
|
expect(text).toContain('"environmentNormalizedValue":2016');
|
|
expect(text).toContain('"supplementalDisclosurePolicyVersion":"1.0.0"');
|
|
expect(text).not.toMatch(/environmentOriginalValue|environmentSubjectIdentity|sourceLocator|Private Customer/i);
|
|
});
|
|
|
|
it('rejects an unclassified supplemental record instead of exporting it', () => {
|
|
const unclassified: UnifiedResultProjection = {
|
|
...result('unsafe-environment'),
|
|
sourceKind: 'environment-observation',
|
|
environmentOriginalValue: 'Private Customer'
|
|
};
|
|
|
|
expect(() => buildUnifiedResultJsonl({}, [unclassified], '2026-09-03T12:34:56.789Z'))
|
|
.toThrow('unclassified supplemental result');
|
|
});
|
|
|
|
it('does not export personal, company, scope or provenance values from ordinary findings', () => {
|
|
const unsafe = {
|
|
...result('private-result'), sourceTool: 'maester', sourceRuleId: 'MT.1024.applicationCredentialExpiry',
|
|
title: 'Legacy authentication permits person@private-company.example', description: 'Private Company GmbH',
|
|
recommendation: 'Contact Jane Doe', affectedAsset: 'private-company.example',
|
|
reviewOwner: 'Jane Doe', sourceLocator: { objectPath: 'private/company/path' }
|
|
} satisfies UnifiedResultProjection;
|
|
const text = buildUnifiedResultJsonl({
|
|
projectId: 'private-company', scanRunIds: ['private-run'], search: 'person@private-company.example'
|
|
}, [unsafe], '2026-09-03T12:34:56.789Z');
|
|
|
|
expect(text).toContain('Legacy authentication permits [email]');
|
|
expect(text).toContain('MT.1024.applicationCredentialExpiry');
|
|
expect(text).toContain('[organization]');
|
|
expect(text).not.toMatch(/person@|Private Company|Jane Doe|private-company|private-run|private\/company\/path/i);
|
|
});
|
|
|
|
it('never exports client-owned review, priority or risk-acceptance state', () => {
|
|
const reviewed = {
|
|
...result('synthetic-a'), sourceTool: 'purple-knight', sourceRuleId: 'SI000055',
|
|
reviewStatus: 'open', reviewDisposition: 'accepted', reviewPriority: 'high',
|
|
reviewOwner: 'Synthetic Owner', reviewDueDate: '2026-10-01'
|
|
} satisfies UnifiedResultProjection;
|
|
const text = buildUnifiedResultJsonl({ reviewDisposition: ['accepted'] }, [reviewed], '2026-09-28T10:00:00.000Z');
|
|
const [manifest, line] = text.trimEnd().split('\n').map(item => JSON.parse(item));
|
|
|
|
expect(manifest.handling).toEqual({ classification: 'confidential-security-posture', notice: expect.any(String) });
|
|
expect(manifest.filters).toEqual({});
|
|
expect(line.result.sourceRuleId).toBe('SI000055');
|
|
expect(text).not.toMatch(/review|priority|accepted|Synthetic Owner|2026-10-01/i);
|
|
});
|
|
|
|
it('drops fields that the disclosure policy has not classified', () => {
|
|
const extended = { ...result('extended'), futureCustomerField: 'Synthetic Customer' } as UnifiedResultProjection;
|
|
const text = buildUnifiedResultJsonl({}, [extended], '2026-09-28T10:00:00.000Z');
|
|
|
|
expect(text).not.toMatch(/futureCustomerField|Synthetic Customer/);
|
|
});
|
|
|
|
it('exports the GPO setting, Soll value and baseline family without customer values', () => {
|
|
const policy = {
|
|
...result('policy-result'), sourceKind: 'policy-evaluation' as const, sourceTool: 'GPOs',
|
|
sourceRuleId: 'registry:machine:software\\policies\\synthetic:mode', title: 'Synthetic policy mode',
|
|
policyIdentityKey: 'private-policy-hash', policyGpoId: '{A1B2C3D4-1111-2222-3333-444455556666}',
|
|
policyBaselineLabel: 'DUMMY Microsoft Security Baseline Windows Server 2022 v1',
|
|
policyActualValue: { operation: 'set-value', data: { kind: 'dword', value: 0 },
|
|
principals: ['BUILTIN\\Administrators', 'DUMMY\\Domain Admins', 'DUMMY\\named.user',
|
|
'S-1-5-21-123456789-234567890-345678901-512'] },
|
|
policyBaselineValue: { operation: 'set-value', data: { kind: 'dword', value: 1 } },
|
|
policyClassification: 'misconfigured' as const
|
|
} satisfies UnifiedResultProjection;
|
|
|
|
const text = buildUnifiedResultJsonl({}, [policy], '2026-09-23T10:00:00.000Z');
|
|
expect(text).toContain('Synthetic policy mode');
|
|
expect(text).toContain('registry:machine:software');
|
|
expect(text).toContain('policyBaselineValue');
|
|
expect(text).toContain('"policyBaselineLabel":"Microsoft Security Baseline - Windows Server"');
|
|
expect(text).not.toContain('policyActualValue');
|
|
expect(text).not.toMatch(/Administrators|Domain Admins|PRINCIPAL|S-1-5-21|2022/);
|
|
expect(text).not.toMatch(/DUMMY|named\.user|123456789|A1B2C3D4|private-policy-hash/);
|
|
});
|
|
|
|
it('exports the GPO Soll value and public CIS guidance without the customer value or a system SID', () => {
|
|
const systemSid = 'S-1-5-82-123456789-234567890-345678901-456789012-567890123';
|
|
const cis = { ...result('cis'), sourceTool: 'cis-cat', sourceRuleId: '18.10.36.1',
|
|
title: "18.10.36.1 Ensure 'Turn off location' is set to 'Enabled'",
|
|
recommendation: 'Computer Configuration\\Administrative Templates\\System\\Location and Sensors\\Turn off location'
|
|
} satisfies UnifiedResultProjection;
|
|
const gpo = { ...result('gpo'), sourceKind: 'policy-evaluation' as const, sourceTool: 'GPOs',
|
|
title: 'System Access · LockoutBadCount',
|
|
policyActualValue: { kind: 'text', value: '5', principals: [systemSid] },
|
|
policyBaselineValue: { kind: 'text', value: '10' }
|
|
} satisfies UnifiedResultProjection;
|
|
|
|
const exported = buildUnifiedResultJsonl({}, [cis, gpo], '2026-09-25T10:00:00.000Z');
|
|
expect(exported).toContain('18.10.36.1');
|
|
expect(exported).toContain('Computer Configuration\\\\Administrative Templates');
|
|
expect(exported).toContain('"value":"10"');
|
|
expect(exported).not.toContain('"value":"5"');
|
|
expect(exported).not.toContain('PRINCIPAL');
|
|
expect(exported).not.toContain(systemSid);
|
|
});
|
|
});
|