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.
This commit is contained in:
Anso
2026-05-07 13:48:11 -04:00
committed by GitHub
parent 4007709590
commit a284732a95
3 changed files with 143 additions and 1 deletions
+6 -1
View File
@@ -410,7 +410,12 @@ securityRouter.get(
securityRouter.get('/policies', authMiddleware, (req: Request, res: Response): void => {
if (!requirePaid(req, res)) return;
res.json(DatabaseService.getInstance().getScanPolicies());
// Replicas see only policies that apply to themselves: local-only rows plus
// fleet-wide and self-identity-matched replicated rows. Identity-scoped
// rows targeting other replicas are filtered out at the SQL boundary.
const policies = DatabaseService.getInstance()
.getScanPoliciesForUi(FleetSyncService.getRole(), FleetSyncService.getSelfIdentity());
res.json(policies);
});
securityRouter.post('/policies', authMiddleware, (req: Request, res: Response): void => {