Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 12 additions & 2 deletions .github/scripts/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from __future__ import annotations

import argparse
import hashlib
import ipaddress
import json
from datetime import datetime, timezone
Expand All @@ -33,19 +34,27 @@ def load_plane(plane: str) -> dict:
raise SystemExit(f"missing {path} — refusing to build")
doc = json.loads(path.read_text())
# Release assets are the public document. $schema is only for editors in the repo.
return {"endpoints": doc["endpoints"]}
return {"sync_token": sync_token(doc["endpoints"]), "endpoints": doc["endpoints"]}


def sync_token(endpoints: list[dict]) -> str:
"""Deterministic hash of the endpoint list. Changes only when an address or hostname changes,
so automation can compare one field instead of diffing the whole file."""
canonical = json.dumps(endpoints, sort_keys=True, separators=(",", ":"))
return hashlib.sha256(canonical.encode()).hexdigest()[:16]


def write_json(path: Path, payload: dict) -> None:
path.write_text(json.dumps(payload, indent=2) + "\n")
print(f"wrote {path.relative_to(ROOT)}")


def write_text(path: Path, plane: str, version: str, timestamp: str, ips: list[str]) -> None:
def write_text(path: Path, plane: str, version: str, timestamp: str, token: str, ips: list[str]) -> None:
header = [
f"# TrueFoundry {plane.upper()} outbound NAT IPs",
f"# version: {version}",
f"# generated: {timestamp}",
f"# sync_token: {token}",
f"# {REPO}",
]
path.write_text("\n".join(header + ips) + "\n")
Expand All @@ -68,6 +77,7 @@ def main() -> int:
plane,
args.version,
timestamp,
doc["sync_token"],
flat_ips(doc["endpoints"]),
)

Expand Down
72 changes: 55 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,29 +1,35 @@
# TrueFoundry IP ranges

Outbound IP addresses TrueFoundry uses when it connects to your systems. Allowlist these if you restrict inbound access to your model endpoints, webhooks, databases, or private APIs.
Outbound IP addresses TrueFoundry uses when it connects to your systems — model endpoints, MCP servers, webhooks, databases, or private APIs. Allowlist them if you restrict inbound access to those systems.

The directory is the control plane:
These files are meant for automation. Build your allowlist from them rather than copying addresses by hand. See [Connect Private Models and MCP Servers](https://www.truefoundry.com/docs/ai-gateway/connect-private-models-mcp-servers#option-2-allowlist-truefoundry-ip-addresses) in the TrueFoundry docs for the full guide.

| Control plane | List |
| --- | --- |
| Global | [`global/ip-ranges.json`](global/ip-ranges.json) |
| US | [`us/ip-ranges.json`](us/ip-ranges.json) |
> [!IMPORTANT]
> These addresses are shared by every tenant on a control plane. An IP allowlist proves a request came from TrueFoundry, not that it came from **your** tenant. Keep authentication — an API key, OAuth, or mTLS — on any endpoint you allowlist.

## Pick the list for your control plane

The EU control plane is the global control plane. Always-latest URLs are on GitHub Pages:
Each control plane has its own list. Use the one that matches your tenant URL:

- https://truefoundry.github.io/ip-ranges/global.json
- https://truefoundry.github.io/ip-ranges/us.json
| Your tenant URL | Control plane | Gateways | Always-latest list |
| --- | --- | --- | --- |
| `<tenant>.truefoundry.cloud` | Global, hosted in Europe (Ireland) | Worldwide | https://truefoundry.github.io/ip-ranges/global.json |
| `<tenant>.us.truefoundry.cloud` | US | US | https://truefoundry.github.io/ip-ranges/us.json |

Replace `.json` with `.txt` for one CIDR per line.
The source files are [`global/ip-ranges.json`](global/ip-ranges.json) and [`us/ip-ranges.json`](us/ip-ranges.json).

Each [GitHub Release](https://github.com/truefoundry/ip-ranges/releases) attaches the same lists, so a version can be pinned: `https://github.com/truefoundry/ip-ranges/releases/download/v2026.10.12/global.json`.
> [!NOTE]
> The US control plane is being rolled out. `us.json` currently lists only the control plane. US gateway addresses will be added through releases as they come online, following the [15-day notice](#when-addresses-change).

Replace `.json` with `.txt` for one CIDR per line. Each [GitHub Release](https://github.com/truefoundry/ip-ranges/releases) attaches the same lists, so you can pin a version: `https://github.com/truefoundry/ip-ranges/releases/download/v2026.10.12/global.json`.

## What to allowlist

Find the hostname you connect to. `nat_ips` is every address that hostname can connect from.
Each list has one entry per hostname. `nat_ips` is every address TrueFoundry can send traffic from for that hostname.

```json
{
"sync_token": "4d074f6e6d95f47e",
"endpoints": [
{
"type": "control_plane",
Expand All @@ -45,9 +51,17 @@ Find the hostname you connect to. `nat_ips` is every address that hostname can c
}
```

`type` is `control_plane` or `gateway`. Airport endpoints include `airport_code`. Continent hostnames combine the regional gateways that can serve them, including the documented fallback, and `nat_ips` is that combined list. Which gateway you connect to is in the [globally distributed SaaS gateway](https://www.truefoundry.com/docs/ai-gateway/globally-distributed-saas) docs:
Allowlist **two** entries:

1. The **`control_plane`** entry. The control plane also connects to your services.
2. The **`gateway`** entry for the gateway endpoint your tenant sends traffic through:

| Hostname | Addresses |
| Your tenant's gateway endpoint | Gateway entry to allowlist |
| --- | --- |
| `gateway.truefoundry.ai` (the default) | `gateway.truefoundry.ai`, which contains every regional gateway |
| A [multi-regional endpoint](https://www.truefoundry.com/docs/ai-gateway/globally-distributed-saas#configure-a-multi-regional-gateway-endpoint) | That region's hostname below, which already includes its fallback locations |

| Hostname | Gateway locations included |
| --- | --- |
| `gateway.truefoundry.ai` | Every regional gateway |
| `us.gateway.truefoundry.ai` | North Virginia, San Francisco, Dallas, and Toronto |
Expand All @@ -56,20 +70,44 @@ Find the hostname you connect to. `nat_ips` is every address that hostname can c
| `au.gateway.truefoundry.ai` | Sydney, Melbourne, and Singapore |
| `as.gateway.truefoundry.ai` | Mumbai, Singapore, Doha, and Melbourne |

If you pinned a single location such as `orf.gateway.truefoundry.ai`, use the entry with that `airport_code`. Cape Town (`cpt`) is not part of any multi-regional endpoint, so it is only covered by `gateway.truefoundry.ai` and `cpt.gateway.truefoundry.ai`.

The gateway endpoint is configured per tenant. See [globally distributed SaaS gateway](https://www.truefoundry.com/docs/ai-gateway/globally-distributed-saas) for the locations and how to change it.

### Build the allowlist

```bash
# Global control plane, US multi-regional gateway endpoint.
# Use gw=gateway.truefoundry.ai for the default global endpoint,
# and us.json if your tenant is on the US control plane.
curl -s https://truefoundry.github.io/ip-ranges/global.json \
| jq -r --arg gw us.gateway.truefoundry.ai \
'.endpoints[] | select(.type == "control_plane" or .hostname == $gw) | .nat_ips[]' \
| sort -u
```

## When addresses change

Address changes are published as a GitHub Release.
> [!WARNING]
> TrueFoundry does not notify customers individually when these addresses change. Keeping your allowlist current is your responsibility. Subscribe to releases and automate your allowlist so changes are picked up.

A new address is announced in a release and starts carrying traffic **15 days** after that release. Allowlist it during those 15 days.
Every change is published as a [GitHub Release](https://github.com/truefoundry/ip-ranges/releases), and the always-latest lists update with it.

When an address is removed, a new release is published. Drop it from your allowlist after that release.
- A **new address** is announced in a release and starts carrying traffic **15 days** after that release. Allowlist it during those 15 days. The lists include it from the release that announces it.
- When an address is **removed**, a new release is published. Drop it from your allowlist after that release.

To be notified, choose **Watch → Custom → Releases** on this repository, or subscribe to:

```
https://github.com/truefoundry/ip-ranges/releases.atom
```

### Automate it

Run the [allowlist command](#build-the-allowlist) on a schedule — daily is plenty given the 15-day notice — and apply the result to your firewall, security group, or WAF.

Each JSON file has a top-level `sync_token`, which is also in the `.txt` header. It is a hash of the endpoint list and changes only when an address or hostname changes. Compare it with the last value you applied to skip work when nothing has changed.

## Reporting a problem

Something wrong or missing? [Open an issue](https://github.com/truefoundry/ip-ranges/issues) or contact TrueFoundry support. For anything security-sensitive, email security@truefoundry.com.
Loading