slackhq/nebula · error
could not parse rule
Error message
could not parse rule
What it means
Not a sentinel: an anonymous error created inline in convertRule (firewall.go:950) with message "could not parse rule". convertRule converts a raw config value (from the firewall rules section of the YAML/JSON config) into a rule struct, and returns this error when the value is not a map[string]any — i.e. the rule entry in the config is structurally invalid before any per-field parsing happens. It aborts firewall rule loading so the operator knows the config is malformed.
Source
Thrown at firewall.go:950
type rule struct {
Port string
Code string
Proto string
Host string
Groups []string
Cidr string
LocalCidr string
CAName string
CASha string
}
func convertRule(l *slog.Logger, p any, table string, i int) (rule, error) {
r := rule{}
m, ok := p.(map[string]any)
if !ok {
return r, errors.New("could not parse rule")
}
toString := func(k string, m map[string]any) string {
v, ok := m[k]
if !ok {
return ""
}
return fmt.Sprintf("%v", v)
}
r.Port = toString("port", m)
r.Code = toString("code", m)
r.Proto = toString("proto", m)
r.Host = toString("host", m)
r.Cidr = toString("cidr", m)
r.LocalCidr = toString("local_cidr", m)
r.CAName = toString("ca_name", m)
r.CASha = toString("ca_sha", m)View on GitHub (pinned to dd8f660c0a)
Solutions
- Open the config at the reported table/index and make the rule entry an object/map with keys like port, proto, host, group, cidr
- Fix YAML indentation so each rule is a map element of the inbound/outbound list, not a scalar
- If rules are provided programmatically, pass map[string]any (decoded JSON object) per rule, not strings or arrays
- Run a config validation/dry-run before reload to catch malformed rule entries early
Example fix
// before (malformed YAML: rule collapsed to a string)
firewall:
inbound:
- "port: 443 proto: any"
// after
firewall:
inbound:
- port: 443
proto: any
host: web Defensive patterns
Strategy: validation
Validate before calling
// validate every firewall rule entry is an object before handing config to the firewall
for i, r := range cfg.Firewall.Inbound {
if _, ok := r.(map[string]any); !ok {
return fmt.Errorf("firewall.inbound[%d] must be a mapping, got %T", i, r)
}
} Type guard
func isRuleMap(v any) bool {
_, ok := v.(map[string]any)
return ok
} Try / catch
r, err := convertRule(logger, raw, "inbound", i)
if err != nil {
if err.Error() == "could not parse rule" {
// raw entry is not a map: log the index and offending value, then abort reload
}
} Prevention
- Keep each firewall rule as a properly indented YAML mapping inside the inbound/outbound lists
- Lint the config (yaml parser + schema check) before every reload or hot-apply
- When generating rules programmatically, emit map[string]any objects, never serialized strings
- Beware partially-written config files during hot reload; write atomically (temp file + rename)
When it happens
Trigger: Calling Firewall reload/add-firewall-rule config paths where an entry in the firewall.inbound/outbound list is not a mapping/object — e.g. it's a string, number, array, or null — so the type assertion p.(map[string]any) at firewall.go:950 fails.
Common situations: YAML indentation mistakes that turn a rule into a scalar or list; quoting a whole rule as a string; mixing list and map syntax; programmatically supplied rule JSON with wrong shape (array instead of object); config hot-reload picking up a partially written file.
Related errors
- %s failed to parse, should be an array of rules
- %s rule #%v; %s
- lighthouse %s does not have a static_host_map entry
- ErrInvalidLocalIP
- ErrNoCredential
AI-assisted analysis of slackhq/nebula@dd8f660c0a (2026-09-03).
Data as JSON: /api/errors/2b3f4c1817b0358d.
Report an issue: GitHub.