Skip to content

feat: add inCidr and notInCidr CIDR matching conditions#10

Closed
abnegate wants to merge 2 commits into
mainfrom
feat-cidr-conditions
Closed

feat: add inCidr and notInCidr CIDR matching conditions#10
abnegate wants to merge 2 commits into
mainfrom
feat-cidr-conditions

Conversation

@abnegate

@abnegate abnegate commented Jul 22, 2026

Copy link
Copy Markdown
Member

What

Adds two new condition types to the rule engine: inCidr and notInCidr, plus CIDR value validation in the Conditions validator and builder methods Condition::inCidr() / Condition::notInCidr().

Why

IP-based rules can only exact-match today (equal / notEqual on the resolved address). Both consumers need range matching: the edge database network allowlist (DAT-1944) and HTTP WAF IP rules for functions/sites. This closes that gap in the shared library so both inherit the same semantics.

Semantics

Case inCidr notInCidr
Address inside any listed block matches no match
Address outside every listed block no match matches
IPv4 address vs IPv6 block (and vice versa) never matches that block block is ignored
Bare address as a block value (1.2.3.4, 2001:db8::1) treated as /32 / /128 host route same
/0 block matches everything of the same family excludes everything of the same family
Malformed CIDR entry entry never contains anything (valid siblings still evaluated) same
Zero usable entries (empty list, or every entry malformed) no match no match (fail closed — never vacuously matches everything)
Malformed attribute address no match (fail closed) no match (fail closed — unparseable input is not provably outside)

Comparison is a bytewise prefix compare over inet_pton-packed addresses, so there are no integer/float conversions to break on 64-bit boundaries or IPv6.

The Conditions validator rejects inCidr / notInCidr definitions with empty value lists or entries that are not valid CIDR blocks or bare addresses.

Rollout

Consumption in appwrite-labs cloud and edge follows in separate changes after this is released.

🤖 Generated with Claude Code

IP conditions could only exact-match, so allowlist and blocklist rules
had no way to target address ranges. inCidr matches when the attribute
address falls inside any listed CIDR block; notInCidr when it falls
inside none. IPv4 and IPv6 are supported via bytewise prefix comparison
of inet_pton-packed addresses, a bare address defaults to a host route
(/32 or /128), families never cross-match, and malformed input fails
closed for both operators.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 22, 2026 07:44

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@greptile-apps

greptile-apps Bot commented Jul 22, 2026

Copy link
Copy Markdown

Greptile Summary

This PR adds CIDR-based matching to the rule engine. The main changes are:

  • New inCidr and notInCidr condition builders and matching logic.
  • IPv4 and IPv6 validation for CIDR blocks and bare addresses.
  • Fail-closed handling for malformed addresses and unusable CIDR lists.
  • Tests for matching, validation, serialization, and firewall integration.
  • Updated operator documentation.

Confidence Score: 5/5

This looks safe to merge.

  • Empty and fully malformed notInCidr lists now match nothing.
  • Direct construction and validation-bypass paths retain fail-closed behavior.
  • Validation and runtime matching use the same CIDR parser.
  • No blocking issues remain in the changed code.

Important Files Changed

Filename Overview
src/Condition.php Adds CIDR operators, address parsing, prefix matching, and safe handling of empty or malformed block lists.
src/Validator/Conditions.php Rejects empty CIDR lists and invalid CIDR or address values.
tests/ConditionTest.php Covers IPv4 and IPv6 membership, malformed inputs, empty lists, inversion, and serialization.
tests/FirewallTest.php Covers CIDR condition evaluation through firewall rules.
tests/Validator/ConditionsTest.php Covers acceptance of valid CIDR definitions and rejection of invalid definitions.

Reviews (2): Last reviewed commit: "fix: match nothing when a CIDR list has ..." | Re-trigger Greptile

Comment thread src/Condition.php
An empty or fully malformed values list made notInCidr vacuously true,
so a bypass rule built with Condition::notInCidr('ip', []) admitted all
traffic despite the validator rejecting such definitions. Both CIDR
operators now match nothing when zero entries parse, keeping runtime
fail-closed even when validation is skipped.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@abnegate

Copy link
Copy Markdown
Member Author

Recommendation: hold this until there's a live consumer

Flagging a scope concern before this merges. Edge already owns a complete, deployed IP-allowlist enforcement subsystem in src/Edge/K8s/Database/Network/:

  • AccessPolicy::decide(EnforcementMode, cidrs, clientIp) — the single pure decision function
  • Cidr::matchesAny(ip, cidrs) — IPv4/IPv6 CIDR membership, same inet_pton bytewise-prefix approach as this PR
  • EnforcementMode (disabled/permissive/strict) + AllowlistUnavailable fail-closed semantics

That is what actually enforces DAT-1944 today (_EDGE_DB_IP_ENFORCEMENT_MODE=strict, live in prod). It does not use utopia-php/waf. And the other stated consumer — HTTP WAF IP rules for functions/sites — doesn't exist yet. So these operators currently have no caller in either repo.

The implementation here is correct and I'd keep the dedicated inCidr/notInCidr operators over any between/range approach (range-checks break on IPv6's 128-bit space and would push CIDR expansion + fail-closed handling into every caller). But adding them to the shared library ahead of any consumer is premature — and it would mean a future refactor of edge's already-working AccessPolicy/Cidr onto the WAF just to avoid duplication.

Suggest: hold/close this until the consolidation is actually scheduled (i.e. when the HTTP-WAF IP-rule consumer lands, or when we decide to migrate edge's AccessPolicy onto the shared lib). At that point merge it together with its first caller so it doesn't sit unused. Not merging for now.

@abnegate abnegate closed this Jul 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants