@zuplo/cli 7.12.3 → 7.12.5

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 (149) hide show
  1. package/node_modules/@inquirer/checkbox/dist/index.js +4 -0
  2. package/node_modules/@inquirer/checkbox/package.json +3 -3
  3. package/node_modules/@inquirer/confirm/package.json +3 -3
  4. package/node_modules/@inquirer/core/dist/lib/create-prompt.js +3 -1
  5. package/node_modules/@inquirer/core/package.json +2 -2
  6. package/node_modules/@inquirer/editor/package.json +3 -3
  7. package/node_modules/@inquirer/expand/package.json +3 -3
  8. package/node_modules/@inquirer/input/package.json +3 -3
  9. package/node_modules/@inquirer/number/dist/index.js +1 -1
  10. package/node_modules/@inquirer/number/package.json +3 -3
  11. package/node_modules/@inquirer/password/package.json +3 -3
  12. package/node_modules/@inquirer/rawlist/package.json +3 -3
  13. package/node_modules/@inquirer/search/package.json +3 -3
  14. package/node_modules/@inquirer/select/package.json +3 -3
  15. package/node_modules/@modelcontextprotocol/client/node_modules/jose/README.md +2 -2
  16. package/node_modules/@modelcontextprotocol/client/node_modules/jose/dist/types/jwe/compact/decrypt.d.ts +5 -4
  17. package/node_modules/@modelcontextprotocol/client/node_modules/jose/dist/types/jwe/compact/encrypt.d.ts +4 -4
  18. package/node_modules/@modelcontextprotocol/client/node_modules/jose/dist/types/jwe/flattened/decrypt.d.ts +5 -4
  19. package/node_modules/@modelcontextprotocol/client/node_modules/jose/dist/types/jwe/flattened/encrypt.d.ts +5 -5
  20. package/node_modules/@modelcontextprotocol/client/node_modules/jose/dist/types/jwe/general/decrypt.d.ts +5 -4
  21. package/node_modules/@modelcontextprotocol/client/node_modules/jose/dist/types/jwe/general/encrypt.d.ts +5 -5
  22. package/node_modules/@modelcontextprotocol/client/node_modules/jose/dist/types/jws/compact/sign.d.ts +5 -4
  23. package/node_modules/@modelcontextprotocol/client/node_modules/jose/dist/types/jws/compact/verify.d.ts +5 -5
  24. package/node_modules/@modelcontextprotocol/client/node_modules/jose/dist/types/jws/flattened/sign.d.ts +5 -4
  25. package/node_modules/@modelcontextprotocol/client/node_modules/jose/dist/types/jws/flattened/verify.d.ts +6 -5
  26. package/node_modules/@modelcontextprotocol/client/node_modules/jose/dist/types/jws/general/sign.d.ts +9 -6
  27. package/node_modules/@modelcontextprotocol/client/node_modules/jose/dist/types/jws/general/verify.d.ts +7 -5
  28. package/node_modules/@modelcontextprotocol/client/node_modules/jose/dist/types/jwt/decrypt.d.ts +7 -6
  29. package/node_modules/@modelcontextprotocol/client/node_modules/jose/dist/types/jwt/encrypt.d.ts +1 -1
  30. package/node_modules/@modelcontextprotocol/client/node_modules/jose/dist/types/jwt/sign.d.ts +1 -1
  31. package/node_modules/@modelcontextprotocol/client/node_modules/jose/dist/types/jwt/unsecured.d.ts +1 -1
  32. package/node_modules/@modelcontextprotocol/client/node_modules/jose/dist/types/jwt/verify.d.ts +3 -5
  33. package/node_modules/@modelcontextprotocol/client/node_modules/jose/dist/types/key/export.d.ts +1 -1
  34. package/node_modules/@modelcontextprotocol/client/node_modules/jose/dist/types/key/generate_secret.d.ts +1 -1
  35. package/node_modules/@modelcontextprotocol/client/node_modules/jose/dist/types/key/import.d.ts +2 -2
  36. package/node_modules/@modelcontextprotocol/client/node_modules/jose/dist/types/types.d.ts +165 -107
  37. package/node_modules/@modelcontextprotocol/client/node_modules/jose/dist/types/util/decode_jwt.d.ts +2 -2
  38. package/node_modules/@modelcontextprotocol/client/node_modules/jose/dist/types/util/errors.d.ts +1 -1
  39. package/node_modules/@modelcontextprotocol/client/node_modules/jose/dist/webapi/jwe/general/encrypt.js +3 -3
  40. package/node_modules/@modelcontextprotocol/client/node_modules/jose/dist/webapi/jwk/thumbprint.js +8 -8
  41. package/node_modules/@modelcontextprotocol/client/node_modules/jose/dist/webapi/jwks/local.js +2 -1
  42. package/node_modules/@modelcontextprotocol/client/node_modules/jose/dist/webapi/jwks/remote.js +4 -4
  43. package/node_modules/@modelcontextprotocol/client/node_modules/jose/dist/webapi/jws/general/sign.js +3 -3
  44. package/node_modules/@modelcontextprotocol/client/node_modules/jose/dist/webapi/jws/general/verify.js +3 -3
  45. package/node_modules/@modelcontextprotocol/client/node_modules/jose/dist/webapi/jwt/encrypt.js +2 -2
  46. package/node_modules/@modelcontextprotocol/client/node_modules/jose/dist/webapi/jwt/sign.js +2 -2
  47. package/node_modules/@modelcontextprotocol/client/node_modules/jose/dist/webapi/jwt/unsecured.js +3 -3
  48. package/node_modules/@modelcontextprotocol/client/node_modules/jose/dist/webapi/key/generate_secret.js +4 -4
  49. package/node_modules/@modelcontextprotocol/client/node_modules/jose/dist/webapi/lib/asn1.js +2 -2
  50. package/node_modules/@modelcontextprotocol/client/node_modules/jose/dist/webapi/lib/buffer_utils.js +4 -4
  51. package/node_modules/@modelcontextprotocol/client/node_modules/jose/dist/webapi/lib/content_encryption.js +8 -8
  52. package/node_modules/@modelcontextprotocol/client/node_modules/jose/dist/webapi/lib/jwe_decrypt.js +14 -14
  53. package/node_modules/@modelcontextprotocol/client/node_modules/jose/dist/webapi/lib/jwe_encrypt.js +3 -3
  54. package/node_modules/@modelcontextprotocol/client/node_modules/jose/dist/webapi/lib/jws_algorithms.js +2 -2
  55. package/node_modules/@modelcontextprotocol/client/node_modules/jose/dist/webapi/lib/jws_sign.js +11 -11
  56. package/node_modules/@modelcontextprotocol/client/node_modules/jose/dist/webapi/lib/jws_verify.js +19 -16
  57. package/node_modules/@modelcontextprotocol/client/node_modules/jose/dist/webapi/lib/jwt_claims_set.js +44 -43
  58. package/node_modules/@modelcontextprotocol/client/node_modules/jose/dist/webapi/lib/key.js +4 -4
  59. package/node_modules/@modelcontextprotocol/client/node_modules/jose/dist/webapi/lib/key_management.js +17 -17
  60. package/node_modules/@modelcontextprotocol/client/node_modules/jose/dist/webapi/lib/validate.js +10 -10
  61. package/node_modules/@modelcontextprotocol/client/node_modules/jose/dist/webapi/util/decode_jwt.js +12 -12
  62. package/node_modules/@modelcontextprotocol/client/node_modules/jose/dist/webapi/util/decode_protected_header.js +5 -5
  63. package/node_modules/@modelcontextprotocol/client/node_modules/jose/package.json +1 -1
  64. package/node_modules/@modelcontextprotocol/sdk/node_modules/jose/README.md +2 -2
  65. package/node_modules/@modelcontextprotocol/sdk/node_modules/jose/dist/types/jwe/compact/decrypt.d.ts +5 -4
  66. package/node_modules/@modelcontextprotocol/sdk/node_modules/jose/dist/types/jwe/compact/encrypt.d.ts +4 -4
  67. package/node_modules/@modelcontextprotocol/sdk/node_modules/jose/dist/types/jwe/flattened/decrypt.d.ts +5 -4
  68. package/node_modules/@modelcontextprotocol/sdk/node_modules/jose/dist/types/jwe/flattened/encrypt.d.ts +5 -5
  69. package/node_modules/@modelcontextprotocol/sdk/node_modules/jose/dist/types/jwe/general/decrypt.d.ts +5 -4
  70. package/node_modules/@modelcontextprotocol/sdk/node_modules/jose/dist/types/jwe/general/encrypt.d.ts +5 -5
  71. package/node_modules/@modelcontextprotocol/sdk/node_modules/jose/dist/types/jws/compact/sign.d.ts +5 -4
  72. package/node_modules/@modelcontextprotocol/sdk/node_modules/jose/dist/types/jws/compact/verify.d.ts +5 -5
  73. package/node_modules/@modelcontextprotocol/sdk/node_modules/jose/dist/types/jws/flattened/sign.d.ts +5 -4
  74. package/node_modules/@modelcontextprotocol/sdk/node_modules/jose/dist/types/jws/flattened/verify.d.ts +6 -5
  75. package/node_modules/@modelcontextprotocol/sdk/node_modules/jose/dist/types/jws/general/sign.d.ts +9 -6
  76. package/node_modules/@modelcontextprotocol/sdk/node_modules/jose/dist/types/jws/general/verify.d.ts +7 -5
  77. package/node_modules/@modelcontextprotocol/sdk/node_modules/jose/dist/types/jwt/decrypt.d.ts +7 -6
  78. package/node_modules/@modelcontextprotocol/sdk/node_modules/jose/dist/types/jwt/encrypt.d.ts +1 -1
  79. package/node_modules/@modelcontextprotocol/sdk/node_modules/jose/dist/types/jwt/sign.d.ts +1 -1
  80. package/node_modules/@modelcontextprotocol/sdk/node_modules/jose/dist/types/jwt/unsecured.d.ts +1 -1
  81. package/node_modules/@modelcontextprotocol/sdk/node_modules/jose/dist/types/jwt/verify.d.ts +3 -5
  82. package/node_modules/@modelcontextprotocol/sdk/node_modules/jose/dist/types/key/export.d.ts +1 -1
  83. package/node_modules/@modelcontextprotocol/sdk/node_modules/jose/dist/types/key/generate_secret.d.ts +1 -1
  84. package/node_modules/@modelcontextprotocol/sdk/node_modules/jose/dist/types/key/import.d.ts +2 -2
  85. package/node_modules/@modelcontextprotocol/sdk/node_modules/jose/dist/types/types.d.ts +165 -107
  86. package/node_modules/@modelcontextprotocol/sdk/node_modules/jose/dist/types/util/decode_jwt.d.ts +2 -2
  87. package/node_modules/@modelcontextprotocol/sdk/node_modules/jose/dist/types/util/errors.d.ts +1 -1
  88. package/node_modules/@modelcontextprotocol/sdk/node_modules/jose/dist/webapi/jwe/general/encrypt.js +3 -3
  89. package/node_modules/@modelcontextprotocol/sdk/node_modules/jose/dist/webapi/jwk/thumbprint.js +8 -8
  90. package/node_modules/@modelcontextprotocol/sdk/node_modules/jose/dist/webapi/jwks/local.js +2 -1
  91. package/node_modules/@modelcontextprotocol/sdk/node_modules/jose/dist/webapi/jwks/remote.js +4 -4
  92. package/node_modules/@modelcontextprotocol/sdk/node_modules/jose/dist/webapi/jws/general/sign.js +3 -3
  93. package/node_modules/@modelcontextprotocol/sdk/node_modules/jose/dist/webapi/jws/general/verify.js +3 -3
  94. package/node_modules/@modelcontextprotocol/sdk/node_modules/jose/dist/webapi/jwt/encrypt.js +2 -2
  95. package/node_modules/@modelcontextprotocol/sdk/node_modules/jose/dist/webapi/jwt/sign.js +2 -2
  96. package/node_modules/@modelcontextprotocol/sdk/node_modules/jose/dist/webapi/jwt/unsecured.js +3 -3
  97. package/node_modules/@modelcontextprotocol/sdk/node_modules/jose/dist/webapi/key/generate_secret.js +4 -4
  98. package/node_modules/@modelcontextprotocol/sdk/node_modules/jose/dist/webapi/lib/asn1.js +2 -2
  99. package/node_modules/@modelcontextprotocol/sdk/node_modules/jose/dist/webapi/lib/buffer_utils.js +4 -4
  100. package/node_modules/@modelcontextprotocol/sdk/node_modules/jose/dist/webapi/lib/content_encryption.js +8 -8
  101. package/node_modules/@modelcontextprotocol/sdk/node_modules/jose/dist/webapi/lib/jwe_decrypt.js +14 -14
  102. package/node_modules/@modelcontextprotocol/sdk/node_modules/jose/dist/webapi/lib/jwe_encrypt.js +3 -3
  103. package/node_modules/@modelcontextprotocol/sdk/node_modules/jose/dist/webapi/lib/jws_algorithms.js +2 -2
  104. package/node_modules/@modelcontextprotocol/sdk/node_modules/jose/dist/webapi/lib/jws_sign.js +11 -11
  105. package/node_modules/@modelcontextprotocol/sdk/node_modules/jose/dist/webapi/lib/jws_verify.js +19 -16
  106. package/node_modules/@modelcontextprotocol/sdk/node_modules/jose/dist/webapi/lib/jwt_claims_set.js +44 -43
  107. package/node_modules/@modelcontextprotocol/sdk/node_modules/jose/dist/webapi/lib/key.js +4 -4
  108. package/node_modules/@modelcontextprotocol/sdk/node_modules/jose/dist/webapi/lib/key_management.js +17 -17
  109. package/node_modules/@modelcontextprotocol/sdk/node_modules/jose/dist/webapi/lib/validate.js +10 -10
  110. package/node_modules/@modelcontextprotocol/sdk/node_modules/jose/dist/webapi/util/decode_jwt.js +12 -12
  111. package/node_modules/@modelcontextprotocol/sdk/node_modules/jose/dist/webapi/util/decode_protected_header.js +5 -5
  112. package/node_modules/@modelcontextprotocol/sdk/node_modules/jose/package.json +1 -1
  113. package/node_modules/@zuplo/core/customer.cli.minified.js +269 -269
  114. package/node_modules/@zuplo/core/index.minified.js +275 -275
  115. package/node_modules/@zuplo/core/package.json +1 -1
  116. package/node_modules/@zuplo/graphql/package.json +1 -1
  117. package/node_modules/@zuplo/openapi-tools/package.json +1 -1
  118. package/node_modules/@zuplo/otel/package.json +1 -1
  119. package/node_modules/@zuplo/runtime/out/esm/{chunk-3UPXKWR6.js → chunk-RH345HSZ.js} +3 -3
  120. package/node_modules/@zuplo/runtime/out/esm/chunk-RH345HSZ.js.map +1 -0
  121. package/node_modules/@zuplo/runtime/out/esm/chunk-WIQFTAJL.js +26 -0
  122. package/node_modules/@zuplo/runtime/out/esm/chunk-WIQFTAJL.js.map +1 -0
  123. package/node_modules/@zuplo/runtime/out/esm/{chunk-JBEZQ336.js → chunk-WTWRZT3M.js} +112 -111
  124. package/node_modules/@zuplo/runtime/out/esm/chunk-WTWRZT3M.js.map +1 -0
  125. package/node_modules/@zuplo/runtime/out/esm/index.js +1 -1
  126. package/node_modules/@zuplo/runtime/out/esm/index.js.map +1 -1
  127. package/node_modules/@zuplo/runtime/out/esm/internal/index.js +1 -1
  128. package/node_modules/@zuplo/runtime/out/esm/mcp-gateway/index.js +1 -1
  129. package/node_modules/@zuplo/runtime/out/esm/mocks/index.js +1 -1
  130. package/node_modules/@zuplo/runtime/out/esm/sdks/aws/index.js +1 -1
  131. package/node_modules/@zuplo/runtime/out/types/index.d.ts +140 -16
  132. package/node_modules/@zuplo/runtime/out/types/internal/index.d.ts +93 -16
  133. package/node_modules/@zuplo/runtime/package.json +1 -1
  134. package/node_modules/fast-uri/.github/workflows/release.yml +35 -0
  135. package/node_modules/fast-uri/README.md +8 -0
  136. package/node_modules/fast-uri/index.js +98 -18
  137. package/node_modules/fast-uri/lib/utils.js +52 -0
  138. package/node_modules/fast-uri/package.json +1 -1
  139. package/node_modules/fast-uri/test/host-delimiter-security.test.js +94 -0
  140. package/node_modules/fast-uri/test/relative-normalization-security.test.js +156 -0
  141. package/node_modules/fast-uri/test/reserved-path-normalization.test.js +32 -4
  142. package/node_modules/fast-uri/test/scheme-validation.test.js +53 -14
  143. package/package.json +11 -11
  144. package/node_modules/@zuplo/runtime/out/esm/chunk-3UPXKWR6.js.map +0 -1
  145. package/node_modules/@zuplo/runtime/out/esm/chunk-GQMNFQTN.js +0 -26
  146. package/node_modules/@zuplo/runtime/out/esm/chunk-GQMNFQTN.js.map +0 -1
  147. package/node_modules/@zuplo/runtime/out/esm/chunk-JBEZQ336.js.map +0 -1
  148. /package/node_modules/@zuplo/runtime/out/esm/{chunk-3UPXKWR6.js.LEGAL.txt → chunk-RH345HSZ.js.LEGAL.txt} +0 -0
  149. /package/node_modules/@zuplo/runtime/out/esm/{chunk-JBEZQ336.js.LEGAL.txt → chunk-WTWRZT3M.js.LEGAL.txt} +0 -0
@@ -1,21 +1,29 @@
1
1
  'use strict'
2
2
 
3
- const { normalizeIPv6, removeDotSegments, recomposeAuthority, normalizePercentEncoding, normalizePathEncoding, serializePathEncoding, normalizeQueryFragmentEncoding, encodeQuery, encodeFragment, reescapeHostDelimiters, isIPv4, nonSimpleDomain } = require('./lib/utils')
3
+ const { normalizeIPv6, removeDotSegments, removeDotSegmentsRelative, recomposeAuthority, normalizePercentEncoding, normalizePathEncoding, serializePathEncoding, normalizeQueryFragmentEncoding, encodeQuery, encodeFragment, reescapeHostDelimiters, isIPv4, nonSimpleDomain } = require('./lib/utils')
4
4
  const { SCHEMES, getSchemeHandler } = require('./lib/schemes')
5
5
 
6
6
  const VALID_SCHEME = /^[A-Za-z][A-Za-z0-9+.-]*$/u
7
7
  const MALFORMED_SCHEME_ERROR = 'URI scheme is malformed.'
8
+ const MALFORMED_HOST_ERROR = 'URI host is malformed.'
9
+ // ASCII characters outside the RFC 3986 reg-name set (unreserved, sub-delims
10
+ // and "%" for pct-encoded): C0 controls, space, DEL, and "#/:<>?@[\]^`{|}.
11
+ const FORBIDDEN_DOMAIN_HOST_CHARACTER = /[\0-\x20"#/:<>?@[\\\]^`{|}\x7F]/u
8
12
 
9
13
  /**
14
+ * RFC 3986 section 3.1 does not allow percent-encoding in a scheme, so the
15
+ * scheme is validated as-is and never decoded. Decoding would let inert input
16
+ * such as `%6Aavascript:` become a live `javascript:` scheme.
17
+ *
10
18
  * @param {string} scheme
11
19
  * @returns {string}
12
20
  */
13
- function decodeValidScheme (scheme) {
14
- const decodedScheme = unescape(String(scheme))
15
- if (!VALID_SCHEME.test(decodedScheme)) {
21
+ function validateScheme (scheme) {
22
+ const stringScheme = String(scheme)
23
+ if (!VALID_SCHEME.test(stringScheme)) {
16
24
  throw new TypeError(MALFORMED_SCHEME_ERROR)
17
25
  }
18
- return decodedScheme
26
+ return stringScheme
19
27
  }
20
28
 
21
29
  /**
@@ -76,6 +84,12 @@ function resolve (baseURI, relativeURI, options) {
76
84
  const resolvedHost = resolved.host
77
85
  const resolvedHostIsIP = resolvedHost !== undefined && resolvedHost !== '' &&
78
86
  (isIPv4(resolvedHost) || normalizeIPv6(resolvedHost).isIPV6)
87
+ // Inputs are parsed scheme-neutrally, so delimiters and controls in a domain
88
+ // host are only detected here, once the final scheme is known. They always
89
+ // fail closed, including in percent-encoded hosts.
90
+ if (hasForbiddenDomainHost(resolved, options || {}, resolvedSchemeHandler, resolvedHostIsIP)) {
91
+ throw new Error(MALFORMED_HOST_ERROR)
92
+ }
79
93
  canonicalizeHost(resolved, options || {}, resolvedSchemeHandler, resolvedHostIsIP)
80
94
  // Percent escapes in an ASCII reg-name are encoded data. The WHATWG hostname
81
95
  // parser can reject them even though fast-uri preserves them safely as RFC
@@ -133,14 +147,17 @@ function resolveComponent (base, relative, options, skipNormalization) {
133
147
  if (relative.path[0] === '/') {
134
148
  target.path = removeDotSegments(relative.path)
135
149
  } else {
136
- if ((base.userinfo !== undefined || base.host !== undefined || base.port !== undefined) && !base.path) {
150
+ const baseHasAuthority = base.userinfo !== undefined || base.host !== undefined || base.port !== undefined
151
+ if (baseHasAuthority && !base.path) {
137
152
  target.path = '/' + relative.path
138
153
  } else if (!base.path) {
139
154
  target.path = relative.path
140
155
  } else {
141
156
  target.path = base.path.slice(0, base.path.lastIndexOf('/') + 1) + relative.path
142
157
  }
143
- target.path = removeDotSegments(target.path)
158
+ target.path = base.scheme === undefined && !baseHasAuthority && target.path[0] !== '/'
159
+ ? removeDotSegmentsRelative(target.path)
160
+ : removeDotSegments(target.path)
144
161
  }
145
162
  target.query = relative.query
146
163
  }
@@ -196,7 +213,7 @@ function serialize (cmpts, opts) {
196
213
  const uriTokens = []
197
214
 
198
215
  if (component.scheme) {
199
- component.scheme = decodeValidScheme(component.scheme)
216
+ component.scheme = validateScheme(component.scheme)
200
217
  }
201
218
 
202
219
  // find scheme handler
@@ -206,7 +223,11 @@ function serialize (cmpts, opts) {
206
223
  if (schemeHandler && schemeHandler.serialize) schemeHandler.serialize(component, options)
207
224
 
208
225
  const hasAuthority = component.userinfo !== undefined || component.host !== undefined || component.port !== undefined
209
- const pathNoScheme = !options.skipEscape && component.scheme === undefined && !hasAuthority
226
+ // A reference with no emitted scheme is path-noscheme: a bare ':' in its
227
+ // first path segment would reparse as a scheme (RFC 3986 3.3/4.2). Enforced
228
+ // under skipEscape too (resolve()), so the reapply below gates on this flag.
229
+ const schemelessReference = (component.scheme === undefined || options.reference === 'suffix') && !hasAuthority
230
+ const pathNoScheme = !options.skipEscape && schemelessReference
210
231
 
211
232
  if (component.path !== undefined) {
212
233
  if (!options.skipEscape) {
@@ -218,7 +239,7 @@ function serialize (cmpts, opts) {
218
239
 
219
240
  if (options.reference !== 'suffix' && component.scheme) {
220
241
  // Scheme handlers may replace the scheme during serialization.
221
- component.scheme = decodeValidScheme(component.scheme)
242
+ component.scheme = validateScheme(component.scheme)
222
243
  uriTokens.push(component.scheme, ':')
223
244
  }
224
245
 
@@ -238,13 +259,18 @@ function serialize (cmpts, opts) {
238
259
  let s = component.path
239
260
 
240
261
  if (!options.absolutePath && (!schemeHandler || !schemeHandler.absolutePath)) {
241
- s = removeDotSegments(s)
262
+ // A relative-path reference keeps its unresolvable leading ".." segments
263
+ // and must never become root-relative (RFC 3986 section 5.2.4 applies to
264
+ // merged target paths, not to the reference itself).
265
+ s = schemelessReference && s[0] !== '/'
266
+ ? removeDotSegmentsRelative(s)
267
+ : removeDotSegments(s)
242
268
  }
243
269
 
244
270
  // Dot-segment removal can expose a colon that was not originally in the
245
271
  // first segment (for example, "./a:b"). Reapply path-noscheme encoding so
246
272
  // the serialized relative reference cannot be reparsed as a URI scheme.
247
- if (pathNoScheme) {
273
+ if (schemelessReference) {
248
274
  s = serializePathEncoding(s, true)
249
275
  }
250
276
 
@@ -359,6 +385,11 @@ function hasMalformedComponentPercentEncoding (matches) {
359
385
  * @returns {boolean} whether host conversion failed
360
386
  */
361
387
  function canonicalizeHost (parsed, options, schemeHandler, isIP) {
388
+ if (hasForbiddenDomainHost(parsed, options, schemeHandler, isIP)) {
389
+ parsed.error = parsed.error || MALFORMED_HOST_ERROR
390
+ return true
391
+ }
392
+
362
393
  if (
363
394
  !options.unicodeSupport &&
364
395
  (!schemeHandler || !schemeHandler.unicodeSupport) &&
@@ -368,16 +399,62 @@ function canonicalizeHost (parsed, options, schemeHandler, isIP) {
368
399
  isIP === false &&
369
400
  nonSimpleDomain(parsed.host)
370
401
  ) {
402
+ let url
371
403
  try {
372
- parsed.host = new URL('http://' + parsed.host).hostname
404
+ url = new URL('http://' + parsed.host)
373
405
  } catch (e) {
374
406
  parsed.error = parsed.error || "Host's domain name can not be converted to ASCII: " + e
375
407
  return true
376
408
  }
409
+ // The WHATWG parser must have read the whole input as a host. Anything
410
+ // else means it found a delimiter fast-uri did not, so its hostname would
411
+ // describe a different authority.
412
+ if (!isHostOnlyURL(url)) {
413
+ parsed.error = parsed.error || MALFORMED_HOST_ERROR
414
+ return true
415
+ }
416
+ parsed.host = url.hostname
377
417
  }
378
418
  return false
379
419
  }
380
420
 
421
+ /**
422
+ * Whether a domain host contains an ASCII character that RFC 3986 does not
423
+ * allow in a reg-name (unreserved, sub-delims, or pct-encoded). Such a host is
424
+ * rejected instead of being handed to the WHATWG URL parser, which would treat
425
+ * "@" as a userinfo delimiter (silently dropping the data before it) and strip
426
+ * TAB, LF, and CR. Non-ASCII characters remain allowed for IDNs.
427
+ *
428
+ * @param {import('./types/index').URIComponent} parsed
429
+ * @param {import('./types/index').Options} options
430
+ * @param {{ domainHost?: boolean }|undefined} schemeHandler
431
+ * @param {boolean} isIP
432
+ * @returns {boolean}
433
+ */
434
+ function hasForbiddenDomainHost (parsed, options, schemeHandler, isIP) {
435
+ return Boolean(
436
+ parsed.host &&
437
+ isIP === false &&
438
+ !isIPLiteral(parsed.host) &&
439
+ (options.domainHost || (schemeHandler && schemeHandler.domainHost)) &&
440
+ FORBIDDEN_DOMAIN_HOST_CHARACTER.test(parsed.host)
441
+ )
442
+ }
443
+
444
+ /**
445
+ * @param {URL} url
446
+ * @returns {boolean}
447
+ */
448
+ function isHostOnlyURL (url) {
449
+ return url.username === '' &&
450
+ url.password === '' &&
451
+ url.port === '' &&
452
+ url.pathname === '/' &&
453
+ url.search === '' &&
454
+ url.hash === '' &&
455
+ url.hostname !== ''
456
+ }
457
+
381
458
  /**
382
459
  * @param {string} uri
383
460
  * @param {import('./types/index').Options} [opts]
@@ -460,9 +537,8 @@ function parseWithStatus (uri, opts) {
460
537
  parsed.fragment = matches[8]
461
538
 
462
539
  if (parsed.scheme !== undefined) {
463
- const decodedScheme = unescape(parsed.scheme)
464
- if (VALID_SCHEME.test(decodedScheme)) {
465
- parsed.scheme = decodedScheme.toLowerCase()
540
+ if (VALID_SCHEME.test(parsed.scheme)) {
541
+ parsed.scheme = parsed.scheme.toLowerCase()
466
542
  } else {
467
543
  parsed.error = parsed.error || MALFORMED_SCHEME_ERROR
468
544
  malformedScheme = true
@@ -496,7 +572,7 @@ function parseWithStatus (uri, opts) {
496
572
  parsed.host = isIP ? ipv6result.host : ipv6result.host.toLowerCase()
497
573
 
498
574
  if (malformedIPLiteral) {
499
- parsed.error = parsed.error || 'URI host is malformed.'
575
+ parsed.error = parsed.error || MALFORMED_HOST_ERROR
500
576
  malformedAuthorityOrPort = true
501
577
  }
502
578
  } else {
@@ -533,7 +609,11 @@ function parseWithStatus (uri, opts) {
533
609
  // pass only restores uppercase hex in escapes that remain encoded.
534
610
  host = normalizePercentEncoding(host.toLowerCase())
535
611
  }
536
- parsed.host = reescapeHostDelimiters(host, isIP)
612
+ // Resolution parses both inputs with the internal `null` scheme before
613
+ // the effective scheme is known. Keep raw host delimiters for that step
614
+ // so resolve() can reject them for domain-host schemes; the resolved
615
+ // authority is encoded when it is serialized.
616
+ parsed.host = isIP || options.scheme !== 'null' ? reescapeHostDelimiters(host, isIP) : host
537
617
  }
538
618
 
539
619
  if (!schemeHandler || (schemeHandler && !schemeHandler.skipNormalize)) {
@@ -345,6 +345,57 @@ function removeDotSegments (path) {
345
345
  return output.join('')
346
346
  }
347
347
 
348
+ /**
349
+ * Removes dot segments from the path of a relative reference (a path that does
350
+ * not start with "/") without changing what it resolves to against any base.
351
+ * Unlike `removeDotSegments`, leading ".." segments that cannot be collapsed
352
+ * are kept, the result never becomes root-relative, and a result that would be
353
+ * empty or start with an empty segment is prefixed with "." (an empty path
354
+ * means "same document" and a leading "//" would introduce an authority).
355
+ *
356
+ * @param {string} path
357
+ * @returns {string}
358
+ */
359
+ function removeDotSegmentsRelative (path) {
360
+ if (path === '' || path[0] === '/') {
361
+ return removeDotSegments(path)
362
+ }
363
+
364
+ const segments = path.split('/')
365
+ const last = segments.length - 1
366
+ /** @type {string[]} */
367
+ const output = []
368
+ let trailingDirectory = false
369
+
370
+ for (let i = 0; i <= last; i++) {
371
+ const segment = segments[i]
372
+ if (segment === '.' || segment === '..') {
373
+ if (segment === '..') {
374
+ if (output.length !== 0 && output[output.length - 1] !== '..') {
375
+ output.pop()
376
+ } else {
377
+ output.push('..')
378
+ }
379
+ }
380
+ trailingDirectory = i === last
381
+ } else {
382
+ output.push(segment)
383
+ }
384
+ }
385
+
386
+ if (trailingDirectory && output[output.length - 1] !== '..') {
387
+ output.push('')
388
+ }
389
+ if (output.length === 0 || output[0] === '' || output[0].includes(':')) {
390
+ // An empty path, a leading empty segment ("//"), or a first segment
391
+ // containing ":" (which would be mistaken for a scheme, RFC 3986 section
392
+ // 4.2) must stay behind a "." segment so the reference keeps its meaning.
393
+ output.unshift('.')
394
+ }
395
+
396
+ return output.join('/')
397
+ }
398
+
348
399
  /**
349
400
  * Re-escape RFC 3986 gen-delims that must not appear literally in the host.
350
401
  * After the URI regex parses, these characters cannot be literal in the host
@@ -740,6 +791,7 @@ module.exports = {
740
791
  encodeFragment,
741
792
  escapePreservingEscapes,
742
793
  removeDotSegments,
794
+ removeDotSegmentsRelative,
743
795
  isIPv4,
744
796
  isUUID,
745
797
  normalizeIPv6,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "fast-uri",
3
3
  "description": "Dependency-free RFC 3986 URI toolbox",
4
- "version": "3.1.8",
4
+ "version": "3.1.9",
5
5
  "main": "index.js",
6
6
  "type": "commonjs",
7
7
  "types": "types/index.d.ts",
@@ -0,0 +1,94 @@
1
+ 'use strict'
2
+
3
+ const test = require('tape')
4
+ const fastURI = require('..')
5
+
6
+ const MALFORMED_HOST_ERROR = 'URI host is malformed.'
7
+
8
+ const domainHostSchemes = ['http', 'https', 'ws', 'wss']
9
+
10
+ // Hosts containing ASCII characters that RFC 3986 does not allow in a
11
+ // reg-name. Handing them to the WHATWG URL parser would read "@" as a userinfo
12
+ // delimiter (dropping the data before it) and strip TAB, LF and CR.
13
+ const forbiddenHosts = [
14
+ 'b@evil.example',
15
+ 'ev\til.example',
16
+ 'ev\nil.example',
17
+ 'ev\ril.example',
18
+ 'ev\r\nil.example',
19
+ 'a b.example',
20
+ 'a"b.example',
21
+ 'a<b.example',
22
+ 'a>b.example',
23
+ 'a^b.example',
24
+ 'a`b.example',
25
+ 'a{b.example',
26
+ 'a|b.example',
27
+ 'a}b.example',
28
+ 'a\u007Fb.example',
29
+ 'a\u0000b.example'
30
+ ]
31
+
32
+ test('domain hosts with forbidden ASCII characters fail closed', (t) => {
33
+ for (const scheme of domainHostSchemes) {
34
+ for (const host of forbiddenHosts) {
35
+ const uri = `${scheme}://user@${host}/path`
36
+ const label = JSON.stringify(uri)
37
+
38
+ const parsed = fastURI.parse(uri)
39
+ t.equal(parsed.error, MALFORMED_HOST_ERROR, `${label} reports a malformed host`)
40
+ t.notEqual(parsed.host, 'evil.example', `${label} does not report a truncated host`)
41
+
42
+ t.equal(fastURI.normalize(uri), uri, `${label} normalizes to itself`)
43
+ t.equal(fastURI.normalize(uri, { unicodeSupport: true }), uri, `${label} normalizes to itself with unicodeSupport`)
44
+ t.equal(fastURI.equal(uri, uri), false, `${label} is not comparable`)
45
+ t.equal(fastURI.equal(uri, `${scheme}://user@evil.example/path`), false, `${label} does not alias the stripped host`)
46
+
47
+ t.throws(
48
+ () => fastURI.resolve(`${scheme}://trusted.example/`, `//user@${host}/path`),
49
+ /URI host is malformed\./,
50
+ `${label} is rejected by resolve`
51
+ )
52
+ }
53
+ }
54
+ t.end()
55
+ })
56
+
57
+ test('a second "@" cannot move the authority to a different host', (t) => {
58
+ const uri = 'http://a@b@evil.example/'
59
+ const parsed = fastURI.parse(uri)
60
+ t.equal(parsed.userinfo, 'a')
61
+ t.notEqual(parsed.host, 'evil.example', 'host is not silently truncated')
62
+ t.equal(parsed.error, MALFORMED_HOST_ERROR)
63
+ t.equal(fastURI.normalize(uri), uri)
64
+
65
+ t.throws(
66
+ () => fastURI.resolve('http://trusted.example/', '//a@b%41@evil.example/p'),
67
+ /URI host is malformed\./,
68
+ 'percent-encoded data does not bypass the check in resolve'
69
+ )
70
+ t.end()
71
+ })
72
+
73
+ test('the domainHost option applies the same rules to other schemes', (t) => {
74
+ const parsed = fastURI.parse('foo://a@b@evil.example/', { domainHost: true })
75
+ t.equal(parsed.error, MALFORMED_HOST_ERROR)
76
+ t.end()
77
+ })
78
+
79
+ test('valid domain hosts are unaffected', (t) => {
80
+ t.equal(fastURI.normalize('http://Example.COM/'), 'http://example.com/')
81
+ t.equal(fastURI.normalize('http://%65xample.com/'), 'http://example.com/')
82
+ t.equal(fastURI.normalize('http://bücher.de/'), 'http://xn--bcher-kva.de/')
83
+ t.equal(fastURI.normalize('http://bücher.de/', { unicodeSupport: true }), 'http://bücher.de/')
84
+ t.equal(fastURI.parse("http://a!$&'()*+,;=b.example/").error, undefined, 'sub-delims are allowed')
85
+ t.equal(fastURI.parse('http://user:pass@example.com:8080/').host, 'example.com')
86
+ t.equal(fastURI.resolve('http://trusted.example/', '//user@example.com/p'), 'http://user@example.com/p')
87
+ t.end()
88
+ })
89
+
90
+ test('schemes without domain hosts keep encoding reg-name data', (t) => {
91
+ t.equal(fastURI.normalize('foo://a@b@evil.example/'), 'foo://a@b%40evil.example/')
92
+ t.equal(fastURI.resolve('foo://x/', '//a@b@evil.example/p'), 'foo://a@b%40evil.example/p')
93
+ t.end()
94
+ })
@@ -0,0 +1,156 @@
1
+ 'use strict'
2
+
3
+ const test = require('tape')
4
+ const fastURI = require('..')
5
+
6
+ const ABSOLUTE_BASES = [
7
+ 'http://h/p/q/r',
8
+ 'http://h/',
9
+ 'foo://h/a/b/',
10
+ 'http://h/p/q/'
11
+ ]
12
+
13
+ const RELATIVE_BASES = ['', 'base', 'relative/base']
14
+
15
+ test('normalize keeps the meaning of relative-path references', (t) => {
16
+ const cases = [
17
+ ['a/../b', 'b'],
18
+ ['x/..', './'],
19
+ ['../x', '../x'],
20
+ ['./../x', '../x'],
21
+ ['a/b/../../..', '..'],
22
+ ['a/./b', 'a/b'],
23
+ ['a/b/..', 'a/'],
24
+ ['.', './'],
25
+ ['..', '..'],
26
+ ['../', '../'],
27
+ ['../../a/./b/../c', '../../a/c'],
28
+ ['a//..', 'a/'],
29
+ ['a/../b?q#f', 'b?q#f']
30
+ ]
31
+ for (const [input, expected] of cases) {
32
+ t.equal(fastURI.normalize(input), expected, `${input} -> ${expected}`)
33
+ }
34
+ t.end()
35
+ })
36
+
37
+ test('normalize never turns a relative-path reference into a root or network path', (t) => {
38
+ t.equal(fastURI.normalize('.//x'), './/x', 'leading empty segment is kept behind "."')
39
+ t.equal(fastURI.normalize('a/..//x'), './/x', 'collapsed prefix does not expose "//"')
40
+ t.equal(fastURI.parse(fastURI.normalize('a/..//x')).host, undefined, 'no authority is introduced')
41
+ t.equal(fastURI.normalize('./a:b'), './a:b', 'first-segment colon is kept behind a "." segment (RFC 3986 section 4.2)')
42
+ t.equal(fastURI.parse(fastURI.normalize('./a:b')).scheme, undefined, 'no scheme is introduced')
43
+ t.end()
44
+ })
45
+
46
+ test('normalize keeps the existing behavior for root-relative and absolute paths', (t) => {
47
+ t.equal(fastURI.normalize('/a/../b'), '/b')
48
+ t.equal(fastURI.normalize('/../x'), '/x')
49
+ t.equal(fastURI.normalize('http://h/a/../../b'), 'http://h/b')
50
+ t.end()
51
+ })
52
+
53
+ test('equal does not alias relative references with different targets', (t) => {
54
+ t.equal(fastURI.equal('../x', 'x'), false)
55
+ t.equal(fastURI.equal('a/../b', '/b'), false)
56
+ t.equal(fastURI.equal('x/..', '/'), false)
57
+ t.equal(fastURI.equal('a/../b', 'b'), true)
58
+ t.end()
59
+ })
60
+
61
+ test('resolving a normalized relative reference matches resolving the original', (t) => {
62
+ for (const base of [...ABSOLUTE_BASES, ...RELATIVE_BASES]) {
63
+ for (const ref of ['a/../b', 'x/..', '../x', './../x', 'a/b/../../..', '.', '..', './/x']) {
64
+ t.equal(
65
+ fastURI.resolve(base, fastURI.normalize(ref)),
66
+ fastURI.resolve(base, ref),
67
+ `${base} + ${ref}`
68
+ )
69
+ }
70
+ }
71
+ t.equal(fastURI.resolve('http://h/p/q/r', fastURI.normalize('a/../b')), 'http://h/p/q/b')
72
+ t.equal(fastURI.resolve('http://h/p/q/r', fastURI.normalize('../x')), 'http://h/p/x')
73
+ t.end()
74
+ })
75
+
76
+ test('property: normalization of relative references is resolution-preserving and idempotent', (t) => {
77
+ const segments = ['a', 'b', '.', '..', '', 'a:b']
78
+ // Deterministic linear congruential generator so failures are reproducible.
79
+ let seed = 0x5ec02
80
+ const next = (n) => {
81
+ seed = (seed * 1103515245 + 12345) % 2147483648
82
+ return Math.floor((seed / 2147483648) * n)
83
+ }
84
+
85
+ let failures = 0
86
+ let checked = 0
87
+ for (let i = 0; i < 4000; i++) {
88
+ const length = 1 + next(6)
89
+ const parts = []
90
+ for (let j = 0; j < length; j++) parts.push(segments[next(segments.length)])
91
+ const ref = parts.join('/')
92
+ // Only relative-path references: empty refs, refs starting with "/", and
93
+ // refs whose first segment contains ":" are not affected by this change.
94
+ if (ref === '' || ref[0] === '/' || fastURI.parse(ref).scheme !== undefined) continue
95
+
96
+ checked++
97
+ const normalized = fastURI.normalize(ref)
98
+ if (fastURI.normalize(normalized) !== normalized) {
99
+ failures++
100
+ t.fail(`not idempotent: ${ref} -> ${normalized} -> ${fastURI.normalize(normalized)}`)
101
+ }
102
+ // 3.x's removeDotSegments (used by resolve) collapses a trailing "//" to
103
+ // "/" (fixed in v4 by #185), so resolve() itself is inconsistent for
104
+ // these paths regardless of normalization.
105
+ if (normalized.endsWith('//')) continue
106
+ for (const base of ABSOLUTE_BASES) {
107
+ const expected = fastURI.resolve(base, ref)
108
+ const actual = fastURI.resolve(base, normalized)
109
+ if (actual !== expected) {
110
+ failures++
111
+ t.fail(`${base} + ${ref}: normalized ${normalized} resolves to ${actual}, expected ${expected}`)
112
+ }
113
+ }
114
+ }
115
+
116
+ t.ok(checked > 1000, `checked ${checked} relative references`)
117
+ t.equal(failures, 0, 'no relative reference changed meaning')
118
+ t.end()
119
+ })
120
+
121
+ test('suffix normalization preserves relative path meaning', (t) => {
122
+ const options = { reference: 'suffix', scheme: 'x' }
123
+ for (const [input, expected] of [
124
+ ['a/../b', 'b'],
125
+ ['x/..', './'],
126
+ ['a/../a:b', './a:b']
127
+ ]) {
128
+ t.equal(fastURI.normalize(input, options), expected, `${input} -> ${expected}`)
129
+ }
130
+ t.end()
131
+ })
132
+
133
+ test('normalize does not promote a colon-first relative reference to a scheme (skipEscape)', (t) => {
134
+ // RFC 3986 section 4.2: a first path segment containing ":" must stay
135
+ // protected by a leading "./", or it is mistaken for a scheme. Collapsing
136
+ // the protecting dot segment must not expose the colon. The default path
137
+ // escapes the colon; with skipEscape the colon is emitted raw and the
138
+ // reference is reparsed as absolute (scheme "a"), changing its meaning.
139
+ const colonRefs = ['./a:b', 'a/../a:b']
140
+ for (const ref of colonRefs) {
141
+ const normalized = fastURI.normalize(ref, { skipEscape: true })
142
+ t.equal(
143
+ fastURI.parse(normalized).scheme,
144
+ undefined,
145
+ `${ref} -> ${normalized} must not introduce a scheme`
146
+ )
147
+ for (const base of ABSOLUTE_BASES) {
148
+ t.equal(
149
+ fastURI.resolve(base, normalized, { skipEscape: true }),
150
+ fastURI.resolve(base, ref, { skipEscape: true }),
151
+ `${base} + ${ref}: normalized ${normalized} must resolve the same`
152
+ )
153
+ }
154
+ }
155
+ t.end()
156
+ })
@@ -77,13 +77,13 @@ test('serialize uses the RFC 3986 path character set without opening escapes', (
77
77
  )
78
78
  t.equal(
79
79
  fastURI.serialize({ path: './a:b/c:d' }),
80
- 'a%3Ab/c:d',
81
- 'escapes a first-segment colon exposed by dot-segment removal'
80
+ './a:b/c:d',
81
+ 'a first-segment colon exposed by dot-segment removal is kept behind a "." segment'
82
82
  )
83
83
  t.equal(
84
84
  fastURI.normalize('./a:b'),
85
- 'a%3Ab',
86
- 'normalization keeps a relative path from becoming a scheme'
85
+ './a:b',
86
+ 'normalization keeps a relative path from becoming a scheme without re-encoding the colon'
87
87
  )
88
88
  t.equal(
89
89
  fastURI.serialize({ path: '/Kſ' }),
@@ -107,3 +107,31 @@ test('equal distinguishes escaped reserved path data from literal syntax', (t) =
107
107
  )
108
108
  t.end()
109
109
  })
110
+
111
+ // A suffix reference never emits its scheme token, so it is a scheme-less
112
+ // (path-noscheme) reference: a bare ':' in its first path segment must be
113
+ // escaped, or the serialized output reparses as an absolute URI with a scheme.
114
+ // @see https://github.com/fastify/fast-uri/security/advisories/GHSA-333x-w4r6-6crp
115
+ test('suffix reference escapes a first-segment colon instead of emitting a scheme', (t) => {
116
+ const dangerous = /^(?:javascript|data|vbscript):/i
117
+ const options = { reference: 'suffix', scheme: 'x' }
118
+
119
+ for (const input of ['javascript:alert(1)', '%6Aavascript:alert(1)', 'data:text/html,x', 'vbscript:msgbox(1)']) {
120
+ t.notOk(dangerous.test(fastURI.normalize(input, options)), `${input} normalizes to an inert reference`)
121
+ }
122
+
123
+ t.equal(
124
+ fastURI.normalize('javascript:alert(1)', options),
125
+ 'javascript%3Aalert(1)',
126
+ 'escapes the leading-segment colon to %3A'
127
+ )
128
+ t.notOk(
129
+ dangerous.test(fastURI.resolve('https://a.example/base/', '%6Aavascript:alert(1)', { reference: 'suffix' })),
130
+ 'resolve in suffix mode does not emit a live scheme'
131
+ )
132
+
133
+ // legitimate suffix usage is unchanged
134
+ t.equal(fastURI.normalize('path/to/file', options), 'path/to/file', 'plain path unchanged')
135
+ t.equal(fastURI.normalize('a/b:c', options), 'a/b:c', 'colon in a later segment stays literal')
136
+ t.end()
137
+ })