zuplo 7.7.17 → 7.8.0

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.
@@ -243,23 +243,25 @@ into it.
243
243
 
244
244
  ## Policies on Bedrock Runtime routes
245
245
 
246
- These routes run your app's [policy chain](./policy-chains.mdx) like any other,
247
- with two differences that follow from the request body being Bedrock's shape
248
- rather than one the gateway parses:
249
-
250
- | Policy | On Bedrock Runtime routes |
251
- | ------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------ |
252
- | [Authentication](../policies/ai-gateway-auth-v2-inbound.mdx) | Works. Reads the app key from `Authorization: Bearer` |
253
- | [Model Filtering](../policies/ai-gateway-model-filtering-v2-inbound.mdx) | Works. Refuses a model outside the allow list before any AWS call |
254
- | [Metering and budgets](../policies/ai-gateway-metering-v2-inbound.mdx) | Works. Meters tokens and cost on all four operations, streamed or not |
255
- | [DLP](../policies/ai-gateway-dlp-inbound.mdx) and content guardrails | **Blocks by default.** The gateway doesn't parse these bodies, so `onUnknownShape` applies |
256
- | Semantic Cache | Skipped |
246
+ These routes run your app's [policy chain](./policy-chains.mdx) like any other.
247
+ What changes is that the request body is Bedrock's shape rather than one the
248
+ gateway parses, so a policy that reads the body applies its `onUnknownShape`
249
+ setting instead. Each policy's default follows from its job: a guardrail fails
250
+ closed, while an observer or an optimization fails open.
251
+
252
+ | Policy | On Bedrock Runtime routes |
253
+ | ----------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
254
+ | [Authentication](../policies/ai-gateway-auth-v2-inbound.mdx) | Works. Reads the app key from `Authorization: Bearer` |
255
+ | [Model Filtering](../policies/ai-gateway-model-filtering-v2-inbound.mdx) | Works. Refuses a model outside the allow list before any AWS call |
256
+ | [Metering and budgets](../policies/ai-gateway-metering-v2-inbound.mdx) | Works. Meters tokens and cost on all four operations, streamed or not |
257
+ | [DLP](../policies/ai-gateway-dlp-inbound.mdx) and content guardrails | **Blocks by default.** The gateway can't read these bodies, so the policy denies the request. Set `onUnknownShape` to `skip` to forward it uninspected |
258
+ | [Galileo](../policies/galileo-tracing-v2-inbound.mdx) and [Comet Opik](../policies/comet-opik-tracing-v2-inbound.mdx) tracing | **Skipped by default.** The request goes through untraced. Set `onUnknownShape` to `deny` to make a trace a hard requirement |
259
+ | Semantic Cache | Skipped |
257
260
 
258
261
  The table lists every policy the gateway supports on these routes. It audits the
259
- chain and refuses any other policy, such as a custom policy, Smart Router,
260
- [Fallback Model](./fallback.mdx), or tracing, with a `400 ValidationException`
261
- that names the policy. Remove it from the chain to use Bedrock Runtime through
262
- that app.
262
+ chain and refuses any other policy, such as a custom policy, Smart Router, or
263
+ [Fallback Model](./fallback.mdx), with a `400 ValidationException` that names
264
+ the policy. Remove it from the chain to use Bedrock Runtime through that app.
263
265
 
264
266
  If you need content inspection on Bedrock traffic, use
265
267
  [Bedrock's own guardrails](https://docs.aws.amazon.com/bedrock/latest/userguide/guardrails.html)
@@ -406,6 +406,32 @@ The corresponding expression is `request.headers.get("x-budget-customer")`.
406
406
  Configuration code should create that ordinary string and pass the enclosing
407
407
  object to `JSON.stringify`; the serializer adds the JSON transport escaping.
408
408
 
409
+ Headers you set or remove in the inbound chain control what reaches your AI
410
+ provider. For example, you can add a session header or remove an internal
411
+ customer header before sending the request. Gateway-owned headers are excluded,
412
+ including credentials, `Content-Type`, `Accept`, and headers starting with `zp-`
413
+ or `cf-`; setting a credential header does not override your configured provider
414
+ credential.
415
+
416
+ That includes headers you add only for the gateway's own use. The
417
+ `x-budget-customer` header above carries the caller's user id, and unless you
418
+ remove it after the metering policy has read it, the provider receives it on
419
+ every request. Add a `remove-headers-inbound` entry after the metering entry:
420
+
421
+ ```json
422
+ [
423
+ { "name": "set-budget-dimension" },
424
+ { "name": "ai-gateway-metering-v2-inbound" },
425
+ {
426
+ "name": "remove-headers-inbound",
427
+ "options": { "headers": ["x-budget-customer"] }
428
+ }
429
+ ]
430
+ ```
431
+
432
+ Declare `remove-headers-inbound` in `config/policies.json` so the chain can
433
+ select it.
434
+
409
435
  Invalid budget rules and unsupported expressions are logged and skipped. Other
410
436
  valid rules continue to apply.
411
437
 
@@ -52,7 +52,7 @@
52
52
  },
53
53
  "clientSecret": {
54
54
  "type": "string",
55
- "examples": ["$env(MCP_AUTH0_CLIENT_SECRET)"],
55
+ "examples": ["$env(AUTH0_CLIENT_SECRET)"],
56
56
  "description": "The Auth0 client\\_secret. Use $env(...) to source from a secret environment variable."
57
57
  },
58
58
  "scope": {
@@ -209,7 +209,7 @@
209
209
  "sessionTtlSeconds": 28800,
210
210
  "stateTtlSeconds": 900
211
211
  },
212
- "clientSecret": "$env(MCP_AUTH0_CLIENT_SECRET)",
212
+ "clientSecret": "$env(AUTH0_CLIENT_SECRET)",
213
213
  "gateway": {
214
214
  "accessTokenTtlSeconds": 900,
215
215
  "cimdEnabled": true,
@@ -73,7 +73,7 @@
73
73
  "type": "string",
74
74
  "format": "uri",
75
75
  "examples": ["https://my-tenant.us.auth0.com/authorize"],
76
- "description": "The IdP /authorize endpoint to redirect the user to. For local development on loopback, use http://127.0.0.1:9000/oauth/dev-login."
76
+ "description": "The IdP /authorize endpoint to redirect the user to. For local development on loopback, use http://127.0.0.1:9000/\\_\\_zuplo/oauth/dev-login (the gateway's default basePath is /\\_\\_zuplo; adjust if the plugin is configured with a different one)."
77
77
  },
78
78
  "tokenUrl": {
79
79
  "type": "string",
@@ -108,6 +108,52 @@ as Entra tenant admin consent. If connections start requiring frequent
108
108
  reauthorization after setting `prompt: false`, remove the option so the consent
109
109
  grant is requested again.
110
110
 
111
+ ## Authorization Server Issuer Mismatch
112
+
113
+ Some providers publish inconsistent OAuth metadata: the protected-resource
114
+ document advertises a path-scoped authorization server, but that server's own
115
+ metadata reports a different (usually root-origin) `issuer`. ServiceNow is one —
116
+ its protected-resource metadata names
117
+ `https://<instance>.service-now.com/sncapps/...` while the metadata served there
118
+ reports `https://<instance>.service-now.com`. Atlassian and Okta
119
+ org-versus-custom authorization servers publish the same shape.
120
+
121
+ RFC 8414 section 3.3 requires a client to reject a metadata document whose
122
+ `issuer` differs from the identifier the well-known URL was built from, so the
123
+ gateway hard-fails discovery against these providers. Set
124
+ `skipIssuerMetadataValidation` to `true` on that connection to proceed anyway:
125
+
126
+ ```json
127
+ {
128
+ "name": "mcp-token-exchange-servicenow",
129
+ "policyType": "mcp-token-exchange-inbound",
130
+ "handler": {
131
+ "module": "$import(@zuplo/runtime/mcp-gateway)",
132
+ "export": "McpTokenExchangeInboundPolicy",
133
+ "options": {
134
+ "displayName": "ServiceNow",
135
+ "authMode": "user-oauth",
136
+ "skipIssuerMetadataValidation": true
137
+ }
138
+ }
139
+ }
140
+ ```
141
+
142
+ **Warning**: this puts the connection out of spec. The issuer check is an
143
+ unqualified MUST in both RFC 8414 section 3.3 and the MCP authorization spec,
144
+ whose own example is the attack it prevents — a document served from
145
+ `https://attacker.example/.well-known/oauth-authorization-server` claiming
146
+ `"issuer": "https://honest.example"` must be rejected. The check binds the
147
+ metadata to the TLS-authenticated host it came from, and with it off the gateway
148
+ trusts the authorization, token and JWKS endpoints from a document that never
149
+ proved it speaks for that issuer.
150
+
151
+ Only enable it for a specific upstream known to publish a mismatched issuer, and
152
+ only when the metadata URL's host is itself trusted. Never enable it as a
153
+ default. It relaxes the discovery-time check only: the RFC 9207
154
+ authorization-response check and the callback-leg authorization-server binding
155
+ still apply.
156
+
111
157
  ## ID-JAG Token Exchange (Cross-App Access)
112
158
 
113
159
  Set `authMode` to `id-jag` when the upstream is protected by an Identity
@@ -128,8 +174,8 @@ Use this mode in enterprise deployments where the gateway and the upstream trust
128
174
  the same identity provider (for example, Okta Cross-App Access), so upstream
129
175
  access follows the caller's identity without a per-user upstream consent flow.
130
176
  The `user-oauth`/`shared-oauth` options — `scopes`, `scopeDelimiter`, `prompt`,
131
- and `clientRegistration` — do not apply; the `id-jag` mode is configured
132
- entirely through the `idJag` option.
177
+ `skipIssuerMetadataValidation`, and `clientRegistration` — do not apply; the
178
+ `id-jag` mode is configured entirely through the `idJag` option.
133
179
 
134
180
  ```json
135
181
  {
@@ -56,6 +56,9 @@
56
56
  {
57
57
  "required": ["prompt"]
58
58
  },
59
+ {
60
+ "required": ["skipIssuerMetadataValidation"]
61
+ },
59
62
  {
60
63
  "required": ["clientRegistration"]
61
64
  }
@@ -120,6 +123,11 @@
120
123
  }
121
124
  ]
122
125
  },
126
+ "skipIssuerMetadataValidation": {
127
+ "type": "boolean",
128
+ "x-advanced": true,
129
+ "description": "Skips the RFC 8414 section 3.3 issuer-echo check when discovering the upstream authorization server. That check requires the `issuer` in the authorization server's metadata to equal the `authorization_servers` entry the protected-resource metadata advertised. Some providers publish a path-scoped authorization server whose metadata reports the root origin instead — for example ServiceNow advertises `https://<instance>.service-now.com/sncapps/...` but its metadata reports `https://<instance>.service-now.com` — so discovery hard-fails without this option. Warning: enabling this puts the connection out of spec. The check is an unqualified MUST in both RFC 8414 section 3.3 and the MCP authorization spec, which require rejecting a metadata document whose `issuer` differs from the identifier the well-known URL was built from; the spec's own example is a document served from `https://attacker.example/.well-known/oauth-authorization-server` claiming `\"issuer\": \"https://honest.example\"`. The echo check binds the metadata to the TLS-authenticated host it came from, and with it off the gateway trusts the authorization, token and JWKS endpoints from a document that never proved it speaks for that issuer. Only enable it for a specific upstream known to publish a mismatched issuer, and only when the metadata URL's host is itself trusted. It relaxes the discovery-time check only — the RFC 9207 authorization-response check and the callback-leg authorization-server binding still apply."
130
+ },
123
131
  "clientRegistration": {
124
132
  "x-advanced": true,
125
133
  "description": "OAuth client registration mode. Defaults to `auto`, which uses Client ID Metadata Documents when the upstream advertises support and falls back to Dynamic Client Registration otherwise.",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zuplo",
3
- "version": "7.7.17",
3
+ "version": "7.8.0",
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.7.17",
36
- "@zuplo/core": "7.7.17",
37
- "@zuplo/runtime": "7.7.17",
38
- "@zuplo/test": "7.7.17"
35
+ "@zuplo/cli": "7.8.0",
36
+ "@zuplo/core": "7.8.0",
37
+ "@zuplo/runtime": "7.8.0",
38
+ "@zuplo/test": "7.8.0"
39
39
  }
40
40
  }