Airlock can restrict MCP token traffic to a set of IP ranges. A bearer token presented from outside its configured allowlist is rejected before any tool call executes, every denial is auditable, and the rejection is opaque to the caller so denied requests can't learn anything about the policy.
Where allowlists live
Allowlists are layered. A request must pass every applicable layer to proceed.
| Layer | Where to configure | Applies to |
|---|---|---|
| Organization | Settings → Security → Network access | Every MCP connection in the org — service account tokens, personal access tokens, and users signed in through their MCP client's OAuth flow |
| Service account | Service accounts → [account] → Network access | Any token bound to that service account |
| Service token | PATCH /v1/orgs/{orgSlug}/service-tokens/{id} (API only) | An org-scoped service token that is not bound to a service account |
An empty (or missing) allowlist at a layer means "no restriction at this level" — the next layer's restriction (if any) still applies. Setting an org-level allowlist therefore restricts every connection in the org; setting a service-account-level allowlist restricts only that account's tokens.
The organization layer covers human sign-ins too. An org-level allowlist will cut off team members connecting from anywhere outside it — home networks, mobile tethering, travel. When you only mean to constrain a bot, scope the restriction to a service account instead.
If a token is bound to a service account, the service account's allowlist is the one enforced at the entity layer and the token's own list is ignored — set the restriction on the service account in that case. Personal access tokens have no per-token allowlist; only the organization layer applies to them.
CIDR format reference
Each entry is a CIDR — bare IPs are not accepted at the API layer. Examples:
| Notation | Meaning |
|---|---|
203.0.113.42/32 | One specific IPv4 address |
203.0.113.0/24 | The 256-address IPv4 range 203.0.113.0–203.0.113.255 |
10.0.0.0/8 | Private RFC 1918 range |
::1/128 | One specific IPv6 address (loopback) |
2001:db8::/32 | An IPv6 prefix |
0.0.0.0/0 | Every IPv4 address (IPv4-only — does not match IPv6 clients) |
::/0 | Every IPv6 address (IPv6-only — does not match IPv4 clients) |
To allow both IPv4 and IPv6 traffic, include both 0.0.0.0/0 and ::/0. In practice, "allow everything" is the same as having no restriction at all — leave the list empty instead.
CIDRs are stored in a canonical form: lowercase, with IPv6 zero groups written out in full. So 2001:DB8::/32 is saved and displayed as 2001:db8:0:0:0:0:0:0/32, and ::1/128 as 0:0:0:0:0:0:0:1/128. The rewritten entry covers exactly the same addresses as what you typed.
IPv4-mapped-IPv6 note. Some hosts emit IPv4 connections as ::ffff:192.0.2.1. Airlock unwraps that form before evaluation, so an IPv4 CIDR like 192.0.2.0/24 covers both the bare IPv4 address and the IPv4-mapped IPv6 representation. You do not need to enumerate both.
Layered evaluation
For every incoming MCP request:
- Airlock resolves the source IP (see "
X-Forwarded-Forhandling" below). - If the organization has an allowlist and the source IP is not in it → reject with HTTP 403.
- If the service account has an allowlist and the source IP is not in it → reject with HTTP 403.
- Otherwise the request proceeds to the policy engine.
Rejections never echo the source IP back to the caller, so a denied client cannot learn what address it was seen from. The full denial — IP, reason, entity, timestamp — is recorded in the audit log instead.
Bulk paste
The chip-input UI accepts a comma- or newline-separated list, splits it into chips, validates each, trims whitespace, drops empties, and dedupes. Invalid entries surface as red chips so you can fix them in place; the form blocks save until every chip is valid.
If you paste more than 50 entries (the cap), airlock accepts the first 50 and shows "X entries dropped (cap is 50)". If you need to cover more addresses than fit, use a broader CIDR — for example 203.0.113.0/24 instead of 256 separate /32 entries.
Test against my current IP
The editor has a "Test my current IP" button that resolves your current public IP and checks it against the pending (unsaved) chip list. Use this before saving to confirm the configuration covers your own connection.
Self-lockout safeguard
When you click Save on a non-empty allowlist, airlock resolves your current public IP and checks it against the proposed list. If your IP is not covered, you see this modal:
Your current IP is not in this allowlist. You will lose access from this connection after save. If you're configuring from a jump host or VPN, this is expected. [Cancel] [Continue and save]
The modal is the only path to save in this state. The intent is that a jump-host admin (who deliberately isn't in the allowlist that covers production hosts) sees the warning, confirms, and continues.
Recovery path. If you save an allowlist that locks every admin out of the Control Room UI, you can still sign in (Control Room sign-in is governed by Cognito, not the MCP IP allowlist) and edit the list back. The lockout only affects MCP token traffic — Control Room access for admins is unaffected.
X-Forwarded-For handling
By default, airlock does not trust X-Forwarded-For. The source IP comes from the transport (API Gateway's request context) so a caller cannot spoof their address by setting headers.
If airlock runs behind a trusted reverse proxy, an operator can opt in by setting the TRUSTED_PROXY_CIDRS environment variable on the MCP handler to one or more CIDR ranges. When set, and only when the immediate hop matches one of those CIDRs, airlock walks X-Forwarded-For right-to-left and uses the first entry that isn't itself a trusted proxy.
Setting this incorrectly would let any caller spoof an arbitrary IP, so leave the env var unset unless you control the proxy in front of airlock.
Operational visibility
Every denial is recorded in the organization's Security logs, on the Security page in the Control Room. It is one time-ordered feed of all security events; use the type chips to narrow it to IP allowlist denials, and the severity dropdown ("All severities") to narrow further.
Expand a row to see the full context of a denial — the source IP, which layer denied it (org vs entity), the entity type and id, the token, and how the source IP was determined (transport request context vs forwarded header) — so you can verify how a given denial was resolved.
Load older entries with Load more, or use Export to download the visible entries as CSV.
Limits
- Maximum 50 entries per allowlist (per organization, per service account). Prefer broader CIDRs over enumerating individual addresses.
- Both IPv4 and IPv6 are supported, including mixed-family allowlists on the same entity.
- Canonical storage means duplicates that differ only in case (
2001:DB8::/32vs2001:db8::/32) are silently deduped on save.