83 lines
3.5 KiB
JavaScript
83 lines
3.5 KiB
JavaScript
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { mkdtempSync, readFileSync } from 'node:fs';
|
|
import { tmpdir } from 'node:os';
|
|
import { join } from 'node:path';
|
|
import { SecurityProfileStore, sanitizeSecurityProfile } from '../lib/security/security-profile-store.mjs';
|
|
import { createTerminalToolRegistry } from '../lib/agent/terminal-tools.mjs';
|
|
|
|
const SECRET = 'test-only-security-profile-key-at-least-32-chars';
|
|
|
|
function profile() {
|
|
return {
|
|
language: 'de',
|
|
preferredName: 'Test Operator',
|
|
location: { country: 'Deutschland', region: 'NRW', city: 'Koeln' },
|
|
timezone: 'Europe/Berlin',
|
|
household: { adults: 2, children: 1, pets: 1 },
|
|
mobility: ['car', 'rail'],
|
|
riskPriorities: ['weather', 'cyber'],
|
|
criticalDependencies: ['electricity', 'internet'],
|
|
alertPreference: 'important',
|
|
quietHours: '22:00-07:00',
|
|
consentedAt: new Date().toISOString(),
|
|
};
|
|
}
|
|
|
|
test('security profile is encrypted at rest and reloads with the correct key', () => {
|
|
const path = join(mkdtempSync(join(tmpdir(), 'security-profile-')), 'profile.enc');
|
|
const store = new SecurityProfileStore(path, SECRET).init();
|
|
store.save(profile());
|
|
|
|
const raw = readFileSync(path, 'utf8');
|
|
assert.equal(raw.includes('Test Operator'), false);
|
|
assert.equal(raw.includes('Koeln'), false);
|
|
assert.equal(JSON.parse(raw).algorithm, 'aes-256-gcm');
|
|
|
|
const restored = new SecurityProfileStore(path, SECRET).init();
|
|
assert.equal(restored.getProfile().preferredName, 'Test Operator');
|
|
assert.equal(restored.getProfile().location.city, 'Koeln');
|
|
});
|
|
|
|
test('wrong key fails closed and cannot overwrite an existing profile', () => {
|
|
const path = join(mkdtempSync(join(tmpdir(), 'security-profile-')), 'profile.enc');
|
|
new SecurityProfileStore(path, SECRET).init().save(profile());
|
|
const wrong = new SecurityProfileStore(path, 'another-test-key-that-is-at-least-32-characters').init();
|
|
assert.equal(wrong.status().configured, true);
|
|
assert.equal(wrong.status().available, false);
|
|
assert.equal(wrong.getProfile(), null);
|
|
assert.throws(() => wrong.save(profile()));
|
|
});
|
|
|
|
test('profile sanitizer only retains allowlisted risk and dependency categories', () => {
|
|
const value = sanitizeSecurityProfile({
|
|
...profile(),
|
|
riskPriorities: ['weather', 'passwords'],
|
|
criticalDependencies: ['internet', 'bank-login'],
|
|
household: { adults: 999, children: -4, pets: 2 },
|
|
});
|
|
assert.deepEqual(value.riskPriorities, ['weather']);
|
|
assert.deepEqual(value.criticalDependencies, ['internet']);
|
|
assert.deepEqual(value.household, { adults: 20, children: 0, pets: 2 });
|
|
});
|
|
|
|
test('agent can read only the approved profile through the allowlisted tool', async () => {
|
|
const path = join(mkdtempSync(join(tmpdir(), 'security-profile-')), 'profile.enc');
|
|
const store = new SecurityProfileStore(path, SECRET).init();
|
|
store.save(profile());
|
|
const registry = createTerminalToolRegistry({ securityProfileStore: store });
|
|
const result = await registry.execute('get_security_profile');
|
|
assert.equal(result.available, true);
|
|
assert.equal(result.profile.location.country, 'Deutschland');
|
|
assert.equal(Object.hasOwn(result.profile, 'completedAt'), false);
|
|
});
|
|
|
|
test('deleting a security profile removes it from the store', () => {
|
|
const path = join(mkdtempSync(join(tmpdir(), 'security-profile-')), 'profile.enc');
|
|
const store = new SecurityProfileStore(path, SECRET).init();
|
|
store.save(profile());
|
|
store.delete();
|
|
assert.equal(store.exists, false);
|
|
assert.equal(store.getProfile(), null);
|
|
});
|