import { createHash } from 'node:crypto'; import { mkdtemp, rm, writeFile } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { afterEach, describe, expect, it } from 'vitest'; import { loadPolicyEvaluationRuleCatalog, MAXIMUM_POLICY_RULE_CATALOG_BYTES } from '../../server/gpo/policy-evaluation-rule-catalog-loader'; import { POLICY_EVALUATION_RULE_CATALOG, RELEASED_POLICY_EVALUATION_RULE_CATALOG, type PolicyEvaluationRuleCatalog } from '../../src/domain/policy-evaluation-rules'; const directories: string[] = []; function syntheticCatalog(): PolicyEvaluationRuleCatalog { return { version: 1, id: 'synthetic-curated-policy-rules-1', catalog: POLICY_EVALUATION_RULE_CATALOG, status: 'released', rules: [{ version: 1, id: 'synthetic-rule-1', identityId: 'registry:computer:hklm:software\\policies\\synthetic:value', deliveryMechanism: 'registry-policy', contexts: [{ version: 1, productFamily: 'Synthetic Windows', productVersion: '1', roles: ['Member Server'], scopes: ['computer'] }], baselineDisposition: 'explicit-recommendation', comparator: { kind: 'equality' }, riskRule: { version: 1, id: 'synthetic-risk-1', severity: 'high' }, dependencies: [] }] }; } async function catalogFile(content: string): Promise<{ path: string; sha256: string }> { const directory = await mkdtemp(join(tmpdir(), 'assessor-policy-catalog-')); directories.push(directory); const path = join(directory, 'catalog.json'); await writeFile(path, content, 'utf8'); return { path, sha256: createHash('sha256').update(content, 'utf8').digest('hex') }; } afterEach(async () => { await Promise.all(directories.splice(0).map(directory => rm(directory, { recursive: true, force: true }))); }); describe('policy evaluation rule catalog loader', () => { it('keeps the safe empty released catalog when no operator file is configured', async () => { const loaded = await loadPolicyEvaluationRuleCatalog(); expect(loaded).toEqual(RELEASED_POLICY_EVALUATION_RULE_CATALOG); expect(loaded).not.toBe(RELEASED_POLICY_EVALUATION_RULE_CATALOG); }); it('loads a pinned, structurally and semantically valid curated catalog', async () => { const expected = syntheticCatalog(); const source = `\uFEFF${JSON.stringify(expected)}`; const file = await catalogFile(source); await expect(loadPolicyEvaluationRuleCatalog({ path: file.path, expectedSha256: file.sha256 })).resolves.toEqual(expected); }); it('fails closed for incomplete configuration, hash drift, malformed structure, and oversize input', async () => { const valid = await catalogFile(JSON.stringify(syntheticCatalog())); await expect(loadPolicyEvaluationRuleCatalog({ path: valid.path })) .rejects.toThrow('gemeinsam konfiguriert'); await expect(loadPolicyEvaluationRuleCatalog({ path: valid.path, expectedSha256: '0'.repeat(64) })).rejects.toThrow('SHA-256'); const malformed = await catalogFile(JSON.stringify({ version: 1, rules: [null] })); await expect(loadPolicyEvaluationRuleCatalog({ path: malformed.path, expectedSha256: malformed.sha256 })).rejects.toThrow('keine gueltige Struktur'); await expect(loadPolicyEvaluationRuleCatalog({ path: valid.path, expectedSha256: valid.sha256, maximumBytes: 1 })).rejects.toThrow('Groessengrenze'); await expect(loadPolicyEvaluationRuleCatalog({ path: valid.path, expectedSha256: valid.sha256, maximumBytes: MAXIMUM_POLICY_RULE_CATALOG_BYTES + 1 })).rejects.toThrow('Groessengrenze'); }); });