IP Allowlist Administration
Arbitex supports per-organization IP allowlisting. When an allowlist is configured, only requests originating from permitted IP addresses reach the API — all other source addresses receive HTTP 403. The allowlist is opt-in: an organization with no configured entries permits all source IPs.
IP allowlist enforcement runs at the ASGI middleware layer, before authentication and rate limiting. Changes take effect immediately — CRUD operations explicitly invalidate the in-process cache.
Rule types
Section titled “Rule types”Per-org allowlist entries (stored in the org_ip_allowlists table) support three rule formats:
CIDR notation
Section titled “CIDR notation”Standard classless inter-domain routing notation. Supports IPv4 and IPv6. The 0.0.0.0/0 and ::/0 wildcards are explicitly rejected — leave the allowlist empty to allow all IPs.
| CIDR | Matches |
|---|---|
203.0.113.42/32 | Single IPv4 host |
203.0.113.0/24 | 256-address /24 subnet |
10.0.0.0/8 | RFC 1918 class A private range |
172.16.0.0/12 | RFC 1918 class B private range |
192.168.0.0/16 | RFC 1918 class C private range |
2001:db8::1/128 | Single IPv6 host |
2001:db8::/32 | IPv6 /32 block |
CIDR strings are parsed with strict=False — host bits do not need to be zero. 203.0.113.42/24 is accepted and matched as 203.0.113.0/24.
IP range
Section titled “IP range”An explicit address range within a single subnet. IPv4 only. Two forms are supported:
Short form — last-octet range notation:
203.0.113.10-20Matches 203.0.113.10 through 203.0.113.20 inclusive. The right side is a last-octet integer (0–255). The base IP must have four octets.
Full form — both endpoints as complete IPv4 addresses:
203.0.113.10-203.0.113.20Semantically identical to short form. The right side must contain at least one dot (to distinguish from short form). Start address must be ≤ end address.
Single IP
Section titled “Single IP”An exact IPv4 or IPv6 address:
203.0.113.422001:db8::1Equivalent to /32 (IPv4) or /128 (IPv6) CIDR, stored explicitly as rule type single.
How it works
Section titled “How it works”The IPAllowlistMiddleware evaluates each incoming HTTP request in order:
- Emergency bypass — if
_SUPPORT_IP_ALLOWLIST_BYPASS=trueis set (support use only), all IP checks are skipped globally and aCRITICALlog is emitted on every request. - Internal endpoint bypass — requests to
/v1/internal/(already protected by mTLS) bypass IP allowlist checks entirely. - Exempt paths —
/health,/ready,/api/health,/api/auth/login, and/api/auth/registerare always exempt. - Platform bypass CIDRs — IPs matching
IP_ALLOWLIST_BYPASS_CIDRSare always permitted, regardless of any org allowlist. - Per-org allowlist check — for authenticated requests carrying org context (
org_idortenant_idin request state), the client IP is checked against the org’s entries viaIPAllowlistCache. - Legacy global path — for requests without org context, checked against the
ip_allowlist_entriestable (60s TTL cache). - Empty allowlist = allow all — if an org has no enabled entries, all IPs are permitted.
When a request is blocked:
{ "error": "ip_not_allowed", "detail": "Client IP not in organization allowlist"}An ip_allowlist_blocked warning log is emitted with client_ip, org_id, path, and timestamp.
API reference
Section titled “API reference”All endpoints require Org Admin role. Authenticate with a Bearer token (arb_live_* API key or session JWT).
Base path: /api/admin/ip-allowlist/
List entries
Section titled “List entries”GET /api/admin/ip-allowlist/Authorization: Bearer arb_live_your-api-key-hereReturns all IP allowlist entries ordered by created_at descending (newest first).
Response:
[ { "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "ip_range": "203.0.113.0/24", "description": "Corporate HQ egress", "tenant_id": "8d4b2c1e-0a3f-4e9d-b7c6-1a2b3c4d5e6f", "is_active": true, "created_by_id": "1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d", "created_at": "2026-03-01T12:00:00Z" }]Get a single entry
Section titled “Get a single entry”GET /api/admin/ip-allowlist/{entry_id}Authorization: Bearer arb_live_your-api-key-hereReturns a single entry by UUID. Returns 404 if not found.
Create an entry
Section titled “Create an entry”POST /api/admin/ip-allowlist/Authorization: Bearer arb_live_your-api-key-hereContent-Type: application/json
{ "ip_range": "203.0.113.0/24", "description": "Corporate HQ egress", "tenant_id": "8d4b2c1e-0a3f-4e9d-b7c6-1a2b3c4d5e6f", "is_active": true}| Field | Required | Description |
|---|---|---|
ip_range | Yes | Rule value. Accepts CIDR (203.0.113.0/24), range (203.0.113.10-20), or single IP (203.0.113.42). |
description | No | Human-readable label (e.g., "Corporate HQ egress"). |
tenant_id | No | Org UUID to scope this entry. If omitted, falls into the legacy global allowlist. |
is_active | No | Whether this entry is enforced. Defaults to true. |
Returns 201 with the created entry. Cache is cleared immediately.
Update an entry
Section titled “Update an entry”PUT /api/admin/ip-allowlist/{entry_id}Authorization: Bearer arb_live_your-api-key-hereContent-Type: application/json
{ "description": "Updated label", "is_active": false}All fields are optional — only provided fields are updated. Returns 200. Cache is cleared immediately.
Delete an entry
Section titled “Delete an entry”DELETE /api/admin/ip-allowlist/{entry_id}Authorization: Bearer arb_live_your-api-key-hereReturns 204 No Content. Cache is cleared immediately.
Per-org data model
Section titled “Per-org data model”Per-org entries are stored in the org_ip_allowlists table (OrgIPAllowlistEntry):
| Column | Type | Description |
|---|---|---|
id | UUID | Entry identifier |
org_id | UUID | Owning organization UUID |
rule_type | string | cidr, range, or single |
value | string | Rule value string |
description | string | Human-readable label |
created_by | UUID | Admin user who created the entry |
is_enabled | bool | Whether this entry is enforced |
created_at | timestamp | Creation time |
updated_at | timestamp | Last modification time |
This table is separate from the legacy ip_allowlist_entries table (IPAllowlistEntry), which uses only CIDR-format ip_range strings with a tenant_id column. The legacy table is checked only when no org context is present on a request (the global fallback path).
Cache behavior
Section titled “Cache behavior”The middleware uses an in-process per-org TTL cache (IPAllowlistCache) to avoid per-request database queries.
TTL: 60 seconds.
Invalidation: clear_cache() is called after every CRUD write so changes take effect immediately on the next request — the 60-second TTL only applies when no write has occurred.
Cache miss: Rules are loaded from org_ip_allowlists and parsed into ParsedRule objects (network, range, or address). Parsing happens once per cache population.
Load failure: If the database query fails during cache population, an empty list is returned and a warning is logged. Empty list = allow all — the system fails open. Apply monitoring to ip_allowlist_cache: DB query failed log events.
The legacy global path uses a separate 60-second TTL cache keyed to the ip_allowlist_entries table. This cache is only invalidated by CRUD operations on the global entries (not per-org operations).
Platform bypass mechanisms
Section titled “Platform bypass mechanisms”IP_ALLOWLIST_BYPASS_CIDRS
Section titled “IP_ALLOWLIST_BYPASS_CIDRS”Set this environment variable to a comma-separated list of CIDR ranges that are always permitted, regardless of any org allowlist. Parsed once at module load time.
IP_ALLOWLIST_BYPASS_CIDRS=10.0.0.0/8,172.16.0.0/12Use for: platform support access ranges, monitoring infrastructure, internal load balancers.
Invalid CIDR strings emit a warning log but do not prevent startup.
/v1/internal/ prefix bypass
Section titled “/v1/internal/ prefix bypass”Requests to paths beginning with /v1/internal/ bypass IP allowlist checks. These endpoints are protected by mutual TLS — the Outpost client certificate is the access control mechanism.
Exempt paths
Section titled “Exempt paths”Always exempt to ensure health probes and authentication continue functioning:
/health/ready/api/health/api/auth/login/api/auth/register
_SUPPORT_IP_ALLOWLIST_BYPASS (emergency only)
Section titled “_SUPPORT_IP_ALLOWLIST_BYPASS (emergency only)”Setting _SUPPORT_IP_ALLOWLIST_BYPASS=true disables all IP allowlist enforcement globally. A CRITICAL log entry is emitted on every request while active. For emergency lockout recovery only — remove immediately after restoring access.
Interaction with other security controls
Section titled “Interaction with other security controls”API key authentication
Section titled “API key authentication”IP allowlist enforcement runs before API key validation. A blocked IP receives 403 before the key is checked. A valid API key cannot bypass an IP allowlist restriction.
Rate limiting
Section titled “Rate limiting”IP allowlist enforcement runs before rate limit middleware. Blocked IPs do not consume rate limit quota.
mTLS (internal endpoints)
Section titled “mTLS (internal endpoints)”Requests to /v1/internal/ paths (Outpost heartbeat, policy sync) bypass the IP allowlist. These endpoints require mutual TLS.
SCIM provisioning
Section titled “SCIM provisioning”SCIM endpoints (/scim/v2/) are subject to IP allowlist enforcement. If you use SCIM provisioning from a cloud identity provider (Okta, Entra ID), add the IdP’s outbound IP ranges before activating IP restrictions. Both providers publish their outbound IP ranges in their documentation.
Enabling IP restrictions for an org
Section titled “Enabling IP restrictions for an org”- Identify all source IPs or CIDR ranges that should have access: corporate egress IPs, VPN exit nodes, CI/CD runner IPs, IdP SCIM outbound ranges.
- Create allowlist entries for each range via
POST /api/admin/ip-allowlist/. - Verify the entries with
GET /api/admin/ip-allowlist/. - Test from a permitted IP before any users are affected.
Troubleshooting
Section titled “Troubleshooting”Locked out — cannot reach the admin API
Section titled “Locked out — cannot reach the admin API”- Platform bypass CIDRs — if
IP_ALLOWLIST_BYPASS_CIDRScovers your current IP range, access the API from there to add your locked-out IP. - Emergency bypass — ask your infrastructure team to temporarily set
_SUPPORT_IP_ALLOWLIST_BYPASS=true. Remove it immediately after adding the correct entry. - Direct database access — insert a row into
org_ip_allowlists(rule_type='cidr',value='<your-cidr>',is_enabled=true, correctorg_id). The cache expires within 60 seconds; write operations also callclear_cache()directly if available.
403 errors from expected IPs
Section titled “403 errors from expected IPs”- NAT or proxy translation — verify the actual egress IP using an IP echo service. Corporate networks may use multiple egress IPs or rotate them.
- Range too narrow — a single-IP entry may not cover all exit addresses. Widen to a /24 or use range notation.
- Entry disabled — check
is_activeoris_enabledon the relevant entries. - Wrong org_id — entries scoped to a different org do not apply to your requests. Verify the
org_idortenant_idon the entry. - Range format error — if
rule_type=range, confirm203.0.113.10-20has a valid last-octet integer, or use the full form203.0.113.10-203.0.113.20.
Validation errors on create
Section titled “Validation errors on create”| Error | Cause |
|---|---|
Invalid CIDR value | CIDR string is malformed |
0.0.0.0/0 (allow all) is not permitted | Wildcard CIDR rejected — leave allowlist empty to allow all |
Invalid range value | Range format is missing -, has non-numeric end octet, or start > end |
Invalid IP address | Single-IP value is not a valid IPv4 or IPv6 address |
422 Unprocessable Entity | Schema validation failed — check the detail array |
See also
Section titled “See also”- API key management — rotate and scope API keys
- Rate limiting — per-org rate limit tiers and 429 handling
- Security Overview — full security controls overview
- Audit log export — IP addresses captured in audit log
src_ipfield