mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-09-04 06:35:29 +00:00
fix(fleet-sync): hygiene pass on receiver behavior and cleanup (#972)
A bundle of small file-local fixes to the receiver path and node-deletion flow. Changes: - F4 receiver audit log: applyIncomingSync now writes a system audit entry on every applied push so mirrored security-rule changes show up in the replica's audit panel with a clear control-side origin. - F7 pilot-agent skip: pushResource explicitly excludes pilot-agent nodes (they have no api_url for HTTP push) and warns once per node id so the operator sees they will not receive replicated policies. - B4 identity-drift notification: when targetIdentity differs from the cached fleet_self_identity, dispatch a warning so the operator can audit any identity-scoped policies that may need re-targeting. - B6 stack_pattern ReDoS guard: reject patterns with 4+ consecutive wildcards or more than 8 wildcards total. Both control-side validators (POST/PUT scan policies) and the receiver-side row validator share the helper. - B9 deleteNode cascade: clear fleet_sync_status rows for the node inside the existing transaction so the sync-status panel does not render ghost entries after a node is removed. - S6 last_error redaction: formatError strips Bearer tokens and JWT-shaped values from error messages and caps at 500 chars before storing in fleet_sync_status.last_error or logging. Tests: - 8 new vitest cases covering audit-log entry, identity-drift alert, pilot-agent warn-once, formatError redaction (Bearer + JWT), ReDoS validator rejection, and a backtracking-time smoke test. - New database-fleet-sync-cascade.test.ts: deleteNode removes fleet_sync_status rows for the deleted node and leaves siblings untouched. - Full backend suite: 1792 pass / 5 skipped.
This commit is contained in:
@@ -274,6 +274,47 @@ describe('POST /api/fleet/role/reanchor', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('POST /api/fleet/sync/:resource stack_pattern ReDoS guard', () => {
|
||||
it('rejects 4+ consecutive wildcards in a stack_pattern', async () => {
|
||||
const res = await request(app)
|
||||
.post('/api/fleet/sync/scan_policies')
|
||||
.set('Authorization', nodeProxyAuthHeader)
|
||||
.send({
|
||||
rows: [{
|
||||
name: 'redos', node_identity: '', stack_pattern: 'foo****bar',
|
||||
max_severity: 'CRITICAL', block_on_deploy: 0, enabled: 1,
|
||||
}],
|
||||
});
|
||||
expect(res.status).toBe(400);
|
||||
expect(res.body.error).toMatch(/4\+ consecutive wildcards/);
|
||||
});
|
||||
|
||||
it('rejects more than 8 wildcards anywhere in a stack_pattern', async () => {
|
||||
const res = await request(app)
|
||||
.post('/api/fleet/sync/scan_policies')
|
||||
.set('Authorization', nodeProxyAuthHeader)
|
||||
.send({
|
||||
rows: [{
|
||||
name: 'too-many-stars', node_identity: '',
|
||||
stack_pattern: '*a*b*c*d*e*f*g*h*i*',
|
||||
max_severity: 'CRITICAL', block_on_deploy: 0, enabled: 1,
|
||||
}],
|
||||
});
|
||||
expect(res.status).toBe(400);
|
||||
expect(res.body.error).toMatch(/too many wildcards/);
|
||||
});
|
||||
|
||||
it('regex compiled from a max-allowed pattern matches in under 50ms on a worst-case input', () => {
|
||||
const pattern = 'a*b*c*d*e*f*g*h'; // 7 stars, allowed.
|
||||
const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, '\\$&').replace(/\*/g, '.*');
|
||||
const re = new RegExp('^' + escaped + '$');
|
||||
const target = 'a' + 'a'.repeat(2000);
|
||||
const start = Date.now();
|
||||
re.test(target);
|
||||
expect(Date.now() - start).toBeLessThan(50);
|
||||
});
|
||||
});
|
||||
|
||||
describe('POST /api/fleet/role/demote', () => {
|
||||
it('returns 401 without auth', async () => {
|
||||
const res = await request(app).post('/api/fleet/role/demote').send({ confirm: true });
|
||||
|
||||
Reference in New Issue
Block a user