diff --git a/.socket/blob/1ee52493c04ff9be2a83963c4e9e16d6177b125282ad073257a0eb489f39ad95 b/.socket/blob/1ee52493c04ff9be2a83963c4e9e16d6177b125282ad073257a0eb489f39ad95 new file mode 100644 index 00000000..08f54932 --- /dev/null +++ b/.socket/blob/1ee52493c04ff9be2a83963c4e9e16d6177b125282ad073257a0eb489f39ad95 @@ -0,0 +1,306 @@ +// Socket Community Patch: https://socket.dev +// Date: Thu, 30 Jul 2026 15:28:49 GMT +// For more information see https://socket.dev/patch/d4e48cf5-361c-49ba-a4a3-810f58bc94f6 +// This file includes modifications made by Socket, Inc. on Thu, 30 Jul 2026; these modifications are called the "Patch". In some cases, Socket may be required to make the Patch available to you under specific terms, or may be prohibited from restricting certain rights you may have. For example, the terms of another applicable license may require Socket to make the Patch available under specific terms. In those cases, the Patch is made available to you under the required terms, and Socket does not seek to restrict your rights relative to the Patch where prohibited. In all other cases, the Patch is available to you exclusively under the PolyForm Shield License 1.0.0 (https://polyformproject.org/licenses/shield/1.0.0/). The Patch was distributed by Socket with additional information concerning licensing, attribution, and limitation of liability which may be relevant to you and your use of the Patch. As far as the law allows, the Patch and the software including the patch come as is, without any warranty or condition, and Socket will not be liable to you for any damages arising out of the applicable license terms or the use or nature of the Patch or the software including the patch, under any kind of legal claim. + +'use strict' + +const { normalizeIPv6, normalizeIPv4, removeDotSegments, recomposeAuthority, normalizePercentEncoding, normalizePathEncoding, escapePreservingEscapes } = require('./lib/utils') +const SCHEMES = require('./lib/schemes') + +function normalize (uri, options) { + if (typeof uri === 'string') { + uri = serialize(parse(uri, options), options) + } else if (typeof uri === 'object') { + uri = parse(serialize(uri, options), options) + } + return uri +} + +function resolve (baseURI, relativeURI, options) { + const schemelessOptions = Object.assign({ scheme: 'null' }, options) + const resolved = resolveComponents(parse(baseURI, schemelessOptions), parse(relativeURI, schemelessOptions), schemelessOptions, true) + return serialize(resolved, { ...schemelessOptions, skipEscape: true }) +} + +function resolveComponents (base, relative, options, skipNormalization) { + const target = {} + if (!skipNormalization) { + base = parse(serialize(base, options), options) // normalize base components + relative = parse(serialize(relative, options), options) // normalize relative components + } + options = options || {} + + if (!options.tolerant && relative.scheme) { + target.scheme = relative.scheme + // target.authority = relative.authority; + target.userinfo = relative.userinfo + target.host = relative.host + target.port = relative.port + target.path = removeDotSegments(relative.path || '') + target.query = relative.query + } else { + if (relative.userinfo !== undefined || relative.host !== undefined || relative.port !== undefined) { + // target.authority = relative.authority; + target.userinfo = relative.userinfo + target.host = relative.host + target.port = relative.port + target.path = removeDotSegments(relative.path || '') + target.query = relative.query + } else { + if (!relative.path) { + target.path = base.path + if (relative.query !== undefined) { + target.query = relative.query + } else { + target.query = base.query + } + } else { + if (relative.path.charAt(0) === '/') { + target.path = removeDotSegments(relative.path) + } else { + if ((base.userinfo !== undefined || base.host !== undefined || base.port !== undefined) && !base.path) { + target.path = '/' + relative.path + } else if (!base.path) { + target.path = relative.path + } else { + target.path = base.path.slice(0, base.path.lastIndexOf('/') + 1) + relative.path + } + target.path = removeDotSegments(target.path) + } + target.query = relative.query + } + // target.authority = base.authority; + target.userinfo = base.userinfo + target.host = base.host + target.port = base.port + } + target.scheme = base.scheme + } + + target.fragment = relative.fragment + + return target +} + +function equal (uriA, uriB, options) { + if (typeof uriA === 'string') { + uriA = serialize(parse(uriA, options), options) + } else if (typeof uriA === 'object') { + uriA = serialize(uriA, options) + } + + if (typeof uriB === 'string') { + uriB = serialize(parse(uriB, options), options) + } else if (typeof uriB === 'object') { + uriB = serialize(uriB, options) + } + + return uriA.toLowerCase() === uriB.toLowerCase() +} + +function serialize (cmpts, opts) { + const components = { + host: cmpts.host, + scheme: cmpts.scheme, + userinfo: cmpts.userinfo, + port: cmpts.port, + path: cmpts.path, + query: cmpts.query, + nid: cmpts.nid, + nss: cmpts.nss, + uuid: cmpts.uuid, + fragment: cmpts.fragment, + reference: cmpts.reference, + resourceName: cmpts.resourceName, + secure: cmpts.secure, + error: '' + } + const options = Object.assign({}, opts) + const uriTokens = [] + + // find scheme handler + const schemeHandler = SCHEMES[(options.scheme || components.scheme || '').toLowerCase()] + + // perform scheme specific serialization + if (schemeHandler && schemeHandler.serialize) schemeHandler.serialize(components, options) + + if (components.path !== undefined) { + if (!options.skipEscape) { + components.path = escapePreservingEscapes(components.path) + + if (components.scheme !== undefined) { + components.path = components.path.split('%3A').join(':') + } + } else { + components.path = normalizePercentEncoding(components.path) + } + } + + if (options.reference !== 'suffix' && components.scheme) { + uriTokens.push(components.scheme, ':') + } + + const authority = recomposeAuthority(components) + if (authority !== undefined) { + if (options.reference !== 'suffix') { + uriTokens.push('//') + } + + uriTokens.push(authority) + + if (components.path && components.path.charAt(0) !== '/') { + uriTokens.push('/') + } + } + if (components.path !== undefined) { + let s = components.path + + if (!options.absolutePath && (!schemeHandler || !schemeHandler.absolutePath)) { + s = removeDotSegments(s) + } + + if (authority === undefined) { + s = s.replace(/^\/\//u, '/%2F') // don't allow the path to start with "//" + } + + uriTokens.push(s) + } + + if (components.query !== undefined) { + uriTokens.push('?', components.query) + } + + if (components.fragment !== undefined) { + uriTokens.push('#', components.fragment) + } + return uriTokens.join('') +} + +const hexLookUp = Array.from({ length: 127 }, (_v, k) => /[^!"$&'()*+,\-.;=_`a-z{}~]/u.test(String.fromCharCode(k))) + +function nonSimpleDomain (value) { + let code = 0 + for (let i = 0, len = value.length; i < len; ++i) { + code = value.charCodeAt(i) + if (code > 126 || hexLookUp[code]) { + return true + } + } + return false +} + +const URI_PARSE = /^(?:([^#/:?]+):)?(?:\/\/((?:([^#/?@]*)@)?(\[[^#/?\]]+\]|[^#/:?]*)(?::(\d*))?))?([^#?]*)(?:\?([^#]*))?(?:#((?:.|[\n\r])*))?/u + +function parse (uri, opts) { + const options = Object.assign({}, opts) + const parsed = { + scheme: undefined, + userinfo: undefined, + host: '', + port: undefined, + path: '', + query: undefined, + fragment: undefined + } + const gotEncoding = uri.indexOf('%') !== -1 + let isIP = false + if (options.reference === 'suffix') uri = (options.scheme ? options.scheme + ':' : '') + '//' + uri + + const matches = uri.match(URI_PARSE) + + if (matches) { + // store each component + parsed.scheme = matches[1] + parsed.userinfo = matches[3] + parsed.host = matches[4] + parsed.port = parseInt(matches[5], 10) + parsed.path = matches[6] || '' + parsed.query = matches[7] + parsed.fragment = matches[8] + + // fix port number + if (isNaN(parsed.port)) { + parsed.port = matches[5] + } + if (parsed.host) { + const ipv4result = normalizeIPv4(parsed.host) + if (ipv4result.isIPV4 === false) { + const ipv6result = normalizeIPv6(ipv4result.host) + parsed.host = ipv6result.host.toLowerCase() + isIP = ipv6result.isIPV6 + } else { + parsed.host = ipv4result.host + isIP = true + } + } + if (parsed.scheme === undefined && parsed.userinfo === undefined && parsed.host === undefined && parsed.port === undefined && parsed.query === undefined && !parsed.path) { + parsed.reference = 'same-document' + } else if (parsed.scheme === undefined) { + parsed.reference = 'relative' + } else if (parsed.fragment === undefined) { + parsed.reference = 'absolute' + } else { + parsed.reference = 'uri' + } + + // check for reference errors + if (options.reference && options.reference !== 'suffix' && options.reference !== parsed.reference) { + parsed.error = parsed.error || 'URI is not a ' + options.reference + ' reference.' + } + + // find scheme handler + const schemeHandler = SCHEMES[(options.scheme || parsed.scheme || '').toLowerCase()] + + // check if scheme can't handle IRIs + if (!options.unicodeSupport && (!schemeHandler || !schemeHandler.unicodeSupport)) { + // if host component is a domain name + if (parsed.host && (options.domainHost || (schemeHandler && schemeHandler.domainHost)) && isIP === false && nonSimpleDomain(parsed.host)) { + // convert Unicode IDN -> ASCII IDN + try { + parsed.host = URL.domainToASCII(parsed.host.toLowerCase()) + } catch (e) { + parsed.error = parsed.error || "Host's domain name can not be converted to ASCII: " + e + } + } + // convert IRI -> URI + } + + if (!schemeHandler || (schemeHandler && !schemeHandler.skipNormalize)) { + if (gotEncoding && parsed.scheme !== undefined) { + parsed.scheme = unescape(parsed.scheme) + } + if (gotEncoding && parsed.host !== undefined) { + parsed.host = unescape(parsed.host) + } + if (parsed.path) { + parsed.path = normalizePathEncoding(parsed.path) + } + if (parsed.fragment) { + parsed.fragment = encodeURI(decodeURIComponent(parsed.fragment)) + } + } + + // perform scheme specific parsing + if (schemeHandler && schemeHandler.parse) { + schemeHandler.parse(parsed, options) + } + } else { + parsed.error = parsed.error || 'URI can not be parsed.' + } + return parsed +} + +const fastUri = { + SCHEMES, + normalize, + resolve, + resolveComponents, + equal, + serialize, + parse +} + +module.exports = fastUri +module.exports.default = fastUri +module.exports.fastUri = fastUri diff --git a/.socket/blob/c756072b817fea82758e509c2f73802c5ff00ba9c2ba1701a65451adc68ea620 b/.socket/blob/c756072b817fea82758e509c2f73802c5ff00ba9c2ba1701a65451adc68ea620 new file mode 100644 index 00000000..3af1e65a --- /dev/null +++ b/.socket/blob/c756072b817fea82758e509c2f73802c5ff00ba9c2ba1701a65451adc68ea620 @@ -0,0 +1,340 @@ +// Socket Community Patch: https://socket.dev +// Date: Thu, 30 Jul 2026 15:28:49 GMT +// For more information see https://socket.dev/patch/d4e48cf5-361c-49ba-a4a3-810f58bc94f6 +// This file includes modifications made by Socket, Inc. on Thu, 30 Jul 2026; these modifications are called the "Patch". In some cases, Socket may be required to make the Patch available to you under specific terms, or may be prohibited from restricting certain rights you may have. For example, the terms of another applicable license may require Socket to make the Patch available under specific terms. In those cases, the Patch is made available to you under the required terms, and Socket does not seek to restrict your rights relative to the Patch where prohibited. In all other cases, the Patch is available to you exclusively under the PolyForm Shield License 1.0.0 (https://polyformproject.org/licenses/shield/1.0.0/). The Patch was distributed by Socket with additional information concerning licensing, attribution, and limitation of liability which may be relevant to you and your use of the Patch. As far as the law allows, the Patch and the software including the patch come as is, without any warranty or condition, and Socket will not be liable to you for any damages arising out of the applicable license terms or the use or nature of the Patch or the software including the patch, under any kind of legal claim. + +'use strict' + +const { HEX } = require('./scopedChars') + +const IPV4_REG = /^(?:(?:25[0-5]|2[0-4]\d|1\d{2}|[1-9]\d|\d)\.){3}(?:25[0-5]|2[0-4]\d|1\d{2}|[1-9]\d|\d)$/u + +function normalizeIPv4 (host) { + if (findToken(host, '.') < 3) { return { host, isIPV4: false } } + const matches = host.match(IPV4_REG) || [] + const [address] = matches + if (address) { + return { host: stripLeadingZeros(address, '.'), isIPV4: true } + } else { + return { host, isIPV4: false } + } +} + +/** + * @param {string[]} input + * @param {boolean} [keepZero=false] + * @returns {string|undefined} + */ +function stringArrayToHexStripped (input, keepZero = false) { + let acc = '' + let strip = true + for (const c of input) { + if (HEX[c] === undefined) return undefined + if (c !== '0' && strip === true) strip = false + if (!strip) acc += c + } + if (keepZero && acc.length === 0) acc = '0' + return acc +} + +function getIPV6 (input) { + let tokenCount = 0 + const output = { error: false, address: '', zone: '' } + const address = [] + const buffer = [] + let isZone = false + let endipv6Encountered = false + let endIpv6 = false + + function consume () { + if (buffer.length) { + if (isZone === false) { + const hex = stringArrayToHexStripped(buffer) + if (hex !== undefined) { + address.push(hex) + } else { + output.error = true + return false + } + } + buffer.length = 0 + } + return true + } + + for (let i = 0; i < input.length; i++) { + const cursor = input[i] + if (cursor === '[' || cursor === ']') { continue } + if (cursor === ':') { + if (endipv6Encountered === true) { + endIpv6 = true + } + if (!consume()) { break } + tokenCount++ + address.push(':') + if (tokenCount > 7) { + // not valid + output.error = true + break + } + if (i - 1 >= 0 && input[i - 1] === ':') { + endipv6Encountered = true + } + continue + } else if (cursor === '%') { + if (!consume()) { break } + // switch to zone detection + isZone = true + } else { + buffer.push(cursor) + continue + } + } + if (buffer.length) { + if (isZone) { + output.zone = buffer.join('') + } else if (endIpv6) { + address.push(buffer.join('')) + } else { + address.push(stringArrayToHexStripped(buffer)) + } + } + output.address = address.join('') + return output +} + +function normalizeIPv6 (host) { + if (findToken(host, ':') < 2) { return { host, isIPV6: false } } + const ipv6 = getIPV6(host) + + if (!ipv6.error) { + let newHost = ipv6.address + let escapedHost = ipv6.address + if (ipv6.zone) { + newHost += '%' + ipv6.zone + escapedHost += '%25' + ipv6.zone + } + return { host: newHost, escapedHost, isIPV6: true } + } else { + return { host, isIPV6: false } + } +} + +function stripLeadingZeros (str, token) { + let out = '' + let skip = true + const l = str.length + for (let i = 0; i < l; i++) { + const c = str[i] + if (c === '0' && skip) { + if ((i + 1 <= l && str[i + 1] === token) || i + 1 === l) { + out += c + skip = false + } + } else { + if (c === token) { + skip = true + } else { + skip = false + } + out += c + } + } + return out +} + +function findToken (str, token) { + let ind = 0 + for (let i = 0; i < str.length; i++) { + if (str[i] === token) ind++ + } + return ind +} + +const RDS1 = /^\.\.?\//u +const RDS2 = /^\/\.(?:\/|$)/u +const RDS3 = /^\/\.\.(?:\/|$)/u +const RDS5 = /^\/?(?:.|\n)*?(?=\/|$)/u + +function removeDotSegments (input) { + const output = [] + + while (input.length) { + if (input.match(RDS1)) { + input = input.replace(RDS1, '') + } else if (input.match(RDS2)) { + input = input.replace(RDS2, '/') + } else if (input.match(RDS3)) { + input = input.replace(RDS3, '/') + output.pop() + } else if (input === '.' || input === '..') { + input = '' + } else { + const im = input.match(RDS5) + if (im) { + const s = im[0] + input = input.slice(s.length) + output.push(s) + } else { + throw new Error('Unexpected dot segment condition') + } + } + } + return output.join('') +} + +/** @type {(value: string) => boolean} */ +const isHexPair = RegExp.prototype.test.bind(/^[\da-f]{2}$/iu) + +/** @type {(value: string) => boolean} */ +const isUnreserved = RegExp.prototype.test.bind(/^[\da-z\-._~]$/iu) + +/** @type {(value: string) => boolean} */ +const isPathCharacter = RegExp.prototype.test.bind(/^[\da-z\-._~!$&'()*+,;=:@/]$/iu) + +/** + * Normalizes percent escapes and optionally decodes only unreserved ASCII bytes. + * Reserved delimiters such as `%2F` and `%2E` stay escaped. + * + * @param {string} input + * @param {boolean} [decodeUnreserved=false] + * @returns {string} + */ +function normalizePercentEncoding (input, decodeUnreserved = false) { + if (input.indexOf('%') === -1) { + return input + } + + let output = '' + + for (let i = 0; i < input.length; i++) { + if (input[i] === '%' && i + 2 < input.length) { + const hex = input.slice(i + 1, i + 3) + if (isHexPair(hex)) { + const normalizedHex = hex.toUpperCase() + const decoded = String.fromCharCode(parseInt(normalizedHex, 16)) + + if (decodeUnreserved && isUnreserved(decoded)) { + output += decoded + } else { + output += '%' + normalizedHex + } + + i += 2 + continue + } + } + + output += input[i] + } + + return output +} + +/** + * Normalizes path data without turning reserved escapes into live path syntax. + * Valid escapes are uppercased, raw unsafe characters are escaped, and only + * unreserved bytes that are not `.` are decoded. + * + * @param {string} input + * @returns {string} + */ +function normalizePathEncoding (input) { + let output = '' + + for (let i = 0; i < input.length; i++) { + if (input[i] === '%' && i + 2 < input.length) { + const hex = input.slice(i + 1, i + 3) + if (isHexPair(hex)) { + const normalizedHex = hex.toUpperCase() + const decoded = String.fromCharCode(parseInt(normalizedHex, 16)) + + if (decoded !== '.' && isUnreserved(decoded)) { + output += decoded + } else { + output += '%' + normalizedHex + } + + i += 2 + continue + } + } + + if (isPathCharacter(input[i])) { + output += input[i] + } else { + output += escape(input[i]) + } + } + + return output +} + +/** + * Escapes a component while preserving existing valid percent escapes. + * + * @param {string} input + * @returns {string} + */ +function escapePreservingEscapes (input) { + let output = '' + + for (let i = 0; i < input.length; i++) { + if (input[i] === '%' && i + 2 < input.length) { + const hex = input.slice(i + 1, i + 3) + if (isHexPair(hex)) { + output += '%' + hex.toUpperCase() + i += 2 + continue + } + } + + output += escape(input[i]) + } + + return output +} + +function recomposeAuthority (components) { + const uriTokens = [] + + if (components.userinfo !== undefined) { + uriTokens.push(components.userinfo) + uriTokens.push('@') + } + + if (components.host !== undefined) { + let host = unescape(components.host) + const ipV4res = normalizeIPv4(host) + + if (ipV4res.isIPV4) { + host = ipV4res.host + } else { + const ipV6res = normalizeIPv6(ipV4res.host) + if (ipV6res.isIPV6 === true) { + host = `[${ipV6res.escapedHost}]` + } else { + host = components.host + } + } + uriTokens.push(host) + } + + if (typeof components.port === 'number' || typeof components.port === 'string') { + uriTokens.push(':') + uriTokens.push(String(components.port)) + } + + return uriTokens.length ? uriTokens.join('') : undefined +}; + +module.exports = { + recomposeAuthority, + normalizePercentEncoding, + normalizePathEncoding, + escapePreservingEscapes, + removeDotSegments, + normalizeIPv4, + normalizeIPv6, + stringArrayToHexStripped +} diff --git a/.socket/blob/cf137993d06cc11c10400f7f9ecc469fae6050e41bf4d5ffd29ed8730b8e7532 b/.socket/blob/cf137993d06cc11c10400f7f9ecc469fae6050e41bf4d5ffd29ed8730b8e7532 new file mode 100644 index 00000000..a73cf9e8 --- /dev/null +++ b/.socket/blob/cf137993d06cc11c10400f7f9ecc469fae6050e41bf4d5ffd29ed8730b8e7532 @@ -0,0 +1,66 @@ +// Socket Community Patch: https://socket.dev +// Date: Thu, 23 Jul 2026 16:13:01 GMT +// For more information see https://socket.dev/patch/7e947070-90ed-4a05-988b-19b2efa7e40f +// This file includes modifications made by Socket, Inc. on Thu, 23 Jul 2026; these modifications are called the "Patch". In some cases, Socket may be required to make the Patch available to you under specific terms, or may be prohibited from restricting certain rights you may have. For example, the terms of another applicable license may require Socket to make the Patch available under specific terms. In those cases, the Patch is made available to you under the required terms, and Socket does not seek to restrict your rights relative to the Patch where prohibited. In all other cases, the Patch is available to you exclusively under the PolyForm Shield License 1.0.0 (https://polyformproject.org/licenses/shield/1.0.0/). The Patch was distributed by Socket with additional information concerning licensing, attribution, and limitation of liability which may be relevant to you and your use of the Patch. As far as the law allows, the Patch and the software including the patch come as is, without any warranty or condition, and Socket will not be liable to you for any damages arising out of the applicable license terms or the use or nature of the Patch or the software including the patch, under any kind of legal claim. + +'use strict'; + +var OPS = [ + '||', + '&&', + ';;', + '|&', + '<(', + '<<<', + '>>', + '>&', + '<&', + '&', + ';', + '(', + ')', + '|', + '<', + '>' +]; +var LINE_TERMINATORS = /[\n\r\u2028\u2029]/; +var GLOB_SHELL_SPECIAL = /[\s#!"$&'():;<=>@\\^`|]/g; + +module.exports = function quote(xs) { + return xs.map(function (s) { + if (s === '') { + return '\'\''; + } + if (s && typeof s === 'object') { + if (s.op === 'glob') { + if (typeof s.pattern !== 'string') { + throw new TypeError('glob token requires a string `pattern`'); + } + if (LINE_TERMINATORS.test(s.pattern)) { + throw new TypeError('glob `pattern` must not contain line terminators'); + } + return s.pattern.replace(GLOB_SHELL_SPECIAL, '\\$&'); + } + if (typeof s.op === 'string') { + if (OPS.indexOf(s.op) < 0) { + throw new TypeError('invalid `op` value: ' + JSON.stringify(s.op)); + } + return s.op.replace(/[\s\S]/g, '\\$&'); + } + if (typeof s.comment === 'string') { + if (LINE_TERMINATORS.test(s.comment)) { + throw new TypeError('`comment` must not contain line terminators'); + } + return '#' + s.comment; + } + throw new TypeError('unrecognized object token shape'); + } + if ((/["\s\\]/).test(s) && !(/'/).test(s)) { + return "'" + s.replace(/(['])/g, '\\$1') + "'"; + } + if ((/["'\s]/).test(s)) { + return '"' + s.replace(/(["\\$`!])/g, '\\$1') + '"'; + } + return String(s).replace(/([A-Za-z]:)?([#!"$&'()*,:;<=>?@[\\\]^`{|}])/g, '$1\\$2'); + }).join(' '); +}; diff --git a/.socket/manifest.json b/.socket/manifest.json new file mode 100644 index 00000000..01f7e7a0 --- /dev/null +++ b/.socket/manifest.json @@ -0,0 +1,54 @@ +{ + "patches": { + "pkg:npm/fast-uri@3.0.6": { + "uuid": "d4e48cf5-361c-49ba-a4a3-810f58bc94f6", + "exportedAt": "Thu, 30 Jul 2026 15:28:49 GMT", + "files": { + "index.js": { + "beforeHash": "ca68a28ad16c7f891f0096a7641a1bb9eb5d358e556b92f4e5bd316bd47f04e6", + "afterHash": "1ee52493c04ff9be2a83963c4e9e16d6177b125282ad073257a0eb489f39ad95" + }, + "lib/utils.js": { + "beforeHash": "44421013e6d07c26c9e1f325eaa6b538c879dbe831628509b458ba115b3cc968", + "afterHash": "c756072b817fea82758e509c2f73802c5ff00ba9c2ba1701a65451adc68ea620" + } + }, + "vulnerabilities": { + "GHSA-q3j6-qgpj-74h6": { + "cves": [ + "CVE-2026-6321" + ], + "summary": "fast-uri vulnerable to path traversal via percent-encoded dot segments", + "severity": "HIGH", + "description": "### Impact\n\n`fast-uri` v3.1.0 and earlier decodes percent-encoded path separators (`%2F`) and dot segments (`%2E`) before applying dot-segment removal in `normalize()` and `equal()`. This makes encoded path data behave like real `/` and `..`, so distinct URIs collapse onto the same normalized path.\n\nFor example, `http://example.com/public/%2e%2e/admin` normalizes to `http://example.com/admin`, and `equal()` considers them the same URI.\n\nApplications that normalize or compare attacker-controlled URLs to enforce path-based policy can be bypassed. A path that looks confined under an allowed prefix can normalize to a different location.\n\n### Patches\n\nUpgrade to `fast-uri` >= 3.1.1, or if you are in the v2.x release line, v2.4.1\n\n### Workarounds\n\nNone. Upgrade to the patched version." + } + }, + "description": "", + "license": "", + "tier": "free" + }, + "pkg:npm/shell-quote@1.8.3": { + "uuid": "7e947070-90ed-4a05-988b-19b2efa7e40f", + "exportedAt": "Thu, 23 Jul 2026 16:13:01 GMT", + "files": { + "quote.js": { + "beforeHash": "2d2f2a9cc9c6c6f8960bc45c90cb8d22ff878b1cf3a2ca249f22b2076acb5cd3", + "afterHash": "cf137993d06cc11c10400f7f9ecc469fae6050e41bf4d5ffd29ed8730b8e7532" + } + }, + "vulnerabilities": { + "GHSA-w7jw-789q-3m8p": { + "cves": [ + "CVE-2026-9277" + ], + "summary": "shell-quote quote() does not escape newlines in object .op values", + "severity": "CRITICAL", + "description": "### Summary\n\n`shell-quote`'s `quote()` function did not validate object-token inputs against the operator model used by `parse()`. The `.op` field was backslash-escaped character by character using `/(.)/g`, which in JavaScript does not match line terminators (`\\n`, `\\r`, U+2028, U+2029). A line terminator in `.op` therefore passed through unescaped into the output; POSIX shells treat a literal `\\n` as a command separator, so any content after it would execute as a second command.\n\nThe vulnerable code path is reachable in two ways. Neither requires the parser to misbehave — `parse()` only emits ops from a fixed control set — but both are documented API surface:\n\n1. **Direct construction.** A caller builds `{ op: '...\\n...' }` from external input (e.g. a deserialized argument array) and passes it to `quote()`.\n2. **`envFn` return.** `parse(cmd, envFn)` is documented to splice the return value of `envFn` into the result array when it is an object. An attacker-influenced data source consulted by `envFn` can introduce an object token whose `.op` reaches `quote()`.\n\n### Impact\n\nShell command injection in callers that pass object tokens with attacker-influenced `.op` values to `quote()` and then hand the result to a shell. The preconditions are narrower than ordinary string injection — they require the caller to feed object tokens into `quote()` — but object tokens are a public, documented part of the API surface, and `quote()` is intended to be a shell-safety boundary.\n\n### PoC\n\n```js\nconst { parse, quote } = require('shell-quote');\n\n// Direct construction\nquote([{ op: ';\\nid' }]);\n// → \"\\;\\n\\\\i\\\\d\" ← literal newline; second line executes as a command\n\n// Via parse() with an envFn returning attacker-shaped objects\nconst tokens = parse('echo $X', () => ({ op: ';\\nid' }));\nrequire('child_process').execSync(quote(tokens), { shell: true });\n// Executes `id` after `echo \\;`.\n```\n\nConfirmed under `sh`, `bash`, `dash`, and `zsh`.\n\n### Patch\n\nFixed by replacing the per-character escape with strict shape validation in `quote()`. The object-token branch now:\n\n- **`{ op }`** — `.op` must be a string from the same allowlist the parser emits (`||`, `&&`, `;;`, `|&`, `<(`, `<<<`, `>>`, `>&`, `<&`, `&`, `;`, `(`, `)`, `|`, `<`, `>`). Anything else throws `TypeError`. This is the direct fix for the reported issue and removes the entire class of `.op` injection.\n- **`{ op: 'glob', pattern }`** — `.pattern` must be a string with no line terminators. Glob metacharacters (`*`, `?`, `[`, `]`, `{`, `}`, `,`) pass through; all other shell-special characters are backslash-escaped. (Previously the pattern field was discarded entirely and the literal string `\\g\\l\\o\\b` was emitted — a latent bug, not security-relevant.)\n- **`{ comment }`** — `.comment` must be a string with no line terminators (line terminators would end the shell comment and resume command parsing — same injection shape).\n- **Any other object shape** — `TypeError`.\n\nThe fix is allowlist-based rather than a targeted regex tweak, so it closes the reported vector and forecloses adjacent ones (U+2028 / U+2029 line separators in `.op`, line terminators in comments, unknown-shape objects coerced through `.replace`).\n\n### Workarounds\n\nPrior to upgrading, callers that build object tokens from untrusted input should validate `.op` against the parser's operator set themselves, and never construct `{ op }` from attacker-controlled strings.\n\n### Credits\n\nReported by Akshat Sinha" + } + }, + "description": "", + "license": "", + "tier": "free" + } + } +} \ No newline at end of file