Policy engine: nested all/any groups silently match every packet, and there is no "not in list" operator #175

Open
opened 2026-10-27 09:36:12 +01:00 by adam · 0 comments
Owner

I'm writing a plugin that builds policy rules through /api/policy, and I ran into two things in repeater/policy_engine.py.

1. A nested group matches every packet

_rule_matches only looks at the top-level all or any list and hands each entry to _condition_matches, which expects a plain {field, op, value}. If an entry is itself a group (for example {"any": [...]}), it has no field, so field is None, op defaults to equals, value is None, and None == None is true. The nested group therefore matches every packet.

Minimal rule, built through the API:

- id: 2
  name: only nested
  enabled: true
  if:
    all:
      - any:
          - {field: hop_count, op: equals, value: 1}
  then: {action: drop}

I expected this to drop only packets with hop_count 1. Using the engine directly with a mock packet, _rule_matches returns True whatever the packet is. For a drop rule that means it silently drops everything. The only trace is a debug-level line, so nothing shows up in a normal log.

Also, if a rule has both all and any at the top level, any is ignored without a message, and an empty all list matches every packet (all([]) is true).

Suggested minimum: either support nested groups, or reject or warn about an entry that has no field (and a group that is not at the top level) instead of treating it as a match. A rule that fails to validate would be far safer than one that matches everything.

2. No "not in list" operator

The comparison operators are equals, not_equals, greater_than, greater_or_equal, less_than, less_or_equal, contains, in, intersects, starts_with and ends_with. There is in, but no not_in, no not_contains and no not_intersects.

That makes it awkward to write "match packets whose value is not one of these" without one rule per value, and each rule costs CPU time, which matters on a Pi 3. With nested groups unavailable, not_in (and not_contains) can't be built from the existing operators either.

Would a not_in operator (and not_contains / not_intersects) be welcome? Happy to send a patch if you point me at where you'd like it.

Version: current main at the time of writing. I haven't tested older releases.

I'm writing a plugin that builds policy rules through `/api/policy`, and I ran into two things in `repeater/policy_engine.py`. **1. A nested group matches every packet** `_rule_matches` only looks at the top-level `all` or `any` list and hands each entry to `_condition_matches`, which expects a plain `{field, op, value}`. If an entry is itself a group (for example `{"any": [...]}`), it has no `field`, so `field` is `None`, `op` defaults to `equals`, `value` is `None`, and `None == None` is true. The nested group therefore matches every packet. Minimal rule, built through the API: ```yaml - id: 2 name: only nested enabled: true if: all: - any: - {field: hop_count, op: equals, value: 1} then: {action: drop} ``` I expected this to drop only packets with `hop_count` 1. Using the engine directly with a mock packet, `_rule_matches` returns `True` whatever the packet is. For a `drop` rule that means it silently drops everything. The only trace is a debug-level line, so nothing shows up in a normal log. Also, if a rule has both `all` and `any` at the top level, `any` is ignored without a message, and an empty `all` list matches every packet (`all([])` is true). Suggested minimum: either support nested groups, or reject or warn about an entry that has no `field` (and a group that is not at the top level) instead of treating it as a match. A rule that fails to validate would be far safer than one that matches everything. **2. No "not in list" operator** The comparison operators are `equals`, `not_equals`, `greater_than`, `greater_or_equal`, `less_than`, `less_or_equal`, `contains`, `in`, `intersects`, `starts_with` and `ends_with`. There is `in`, but no `not_in`, no `not_contains` and no `not_intersects`. That makes it awkward to write "match packets whose value is not one of these" without one rule per value, and each rule costs CPU time, which matters on a Pi 3. With nested groups unavailable, `not_in` (and `not_contains`) can't be built from the existing operators either. Would a `not_in` operator (and `not_contains` / `not_intersects`) be welcome? Happy to send a patch if you point me at where you'd like it. Version: current `main` at the time of writing. I haven't tested older releases. <!-- github-sync-id:5947169577 -->
Sign in to join this conversation.
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: adam/pyMC_Repeater#175