mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-09-01 21:27:58 +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:
@@ -48,7 +48,10 @@ function validateScanPolicyRow(row: unknown): string | null {
|
||||
if (typeof r.name !== 'string' || r.name.length === 0 || r.name.length > 200) return 'name must be a non-empty string';
|
||||
if (typeof r.max_severity !== 'string' || !POLICY_SEVERITIES.has(r.max_severity)) return 'max_severity must be CRITICAL, HIGH, MEDIUM, or LOW';
|
||||
if (r.stack_pattern !== null && typeof r.stack_pattern !== 'string') return 'stack_pattern must be a string or null';
|
||||
if (typeof r.stack_pattern === 'string' && r.stack_pattern.length > 200) return 'stack_pattern is too long';
|
||||
if (typeof r.stack_pattern === 'string') {
|
||||
const patternError = validateStackPatternForRedos(r.stack_pattern);
|
||||
if (patternError) return patternError;
|
||||
}
|
||||
if (typeof r.node_identity !== 'string') return 'node_identity must be a string';
|
||||
if (r.node_identity.length > 500) return 'node_identity is too long';
|
||||
if (!isIntFlag(r.block_on_deploy)) return 'block_on_deploy must be 0 or 1';
|
||||
@@ -56,6 +59,24 @@ function validateScanPolicyRow(row: unknown): string | null {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reject `stack_pattern` inputs that would compile to a backtracking-prone
|
||||
* regex. The matcher in `getMatchingPolicy` substitutes `*` with `.*`, so a
|
||||
* pattern like `***...` becomes a chain of adjacent `.*` runs that exhibit
|
||||
* catastrophic backtracking on long inputs.
|
||||
*
|
||||
* Caps mirror the limit in routes/security.ts so a control creating a policy
|
||||
* sees the same error as a replica receiving one. Length is gated at 200 by
|
||||
* the surrounding row validator.
|
||||
*/
|
||||
export function validateStackPatternForRedos(pattern: string): string | null {
|
||||
if (pattern.length > 200) return 'stack_pattern is too long';
|
||||
const stars = (pattern.match(/\*/g) ?? []).length;
|
||||
if (stars > 8) return 'stack_pattern has too many wildcards (max 8)';
|
||||
if (/\*{4,}/.test(pattern)) return 'stack_pattern must not contain 4+ consecutive wildcards';
|
||||
return null;
|
||||
}
|
||||
|
||||
function validateCveSuppressionRow(row: unknown): string | null {
|
||||
if (!row || typeof row !== 'object') return 'row must be an object';
|
||||
const r = row as Record<string, unknown>;
|
||||
|
||||
Reference in New Issue
Block a user