GitHub
09/02/2026, 6:09 PMPOST /api/v1/alerts/rules 500 (Internal Server Error) and no usable message.
Root cause: ValidateRule required node_uuid to parse as an RFC-4122 UUID. A
node's UUID is osquery's host_identifier uppercased (cmd/tls/handlers/post.go),
so it is a hostname, instance id, or vendor serial under every --host_identifier
except uuid — legitimate nodes were rejected outright. The resulting error then
fell through respondAlertsErr to the default: branch, so operator input came
back as a 500 whose body was just "error", logged only at debug level.
pkg/alerts/node_scope_test.go already exercised matching with WATCH-U9-style
scopes, so validation contradicted the matcher in the same package.
Changes
pkg/alerts/validate.go
• node_uuid is now bounded to its column width (MaxNodeUUIDLen = 64) instead of
being parsed as a UUID.
• New ErrInvalidRule sentinel wraps every ValidateRule failure, so callers can
tell bad input from a server fault.
cmd/api/handlers/alerts.go
• respondAlertsErr answers 400 with the actual reason for ErrInvalidRule.
• Genuine 500s log at error level (previously debug-only, i.e. invisible in a
normally-configured deployment).
frontend/src/features/alerts/AlertsPage.tsx
• The env filter's default option was labeled "Global (all environments)" but sent
?env=0, which the server filters as environment_id = 0. A rule created from a
node lives in that node's environment, so it was invisible in the default view.
Added an ALL_ENVS sentinel that omits the env param entirely (server returns
every environment) and made it the default; the two meanings are now distinct
options: All environments / Global rules only / per-env. Applies to
channels too.
• Column titles on the rules and channels tables via one sticky TableHeader,
matching the <th> style used on the other admin pages. Each table's grid
template is now a shared const, so header and rows cannot drift out of alignment.
frontend/src/features/nodes/NodeAlertModal.tsx
• Invalidate ['alert-rules'] on create, not only on apply, so the new rule shows
up even if the operator chooses "Apply later".
Tests
• TestAlertRuleCreateNodeScoped — hostname-style node scope → 201, scope round-trips.
• TestAlertRuleCreateInvalidIs400 — blank name → 400 containing "rule name is required".
• TestValidateRuleNodeUUID — rewritten: accepts uuid/hostname/instance-id/empty
scopes, rejects over-column-width, asserts failures wrap ErrInvalidRule.
• Frontend: env filter sends no filter by default, {env: 5} / {env: 0} when
chosen; both tables' column titles asserted.
go test ./pkg/alerts/ ./cmd/api/handlers/ and 228 frontend tests pass; tsc --noEmit clean.
Deploy note
Requires an osctrl-api restart to pick up the validation change.
jmpsec/osctrlGitHub
09/02/2026, 6:15 PM