@zuplo/cli 6.72.15 → 6.72.17
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.
- package/node_modules/@fastify/fast-json-stringify-compiler/node_modules/fast-uri/index.js +17 -0
- package/node_modules/@fastify/fast-json-stringify-compiler/node_modules/fast-uri/package.json +1 -1
- package/node_modules/@fastify/fast-json-stringify-compiler/node_modules/fast-uri/test/security.test.js +62 -0
- package/node_modules/@fastify/reply-from/CLAUDE.md +66 -0
- package/node_modules/@fastify/reply-from/index.js +8 -4
- package/node_modules/@fastify/reply-from/package.json +1 -1
- package/node_modules/@fastify/reply-from/test/fix-GHSA-v574-6498-x57v.test.js +219 -0
- package/node_modules/@fastify/reply-from/test/http1-aborted-request.test.js +61 -0
- package/node_modules/@zuplo/core/package.json +1 -1
- package/node_modules/@zuplo/graphql/package.json +1 -1
- package/node_modules/@zuplo/openapi-tools/package.json +1 -1
- package/node_modules/@zuplo/otel/package.json +1 -1
- package/node_modules/@zuplo/runtime/out/esm/chunk-2WLR6ZDZ.js +26 -0
- package/node_modules/@zuplo/runtime/out/esm/chunk-2WLR6ZDZ.js.map +1 -0
- package/node_modules/@zuplo/runtime/out/esm/chunk-EJIF2ZML.js +26 -0
- package/node_modules/@zuplo/runtime/out/esm/chunk-EJIF2ZML.js.map +1 -0
- package/node_modules/@zuplo/runtime/out/esm/chunk-FPZAMUNA.js +26 -0
- package/node_modules/@zuplo/runtime/out/esm/chunk-FPZAMUNA.js.map +1 -0
- package/node_modules/@zuplo/runtime/out/esm/{chunk-J6K7HSIK.js → chunk-QWO5UTVQ.js} +2 -2
- package/node_modules/@zuplo/runtime/out/esm/{chunk-J6K7HSIK.js.map → chunk-QWO5UTVQ.js.map} +1 -1
- package/node_modules/@zuplo/runtime/out/esm/chunk-S2CH4265.js +385 -0
- package/node_modules/@zuplo/runtime/out/esm/chunk-S2CH4265.js.map +1 -0
- package/node_modules/@zuplo/runtime/out/esm/chunk-SNZSLL63.js +26 -0
- package/node_modules/@zuplo/runtime/out/esm/chunk-SNZSLL63.js.map +1 -0
- package/node_modules/@zuplo/runtime/out/esm/chunk-US6ITPGV.js +26 -0
- package/node_modules/@zuplo/runtime/out/esm/{chunk-32F3RZS4.js.map → chunk-US6ITPGV.js.map} +1 -1
- package/node_modules/@zuplo/runtime/out/esm/{chunk-DSZS6PZJ.js → chunk-VCMIY3WJ.js} +6 -6
- package/node_modules/@zuplo/runtime/out/esm/chunk-VCMIY3WJ.js.map +1 -0
- package/node_modules/@zuplo/runtime/out/esm/index.js +1 -1
- package/node_modules/@zuplo/runtime/out/esm/index.js.map +1 -1
- package/node_modules/@zuplo/runtime/out/esm/internal/index.js +1 -1
- package/node_modules/@zuplo/runtime/out/esm/mcp/messages/index.js +26 -0
- package/node_modules/@zuplo/runtime/out/esm/mcp/messages/index.js.map +1 -0
- package/node_modules/@zuplo/runtime/out/esm/mcp-gateway/index.js +13 -13
- package/node_modules/@zuplo/runtime/out/esm/mcp-gateway/index.js.map +1 -1
- package/node_modules/@zuplo/runtime/out/esm/mocks/index.js +1 -1
- package/node_modules/@zuplo/runtime/out/esm/mocks/index.js.map +1 -1
- package/node_modules/@zuplo/runtime/out/esm/sdks/aws/index.js +1 -1
- package/node_modules/@zuplo/runtime/out/esm/sdks/aws/index.js.map +1 -1
- package/node_modules/@zuplo/runtime/out/types/index.d.ts +1 -0
- package/node_modules/@zuplo/runtime/out/types/mcp/messages/index.d.ts +2439 -0
- package/node_modules/@zuplo/runtime/out/types/mcp-gateway/index.d.ts +1 -0
- package/node_modules/@zuplo/runtime/out/types/mocks/index.d.ts +1 -0
- package/node_modules/@zuplo/runtime/out/types/sdks/aws/index.d.ts +1 -0
- package/node_modules/@zuplo/runtime/package.json +5 -1
- package/package.json +6 -6
- package/node_modules/@zuplo/runtime/out/esm/chunk-32F3RZS4.js +0 -26
- package/node_modules/@zuplo/runtime/out/esm/chunk-4QJJMELB.js +0 -26
- package/node_modules/@zuplo/runtime/out/esm/chunk-4QJJMELB.js.map +0 -1
- package/node_modules/@zuplo/runtime/out/esm/chunk-DSZS6PZJ.js.map +0 -1
- package/node_modules/@zuplo/runtime/out/esm/chunk-SQVIQSIN.js +0 -385
- package/node_modules/@zuplo/runtime/out/esm/chunk-SQVIQSIN.js.map +0 -1
- /package/node_modules/@zuplo/runtime/out/esm/{chunk-SQVIQSIN.js.LEGAL.txt → chunk-S2CH4265.js.LEGAL.txt} +0 -0
- /package/node_modules/@zuplo/runtime/out/esm/{chunk-DSZS6PZJ.js.LEGAL.txt → chunk-VCMIY3WJ.js.LEGAL.txt} +0 -0
|
@@ -202,6 +202,10 @@ function serialize (cmpts, opts) {
|
|
|
202
202
|
|
|
203
203
|
const URI_PARSE = /^(?:([^#/:?]+):)?(?:\/\/((?:([^#/?@]*)@)?(\[[^#/?\]]+\]|[^#/:?]*)(?::(\d*))?))?([^#?]*)(?:\?([^#]*))?(?:#((?:.|[\n\r])*))?/u
|
|
204
204
|
|
|
205
|
+
// Captures the authority component (between "//" and the next "/", "?" or "#"),
|
|
206
|
+
// with or without a scheme prefix, for the literal-backslash rejection below.
|
|
207
|
+
const AUTHORITY_PREFIX = /^(?:[^#/:?]+:)?\/\/([^/?#]*)/
|
|
208
|
+
|
|
205
209
|
/**
|
|
206
210
|
* @param {import('./types/index').URIComponent} parsed
|
|
207
211
|
* @param {RegExpMatchArray} matches
|
|
@@ -248,6 +252,19 @@ function parseWithStatus (uri, opts) {
|
|
|
248
252
|
}
|
|
249
253
|
}
|
|
250
254
|
|
|
255
|
+
// A literal backslash (U+005C) is not a valid RFC 3986 URI character and is
|
|
256
|
+
// not an authority delimiter. Reject it in the authority rather than
|
|
257
|
+
// rewriting it: normalizing "\" -> "/" (WHATWG error recovery) could silently
|
|
258
|
+
// change the resource identified by an otherwise-invalid input, and lets "\"
|
|
259
|
+
// act as a host delimiter here while Node's native URL parses a different
|
|
260
|
+
// host (SSRF / redirect / origin-allowlist bypass). Percent-encoded %5C is
|
|
261
|
+
// untouched and remains valid encoded data.
|
|
262
|
+
const authorityMatch = uri.match(AUTHORITY_PREFIX)
|
|
263
|
+
if (authorityMatch !== null && authorityMatch[1].indexOf('\\') !== -1) {
|
|
264
|
+
parsed.error = 'URI authority must not contain a literal backslash.'
|
|
265
|
+
malformedAuthorityOrPort = true
|
|
266
|
+
}
|
|
267
|
+
|
|
251
268
|
const matches = uri.match(URI_PARSE)
|
|
252
269
|
|
|
253
270
|
if (matches) {
|
|
@@ -159,3 +159,65 @@ test('parse canonicalises IDN / Unicode hosts to their ASCII form', (t) => {
|
|
|
159
159
|
t.equal(parsed.host, expectedHost, `host canonicalised to ASCII: ${description}`)
|
|
160
160
|
})
|
|
161
161
|
})
|
|
162
|
+
|
|
163
|
+
test('parse rejects a literal backslash in the authority as malformed (RFC 3986)', (t) => {
|
|
164
|
+
// Regression for the host-confusion bypass: a literal "\" is invalid RFC 3986
|
|
165
|
+
// syntax and must be flagged malformed, not silently rewritten. Otherwise "\"
|
|
166
|
+
// acts as a host delimiter here while Node's native URL parses a different
|
|
167
|
+
// host, defeating a host-based SSRF/redirect/origin allowlist.
|
|
168
|
+
const cases = [
|
|
169
|
+
'http://evil.com\\@allowed.com',
|
|
170
|
+
'https://169.254.169.254\\@trusted.example.com',
|
|
171
|
+
'http://127.0.0.1\\@public.example.com',
|
|
172
|
+
'https://attacker.com\\@api.internal',
|
|
173
|
+
'http://a\\@b',
|
|
174
|
+
'ws://evil.com\\@allowed.com/chat',
|
|
175
|
+
'wss://evil.com\\@allowed.com/chat',
|
|
176
|
+
'http://evil.com\\%40allowed.com',
|
|
177
|
+
'//evil.com\\@allowed.com'
|
|
178
|
+
]
|
|
179
|
+
|
|
180
|
+
t.plan(cases.length)
|
|
181
|
+
|
|
182
|
+
cases.forEach((input) => {
|
|
183
|
+
t.equal(
|
|
184
|
+
fastURI.parse(input).error,
|
|
185
|
+
'URI authority must not contain a literal backslash.',
|
|
186
|
+
input
|
|
187
|
+
)
|
|
188
|
+
})
|
|
189
|
+
})
|
|
190
|
+
|
|
191
|
+
test('normalize does not canonicalize a literal-backslash URI into a different valid URL', (t) => {
|
|
192
|
+
const cases = [
|
|
193
|
+
'http://evil.com\\@allowed.com',
|
|
194
|
+
'https://attacker.com\\@api.internal'
|
|
195
|
+
]
|
|
196
|
+
|
|
197
|
+
t.plan(cases.length)
|
|
198
|
+
|
|
199
|
+
cases.forEach((input) => {
|
|
200
|
+
t.equal(fastURI.normalize(input), input, input)
|
|
201
|
+
})
|
|
202
|
+
})
|
|
203
|
+
|
|
204
|
+
test('parse leaves percent-encoded %5C untouched as encoded data (not rejected)', (t) => {
|
|
205
|
+
// Only the literal "\" byte is rejected; %5C stays valid encoded data and
|
|
206
|
+
// does not diverge from the native URL parser, so it must not be flagged.
|
|
207
|
+
const input = 'http://evil.com%5C@allowed.com'
|
|
208
|
+
const parsed = fastURI.parse(input)
|
|
209
|
+
|
|
210
|
+
t.plan(2)
|
|
211
|
+
t.notOk(parsed.error, '%5C is valid encoded data, not malformed')
|
|
212
|
+
t.equal(parsed.host, new URL(input).hostname, '%5C host matches native URL (no divergence)')
|
|
213
|
+
})
|
|
214
|
+
|
|
215
|
+
test('parse does not reject a literal backslash in the query or fragment', (t) => {
|
|
216
|
+
// The rejection is scoped to the authority/path (the host-confusion surface);
|
|
217
|
+
// a backslash after "?"/"#" is normalized as encoded data as before.
|
|
218
|
+
const parsed = fastURI.parse('http://host.example.com/?x=\\y#z\\w')
|
|
219
|
+
|
|
220
|
+
t.plan(2)
|
|
221
|
+
t.notOk(parsed.error, 'backslash in query/fragment does not mark the URI malformed')
|
|
222
|
+
t.equal(parsed.host, 'host.example.com', 'host parsed normally')
|
|
223
|
+
})
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
4
|
+
|
|
5
|
+
## Development Commands
|
|
6
|
+
|
|
7
|
+
### Testing
|
|
8
|
+
- `npm test` - Run all tests (unit + TypeScript)
|
|
9
|
+
- `npm run test:unit` - Run unit tests with coverage using c8 and Node.js test runner
|
|
10
|
+
- `npm run test:typescript` - Run TypeScript definition tests using tsd
|
|
11
|
+
|
|
12
|
+
### Linting
|
|
13
|
+
- `npm run lint` - Run ESLint to check code style
|
|
14
|
+
- `npm run lint:fix` - Run ESLint with automatic fixes
|
|
15
|
+
- ESLint configuration uses neostandard with TypeScript support
|
|
16
|
+
|
|
17
|
+
### Building
|
|
18
|
+
- No build step required - this is a CommonJS library
|
|
19
|
+
|
|
20
|
+
## Architecture Overview
|
|
21
|
+
|
|
22
|
+
This is a Fastify plugin for proxying HTTP requests to upstream servers. The plugin decorates the Fastify reply with a `from()` method.
|
|
23
|
+
|
|
24
|
+
### Key Components
|
|
25
|
+
|
|
26
|
+
**Main Plugin (`index.js`)**
|
|
27
|
+
- Entry point that registers the plugin using fastify-plugin
|
|
28
|
+
- Decorates `reply.from(source, opts)` method
|
|
29
|
+
- Handles request proxying, retries, caching, and error handling
|
|
30
|
+
- Supports HTTP/1.1, HTTP/2, and Undici client strategies
|
|
31
|
+
|
|
32
|
+
**Request Builder (`lib/request.js`)**
|
|
33
|
+
- Factory for creating request handlers based on transport (HTTP/1.1, HTTP/2, Undici)
|
|
34
|
+
- Handles different agent configurations (HTTP, HTTPS, Unix sockets)
|
|
35
|
+
- Manages timeouts, connection pooling, and retry logic
|
|
36
|
+
- Returns unified interface: `{ request, close, retryOnError }`
|
|
37
|
+
|
|
38
|
+
**Utilities (`lib/utils.js`)**
|
|
39
|
+
- `filterPseudoHeaders()` - Removes HTTP/2 pseudo-headers (starting with :)
|
|
40
|
+
- `copyHeaders()` - Copies headers to Fastify reply
|
|
41
|
+
- `stripHttp1ConnectionHeaders()` - Removes HTTP/1 connection-specific headers for HTTP/2
|
|
42
|
+
- `buildURL()` - Constructs destination URLs with base path validation
|
|
43
|
+
|
|
44
|
+
**Error Definitions (`lib/errors.js`)**
|
|
45
|
+
- Standardized error types using @fastify/error
|
|
46
|
+
- Maps different error conditions to appropriate HTTP status codes
|
|
47
|
+
- Includes timeout, connection reset, and gateway errors
|
|
48
|
+
|
|
49
|
+
### Transport Strategies
|
|
50
|
+
|
|
51
|
+
1. **Undici (default)** - High-performance HTTP/1.1 client with connection pooling
|
|
52
|
+
2. **HTTP/2** - Native Node.js HTTP/2 client for modern protocols
|
|
53
|
+
3. **HTTP/1.1** - Node.js built-in http/https modules with custom agents
|
|
54
|
+
|
|
55
|
+
### Key Features
|
|
56
|
+
|
|
57
|
+
- **Caching**: URL parsing cache using toad-cache LruMap (default 100 entries)
|
|
58
|
+
- **Retries**: Configurable retry logic for socket hangups and 503 errors
|
|
59
|
+
- **Headers**: Smart header rewriting for HTTP/1.1 ↔ HTTP/2 compatibility
|
|
60
|
+
- **Body handling**: Supports streams, JSON encoding, and custom content types
|
|
61
|
+
- **Unix sockets**: Special handling for unix+http: and unix+https: protocols
|
|
62
|
+
- **Timeouts**: Per-request and session-level timeout configuration
|
|
63
|
+
|
|
64
|
+
### Configuration
|
|
65
|
+
|
|
66
|
+
The plugin accepts extensive configuration for different transport methods, retry behavior, caching, and request/response transformation hooks. See README.md for full option documentation.
|
|
@@ -81,7 +81,7 @@ const fastifyReplyFrom = fp(function from (fastify, opts, next) {
|
|
|
81
81
|
const dest = getUpstream(this.request, base)
|
|
82
82
|
let url
|
|
83
83
|
if (cache) {
|
|
84
|
-
const cacheKey = dest
|
|
84
|
+
const cacheKey = JSON.stringify([dest, source])
|
|
85
85
|
url = cache.get(cacheKey) || buildURL(source, dest)
|
|
86
86
|
cache.set(cacheKey, url)
|
|
87
87
|
} else {
|
|
@@ -231,10 +231,14 @@ const fastifyReplyFrom = fp(function from (fastify, opts, next) {
|
|
|
231
231
|
onError(this, { error: new BadGatewayError() })
|
|
232
232
|
this.request.log.warn(err, 'response has invalid status code')
|
|
233
233
|
}
|
|
234
|
-
if (this.request.raw.aborted
|
|
234
|
+
if (this.request.raw.aborted) {
|
|
235
235
|
// the request could have been canceled before we got a response from the target
|
|
236
|
-
//
|
|
237
|
-
|
|
236
|
+
// clean up the upstream stream to prevent leaks and skip sending a reply
|
|
237
|
+
if (isHttp2) {
|
|
238
|
+
res.stream.close(NGHTTP2_CANCEL)
|
|
239
|
+
} else {
|
|
240
|
+
res.stream.destroy()
|
|
241
|
+
}
|
|
238
242
|
// no need to send a reply for aborted requests or call the onResponse callback
|
|
239
243
|
return
|
|
240
244
|
}
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const t = require('node:test')
|
|
4
|
+
const Fastify = require('fastify')
|
|
5
|
+
const From = require('..')
|
|
6
|
+
|
|
7
|
+
// GHSA-v574-6498-x57v: Cache key collision via ambiguous concatenation.
|
|
8
|
+
// dest + source was used as the cache key. Different (dest, source) pairs
|
|
9
|
+
// could produce identical concatenations while resolving to different URLs,
|
|
10
|
+
// causing a request to be forwarded to the wrong upstream.
|
|
11
|
+
//
|
|
12
|
+
// dest = http://127.0.0.1:31001 , source = /private
|
|
13
|
+
// dest = http://127.0.0.1:3100 , source = 1/private
|
|
14
|
+
//
|
|
15
|
+
// Both concatenate to "http://127.0.0.1:31001/private" but resolve to
|
|
16
|
+
// different URLs.
|
|
17
|
+
|
|
18
|
+
async function run (t) {
|
|
19
|
+
// victim upstream on port 31001 with a private route and a writable route
|
|
20
|
+
const victim = Fastify({ keepAliveTimeout: 1 })
|
|
21
|
+
victim.get('/private', async () => ({ marker: 'VICTIM_PRIVATE_DATA' }))
|
|
22
|
+
victim.get('/state', async () => ({ written: victim.written }))
|
|
23
|
+
victim.post('/private', async (req, reply) => {
|
|
24
|
+
victim.written = req.body
|
|
25
|
+
return { marker: 'VICTIM_PRIVATE_DATA', written: victim.written }
|
|
26
|
+
})
|
|
27
|
+
t.after(() => victim.close())
|
|
28
|
+
await victim.listen({ port: 31001 })
|
|
29
|
+
|
|
30
|
+
// attacker upstream on port 3100
|
|
31
|
+
const attacker = Fastify({ keepAliveTimeout: 1 })
|
|
32
|
+
attacker.get('/1/private', async () => ({ marker: 'ATTACKER_UPSTREAM' }))
|
|
33
|
+
attacker.post('/1/private', async (req, reply) => {
|
|
34
|
+
return { marker: 'ATTACKER_UPSTREAM', written: req.body }
|
|
35
|
+
})
|
|
36
|
+
t.after(() => attacker.close())
|
|
37
|
+
await attacker.listen({ port: 3100 })
|
|
38
|
+
|
|
39
|
+
// proxy using getUpstream() and default cache
|
|
40
|
+
const proxy = Fastify({ keepAliveTimeout: 1 })
|
|
41
|
+
proxy.register(From, {
|
|
42
|
+
base: 'http://localhost',
|
|
43
|
+
http: true
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
proxy.get('/proxy/victim/private', (req, reply) => {
|
|
47
|
+
reply.from('/private', {
|
|
48
|
+
getUpstream () {
|
|
49
|
+
return 'http://127.0.0.1:31001'
|
|
50
|
+
}
|
|
51
|
+
})
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
proxy.post('/proxy/victim/write', (req, reply) => {
|
|
55
|
+
reply.from('/private', {
|
|
56
|
+
method: 'POST',
|
|
57
|
+
body: JSON.stringify(req.body),
|
|
58
|
+
contentType: 'application/json',
|
|
59
|
+
getUpstream () {
|
|
60
|
+
return 'http://127.0.0.1:31001'
|
|
61
|
+
}
|
|
62
|
+
})
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
proxy.get('/proxy/attacker/private', (req, reply) => {
|
|
66
|
+
reply.from('1/private', {
|
|
67
|
+
getUpstream () {
|
|
68
|
+
return 'http://127.0.0.1:3100'
|
|
69
|
+
}
|
|
70
|
+
})
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
proxy.post('/proxy/attacker/write', (req, reply) => {
|
|
74
|
+
reply.from('1/private', {
|
|
75
|
+
method: 'POST',
|
|
76
|
+
body: JSON.stringify(req.body),
|
|
77
|
+
contentType: 'application/json',
|
|
78
|
+
getUpstream () {
|
|
79
|
+
return 'http://127.0.0.1:3100'
|
|
80
|
+
}
|
|
81
|
+
})
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
proxy.get('/proxy/victim/state', (req, reply) => {
|
|
85
|
+
reply.from('/state', {
|
|
86
|
+
getUpstream () {
|
|
87
|
+
return 'http://127.0.0.1:31001'
|
|
88
|
+
}
|
|
89
|
+
})
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
t.after(() => proxy.close())
|
|
93
|
+
await proxy.listen({ port: 31000 })
|
|
94
|
+
|
|
95
|
+
// prime the cache with the victim entry (dest = "http://127.0.0.1:31001", source = "/private")
|
|
96
|
+
const primeRes = await fetch('http://127.0.0.1:31000/proxy/victim/private')
|
|
97
|
+
t.assert.strictEqual(primeRes.status, 200)
|
|
98
|
+
|
|
99
|
+
// now the attacker pair (dest = "http://127.0.0.1:3100", source = "1/private")
|
|
100
|
+
// should NOT reuse the cached URL — it must resolve to the attacker upstream.
|
|
101
|
+
|
|
102
|
+
// 1. attacker read — should return ATTACKER_UPSTREAM, not VICTIM_PRIVATE_DATA
|
|
103
|
+
const attackerReadRes = await fetch('http://127.0.0.1:31000/proxy/attacker/private')
|
|
104
|
+
t.assert.strictEqual(attackerReadRes.status, 200)
|
|
105
|
+
const attackerReadBody = await attackerReadRes.json()
|
|
106
|
+
t.assert.strictEqual(
|
|
107
|
+
attackerReadBody.marker,
|
|
108
|
+
'ATTACKER_UPSTREAM',
|
|
109
|
+
'attacker request must reach attacker upstream, not victim'
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
// 2. attacker write — should write to attacker, not victim
|
|
113
|
+
const attackerWriteRes = await fetch('http://127.0.0.1:31000/proxy/attacker/write', {
|
|
114
|
+
method: 'POST',
|
|
115
|
+
headers: { 'content-type': 'application/json' },
|
|
116
|
+
body: JSON.stringify({ changedBy: 'attacker' })
|
|
117
|
+
})
|
|
118
|
+
t.assert.strictEqual(attackerWriteRes.status, 200)
|
|
119
|
+
|
|
120
|
+
// 3. verify victim state is unchanged (victim.written should be undefined/null)
|
|
121
|
+
const victimStateRes = await fetch('http://127.0.0.1:31000/proxy/victim/state')
|
|
122
|
+
t.assert.strictEqual(victimStateRes.status, 200)
|
|
123
|
+
const victimStateBody = await victimStateRes.json()
|
|
124
|
+
t.assert.strictEqual(
|
|
125
|
+
victimStateBody.written,
|
|
126
|
+
undefined,
|
|
127
|
+
'victim state must not be affected by attacker write'
|
|
128
|
+
)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
t.test('GHSA-v574-6498-x57v: cache key collision via ambiguous concatenation', async (t) => {
|
|
132
|
+
t.plan(6)
|
|
133
|
+
await run(t)
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
t.test('GHSA-v574-6498-x57v: cache key delimiter cannot be injected', async (t) => {
|
|
137
|
+
const victim = Fastify()
|
|
138
|
+
victim.get('/*', async (req) => ({ marker: 'VICTIM', path: req.url }))
|
|
139
|
+
t.after(() => victim.close())
|
|
140
|
+
const victimOrigin = await victim.listen({ host: '127.0.0.1', port: 0 })
|
|
141
|
+
|
|
142
|
+
const attacker = Fastify()
|
|
143
|
+
attacker.get('/*', async (req) => ({ marker: 'ATTACKER', path: req.url }))
|
|
144
|
+
t.after(() => attacker.close())
|
|
145
|
+
const attackerOrigin = await attacker.listen({ host: '127.0.0.1', port: 0 })
|
|
146
|
+
|
|
147
|
+
const victimPair = {
|
|
148
|
+
source: 'private',
|
|
149
|
+
dest: `${victimOrigin}/|${attackerOrigin}/`
|
|
150
|
+
}
|
|
151
|
+
const attackerPair = {
|
|
152
|
+
source: `private|${victimOrigin}/`,
|
|
153
|
+
dest: `${attackerOrigin}/`
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// These pairs collide when the key is source + '|' + dest.
|
|
157
|
+
t.assert.strictEqual(
|
|
158
|
+
victimPair.source + '|' + victimPair.dest,
|
|
159
|
+
attackerPair.source + '|' + attackerPair.dest
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
const proxy = Fastify()
|
|
163
|
+
proxy.register(From, {
|
|
164
|
+
base: 'http://localhost',
|
|
165
|
+
http: true
|
|
166
|
+
})
|
|
167
|
+
proxy.get('/prime', (req, reply) => {
|
|
168
|
+
reply.from(victimPair.source, {
|
|
169
|
+
getUpstream: () => victimPair.dest
|
|
170
|
+
})
|
|
171
|
+
})
|
|
172
|
+
proxy.get('/collide', (req, reply) => {
|
|
173
|
+
reply.from(attackerPair.source, {
|
|
174
|
+
getUpstream: () => attackerPair.dest
|
|
175
|
+
})
|
|
176
|
+
})
|
|
177
|
+
t.after(() => proxy.close())
|
|
178
|
+
|
|
179
|
+
const primeResponse = await proxy.inject('/prime')
|
|
180
|
+
t.assert.strictEqual(primeResponse.statusCode, 200)
|
|
181
|
+
t.assert.strictEqual(primeResponse.json().marker, 'VICTIM')
|
|
182
|
+
|
|
183
|
+
const attackerResponse = await proxy.inject('/collide')
|
|
184
|
+
t.assert.strictEqual(attackerResponse.statusCode, 200)
|
|
185
|
+
t.assert.strictEqual(
|
|
186
|
+
attackerResponse.json().marker,
|
|
187
|
+
'ATTACKER',
|
|
188
|
+
'attacker request must not reuse the victim cache entry'
|
|
189
|
+
)
|
|
190
|
+
})
|
|
191
|
+
|
|
192
|
+
t.test('GHSA-v574-6498-x57v: cache key distinguishes an absent base', async (t) => {
|
|
193
|
+
const target = Fastify()
|
|
194
|
+
target.get('/private', async () => ({ marker: 'TARGET' }))
|
|
195
|
+
t.after(() => target.close())
|
|
196
|
+
const targetOrigin = await target.listen({ host: '127.0.0.1', port: 0 })
|
|
197
|
+
const source = `${targetOrigin}/private`
|
|
198
|
+
|
|
199
|
+
const proxy = Fastify()
|
|
200
|
+
proxy.register(From, { http: true })
|
|
201
|
+
proxy.get('/prime', (req, reply) => reply.from(source))
|
|
202
|
+
proxy.get('/invalid', (req, reply) => {
|
|
203
|
+
reply.from(source, {
|
|
204
|
+
getUpstream: () => 'undefined'
|
|
205
|
+
})
|
|
206
|
+
})
|
|
207
|
+
t.after(() => proxy.close())
|
|
208
|
+
|
|
209
|
+
const primeResponse = await proxy.inject('/prime')
|
|
210
|
+
t.assert.strictEqual(primeResponse.statusCode, 200)
|
|
211
|
+
t.assert.strictEqual(primeResponse.json().marker, 'TARGET')
|
|
212
|
+
|
|
213
|
+
const invalidResponse = await proxy.inject('/invalid')
|
|
214
|
+
t.assert.strictEqual(
|
|
215
|
+
invalidResponse.statusCode,
|
|
216
|
+
500,
|
|
217
|
+
'invalid upstream must not reuse the entry cached without a base'
|
|
218
|
+
)
|
|
219
|
+
})
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const t = require('node:test')
|
|
4
|
+
const Fastify = require('fastify')
|
|
5
|
+
const From = require('..')
|
|
6
|
+
const http = require('node:http')
|
|
7
|
+
const { once } = require('node:events')
|
|
8
|
+
|
|
9
|
+
// See https://github.com/fastify/fastify-reply-from/issues/419
|
|
10
|
+
// When a client aborts an HTTP/1 request before the upstream target responds,
|
|
11
|
+
// reply-from must not attempt to forward the (late) upstream response: it should
|
|
12
|
+
// neither invoke the onResponse callback nor pipe the upstream body to the
|
|
13
|
+
// already-closed reply. The equivalent HTTP/2 path was already handled; this
|
|
14
|
+
// covers the HTTP/1 path.
|
|
15
|
+
t.test('does not forward the upstream response when the HTTP/1 request was aborted', async (t) => {
|
|
16
|
+
t.plan(1)
|
|
17
|
+
|
|
18
|
+
// Target that delays its response long enough for the client to abort first.
|
|
19
|
+
const target = http.createServer((_req, res) => {
|
|
20
|
+
setTimeout(() => {
|
|
21
|
+
res.statusCode = 200
|
|
22
|
+
res.end('hello world')
|
|
23
|
+
}, 500)
|
|
24
|
+
})
|
|
25
|
+
t.after(() => target.close())
|
|
26
|
+
target.listen({ port: 0 })
|
|
27
|
+
await once(target, 'listening')
|
|
28
|
+
|
|
29
|
+
const instance = Fastify()
|
|
30
|
+
t.after(() => instance.close())
|
|
31
|
+
|
|
32
|
+
let onResponseCalled = false
|
|
33
|
+
instance.register(From)
|
|
34
|
+
instance.get('/', (_request, reply) => {
|
|
35
|
+
reply.from(`http://localhost:${target.address().port}`, {
|
|
36
|
+
onResponse (_req, replyInner, res) {
|
|
37
|
+
onResponseCalled = true
|
|
38
|
+
replyInner.send(res.stream)
|
|
39
|
+
}
|
|
40
|
+
})
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
instance.listen({ port: 0 })
|
|
44
|
+
await once(instance.server, 'listening')
|
|
45
|
+
|
|
46
|
+
await new Promise((resolve) => {
|
|
47
|
+
const req = http.request({
|
|
48
|
+
host: 'localhost',
|
|
49
|
+
port: instance.server.address().port,
|
|
50
|
+
path: '/'
|
|
51
|
+
}, (res) => { res.resume() })
|
|
52
|
+
req.on('error', () => {})
|
|
53
|
+
req.end()
|
|
54
|
+
// Abort well before the target responds.
|
|
55
|
+
setTimeout(() => req.destroy(), 50)
|
|
56
|
+
// Wait past the target's response so any (buggy) forwarding would have happened.
|
|
57
|
+
setTimeout(resolve, 800)
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
t.assert.strictEqual(onResponseCalled, false, 'onResponse must not run for an aborted request')
|
|
61
|
+
})
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
|
|
2
|
+
/*---------------------------------------------------------------------------------------------
|
|
3
|
+
* Copyright (c) Zuplo, Inc. All rights reserved.
|
|
4
|
+
*
|
|
5
|
+
* This software and associated documentation files (the "Software") is intended to be used
|
|
6
|
+
* only by Zuplo customers solely to develop and test applications that will be deployed
|
|
7
|
+
* to Zuplo hosted services. You and others in your organization may use these files on your
|
|
8
|
+
* Development Devices solely for the above stated purpose.
|
|
9
|
+
*
|
|
10
|
+
* Outside of uses stated above, no license is granted for any other purpose including
|
|
11
|
+
* without limitation the rights to use, copy, modify, merge, publish, distribute,
|
|
12
|
+
* sublicense, host, and/or sell copies of the Software.
|
|
13
|
+
*
|
|
14
|
+
* The software may include third party components with separate legal notices or governed by
|
|
15
|
+
* other agreements, as described in licenses either embedded in or accompanying the Software.
|
|
16
|
+
*
|
|
17
|
+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
|
18
|
+
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
|
19
|
+
* PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
|
|
20
|
+
* FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
|
21
|
+
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
22
|
+
* DEALINGS IN THE SOFTWARE.
|
|
23
|
+
*--------------------------------------------------------------------------------------------*/
|
|
24
|
+
|
|
25
|
+
var h=Object.create;var f=Object.defineProperty;var i=Object.getOwnPropertyDescriptor;var j=Object.getOwnPropertyNames;var k=Object.getPrototypeOf,l=Object.prototype.hasOwnProperty;var m=(a,b)=>f(a,"name",{value:b,configurable:!0});var n=(a,b,c)=>()=>{if(c)throw c[0];try{return a&&(b=a(a=0)),b}catch(d){throw c=[d],d}};var o=(a,b)=>()=>{try{return b||a((b={exports:{}}).exports,b),b.exports}catch(c){throw b=0,c}},p=(a,b)=>{for(var c in b)f(a,c,{get:b[c],enumerable:!0})},g=(a,b,c,d)=>{if(b&&typeof b=="object"||typeof b=="function")for(let e of j(b))!l.call(a,e)&&e!==c&&f(a,e,{get:()=>b[e],enumerable:!(d=i(b,e))||d.enumerable});return a};var q=(a,b,c)=>(c=a!=null?h(k(a)):{},g(b||!a||!a.__esModule?f(c,"default",{value:a,enumerable:!0}):c,a)),r=a=>g(f({},"__esModule",{value:!0}),a);export{m as a,n as b,o as c,p as d,q as e,r as f};
|
|
26
|
+
//# sourceMappingURL=chunk-2WLR6ZDZ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
|
|
2
|
+
/*---------------------------------------------------------------------------------------------
|
|
3
|
+
* Copyright (c) Zuplo, Inc. All rights reserved.
|
|
4
|
+
*
|
|
5
|
+
* This software and associated documentation files (the "Software") is intended to be used
|
|
6
|
+
* only by Zuplo customers solely to develop and test applications that will be deployed
|
|
7
|
+
* to Zuplo hosted services. You and others in your organization may use these files on your
|
|
8
|
+
* Development Devices solely for the above stated purpose.
|
|
9
|
+
*
|
|
10
|
+
* Outside of uses stated above, no license is granted for any other purpose including
|
|
11
|
+
* without limitation the rights to use, copy, modify, merge, publish, distribute,
|
|
12
|
+
* sublicense, host, and/or sell copies of the Software.
|
|
13
|
+
*
|
|
14
|
+
* The software may include third party components with separate legal notices or governed by
|
|
15
|
+
* other agreements, as described in licenses either embedded in or accompanying the Software.
|
|
16
|
+
*
|
|
17
|
+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
|
18
|
+
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
|
19
|
+
* PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
|
|
20
|
+
* FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
|
21
|
+
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
22
|
+
* DEALINGS IN THE SOFTWARE.
|
|
23
|
+
*--------------------------------------------------------------------------------------------*/
|
|
24
|
+
|
|
25
|
+
import{Y as o}from"./chunk-VCMIY3WJ.js";import{a}from"./chunk-2WLR6ZDZ.js";var t={AI_GATEWAY_COST_SUM:"ai_gateway_cost_sum",AI_GATEWAY_REQUEST_COUNT:"ai_gateway_request_count",AI_GATEWAY_TOKEN_SUM:"ai_gateway_token_sum",AI_GATEWAY_LATENCY_HISTOGRAM:"ai_gateway_latency_histogram",AI_GATEWAY_WARNING_COUNT:"ai_gateway_warning_count",AI_GATEWAY_BLOCKED_COUNT:"ai_gateway_blocked_count",AI_GATEWAY_FALLBACK_COUNT:"ai_gateway_fallback_count",MCP_REQUEST_RECEIVED:"mcp_request_received",MCP_REQUEST_COMPLETED:"mcp_request_completed",MCP_REQUEST_REJECTED:"mcp_request_rejected",MCP_INITIALIZE_NEGOTIATED:"mcp_initialize_negotiated",MCP_CLIENT_UNSUPPORTED_BEHAVIOR:"mcp_client_unsupported_behavior",MCP_CAPABILITY_LISTED:"mcp_capability_listed",MCP_CAPABILITY_INVOKED:"mcp_capability_invoked",MCP_CAPABILITY_COMPLETED:"mcp_capability_completed",MCP_CAPABILITY_FAILED:"mcp_capability_failed",MCP_CAPABILITY_CONNECT_REQUIRED:"mcp_capability_connect_required",MCP_AUTH_DOWNSTREAM_TOKEN_VALIDATED:"mcp_auth_downstream_token_validated",MCP_AUTH_DOWNSTREAM_TOKEN_REJECTED:"mcp_auth_downstream_token_rejected",MCP_OAUTH_CLIENT_REGISTERED:"mcp_oauth_client_registered",MCP_OAUTH_AUTHORIZE_STARTED:"mcp_oauth_authorize_started",MCP_OAUTH_AUTHORIZE_AWAITING_SETUP:"mcp_oauth_authorize_awaiting_setup",MCP_OAUTH_TOKEN_ISSUED:"mcp_oauth_token_issued",MCP_OAUTH_TOKEN_REFRESH_ROTATED:"mcp_oauth_token_refresh_rotated",MCP_OAUTH_TOKEN_REVOKED:"mcp_oauth_token_revoked",MCP_AUTH_UPSTREAM_CONNECT_REQUIRED:"mcp_auth_upstream_connect_required",MCP_AUTH_UPSTREAM_CONNECT_STARTED:"mcp_auth_upstream_connect_started",MCP_AUTH_UPSTREAM_CALLBACK_RECEIVED:"mcp_auth_upstream_callback_received",MCP_AUTH_UPSTREAM_TOKEN_EXCHANGE_SUCCEEDED:"mcp_auth_upstream_token_exchange_succeeded",MCP_AUTH_UPSTREAM_TOKEN_EXCHANGE_FAILED:"mcp_auth_upstream_token_exchange_failed",MCP_AUTH_UPSTREAM_CREDENTIAL_RESOLVED:"mcp_auth_upstream_credential_resolved",MCP_AUTH_UPSTREAM_CREDENTIAL_MISSING:"mcp_auth_upstream_credential_missing",MCP_AUTH_UPSTREAM_RECONSENT_REQUIRED:"mcp_auth_upstream_reconsent_required",GRAPHQL_OPERATION:"graphql_operation",CACHE_OPERATION:"cache_operation"};var i=class{static{a(this,"BaseAnalyticsEvent")}eventId;requestId;timestamp;accountName;projectName;deploymentName;unit;value;constructor(e){this.eventId=e.eventId,this.requestId=e.requestId,this.timestamp=e.timestamp??new Date,this.accountName=e.accountName,this.projectName=e.projectName,this.deploymentName=e.deploymentName,this.unit=e.unit,this.value=e.value}};var s=class extends i{static{a(this,"AiGatewayBlockedCountEvent")}eventType=t.AI_GATEWAY_BLOCKED_COUNT;metadata;constructor(e){super(e),this.metadata=e.metadata,(!this.metadata.type||!this.metadata.type.trim())&&(this.metadata.type="unknown"),(!this.metadata.configId||!this.metadata.configId.trim())&&(this.metadata.configId="unknown")}};var p=class extends i{static{a(this,"AiGatewayCostSumEvent")}eventType=t.AI_GATEWAY_COST_SUM;metadata;constructor(e){super(e),this.metadata=e.metadata,(!this.metadata.model||!this.metadata.model.trim())&&(this.metadata.model="unknown"),(!this.metadata.provider||!this.metadata.provider.trim())&&(this.metadata.provider="unknown"),(!this.metadata.configId||!this.metadata.configId.trim())&&(this.metadata.configId="unknown")}};var _=class extends i{static{a(this,"AiGatewayFallbackCountEvent")}eventType=t.AI_GATEWAY_FALLBACK_COUNT;metadata;constructor(e){super(e),this.metadata=e.metadata,(!this.metadata.type||!this.metadata.type.trim())&&(this.metadata.type="unknown"),(!this.metadata.configId||!this.metadata.configId.trim())&&(this.metadata.configId="unknown")}};var l=class extends i{static{a(this,"AiGatewayLatencyHistogramEvent")}eventType=t.AI_GATEWAY_LATENCY_HISTOGRAM;metadata;constructor(e){super(e),this.metadata=e.metadata,(!this.metadata.model||!this.metadata.model.trim())&&(this.metadata.model="unknown"),(!this.metadata.provider||!this.metadata.provider.trim())&&(this.metadata.provider="unknown"),(!this.metadata.configId||!this.metadata.configId.trim())&&(this.metadata.configId="unknown")}};var T=class extends i{static{a(this,"AiGatewayRequestCountEvent")}eventType=t.AI_GATEWAY_REQUEST_COUNT;metadata;constructor(e){super(e),this.metadata=e.metadata,(!this.metadata.model||!this.metadata.model.trim())&&(this.metadata.model="unknown"),(!this.metadata.provider||!this.metadata.provider.trim())&&(this.metadata.provider="unknown"),(!this.metadata.configId||!this.metadata.configId.trim())&&(this.metadata.configId="unknown")}};var u=class extends i{static{a(this,"AiGatewayTokenSumEvent")}eventType=t.AI_GATEWAY_TOKEN_SUM;metadata;constructor(e){super(e),this.metadata=e.metadata,(!this.metadata.model||!this.metadata.model.trim())&&(this.metadata.model="unknown"),(!this.metadata.provider||!this.metadata.provider.trim())&&(this.metadata.provider="unknown"),(!this.metadata.configId||!this.metadata.configId.trim())&&(this.metadata.configId="unknown")}};var A=class extends i{static{a(this,"AiGatewayWarningCountEvent")}eventType=t.AI_GATEWAY_WARNING_COUNT;metadata;constructor(e){super(e),this.metadata=e.metadata,(!this.metadata.type||!this.metadata.type.trim())&&(this.metadata.type="unknown"),(!this.metadata.configId||!this.metadata.configId.trim())&&(this.metadata.configId="unknown")}};var m=class extends i{static{a(this,"CacheOperationEvent")}eventType=t.CACHE_OPERATION;metadata;constructor(e){super(e),this.metadata=e.metadata}};var c=class extends i{static{a(this,"GraphqlOperationEvent")}eventType=t.GRAPHQL_OPERATION;metadata;constructor(e){super(e),this.metadata=e.metadata}};var d=class extends i{static{a(this,"McpEvent")}eventType;metadata;constructor(e){super(e),this.eventType=e.eventType,this.metadata=e.metadata}};var r=a(n=>e=>new d({...e,eventType:n}),"buildMcpEvent"),g={[t.AI_GATEWAY_COST_SUM]:n=>new p(n),[t.AI_GATEWAY_REQUEST_COUNT]:n=>new T(n),[t.AI_GATEWAY_TOKEN_SUM]:n=>new u(n),[t.AI_GATEWAY_LATENCY_HISTOGRAM]:n=>new l(n),[t.AI_GATEWAY_WARNING_COUNT]:n=>new A(n),[t.AI_GATEWAY_BLOCKED_COUNT]:n=>new s(n),[t.AI_GATEWAY_FALLBACK_COUNT]:n=>new _(n),[t.MCP_REQUEST_RECEIVED]:r(t.MCP_REQUEST_RECEIVED),[t.MCP_REQUEST_COMPLETED]:r(t.MCP_REQUEST_COMPLETED),[t.MCP_REQUEST_REJECTED]:r(t.MCP_REQUEST_REJECTED),[t.MCP_INITIALIZE_NEGOTIATED]:r(t.MCP_INITIALIZE_NEGOTIATED),[t.MCP_CLIENT_UNSUPPORTED_BEHAVIOR]:r(t.MCP_CLIENT_UNSUPPORTED_BEHAVIOR),[t.MCP_CAPABILITY_LISTED]:r(t.MCP_CAPABILITY_LISTED),[t.MCP_CAPABILITY_INVOKED]:r(t.MCP_CAPABILITY_INVOKED),[t.MCP_CAPABILITY_COMPLETED]:r(t.MCP_CAPABILITY_COMPLETED),[t.MCP_CAPABILITY_FAILED]:r(t.MCP_CAPABILITY_FAILED),[t.MCP_CAPABILITY_CONNECT_REQUIRED]:r(t.MCP_CAPABILITY_CONNECT_REQUIRED),[t.MCP_AUTH_DOWNSTREAM_TOKEN_VALIDATED]:r(t.MCP_AUTH_DOWNSTREAM_TOKEN_VALIDATED),[t.MCP_AUTH_DOWNSTREAM_TOKEN_REJECTED]:r(t.MCP_AUTH_DOWNSTREAM_TOKEN_REJECTED),[t.MCP_OAUTH_CLIENT_REGISTERED]:r(t.MCP_OAUTH_CLIENT_REGISTERED),[t.MCP_OAUTH_AUTHORIZE_STARTED]:r(t.MCP_OAUTH_AUTHORIZE_STARTED),[t.MCP_OAUTH_AUTHORIZE_AWAITING_SETUP]:r(t.MCP_OAUTH_AUTHORIZE_AWAITING_SETUP),[t.MCP_OAUTH_TOKEN_ISSUED]:r(t.MCP_OAUTH_TOKEN_ISSUED),[t.MCP_OAUTH_TOKEN_REFRESH_ROTATED]:r(t.MCP_OAUTH_TOKEN_REFRESH_ROTATED),[t.MCP_OAUTH_TOKEN_REVOKED]:r(t.MCP_OAUTH_TOKEN_REVOKED),[t.MCP_AUTH_UPSTREAM_CONNECT_REQUIRED]:r(t.MCP_AUTH_UPSTREAM_CONNECT_REQUIRED),[t.MCP_AUTH_UPSTREAM_CONNECT_STARTED]:r(t.MCP_AUTH_UPSTREAM_CONNECT_STARTED),[t.MCP_AUTH_UPSTREAM_CALLBACK_RECEIVED]:r(t.MCP_AUTH_UPSTREAM_CALLBACK_RECEIVED),[t.MCP_AUTH_UPSTREAM_TOKEN_EXCHANGE_SUCCEEDED]:r(t.MCP_AUTH_UPSTREAM_TOKEN_EXCHANGE_SUCCEEDED),[t.MCP_AUTH_UPSTREAM_TOKEN_EXCHANGE_FAILED]:r(t.MCP_AUTH_UPSTREAM_TOKEN_EXCHANGE_FAILED),[t.MCP_AUTH_UPSTREAM_CREDENTIAL_RESOLVED]:r(t.MCP_AUTH_UPSTREAM_CREDENTIAL_RESOLVED),[t.MCP_AUTH_UPSTREAM_CREDENTIAL_MISSING]:r(t.MCP_AUTH_UPSTREAM_CREDENTIAL_MISSING),[t.MCP_AUTH_UPSTREAM_RECONSENT_REQUIRED]:r(t.MCP_AUTH_UPSTREAM_RECONSENT_REQUIRED),[t.GRAPHQL_OPERATION]:n=>new c(n),[t.CACHE_OPERATION]:n=>new m(n)};function I(n,e){let E=g[n];return E(e)}a(I,"createAnalyticsEvent");var O=class{static{a(this,"ZuploGatewayAnalyticsContext")}#t;#e;constructor(e){this.#t=[],this.#e=e}addAnalyticsEvent(e,E,y,C,M){let v=this.createAnalyticsEvent(e,E,y,C,M);this.#t.push(v)}flushAnalyticsEvents(){let e=[...this.#t];return this.#t.length=0,e}getAnalyticsEvents(){return[...this.#t]}createAnalyticsEvent(e,E,y,C,M){return I(E,{eventId:M??crypto.randomUUID(),requestId:this.#e,accountName:o.instance.accountName??"unknown",projectName:o.instance.projectName??"unknown",deploymentName:o.instance.deploymentName??"unknown",metadata:y,unit:C,value:e})}};export{t as a,O as b};
|
|
26
|
+
//# sourceMappingURL=chunk-EJIF2ZML.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["@zuplo/runtime/metrics/v2/interfaces.ts","@zuplo/runtime/metrics/v2/events/base-analytics-event.ts","@zuplo/runtime/metrics/v2/events/ai-gateway-blocked-count.ts","@zuplo/runtime/metrics/v2/events/ai-gateway-cost-sum.ts","@zuplo/runtime/metrics/v2/events/ai-gateway-fallback-count.ts","@zuplo/runtime/metrics/v2/events/ai-gateway-latency-histogram.ts","@zuplo/runtime/metrics/v2/events/ai-gateway-request-count.ts","@zuplo/runtime/metrics/v2/events/ai-gateway-token-sum.ts","@zuplo/runtime/metrics/v2/events/ai-gateway-warning-count.ts","@zuplo/runtime/metrics/v2/events/cache-operation.ts","@zuplo/runtime/metrics/v2/events/graphql-operation-event.ts","@zuplo/runtime/metrics/v2/events/mcp-event.ts","@zuplo/runtime/metrics/v2/create-events.ts","@zuplo/runtime/metrics/v2/analytics-context.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;2EAuDO,IAAMA,EAAY,CACvB,oBAAqB,sBACrB,yBAA0B,2BAC1B,qBAAsB,uBACtB,6BAA8B,+BAC9B,yBAA0B,2BAC1B,yBAA0B,2BAC1B,0BAA2B,4BAM3B,qBAAsB,uBACtB,sBAAuB,wBACvB,qBAAsB,uBACtB,0BAA2B,4BAC3B,gCAAiC,kCACjC,sBAAuB,wBACvB,uBAAwB,yBACxB,yBAA0B,2BAC1B,sBAAuB,wBACvB,gCAAiC,kCACjC,oCAAqC,sCACrC,mCAAoC,qCACpC,4BAA6B,8BAC7B,4BAA6B,8BAC7B,mCAAoC,qCACpC,uBAAwB,yBACxB,gCAAiC,kCACjC,wBAAyB,0BACzB,mCAAoC,qCACpC,kCAAmC,oCACnC,oCAAqC,sCACrC,2CACE,6CACF,wCACE,0CACF,sCACE,wCACF,qCAAsC,uCACtC,qCAAsC,uCAKtC,kBAAmB,oBAKnB,gBAAiB,iBACnB,EChGO,IAAeC,EAAf,KAEP,CAbA,MAaA,CAAAC,EAAA,2BAEE,QACA,UAIA,UAGA,YACA,YACA,eASA,KACA,MAEA,YAAYC,EAA8B,CACxC,KAAK,QAAUA,EAAQ,QACvB,KAAK,UAAYA,EAAQ,UACzB,KAAK,UAAYA,EAAQ,WAAa,IAAI,KAC1C,KAAK,YAAcA,EAAQ,YAC3B,KAAK,YAAcA,EAAQ,YAC3B,KAAK,eAAiBA,EAAQ,eAC9B,KAAK,KAAOA,EAAQ,KACpB,KAAK,MAAQA,EAAQ,KACvB,CACF,EC/BO,IAAMC,EAAN,cAAyCC,CAA0C,CAhB1F,MAgB0F,CAAAC,EAAA,mCAC/E,UAAYC,EAAU,yBAC/B,SAEA,YAAYC,EAAkD,CAC5D,MAAMA,CAAO,EACb,KAAK,SAAWA,EAAQ,UAEpB,CAAC,KAAK,SAAS,MAAQ,CAAC,KAAK,SAAS,KAAK,KAAK,KAClD,KAAK,SAAS,KAAO,YAEnB,CAAC,KAAK,SAAS,UAAY,CAAC,KAAK,SAAS,SAAS,KAAK,KAC1D,KAAK,SAAS,SAAW,UAE7B,CACF,ECdO,IAAMC,EAAN,cAAoCC,CAA6C,CAjBxF,MAiBwF,CAAAC,EAAA,8BAC7E,UAAYC,EAAU,oBAC/B,SAEA,YAAYC,EAAqD,CAC/D,MAAMA,CAAO,EACb,KAAK,SAAWA,EAAQ,UAEpB,CAAC,KAAK,SAAS,OAAS,CAAC,KAAK,SAAS,MAAM,KAAK,KACpD,KAAK,SAAS,MAAQ,YAEpB,CAAC,KAAK,SAAS,UAAY,CAAC,KAAK,SAAS,SAAS,KAAK,KAC1D,KAAK,SAAS,SAAW,YAEvB,CAAC,KAAK,SAAS,UAAY,CAAC,KAAK,SAAS,SAAS,KAAK,KAC1D,KAAK,SAAS,SAAW,UAE7B,CACF,ECnBO,IAAMC,EAAN,cAA0CC,CAA2C,CAhB5F,MAgB4F,CAAAC,EAAA,oCACjF,UAAYC,EAAU,0BAC/B,SAEA,YAAYC,EAAmD,CAC7D,MAAMA,CAAO,EACb,KAAK,SAAWA,EAAQ,UAEpB,CAAC,KAAK,SAAS,MAAQ,CAAC,KAAK,SAAS,KAAK,KAAK,KAClD,KAAK,SAAS,KAAO,YAEnB,CAAC,KAAK,SAAS,UAAY,CAAC,KAAK,SAAS,SAAS,KAAK,KAC1D,KAAK,SAAS,SAAW,UAE7B,CACF,ECdO,IAAMC,EAAN,cAA6CC,CAAsD,CAjB1G,MAiB0G,CAAAC,EAAA,uCAC/F,UAAYC,EAAU,6BAC/B,SAEA,YAAYC,EAA8D,CACxE,MAAMA,CAAO,EACb,KAAK,SAAWA,EAAQ,UAEpB,CAAC,KAAK,SAAS,OAAS,CAAC,KAAK,SAAS,MAAM,KAAK,KACpD,KAAK,SAAS,MAAQ,YAEpB,CAAC,KAAK,SAAS,UAAY,CAAC,KAAK,SAAS,SAAS,KAAK,KAC1D,KAAK,SAAS,SAAW,YAEvB,CAAC,KAAK,SAAS,UAAY,CAAC,KAAK,SAAS,SAAS,KAAK,KAC1D,KAAK,SAAS,SAAW,UAE7B,CACF,ECfO,IAAMC,EAAN,cAAyCC,CAAkD,CApBlG,MAoBkG,CAAAC,EAAA,mCACvF,UAAYC,EAAU,yBAC/B,SAEA,YAAYC,EAA0D,CACpE,MAAMA,CAAO,EACb,KAAK,SAAWA,EAAQ,UAEpB,CAAC,KAAK,SAAS,OAAS,CAAC,KAAK,SAAS,MAAM,KAAK,KACpD,KAAK,SAAS,MAAQ,YAEpB,CAAC,KAAK,SAAS,UAAY,CAAC,KAAK,SAAS,SAAS,KAAK,KAC1D,KAAK,SAAS,SAAW,YAEvB,CAAC,KAAK,SAAS,UAAY,CAAC,KAAK,SAAS,SAAS,KAAK,KAC1D,KAAK,SAAS,SAAW,UAE7B,CACF,ECnBO,IAAMC,EAAN,cAAqCC,CAA8C,CAnB1F,MAmB0F,CAAAC,EAAA,+BAC/E,UAAYC,EAAU,qBAC/B,SAEA,YAAYC,EAAsD,CAChE,MAAMA,CAAO,EACb,KAAK,SAAWA,EAAQ,UAEpB,CAAC,KAAK,SAAS,OAAS,CAAC,KAAK,SAAS,MAAM,KAAK,KACpD,KAAK,SAAS,MAAQ,YAEpB,CAAC,KAAK,SAAS,UAAY,CAAC,KAAK,SAAS,SAAS,KAAK,KAC1D,KAAK,SAAS,SAAW,YAEvB,CAAC,KAAK,SAAS,UAAY,CAAC,KAAK,SAAS,SAAS,KAAK,KAC1D,KAAK,SAAS,SAAW,UAE7B,CACF,ECrBO,IAAMC,EAAN,cAAyCC,CAA0C,CAhB1F,MAgB0F,CAAAC,EAAA,mCAC/E,UAAYC,EAAU,yBAC/B,SAEA,YAAYC,EAAkD,CAC5D,MAAMA,CAAO,EACb,KAAK,SAAWA,EAAQ,UAEpB,CAAC,KAAK,SAAS,MAAQ,CAAC,KAAK,SAAS,KAAK,KAAK,KAClD,KAAK,SAAS,KAAO,YAEnB,CAAC,KAAK,SAAS,UAAY,CAAC,KAAK,SAAS,SAAS,KAAK,KAC1D,KAAK,SAAS,SAAW,UAE7B,CACF,ECuBO,IAAMC,EAAN,cAAkCC,CAA2C,CAtDpF,MAsDoF,CAAAC,EAAA,4BACzE,UAAYC,EAAU,gBAC/B,SAEA,YAAYC,EAAmD,CAC7D,MAAMA,CAAO,EACb,KAAK,SAAWA,EAAQ,QAC1B,CACF,ECwGO,IAAMC,EAAN,cAAoCC,CAA6C,CAtKxF,MAsKwF,CAAAC,EAAA,8BAC7E,UAAYC,EAAU,kBAC/B,SAEA,YAAYC,EAAuC,CACjD,MAAMA,CAAO,EACb,KAAK,SAAWA,EAAQ,QAC1B,CACF,EC0DO,IAAMC,EAAN,cAAuBC,CAAqC,CAxOnE,MAwOmE,CAAAC,EAAA,iBACxD,UACT,SAEA,YAAYC,EAA0B,CACpC,MAAMA,CAAO,EACb,KAAK,UAAYA,EAAQ,UACzB,KAAK,SAAWA,EAAQ,QAC1B,CACF,ECzNA,IAAMC,EAAgBC,EACpBC,GAEQC,GACN,IAAIC,EAAS,CAAE,GAAGD,EAAS,UAAAD,CAAU,CAAC,EAJpB,iBAOhBG,EAAgB,CACpB,CAACC,EAAU,mBAAmB,EAAIH,GAChC,IAAII,EAAsBJ,CAAO,EACnC,CAACG,EAAU,wBAAwB,EAAIH,GACrC,IAAIK,EAA2BL,CAAO,EACxC,CAACG,EAAU,oBAAoB,EAAIH,GACjC,IAAIM,EAAuBN,CAAO,EACpC,CAACG,EAAU,4BAA4B,EAAIH,GACzC,IAAIO,EAA+BP,CAAO,EAC5C,CAACG,EAAU,wBAAwB,EAAIH,GACrC,IAAIQ,EAA2BR,CAAO,EACxC,CAACG,EAAU,wBAAwB,EAAIH,GACrC,IAAIS,EAA2BT,CAAO,EACxC,CAACG,EAAU,yBAAyB,EAAIH,GACtC,IAAIU,EAA4BV,CAAO,EAEzC,CAACG,EAAU,oBAAoB,EAAGN,EAChCM,EAAU,oBACZ,EACA,CAACA,EAAU,qBAAqB,EAAGN,EACjCM,EAAU,qBACZ,EACA,CAACA,EAAU,oBAAoB,EAAGN,EAChCM,EAAU,oBACZ,EACA,CAACA,EAAU,yBAAyB,EAAGN,EACrCM,EAAU,yBACZ,EACA,CAACA,EAAU,+BAA+B,EAAGN,EAC3CM,EAAU,+BACZ,EACA,CAACA,EAAU,qBAAqB,EAAGN,EACjCM,EAAU,qBACZ,EACA,CAACA,EAAU,sBAAsB,EAAGN,EAClCM,EAAU,sBACZ,EACA,CAACA,EAAU,wBAAwB,EAAGN,EACpCM,EAAU,wBACZ,EACA,CAACA,EAAU,qBAAqB,EAAGN,EACjCM,EAAU,qBACZ,EACA,CAACA,EAAU,+BAA+B,EAAGN,EAC3CM,EAAU,+BACZ,EACA,CAACA,EAAU,mCAAmC,EAAGN,EAC/CM,EAAU,mCACZ,EACA,CAACA,EAAU,kCAAkC,EAAGN,EAC9CM,EAAU,kCACZ,EACA,CAACA,EAAU,2BAA2B,EAAGN,EACvCM,EAAU,2BACZ,EACA,CAACA,EAAU,2BAA2B,EAAGN,EACvCM,EAAU,2BACZ,EACA,CAACA,EAAU,kCAAkC,EAAGN,EAC9CM,EAAU,kCACZ,EACA,CAACA,EAAU,sBAAsB,EAAGN,EAClCM,EAAU,sBACZ,EACA,CAACA,EAAU,+BAA+B,EAAGN,EAC3CM,EAAU,+BACZ,EACA,CAACA,EAAU,uBAAuB,EAAGN,EACnCM,EAAU,uBACZ,EACA,CAACA,EAAU,kCAAkC,EAAGN,EAC9CM,EAAU,kCACZ,EACA,CAACA,EAAU,iCAAiC,EAAGN,EAC7CM,EAAU,iCACZ,EACA,CAACA,EAAU,mCAAmC,EAAGN,EAC/CM,EAAU,mCACZ,EACA,CAACA,EAAU,0CAA0C,EAAGN,EACtDM,EAAU,0CACZ,EACA,CAACA,EAAU,uCAAuC,EAAGN,EACnDM,EAAU,uCACZ,EACA,CAACA,EAAU,qCAAqC,EAAGN,EACjDM,EAAU,qCACZ,EACA,CAACA,EAAU,oCAAoC,EAAGN,EAChDM,EAAU,oCACZ,EACA,CAACA,EAAU,oCAAoC,EAAGN,EAChDM,EAAU,oCACZ,EAEA,CAACA,EAAU,iBAAiB,EAAIH,GAC9B,IAAIW,EAAsBX,CAAO,EAEnC,CAACG,EAAU,eAAe,EAAIH,GAAY,IAAIY,EAAoBZ,CAAO,CAC3E,EAOO,SAASa,EACdd,EACAC,EAC0B,CAC1B,IAAMc,EAAUZ,EAAcH,CAAS,EACvC,OAAOe,EAAQd,CAAO,CACxB,CANgBF,EAAAe,EAAA,wBC9HT,IAAME,EAAN,KAAoE,CAX3E,MAW2E,CAAAC,EAAA,qCAGhEC,GACAC,GAET,YAAYC,EAAmB,CAC7B,KAAKF,GAAU,CAAC,EAChB,KAAKC,GAAaC,CACpB,CAGA,kBACEC,EACAC,EACAC,EACAC,EACAC,EACM,CACN,IAAMC,EAAQ,KAAK,qBACjBL,EACAC,EACAC,EACAC,EACAC,CACF,EACA,KAAKP,GAAQ,KAAKQ,CAAK,CACzB,CAEA,sBAA8C,CAC5C,IAAMC,EAAS,CAAC,GAAG,KAAKT,EAAO,EAC/B,YAAKA,GAAQ,OAAS,EACfS,CACT,CAEA,oBAA4C,CAC1C,MAAO,CAAC,GAAG,KAAKT,EAAO,CACzB,CAEQ,qBACNG,EACAC,EACAC,EACAC,EACAC,EAC0B,CAC1B,OAAOG,EAAqBN,EAAW,CACrC,QAASG,GAAW,OAAO,WAAW,EACtC,UAAW,KAAKN,GAChB,YAAaU,EAAY,SAAS,aAAe,UACjD,YAAaA,EAAY,SAAS,aAAe,UACjD,eAAgBA,EAAY,SAAS,gBAAkB,UACvD,SAAAN,EACA,KAAAC,EACA,MAAAH,CACF,CAAC,CACH,CACF","names":["EventType","BaseAnalyticsEvent","__name","options","AiGatewayBlockedCountEvent","BaseAnalyticsEvent","__name","EventType","options","AiGatewayCostSumEvent","BaseAnalyticsEvent","__name","EventType","options","AiGatewayFallbackCountEvent","BaseAnalyticsEvent","__name","EventType","options","AiGatewayLatencyHistogramEvent","BaseAnalyticsEvent","__name","EventType","options","AiGatewayRequestCountEvent","BaseAnalyticsEvent","__name","EventType","options","AiGatewayTokenSumEvent","BaseAnalyticsEvent","__name","EventType","options","AiGatewayWarningCountEvent","BaseAnalyticsEvent","__name","EventType","options","CacheOperationEvent","BaseAnalyticsEvent","__name","EventType","options","GraphqlOperationEvent","BaseAnalyticsEvent","__name","EventType","options","McpEvent","BaseAnalyticsEvent","__name","options","buildMcpEvent","__name","eventType","options","McpEvent","eventBuilders","EventType","AiGatewayCostSumEvent","AiGatewayRequestCountEvent","AiGatewayTokenSumEvent","AiGatewayLatencyHistogramEvent","AiGatewayWarningCountEvent","AiGatewayBlockedCountEvent","AiGatewayFallbackCountEvent","GraphqlOperationEvent","CacheOperationEvent","createAnalyticsEvent","builder","ZuploGatewayAnalyticsContext","__name","#events","#requestId","requestId","value","eventType","metadata","unit","eventId","event","events","createAnalyticsEvent","Environment"]}
|