zuplo 6.73.31 → 6.74.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,16 +2,175 @@
2
2
  title: How to check an incoming IP address
3
3
  sidebar_label: "Check IP Address"
4
4
  description:
5
- Learn how to access the true client IP address of requests using the
6
- true-client-ip header.
5
+ Read the client IP address of an incoming request, and understand when that
6
+ address can be trusted on each deployment type.
7
7
  tags:
8
8
  - request-handling
9
9
  - deployment
10
10
  ---
11
11
 
12
- Sometimes you want to access the true IP address of the gateway's client making
13
- the current request. To do this you can read the `true-client-ip` header:
12
+ To get the IP address of the client that made the current request, read it from
13
+ the request context:
14
14
 
15
15
  ```ts
16
- const ip = request.headers.get("true-client-ip");
16
+ export default async function (request: ZuploRequest, context: ZuploContext) {
17
+ const ip = context.incomingRequestProperties.ip;
18
+ return new Response(ip ?? "unknown");
19
+ }
17
20
  ```
21
+
22
+ This is the official way to read the client IP. Zuplo resolves the address from
23
+ the layer in front of your gateway, so you get the same property regardless of
24
+ where the gateway runs.
25
+
26
+ ## Handle an unknown address
27
+
28
+ The value is `undefined` when nothing identified the caller. Handle that case
29
+ explicitly:
30
+
31
+ ```ts
32
+ const ip = context.incomingRequestProperties.ip;
33
+ if (!ip) {
34
+ return HttpProblems.badRequest(request, context, {
35
+ detail: "Could not determine the client IP address",
36
+ });
37
+ }
38
+ ```
39
+
40
+ Don't substitute a placeholder such as `"unknown"` or `0.0.0.0` and then key on
41
+ it. Every unidentified caller collapses into that single value, so a rate limit
42
+ keyed on it throttles them as one client, and an allow list keyed on it either
43
+ admits all of them or none.
44
+
45
+ ## When the address can be trusted
46
+
47
+ An IP address is only as trustworthy as the layer that determined it. Zuplo
48
+ reports an address only where that layer establishes it, so the value is either
49
+ trustworthy or `undefined` — never one the caller chose.
50
+
51
+ | Deployment | Determined by | Trustworthy |
52
+ | ----------------- | ------------------------------------- | ---------------------------------------- |
53
+ | Zuplo Cloud | The Zuplo edge | Yes |
54
+ | Zuplo Dedicated | Your environment, configured by Zuplo | Yes |
55
+ | Self-hosted | The proxy you run | Only if that proxy overwrites the header |
56
+ | Local development | Nothing — the caller is loopback | Reports `127.0.0.1` |
57
+
58
+ Zuplo Cloud and Zuplo Dedicated need no configuration. Self-hosted deployments
59
+ depend on the proxy you run, covered in
60
+ [Self-hosted deployments](#self-hosted-deployments).
61
+
62
+ :::caution
63
+
64
+ Don't read an IP header from `request.headers` yourself. Which header carries
65
+ the caller's address depends on where the gateway runs, so a header that holds
66
+ it on one deployment may be absent on another — or may hold whatever the caller
67
+ put there. Use `context.incomingRequestProperties.ip`, which resolves the right
68
+ one for the deployment.
69
+
70
+ :::
71
+
72
+ ## The `x-forwarded-for` header
73
+
74
+ `x-forwarded-for` carries a client address through a chain of proxies. Each
75
+ proxy appends the address it received the request from, producing a list:
76
+
77
+ ```
78
+ X-Forwarded-For: 203.0.113.7, 198.51.100.4
79
+ ```
80
+
81
+ The header reaches your code and your backend unchanged, so you can read it
82
+ directly:
83
+
84
+ ```ts
85
+ const chain = (request.headers.get("x-forwarded-for") ?? "")
86
+ .split(",")
87
+ .map((entry) => entry.trim())
88
+ .filter(Boolean);
89
+ ```
90
+
91
+ To use it correctly, understand what the list contains. A caller can send an
92
+ `X-Forwarded-For` of its own, and every proxy after that appends to whatever
93
+ arrived. So the front of the list is whatever the caller wrote, and only entries
94
+ added by a hop you operate mean anything.
95
+
96
+ That leads to a few rules:
97
+
98
+ - Count positions from the **end**. The last entry was added by the hop nearest
99
+ your gateway; the first came from the caller.
100
+ - Decide in advance how many hops you operate, and ignore anything beyond them.
101
+ A caller can pad the front with as many entries as it likes.
102
+ - Don't search the list for an address you recognize — a caller can put one
103
+ there.
104
+ - Expect IPv6 entries, which may be bracketed and carry a port, such as
105
+ `[2001:db8::1]:8080`.
106
+
107
+ :::danger
108
+
109
+ An address is only as reliable as the hop that established it. If you key a rate
110
+ limit on a value the caller can choose, they escape the limit by rotating it.
111
+ The same value in an allow list can be used to impersonate an allowed client,
112
+ and in an audit record it produces entries that didn't happen.
113
+
114
+ :::
115
+
116
+ If you only need the caller's address, `context.incomingRequestProperties.ip`
117
+ already gives it to you without any of this.
118
+
119
+ ## Running your own CDN or proxy in front of Zuplo
120
+
121
+ If your own CDN, load balancer, or WAF sits in front of your gateway, then that
122
+ hop — not the end user — is the client Zuplo sees.
123
+ `incomingRequestProperties.ip` returns its address.
124
+
125
+ To identify the end user, have your CDN write the address into a header of your
126
+ own and read that header in your code:
127
+
128
+ ```ts
129
+ const endUserIp = request.headers.get("x-acme-client-ip");
130
+ ```
131
+
132
+ This works only if your CDN **overwrites** that header on every request. If it
133
+ merely adds the header when absent, a caller can supply their own value and it
134
+ passes straight through to your code.
135
+
136
+ ## Zuplo Dedicated
137
+
138
+ Zuplo configures your Dedicated environment to determine the client address, so
139
+ there's nothing for you to set up. `context.incomingRequestProperties.ip`
140
+ returns the caller's address the same way it does on Zuplo Cloud.
141
+
142
+ Dedicated environments aren't identical to one another — they differ by cloud,
143
+ region, and what sits in front of the gateway, such as a CDN or WAF you already
144
+ operate. If you need to know exactly how the address is determined for your
145
+ environment, or you're adding a network layer in front of it, contact your
146
+ account team or [support](mailto:support@zuplo.com). Adding a hop in front of
147
+ the gateway changes which address the gateway sees, as described in
148
+ [Running your own CDN or proxy in front of Zuplo](#running-your-own-cdn-or-proxy-in-front-of-zuplo).
149
+
150
+ ## Self-hosted deployments
151
+
152
+ On a self-hosted deployment you run the proxy in front of the gateway, so
153
+ determining the client address is yours to configure. The gateway reads it from
154
+ the `x-real-ip` request header.
155
+
156
+ Your proxy must **overwrite** that header rather than pass one through. If a
157
+ client-supplied `x-real-ip` reaches the gateway untouched, any caller can choose
158
+ its own address by sending it. NGINX's `real_ip` module does this correctly by
159
+ default. Confirm the behavior against your own configuration before relying on
160
+ the address for anything security-sensitive.
161
+
162
+ :::warning
163
+
164
+ A gateway reachable directly, with no proxy in front of it, has nothing
165
+ determining the client address. Don't expose a self-hosted gateway to the
166
+ internet directly if you rely on the client IP.
167
+
168
+ :::
169
+
170
+ ## Geolocation
171
+
172
+ To act on where a caller is rather than their address,
173
+ `incomingRequestProperties` already carries resolved geolocation — `country`,
174
+ `city`, `region`, `latitude`, `longitude`, `asn`, and more. There's no need to
175
+ geolocate the IP address yourself. See
176
+ [ZuploContext](../programmable-api/zuplo-context.mdx#incomingrequestproperties).
@@ -301,7 +301,7 @@ Next, you need to apply the Monetization policy to some or all of your routes.
301
301
  1. Click on the three-dot menu on the **monetization-inbound** policy.
302
302
  2. Select **Apply Policy**.
303
303
  3. Choose individual routes that you want to count towards the metered requests,
304
- or click **Select All** to add the policy to every route in the project.
304
+ or click **Select all** to add the policy to every route in the project.
305
305
 
306
306
  ![Adding the policy to add routes](../../../public/media/monetization/policy-add-routes.png)
307
307
 
@@ -40,15 +40,15 @@ Virtual Server**, with these choices:
40
40
  **Path** you want to expose on your gateway.
41
41
  2. **Inbound Auth**: pick whatever your clients should authenticate with. OAuth
42
42
  through an identity provider works exactly as it does for OAuth upstreams.
43
- 3. **Tools**: choose **Passthrough** or **Curate** as usual.
44
- 4. **Outbound Auth**: choose **None**, then set the inbound `Authorization`
45
- header handling to **Remove auth token**. The upstream doesn't use OAuth, so
46
- the gateway shouldn't run a token exchange, and the inbound client's token
47
- must be stripped before forwarding so it doesn't leak to the upstream.
43
+ 3. **Tools**: choose **Filter & curate** or **Passthrough** as usual.
44
+ 4. **Outbound Auth**: choose **None**, then turn on **Remove inbound
45
+ Authorization header**. The upstream doesn't use OAuth, so the gateway
46
+ shouldn't run a token exchange, and the inbound client's token must be
47
+ stripped before forwarding so it doesn't leak to the upstream.
48
48
 
49
49
  <ModalScreenshot size="md">
50
50
 
51
- ![Outbound auth set to None with Remove auth token selected](../../../public/media/mcp-gateway-upstream-api-key/01-outbound-auth-none.png)
51
+ ![Outbound auth set to None with the inbound Authorization header removed](../../../public/media/mcp-gateway-upstream-api-key/01-outbound-auth-none.png)
52
52
 
53
53
  </ModalScreenshot>
54
54
 
@@ -14,8 +14,8 @@ description:
14
14
 
15
15
  When an upstream MCP server exposes more capabilities than belong in front of an
16
16
  AI client, curate the subset that passes through. In the Portal, the **MCP
17
- Gateway Virtual Server** wizard does this on its **Tools** step: choose
18
- **Curate** instead of **Passthrough** and pick exactly what to expose. The
17
+ Gateway Virtual Server** wizard does this on its **Tools** step: choose **Filter
18
+ & curate** instead of **Passthrough** and pick exactly what to expose. The
19
19
  wizard writes an `mcp-capability-filter-inbound` policy and attaches it to the
20
20
  route for you.
21
21
 
@@ -47,7 +47,7 @@ For a full walkthrough of creating one, see the
47
47
 
48
48
  <ModalScreenshot size="md">
49
49
 
50
- ![Choose Passthrough or Curate on the Tools step](../../../public/media/mcp-gateway-quickstart/04-tools.png)
50
+ ![Choose Filter & curate or Passthrough on the Tools step](../../../public/media/mcp-gateway-quickstart/04-tools.png)
51
51
 
52
52
  </ModalScreenshot>
53
53
 
@@ -103,15 +103,15 @@ result on your machine.
103
103
  server exposes to its clients:
104
104
  - **Passthrough** federates the upstream's catalog live. Zero config, and the
105
105
  safest default. Everything the upstream offers is exposed.
106
- - **Curate** lets you pick specific tools, prompts, and resources. Use this
107
- to control what users can do. For example, drop all destructive tools and
108
- expose only read and write tools.
106
+ - **Filter & curate** lets you pick specific tools, prompts, and resources.
107
+ Use this to control what users can do. For example, drop all destructive
108
+ tools and expose only read and write tools.
109
109
 
110
110
  Choose **Passthrough** and click **Next**.
111
111
 
112
112
  <ModalScreenshot size="md">
113
113
 
114
- ![Choose Passthrough or Curate for the exposed catalog](../../public/media/mcp-gateway-quickstart/04-tools.png)
114
+ ![Choose Filter & curate or Passthrough for the exposed catalog](../../public/media/mcp-gateway-quickstart/04-tools.png)
115
115
 
116
116
  </ModalScreenshot>
117
117
 
@@ -48,9 +48,15 @@ const elapsed = Date.now() - context.custom.startTime;
48
48
 
49
49
  ### `incomingRequestProperties`
50
50
 
51
- Information about the incoming request such as geolocation data. This is a
52
- read-only object with the following properties:
53
-
51
+ Information about the incoming request such as the client IP address and
52
+ geolocation data. This is a read-only object with the following properties:
53
+
54
+ - `ip` - The IP address of the client that made the request, for example,
55
+ "203.0.113.7". This is the only supported way to read the client IP; don't
56
+ read an IP header from `request.headers` yourself. The value is `undefined`
57
+ when nothing identified the caller. See
58
+ [how to check an incoming IP address](../articles/check-ip-address.mdx) for
59
+ when the address can be trusted on each deployment type.
54
60
  - `asn` - ASN of the incoming request, for example, 395747.
55
61
  - `asOrganization` - The organization which owns the ASN of the incoming
56
62
  request, for example, Google Cloud.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zuplo",
3
- "version": "6.73.31",
3
+ "version": "6.74.6",
4
4
  "type": "module",
5
5
  "description": "The programmable API Gateway",
6
6
  "author": "Zuplo, Inc.",
@@ -19,9 +19,9 @@
19
19
  "zuplo": "zuplo.js"
20
20
  },
21
21
  "dependencies": {
22
- "@zuplo/cli": "6.73.31",
23
- "@zuplo/core": "6.73.31",
24
- "@zuplo/runtime": "6.73.31",
22
+ "@zuplo/cli": "6.74.6",
23
+ "@zuplo/core": "6.74.6",
24
+ "@zuplo/runtime": "6.74.6",
25
25
  "@zuplo/test": "1.4.4"
26
26
  }
27
27
  }