zuplo 7.9.13 → 7.9.15

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.
Files changed (47) hide show
  1. package/docs/ai-gateway/bedrock-runtime.mdx +1 -1
  2. package/docs/ai-gateway/custom-policies.mdx +1 -1
  3. package/docs/ai-gateway/fallback.mdx +1 -1
  4. package/docs/ai-gateway/integrations/ai-sdk.mdx +5 -5
  5. package/docs/ai-gateway/integrations/claude-code.mdx +6 -8
  6. package/docs/ai-gateway/integrations/codex.mdx +6 -5
  7. package/docs/ai-gateway/integrations/goose.mdx +3 -3
  8. package/docs/ai-gateway/integrations/langchain.mdx +1 -1
  9. package/docs/ai-gateway/integrations/openai.mdx +3 -3
  10. package/docs/ai-gateway/managing-providers.mdx +1 -1
  11. package/docs/ai-gateway/openrouter.mdx +14 -13
  12. package/docs/ai-gateway/overview.mdx +4 -2
  13. package/docs/ai-gateway/providers.mdx +2 -2
  14. package/docs/ai-gateway/source-control.mdx +61 -2
  15. package/docs/ai-gateway/universal-api.mdx +1 -1
  16. package/docs/ai-gateway/user-apps.mdx +5 -2
  17. package/docs/articles/accounts/audit-logs.mdx +3 -9
  18. package/docs/articles/environment-variables.mdx +56 -231
  19. package/docs/articles/graphql-support.mdx +59 -18
  20. package/docs/articles/local-development-env-variables.mdx +37 -8
  21. package/docs/articles/logging.mdx +21 -0
  22. package/docs/articles/opentelemetry.mdx +1 -1
  23. package/docs/cli/source-upgrade.mdx +39 -0
  24. package/docs/cli/source-upgrade.partial.mdx +11 -0
  25. package/docs/concepts/ai-gateway.mdx +1 -1
  26. package/docs/dev-portal/zudoku/guides/environment-variables.md +75 -0
  27. package/docs/dev-portal/zudoku-partials/guides/environment-variables.partial.md +74 -0
  28. package/docs/handlers/mcp-server.mdx +72 -0
  29. package/docs/mcp-server/overview.mdx +7 -0
  30. package/docs/mcp-server/tools.mdx +72 -0
  31. package/docs/policies/_index.md +1 -1
  32. package/docs/policies/ai-gateway-auth-inbound/doc.md +2 -2
  33. package/docs/policies/ai-gateway-configuration-executor-inbound/doc.md +3 -3
  34. package/docs/policies/ai-gateway-prompt-injection/doc.md +1 -2
  35. package/docs/policies/ai-gateway-prompt-injection/schema.json +2 -2
  36. package/docs/policies/ai-gateway-smart-router-inbound/doc.md +112 -29
  37. package/docs/policies/ai-gateway-smart-router-inbound/schema.json +109 -12
  38. package/docs/policies/internal-only-inbound/doc.md +206 -0
  39. package/docs/policies/internal-only-inbound/intro.md +10 -0
  40. package/docs/policies/internal-only-inbound/schema.json +75 -0
  41. package/docs/programmable-api/environment.mdx +220 -48
  42. package/docs/programmable-api/hooks.mdx +24 -0
  43. package/docs/programmable-api/mcp-sdk.mdx +22 -0
  44. package/package.json +5 -5
  45. package/docs/policies/ai-gateway-internal-only-inbound/doc.md +0 -102
  46. package/docs/policies/ai-gateway-internal-only-inbound/intro.md +0 -11
  47. package/docs/policies/ai-gateway-internal-only-inbound/schema.json +0 -50
@@ -1,26 +1,42 @@
1
1
  ---
2
- title: Environment Variables API
2
+ title: Gateway Environment Variables
3
3
  sidebar_label: Environment Variables
4
4
  ---
5
5
 
6
- The `environment` object provides access to environment variables in your Zuplo
7
- Gateway. It returns a record of environment variable names to their values.
6
+ Your API gateway can read every environment variable set for its environment,
7
+ including secrets. This page covers how to read variables in code, configuration
8
+ files, and URL handlers, and which variables Zuplo sets for you.
8
9
 
9
10
  :::info{title="Configuration Guide"}
10
11
 
11
- For information on how to set and manage environment variables in the Zuplo
12
- Portal, see
12
+ To set and manage environment variables, and to learn how gateway variables
13
+ differ from Developer Portal variables, see
13
14
  [Configuring Environment Variables](../articles/environment-variables.mdx).
14
15
 
15
16
  :::
16
17
 
17
- ## Interface
18
+ ## Where to Use Variables
19
+
20
+ Each location in your project has its own syntax:
21
+
22
+ | Location | Syntax | Resolved |
23
+ | ----------------------------------------------------- | ---------------------- | --------------------- |
24
+ | Custom code (handlers, policies, hooks) | `environment.VAR_NAME` | Runtime |
25
+ | Configuration files (`policies.json`, OpenAPI routes) | `$env(VAR_NAME)` | Build time |
26
+ | URL Rewrite, URL Forward, and WebSocket handlers | `${env.VAR_NAME}` | Runtime (per request) |
27
+
28
+ ## In Code
29
+
30
+ The `environment` object from `@zuplo/runtime` returns a record of environment
31
+ variable names to their values.
32
+
33
+ ### Interface
18
34
 
19
35
  ```ts
20
36
  const environment: Record<string, string | undefined>;
21
37
  ```
22
38
 
23
- ## Usage
39
+ ### Usage
24
40
 
25
41
  Access environment variables using the `environment` object:
26
42
 
@@ -49,7 +65,7 @@ export default async function handler(
49
65
  }
50
66
  ```
51
67
 
52
- ## Type Safety
68
+ ### Type Safety
53
69
 
54
70
  Since environment variables might not be defined, they return
55
71
  `string | undefined`:
@@ -70,6 +86,185 @@ if (value) {
70
86
  const port = environment.PORT || "3000";
71
87
  ```
72
88
 
89
+ ## In Configuration Files
90
+
91
+ Inside policy options, route handler options, and CORS policy options,
92
+ environment variables can be referenced with the `$env(VAR_NAME)` pattern.
93
+ Substitutions happen at build time — the build replaces each `$env()` expression
94
+ with a reference to the runtime `environment` object before the project is
95
+ deployed.
96
+
97
+ ### Where `$env()` is allowed
98
+
99
+ - `config/policies.json` — any property under `policies[].handler.options`,
100
+ including nested objects and array elements
101
+ - `config/policies.json` — any direct property of a `corsPolicies[]` entry, such
102
+ as `allowedOrigins`, `allowedHeaders`, `allowedMethods`, `exposeHeaders`, and
103
+ `maxAge`
104
+ - `config/routes.oas.json` (and other OpenAPI route files) — any property under
105
+ a route's `x-zuplo-route.handler.options`, including nested objects and array
106
+ elements
107
+
108
+ `$env()` is **not** allowed in other locations such as policy `name`,
109
+ `policyType`, `module`, route paths, or top-level OpenAPI fields. Using it
110
+ elsewhere produces a build error:
111
+
112
+ ```text
113
+ An $env() statement is not expected at this location.
114
+ ```
115
+
116
+ ### Standalone substitution
117
+
118
+ When the value is _only_ an `$env()` expression, the variable's value is
119
+ inserted directly. The runtime type matches the type of the environment variable
120
+ (always a string when set, `undefined` when not).
121
+
122
+ ```json
123
+ {
124
+ "name": "my-custom-code-inbound-policy",
125
+ "policyType": "custom-code-inbound",
126
+ "handler": {
127
+ "export": "default",
128
+ "module": "$import(./modules/YOUR_MODULE)",
129
+ "options": {
130
+ "apiKey": "$env(BACKEND_API_KEY)",
131
+ "config2": true
132
+ }
133
+ }
134
+ }
135
+ ```
136
+
137
+ ### String interpolation
138
+
139
+ You can mix `$env()` with literal text to compose a value. The build wraps the
140
+ result in a JavaScript template literal that's evaluated at runtime, so each
141
+ request gets the current value of the variable.
142
+
143
+ ```json
144
+ {
145
+ "options": {
146
+ "endpoint": "https://$env(API_HOST)/v1",
147
+ "userAgent": "MyGateway/$env(APP_VERSION) ($env(ENVIRONMENT_NAME))"
148
+ }
149
+ }
150
+ ```
151
+
152
+ :::note
153
+
154
+ If the variable isn't set, the interpolated portion resolves to an empty string.
155
+ For example, with `API_HOST` unset, `"https://$env(API_HOST)/v1"` becomes
156
+ `"https:///v1"`. Use a standalone `$env()` (no surrounding text) if you need to
157
+ detect an unset variable in your handler or policy code.
158
+
159
+ :::
160
+
161
+ ### In arrays
162
+
163
+ `$env()` works inside string array elements, including mixed arrays where some
164
+ elements are static and others are interpolated:
165
+
166
+ ```json
167
+ {
168
+ "options": {
169
+ "allowedKeys": ["$env(PRIMARY_KEY)", "$env(SECONDARY_KEY)"],
170
+ "tags": ["public", "$env(REGION)", "tier-$env(SERVICE_TIER)"]
171
+ }
172
+ }
173
+ ```
174
+
175
+ ### In nested objects
176
+
177
+ `$env()` works at any depth within a handler or policy `options` object:
178
+
179
+ ```json
180
+ {
181
+ "options": {
182
+ "database": {
183
+ "host": "$env(DB_HOST)",
184
+ "credentials": {
185
+ "username": "$env(DB_USER)",
186
+ "password": "$env(DB_PASSWORD)"
187
+ }
188
+ }
189
+ }
190
+ }
191
+ ```
192
+
193
+ ### Variable name rules
194
+
195
+ The name inside `$env(...)` is matched literally up to the first closing
196
+ parenthesis. Use only letters, digits, and underscores in the name — matching
197
+ what the
198
+ [Zuplo Portal](../articles/environment-variables.mdx#environment-variable-editor)
199
+ accepts when you create the variable.
200
+
201
+ ## Rewrite, Forwarding, and WebSocket Handlers
202
+
203
+ The URL Rewrite handler's `rewritePattern`, the URL Forward handler's `baseUrl`,
204
+ and the WebSocket handler's `rewritePattern` use a different syntax. These
205
+ options are evaluated as JavaScript template literals at request time, so you
206
+ reference variables with `${env.VAR_NAME}`:
207
+
208
+ ```json
209
+ {
210
+ "handler": {
211
+ "export": "urlForwardHandler",
212
+ "module": "$import(@zuplo/runtime)",
213
+ "options": {
214
+ "baseUrl": "https://${env.BACKEND_HOST}"
215
+ }
216
+ }
217
+ }
218
+ ```
219
+
220
+ Because `rewritePattern` and `baseUrl` run as code, they also have access to the
221
+ request, URL parts, route params, and query string. See the
222
+ [URL Rewrite Handler](../handlers/url-rewrite.mdx) docs for the full list of
223
+ available variables.
224
+
225
+ :::caution
226
+
227
+ Using `$env(VAR_NAME)` inside `rewritePattern` or `baseUrl` is the most common
228
+ mistake. The build will warn you when it detects this, but the value will be
229
+ passed through to the handler as a literal string and the rewrite will fail at
230
+ runtime. Always use `${env.VAR_NAME}` in these two options.
231
+
232
+ :::
233
+
234
+ ## System Environment Variables
235
+
236
+ Zuplo sets the following variables for your gateway. You can't create variables
237
+ with these names.
238
+
239
+ | Variable | Value |
240
+ | -------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
241
+ | `ZUPLO_ENVIRONMENT_STAGE` | The environment stage: `production`, `preview`, `working-copy`, or `local`. Use this variable to check which environment the gateway runs in. |
242
+ | `ZUPLO_ENVIRONMENT_TYPE` | The kind of host that runs the gateway, such as `edge` or `local`. |
243
+ | `ZUPLO_ENVIRONMENT_NAME` | The globally unique name of the environment, as used in its default URL. For example, `my-project-main-1235` in `my-project-main-1235.zuplo.app`. Custom domains don't change this value. |
244
+ | `ZUPLO_ACCOUNT_NAME` | The name of the Zuplo account that owns the environment. |
245
+ | `ZUPLO_PROJECT_NAME` | The name of the project that owns the environment. |
246
+ | `ZUPLO_BUILD_ID` | A unique ID (UUID) for the build of the environment. |
247
+ | `ZUPLO_COMPATIBILITY_DATE` | The [compatibility date](./compatibility-dates.mdx) of the runtime environment. |
248
+
249
+ :::note
250
+
251
+ Your Developer Portal gets a different set of system variables, and its
252
+ `ZUPLO_ENVIRONMENT_TYPE` has different values. See
253
+ [Dev Portal environment variables](../dev-portal/zudoku/guides/environment-variables.md).
254
+
255
+ :::
256
+
257
+ ## Environment Detection
258
+
259
+ Zuplo doesn't set `NODE_ENV`, because the gateway runs on a V8 runtime, not
260
+ Node.js. Use `ZUPLO_ENVIRONMENT_STAGE` instead:
261
+
262
+ ```ts
263
+ import { environment } from "@zuplo/runtime";
264
+
265
+ const isProduction = environment.ZUPLO_ENVIRONMENT_STAGE === "production";
266
+ ```
267
+
73
268
  ## Common Patterns
74
269
 
75
270
  ### Required Environment Variables
@@ -158,28 +353,19 @@ export default async function handler(
158
353
 
159
354
  ### Environment-Specific Configuration
160
355
 
161
- To handle environment-specific logic, use environment variables that you define:
356
+ Use `ZUPLO_ENVIRONMENT_STAGE` to change behavior between production, preview,
357
+ and development environments:
162
358
 
163
359
  ```ts
164
- import { environment } from "@zuplo/runtime";
360
+ import { environment, ZuploContext, ZuploRequest } from "@zuplo/runtime";
165
361
 
166
- // Define your own environment variable to identify the environment
167
- const isProduction = environment.ENVIRONMENT === "production";
168
- const isStaging = environment.ENVIRONMENT === "staging";
169
- const isDevelopment = environment.ENVIRONMENT === "development";
362
+ const isProduction = environment.ZUPLO_ENVIRONMENT_STAGE === "production";
170
363
 
171
364
  export default async function handler(
172
365
  request: ZuploRequest,
173
366
  context: ZuploContext,
174
367
  ) {
175
- // Use different endpoints based on environment
176
- const apiUrl = isProduction
177
- ? "https://api.example.com"
178
- : isStaging
179
- ? "https://staging-api.example.com"
180
- : "https://dev-api.example.com";
181
-
182
- // Enable debug logging in non-production
368
+ // Enable debug logging outside production
183
369
  if (!isProduction) {
184
370
  context.log.debug("Incoming request", {
185
371
  headers: Object.fromEntries(request.headers),
@@ -187,19 +373,15 @@ export default async function handler(
187
373
  });
188
374
  }
189
375
 
190
- // Use stricter security in production
191
- const headers: HeadersInit = {
192
- "Content-Type": "application/json",
193
- };
194
-
195
- if (isProduction) {
196
- headers["Strict-Transport-Security"] = "max-age=31536000";
197
- }
198
-
199
- return new Response("OK", { headers });
376
+ return new Response("OK");
200
377
  }
201
378
  ```
202
379
 
380
+ To use different values per environment, such as a backend URL, set the same
381
+ variable with a different value for each
382
+ [environment](../articles/environment-variables.mdx#environments) instead of
383
+ branching in code.
384
+
203
385
  ### Secret Management
204
386
 
205
387
  ```ts
@@ -242,13 +424,6 @@ function validateSecrets() {
242
424
  validateSecrets();
243
425
  ```
244
426
 
245
- ## Environment Detection
246
-
247
- Zuplo doesn't provide a built-in `NODE_ENV` variable since it runs on a custom
248
- V8 runtime, not Node.js. To implement environment-specific logic, define your
249
- own environment variables (for example, `ENVIRONMENT`, `STAGE`, or `ENV_TYPE`)
250
- and set them appropriately for each environment in your project settings.
251
-
252
427
  ## Best Practices
253
428
 
254
429
  ### 1. Use Descriptive Names
@@ -313,17 +488,14 @@ context.log.info("Config loaded", {
313
488
 
314
489
  ## Setting Environment Variables
315
490
 
316
- Set environment variables in the Zuplo Portal:
317
-
318
- 1. Open the
319
- [**Environments**](https://portal.zuplo.com/+/account/project/environments)
320
- tab of your project.
321
- 2. Select an environment and add variables under **Environment Variables**.
322
- 3. Zuplo encrypts variables at rest and in transit.
491
+ Set environment variables in the Zuplo Portal under **Settings** >
492
+ [**Environment Variables**](https://portal.zuplo.com/+/account/project/settings/environment-variables).
493
+ For local development, use a `.env` file. See
494
+ [Configuring Environment Variables Locally](../articles/local-development-env-variables.mdx).
323
495
 
324
496
  ## See Also
325
497
 
326
- - [Environment Variables](../articles/environment-variables.mdx) - Setting up
327
- environment variables
498
+ - [Configuring Environment Variables](../articles/environment-variables.mdx) -
499
+ Setting up environment variables
328
500
  - [ConfigurationError](./runtime-errors.mdx#configurationerror) - Error handling
329
501
  for configuration issues
@@ -307,6 +307,30 @@ Hooks can be registered in two ways:
307
307
  2. **Globally via Runtime Extensions** - Register hooks that apply to all
308
308
  requests using [Runtime Extensions](./runtime-extensions.mdx)
309
309
 
310
+ ## Internal route invocations
311
+
312
+ Global runtime hooks also run when
313
+ [`context.invokeRoute`](./zuplo-context.mdx#invokeroute) invokes another route
314
+ in the same gateway. This includes API routes invoked by the MCP Server handler
315
+ for tool calls. The hooks receive the internal route's request and a new context
316
+ whose `parentContext` points to the calling context.
317
+
318
+ In request and response hooks, use `context.parentContext === undefined` to
319
+ select only client requests, or check for a parent context to select internal
320
+ invocations. Pre-routing hooks receive only a request, so they can't use this
321
+ context check.
322
+
323
+ By default, an internal invocation inherits the parent's `requestId`, while each
324
+ context has a distinct `contextId`. Group related analytics events by
325
+ `requestId` and distinguish executions by `contextId`.
326
+
327
+ For a complete MCP example, including how to identify the tool and avoid
328
+ counting both the client request and its internal invocation, see
329
+ [Track tool calls with runtime hooks](../mcp-server/tools.mdx#track-tool-calls-with-runtime-hooks).
330
+ Use an [`addResponseSendingFinalHook`](#hook-onresponsesendingfinal) when your
331
+ event needs the response status. For an internal invocation, that status belongs
332
+ to the API route response; the outer `/mcp` response contains the MCP result.
333
+
310
334
  ## Complete Hook Pipeline Example
311
335
 
312
336
  This example demonstrates how all hooks work together in a request/response
@@ -177,6 +177,28 @@ export default async function (request: ZuploRequest, context: ZuploContext) {
177
177
  Unlike `setRawCallToolResult`, this method can be called any number of times —
178
178
  the request object is not consumed.
179
179
 
180
+ #### MCP metadata in runtime hooks
181
+
182
+ The MCP Server handler stores the original `tools/call` request when it starts
183
+ processing the request. Availability therefore depends on where the hook runs:
184
+
185
+ | Hook location | `getRawCallToolRequest()` result |
186
+ | ------------------------------------------------- | --------------------------------------------------------------------------- |
187
+ | Global request hook on the initial `/mcp` request | `null`: the MCP handler hasn't run yet |
188
+ | Request or response hook on the tool's API route | The original tool call, read from the parent context |
189
+ | Response hook on the outer `/mcp` request | The original tool call, if the MCP handler processed a `tools/call` request |
190
+
191
+ Use `context.parentContext` to distinguish an internal invocation from the
192
+ initial client request. The presence of a tool call alone doesn't distinguish
193
+ them in a response hook. See
194
+ [Track tool calls with runtime hooks](../mcp-server/tools.mdx#track-tool-calls-with-runtime-hooks)
195
+ for an analytics example.
196
+
197
+ The helper checks only the current context and its immediate parent. If your
198
+ tool's API route invokes another route, the deeper invocation can't read the
199
+ original tool call through this helper unless your code passes the metadata
200
+ along. Calls such as `tools/list` don't populate tool-call metadata.
201
+
180
202
  ## Output schema enforcement
181
203
 
182
204
  When a tool advertises an `outputSchema` (via `includeOutputSchema` on the
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zuplo",
3
- "version": "7.9.13",
3
+ "version": "7.9.15",
4
4
  "type": "module",
5
5
  "description": "The official Zuplo CLI for local development and platform management",
6
6
  "homepage": "https://zuplo.com/docs/cli/overview",
@@ -32,9 +32,9 @@
32
32
  "zuplo": "zuplo.js"
33
33
  },
34
34
  "dependencies": {
35
- "@zuplo/cli": "7.9.13",
36
- "@zuplo/core": "7.9.13",
37
- "@zuplo/runtime": "7.9.13",
38
- "@zuplo/test": "7.9.13"
35
+ "@zuplo/cli": "7.9.15",
36
+ "@zuplo/core": "7.9.15",
37
+ "@zuplo/runtime": "7.9.15",
38
+ "@zuplo/test": "7.9.15"
39
39
  }
40
40
  }
@@ -1,102 +0,0 @@
1
- ## How it works
2
-
3
- `context.invokeRoute` runs a route on this gateway without leaving the process,
4
- and the runtime marks the request it creates by setting `context.parentContext`
5
- to the calling request's context. Nothing else sets that property. A request
6
- that arrives over the network never has one, whatever headers, URL, or body it
7
- carries. This policy passes a request when `context.parentContext` is present
8
- and rejects it otherwise.
9
-
10
- | Request origin | Result |
11
- | ------------------------------------------------------------------------------ | ------ |
12
- | Any HTTP client, with or without an API key | `403` |
13
- | `context.invokeRoute` from a policy, handler, or custom module on this gateway | Passes |
14
- | The MCP server handler calling the route as a tool | Passes |
15
- | An internal application calling another internal application | Passes |
16
-
17
- The rejection is a `403 Forbidden` problem response. On AWS Bedrock Runtime
18
- routes it uses the AWS error envelope (`AccessDeniedException`) so the AWS SDKs
19
- surface the message.
20
-
21
- The policy does not authenticate the caller and does not set `request.user`.
22
- Metering records the request against the application with no consumer, and a
23
- budget rule keyed on `request.user` has no value to accumulate for these
24
- requests. Attach per-consumer budgets to the calling application instead.
25
-
26
- ## Add the policy
27
-
28
- Declare the policy in `config/policies.json`:
29
-
30
- ```json
31
- {
32
- "policies": [
33
- {
34
- "name": "ai-gateway-internal-only-inbound",
35
- "policyType": "ai-gateway-internal-only",
36
- "handler": {
37
- "export": "AIGatewayInternalOnlyInboundPolicy",
38
- "module": "$import(@zuplo/runtime)",
39
- "options": {}
40
- }
41
- }
42
- ]
43
- }
44
- ```
45
-
46
- Then select it in the application's `inboundPolicyChain` in place of
47
- `ai-gateway-auth-inbound`. Put it first so nothing else runs for a rejected
48
- request:
49
-
50
- ```json
51
- {
52
- "inboundPolicyChain": [
53
- { "name": "ai-gateway-internal-only-inbound" },
54
- { "name": "ai-gateway-model-filtering-inbound" },
55
- { "name": "ai-gateway-metering-inbound" }
56
- ]
57
- }
58
- ```
59
-
60
- The policy also works as a route-level inbound policy on any route that should
61
- be reachable only through `context.invokeRoute`.
62
-
63
- ## Call the application
64
-
65
- Call the application from a policy or handler on the same gateway. No
66
- `Authorization` header is needed:
67
-
68
- ```ts
69
- const response = await context.invokeRoute(
70
- `/${classifierAppId}/v1/chat/completions`,
71
- {
72
- method: "POST",
73
- headers: { "content-type": "application/json" },
74
- body: JSON.stringify({ model: "openai/gpt-5-mini", messages }),
75
- }
76
- );
77
- ```
78
-
79
- The Smart Router and Prompt Injection Protection policies already call their
80
- classifier applications this way.
81
-
82
- A direct call from outside the gateway is rejected:
83
-
84
- ```bash
85
- curl https://gateway.example.com/my-classifier-app/v1/chat/completions \
86
- --header "Content-Type: application/json" \
87
- --data '{
88
- "model": "openai/gpt-5-mini",
89
- "messages": [{ "role": "user", "content": "Hello" }]
90
- }'
91
- ```
92
-
93
- The response is `403 Forbidden`, and its `detail` explains that the application
94
- only accepts requests made from inside the gateway with `context.invokeRoute`.
95
-
96
- ## What it does not check
97
-
98
- This policy asserts that a request originated inside this gateway deployment,
99
- not that the caller is trusted. Any route on the same gateway that forwards
100
- requests through `context.invokeRoute`, including an MCP server that exposes the
101
- application as a tool, reaches the application. Protect such routes with their
102
- own authentication, exactly as you would an internal URL.
@@ -1,11 +0,0 @@
1
- The Ensure Gateway Internal Invocation Only policy admits a request only when
2
- this gateway made it with `context.invokeRoute`, and answers every request that
3
- arrived over the network with `403 Forbidden`.
4
-
5
- Add it to an application's `inboundPolicyChain` in place of AI Gateway
6
- Authentication when the application is only ever called by other routes and
7
- policies on the same gateway, such as the classifier application behind the
8
- Smart Router or Prompt Injection Protection policies. Those calls never leave
9
- the gateway, so an API key on them protects nothing; this policy checks the one
10
- thing that matters. It has no options, makes no network calls, and never reads
11
- the request body.
@@ -1,50 +0,0 @@
1
- {
2
- "$schema": "https://json-schema.org/draft-07/schema",
3
- "$id": "https://cdn.zuplo.com/policies/runtime/schemas/ai-gateway-internal-only-inbound.json",
4
- "type": "object",
5
- "title": "Ensure Gateway Internal Invocation Only",
6
- "isDeprecated": false,
7
- "isPaidAddOn": false,
8
- "isEnterprise": false,
9
- "isInternal": false,
10
- "isBeta": false,
11
- "isHidden": false,
12
- "requiresAI": true,
13
- "policyType": "ai-gateway-internal-only",
14
- "products": ["ai-gateway"],
15
- "description": "Accepts only requests that this gateway made itself with `context.invokeRoute`, and rejects every request that arrived over the network with `403 Forbidden`.\n\nUse it in place of AI Gateway Authentication on an application that is only ever called by other routes and policies on the same gateway, such as the classifier application behind the Smart Router or Prompt Injection Protection policies. Those calls never leave the gateway, so an API key on them protects nothing. This policy asserts the one thing that matters: the request originated inside the deployment.\n\nThe check reads `context.parentContext`, which the runtime sets only on the child context that `context.invokeRoute` creates. A client cannot supply it through a header, the URL, or the body. The policy has no options, makes no network calls, reads no body, and leaves `request.user` unset.",
16
- "deprecatedMessage": "",
17
- "required": ["handler"],
18
- "properties": {
19
- "handler": {
20
- "type": "object",
21
- "default": {},
22
- "required": ["export", "module", "options"],
23
- "properties": {
24
- "export": {
25
- "const": "AIGatewayInternalOnlyInboundPolicy",
26
- "description": "The name of the exported type"
27
- },
28
- "module": {
29
- "const": "$import(@zuplo/runtime)",
30
- "description": "The module containing the policy"
31
- },
32
- "options": {
33
- "title": "AIGatewayInternalOnlyInboundPolicyOptions",
34
- "type": "object",
35
- "description": "The options for this policy. It has none.",
36
- "additionalProperties": false,
37
- "required": [],
38
- "properties": {}
39
- }
40
- },
41
- "examples": [
42
- {
43
- "export": "AIGatewayInternalOnlyInboundPolicy",
44
- "module": "$import(@zuplo/runtime)",
45
- "options": {}
46
- }
47
- ]
48
- }
49
- }
50
- }