Context
PR #48 (fixing #47) ships a short-term fix for the Alert Triage blank-panel crash: some ECS alert fields (host.name, user.name, process.name, source.ip, …) come back from Elasticsearch as arrays instead of the scalars SecurityAlert._source types promise, and unguarded .localeCompare() in useAlertSort.ts threw and blanked the widget.
That fix is intentionally pragmatic. This issue tracks doing it properly going forward.
Why the short-term fix is not the final shape
The PR #48 fix has two layers at two different altitudes:
-
normalizeAlertEntityFields() in AlertsService.getAlerts() — a hand-maintained allowlist of specific nested fields to coerce (host.name, user.name/domain, process.name/executable, process.parent.name/executable, file.name/path, source.ip, destination.ip).
- It manually duplicates the structure of the
SecurityAlert._source type. Nothing (type, test, lint) fails when the list drifts from reality.
- Any field not on the list that arrives array-shaped is untouched. Notably
sortAlerts()'s "rule" case calls kibana.alert.rule.name.localeCompare(...) — not normalized and not guarded — so the identical crash class can still occur there.
- It's a special case layered on shared infrastructure — a sign the fix isn't at the right depth.
-
ErrorBoundary wrapping the Alert Triage view root — the genuinely general safety net, but it only degrades crash → visible fallback message, not crash → correctly rendered data. A new array-shaped field in a sort key still collapses the entire view to the fallback.
-
Coverage gap: the ErrorBoundary is reusable but applied to only 1 of 6 views. detection-rules, case-management, and attack-discovery use the same .localeCompare() sort pattern and are unwrapped.
Definition of done
A field arriving as an array instead of a scalar should never crash a view, and the protection should not depend on remembering to add each field to a manual list. Pick one (or combine):
Out of scope
Related: #47, PR #48
Context
PR #48 (fixing #47) ships a short-term fix for the Alert Triage blank-panel crash: some ECS alert fields (
host.name,user.name,process.name,source.ip, …) come back from Elasticsearch as arrays instead of the scalarsSecurityAlert._sourcetypes promise, and unguarded.localeCompare()inuseAlertSort.tsthrew and blanked the widget.That fix is intentionally pragmatic. This issue tracks doing it properly going forward.
Why the short-term fix is not the final shape
The PR #48 fix has two layers at two different altitudes:
normalizeAlertEntityFields()inAlertsService.getAlerts()— a hand-maintained allowlist of specific nested fields to coerce (host.name,user.name/domain,process.name/executable,process.parent.name/executable,file.name/path,source.ip,destination.ip).SecurityAlert._sourcetype. Nothing (type, test, lint) fails when the list drifts from reality.sortAlerts()'s"rule"case callskibana.alert.rule.name.localeCompare(...)— not normalized and not guarded — so the identical crash class can still occur there.ErrorBoundarywrapping the Alert Triage view root — the genuinely general safety net, but it only degrades crash → visible fallback message, not crash → correctly rendered data. A new array-shaped field in a sort key still collapses the entire view to the fallback.Coverage gap: the
ErrorBoundaryis reusable but applied to only 1 of 6 views.detection-rules,case-management, andattack-discoveryuse the same.localeCompare()sort pattern and are unwrapped.Definition of done
A field arriving as an array instead of a scalar should never crash a view, and the protection should not depend on remembering to add each field to a manual list. Pick one (or combine):
scalarField(src, "host.name")) and use it at every sort/group/compare site in the alert-triage hooks, so the crash class is removed at the point of use regardless of which field is multi-valued. This likely makesnormalizeAlertEntityFieldslargely unnecessary."rule"sort key (and audit other.localeCompare/.toLowerCase/new Datecall sites on ES-sourced fields).ErrorBoundaryscope for the remaining views (detection-rules,case-management,attack-discovery) — either wrap their roots or consciously document why not.Out of scope
Related: #47, PR #48