Files
sencho/backend/src/__tests__/database-policies-for-ui.test.ts
T
Anso a284732a95 feat(fleet-sync): hide other replicas' identity-scoped policies on a replica (#973)
GET /api/security/policies on a replica now returns only the policies
that apply to THIS replica. Replicated rows with a node_identity
targeting a sibling replica are filtered out so an operator cannot
enumerate the names and rules of policies meant for another node in
the fleet. Defense in depth: the security panel is admin-only, but a
backend filter is bypass-proof and matches Sencho's privacy posture.

Internal evaluators (getMatchingPolicy, evaluateScanAgainstPolicies)
keep using the unfiltered list because they already enforce identity
matching at evaluation time.

CVE suppressions are fleet-wide on every replica (no node_identity
column) so no analogous filter is needed.

Public surface:
- DatabaseService.getScanPoliciesForUi(role, selfIdentity): the
  filtered variant, called from securityRouter.get('/policies').

Tests:
- 3 new vitest cases: control sees full set; replica hides
  other-replica scoped rows; replica always includes locally created
  rows.
- Full backend suite: 1795 pass / 5 skipped.
2026-05-07 13:48:11 -04:00

111 lines
3.9 KiB
TypeScript

/**
* Pins the replica-side filtering of `getScanPoliciesForUi`.
*
* On a replica the security-settings panel must only render policies that
* apply to that replica. Replicated rows with a node_identity targeting a
* sibling replica are filtered out so an operator cannot enumerate other
* replicas' identity-scoped rules.
*/
import { describe, it, expect, beforeAll, afterAll, beforeEach } from 'vitest';
import { setupTestDb, cleanupTestDb } from './helpers/setupTestDb';
let tmpDir: string;
let DatabaseService: typeof import('../services/DatabaseService').DatabaseService;
beforeAll(async () => {
tmpDir = await setupTestDb();
({ DatabaseService } = await import('../services/DatabaseService'));
});
afterAll(() => {
cleanupTestDb(tmpDir);
});
beforeEach(() => {
const db = DatabaseService.getInstance();
// Wipe all replicated and local rows between tests so each scenario starts clean.
db.clearReplicatedRows();
for (const p of db.getScanPolicies()) {
db.deleteScanPolicy(p.id);
}
});
function seedFleetWideReplicated(name: string): void {
DatabaseService.getInstance().replaceReplicatedScanPolicies([
{
id: 0,
name,
node_id: null,
node_identity: '',
stack_pattern: '*',
max_severity: 'HIGH',
block_on_deploy: 0,
enabled: 1,
replicated_from_control: 1,
created_at: Date.now(),
updated_at: Date.now(),
},
]);
}
function seedReplicaScopedReplicated(name: string, nodeIdentity: string): void {
const db = DatabaseService.getInstance();
// Append-style: read existing replicated rows + the new scoped one.
const existing = db.getScanPolicies().filter((p) => p.replicated_from_control === 1);
db.replaceReplicatedScanPolicies([
...existing,
{
id: 0,
name,
node_id: null,
node_identity: nodeIdentity,
stack_pattern: '*',
max_severity: 'CRITICAL',
block_on_deploy: 0,
enabled: 1,
replicated_from_control: 1,
created_at: Date.now(),
updated_at: Date.now(),
},
]);
}
describe('getScanPoliciesForUi', () => {
it('returns the full set on a control instance', () => {
const db = DatabaseService.getInstance();
seedFleetWideReplicated('fleet-wide');
seedReplicaScopedReplicated('targets-other', 'https://other.example');
const result = db.getScanPoliciesForUi('control', 'local');
expect(result.map((p) => p.name).sort()).toEqual(['fleet-wide', 'targets-other']);
});
it('hides identity-scoped replicated rows that target a different replica', () => {
const db = DatabaseService.getInstance();
seedFleetWideReplicated('fleet-wide');
seedReplicaScopedReplicated('targets-other', 'https://other.example');
seedReplicaScopedReplicated('targets-self', 'https://me.example');
const result = db.getScanPoliciesForUi('replica', 'https://me.example');
const names = result.map((p) => p.name).sort();
expect(names).toEqual(['fleet-wide', 'targets-self']);
});
it('always includes locally created rows on a replica', () => {
const db = DatabaseService.getInstance();
seedReplicaScopedReplicated('targets-other', 'https://other.example');
db.createScanPolicy({
name: 'local-on-replica',
node_id: null,
node_identity: '',
stack_pattern: null,
max_severity: 'CRITICAL',
block_on_deploy: 0,
enabled: 1,
replicated_from_control: 0,
});
const result = db.getScanPoliciesForUi('replica', 'https://me.example');
const names = result.map((p) => p.name).sort();
expect(names).toContain('local-on-replica');
expect(names).not.toContain('targets-other');
});
});