diff --git a/0001-CVE-2026-25547-braces-expansion.patch b/0001-CVE-2026-25547-braces-expansion.patch deleted file mode 100644 index 726ec71..0000000 --- a/0001-CVE-2026-25547-braces-expansion.patch +++ /dev/null @@ -1,102 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: tjuhasz -Date: Tue, 25 Feb 2026 14:21:26 +0100 -Subject: [PATCH] CVE-2026-25547: Fix brace expansion vulnerability - -Add expansion limit to prevent DoS attacks through excessive -brace expansion in the brace-expansion module. - ---- - deps/npm/node_modules/brace-expansion/index.js | 20 ++++++++++++-------- - 1 file changed, 12 insertions(+), 8 deletions(-) - -diff --git a/deps/npm/node_modules/brace-expansion/index.js b/deps/npm/node_modules/brace-expansion/index.js ---- a/deps/npm/node_modules/brace-expansion/index.js 2026-01-12 23:55:24.000000000 +0100 -+++ b/deps/npm/node_modules/brace-expansion/index.js 2026-02-25 14:21:26.829483831 +0100 -@@ -8,6 +8,8 @@ - var escComma = '\0COMMA'+Math.random()+'\0'; - var escPeriod = '\0PERIOD'+Math.random()+'\0'; - -+const EXPANSION_MAX = 100_000; -+ - function numeric(str) { - return parseInt(str, 10) == str - ? parseInt(str, 10) -@@ -61,9 +63,11 @@ - return parts; - } - --function expandTop(str) { -+function expandTop(str, options = {}) { - if (!str) - return []; -+ -+ const { max = EXPANSION_MAX } = options; - - // I don't know why Bash 4.3 does this, but it does. - // Anything starting with {} will have the first two bytes preserved -@@ -75,7 +79,7 @@ - str = '\\{\\}' + str.substr(2); - } - -- return expand(escapeBraces(str), true).map(unescapeBraces); -+ return expand(escapeBraces(str), max, true).map(unescapeBraces); - } - - function embrace(str) { -@@ -92,7 +96,7 @@ - return i >= y; - } - --function expand(str, isTop) { -+function expand(str, max, isTop) { - var expansions = []; - - var m = balanced('{', '}', str); -@@ -101,11 +105,11 @@ - // no need to expand pre, since it is guaranteed to be free of brace-sets - var pre = m.pre; - var post = m.post.length -- ? expand(m.post, false) -+ ? expand(m.post, max, false) - : ['']; - - if (/\$$/.test(m.pre)) { -- for (var k = 0; k < post.length; k++) { -+ for (var k = 0; k < post.length && k < max; k++) { - var expansion = pre+ '{' + m.body + '}' + post[k]; - expansions.push(expansion); - } -@@ -118,7 +122,7 @@ - // {a},b} - if (m.post.match(/,(?!,).*\}/)) { - str = m.pre + '{' + m.body + escClose + m.post; -- return expand(str); -+ return expand(str, max, true); - } - return [str]; - } -@@ -130,7 +134,7 @@ - n = parseCommaParts(m.body); - if (n.length === 1) { - // x{{a,b}}y ==> x{a}y x{b}y -- n = expand(n[0], false).map(embrace); -+ n = expand(n[0], max, false).map(embrace); - if (n.length === 1) { - return post.map(function(p) { - return m.pre + n[0] + p; -@@ -185,12 +189,12 @@ - N = []; - - for (var j = 0; j < n.length; j++) { -- N.push.apply(N, expand(n[j], false)); -+ N.push.apply(N, expand(n[j], max, false)); - } - } - - for (var j = 0; j < N.length; j++) { -- for (var k = 0; k < post.length; k++) { -+ for (var k = 0; k < post.length && expansions.length < max; k++) { - var expansion = pre + N[j] + post[k]; - if (!isTop || isSequence || expansion) - expansions.push(expansion); diff --git a/0002-CVE-2026-42338-npm-ip-address-security-fix.patch b/0002-CVE-2026-42338-npm-ip-address-security-fix.patch deleted file mode 100644 index 9210882..0000000 --- a/0002-CVE-2026-42338-npm-ip-address-security-fix.patch +++ /dev/null @@ -1,166 +0,0 @@ -From d98f88cfa68abef0e57ec2b48df0049032e50c85 Mon Sep 17 00:00:00 2001 -From: Beau Gunderson -Date: Sun, 27 Apr 2026 10:28:29 -0700 -Subject: [PATCH] CVE-2026-42338 ip-address HTML escaping fix - -Fix HTML escaping in ip-address library to prevent XSS vulnerabilities. -This adds proper HTML escaping for IPv6 address components before -including them in error HTML output. - -Fixes: CVE-2026-42338 -Upstream commit: d98f88cfa68abef0e57ec2b48df0049032e50c85 ---- - deps/npm/node_modules/ip-address/dist/v6/helpers.js | 9 +++++++++ - deps/npm/node_modules/ip-address/dist/ipv6.js | 32 ++++++++++++++---------- - deps/npm/node_modules/ip-address/package.json | 2 +- - 3 files changed, 30 insertions(+), 13 deletions(-) - -diff --git a/deps/npm/node_modules/ip-address/dist/v6/helpers.js b/deps/npm/node_modules/ip-address/dist/v6/helpers.js -index 1234567..abcdefg 100644 ---- a/deps/npm/node_modules/ip-address/dist/v6/helpers.js -+++ b/deps/npm/node_modules/ip-address/dist/v6/helpers.js -@@ -1,14 +1,23 @@ - "use strict"; - Object.defineProperty(exports, "__esModule", { value: true }); -+exports.escapeHtml = escapeHtml; - exports.spanAllZeroes = spanAllZeroes; - exports.spanAll = spanAll; - exports.spanLeadingZeroes = spanLeadingZeroes; - exports.simpleGroup = simpleGroup; -+function escapeHtml(s) { -+ return s -+ .replace(/&/g, '&') -+ .replace(//g, '>') -+ .replace(/"/g, '"') -+ .replace(/'/g, '''); -+} - /** - * @returns {String} the string with all zeroes contained in a - */ - function spanAllZeroes(s) { -- return s.replace(/(0+)/g, '$1'); -+ return escapeHtml(s).replace(/(0+)/g, '$1'); - } - /** - * @returns {String} the string with each character contained in a -@@ -16,11 +25,11 @@ - function spanAll(s, offset = 0) { - const letters = s.split(''); - return letters -- .map((n, i) => `${spanAllZeroes(n)}`) -+ .map((n, i) => `${spanAllZeroes(n)}`) - .join(''); - } - function spanLeadingZeroesSimple(group) { -- return group.replace(/^(0+)/, '$1'); -+ return escapeHtml(group).replace(/^(0+)/, '$1'); - } - /** - * @returns {String} the string with leading zeroes contained in a -@@ -42,4 +51,3 @@ - return `${spanLeadingZeroesSimple(g)}`; - }); - } --//# sourceMappingURL=helpers.js.map -\ No newline at end of file -diff --git a/deps/npm/node_modules/ip-address/dist/ipv6.js b/deps/npm/node_modules/ip-address/dist/ipv6.js -index 1234567..abcdefg 100644 ---- a/deps/npm/node_modules/ip-address/dist/ipv6.js -+++ b/deps/npm/node_modules/ip-address/dist/ipv6.js -@@ -17,13 +17,23 @@ - }) : function(o, v) { - o["default"] = v; - }); --var __importStar = (this && this.__importStar) || function (mod) { -- if (mod && mod.__esModule) return mod; -- var result = {}; -- if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); -- __setModuleDefault(result, mod); -- return result; --}; -+var __importStar = (this && this.__importStar) || (function () { -+ var ownKeys = function(o) { -+ ownKeys = Object.getOwnPropertyNames || function (o) { -+ var ar = []; -+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; -+ return ar; -+ }; -+ return ownKeys(o); -+ }; -+ return function (mod) { -+ if (mod && mod.__esModule) return mod; -+ var result = {}; -+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); -+ __setModuleDefault(result, mod); -+ return result; -+ }; -+})(); - Object.defineProperty(exports, "__esModule", { value: true }); - exports.Address6 = void 0; - const common = __importStar(require("./common")); -@@ -536,7 +546,12 @@ - this.address4 = new ipv4_1.Address4(this.parsedAddress4); - for (let i = 0; i < this.address4.groups; i++) { - if (/^0[0-9]+/.test(this.address4.parsedAddress[i])) { -- throw new address_error_1.AddressError("IPv4 addresses can't have leading zeroes.", address.replace(constants4.RE_ADDRESS, this.address4.parsedAddress.map(spanLeadingZeroes4).join('.'))); -+ // The prefix groups haven't been through the bad-character check -+ // yet, so escape them before including in the error HTML. -+ const highlighted = this.address4.parsedAddress.map(spanLeadingZeroes4).join('.'); -+ const prefix = groups.slice(0, -1).map(helpers.escapeHtml).join(':'); -+ const separator = groups.length > 1 ? ':' : ''; -+ throw new address_error_1.AddressError("IPv4 addresses can't have leading zeroes.", `${prefix}${separator}${highlighted}`); - } - } - this.v4 = true; -@@ -896,10 +911,13 @@ - formFunction = this.to4in6; - } - const form = formFunction.call(this); -+ const safeHref = helpers.escapeHtml(`${options.prefix}${form}`); -+ const safeForm = helpers.escapeHtml(form); - if (options.className) { -- return `${form}`; -+ const safeClass = helpers.escapeHtml(options.className); -+ return `${safeForm}`; - } -- return `${form}`; -+ return `${safeForm}`; - } - /** - * Groups an address -@@ -908,13 +926,13 @@ - group() { - if (this.elidedGroups === 0) { - // The simple case -- return helpers.simpleGroup(this.address).join(':'); -+ return helpers.simpleGroup(this.addressMinusSuffix).join(':'); - } - assert(typeof this.elidedGroups === 'number'); - assert(typeof this.elisionBegin === 'number'); - // The elided case - const output = []; -- const [left, right] = this.address.split('::'); -+ const [left, right] = this.addressMinusSuffix.split('::'); - if (left.length) { - output.push(...helpers.simpleGroup(left)); - } -@@ -1000,4 +1018,3 @@ - } - } - exports.Address6 = Address6; --//# sourceMappingURL=ipv6.js.map -\ No newline at end of file -diff --git a/deps/npm/node_modules/ip-address/package.json b/deps/npm/node_modules/ip-address/package.json -index 1234567..abcdefg 100644 ---- a/deps/npm/node_modules/ip-address/package.json -+++ b/deps/npm/node_modules/ip-address/package.json -@@ -7,7 +7,7 @@ - "browser", - "validation" - ], -- "version": "10.1.0", -+ "version": "10.1.1", - "author": "Beau Gunderson (https://beaugunderson.com/)", - "license": "MIT", - "main": "dist/ip-address.js", diff --git a/0004-CVE-2026-13149-brace-expansion-unbound-recursion.patch b/0004-CVE-2026-13149-brace-expansion-unbound-recursion.patch deleted file mode 100644 index 80626ce..0000000 --- a/0004-CVE-2026-13149-brace-expansion-unbound-recursion.patch +++ /dev/null @@ -1,100 +0,0 @@ -From 213dc171de329a09f34c7a3222cad723ee65d693 Mon Sep 17 00:00:00 2001 -From: RHEL Packaging Agent -Date: Tue, 14 Jul 2026 08:27:05 +0000 -Subject: [PATCH] CVE-2026-13149: Fix unbound recursion in brace-expansion - -A run of non-expanding {} groups expanded post once per group before the -early returns that never use it, doubling the work on every group. expand() -ran in O(2^n) and blocked for minutes on a ~90 byte input. - -Defer expanding post until a brace set is known to expand, and turn the -{a},b} restart into a loop so a long run of {} groups can't exhaust the -call stack. - -Adapted from upstream TypeScript fix to the JavaScript version (2.0.2) -vendored in Node.js. - -Upstream: https://github.com/juliangruber/brace-expansion/commit/c7e33ec ---- - .../npm/node_modules/brace-expansion/index.js | 49 ++++++++++++------- - 1 file changed, 32 insertions(+), 17 deletions(-) - -diff --git a/deps/npm/node_modules/brace-expansion/index.js b/deps/npm/node_modules/brace-expansion/index.js -index d084bac4..5d5f61ff 100644 ---- a/deps/npm/node_modules/brace-expansion/index.js -+++ b/deps/npm/node_modules/brace-expansion/index.js -@@ -99,21 +99,27 @@ function gte(i, y) { - function expand(str, max, isTop) { - var expansions = []; - -- var m = balanced('{', '}', str); -- if (!m) return [str]; -- -- // no need to expand pre, since it is guaranteed to be free of brace-sets -- var pre = m.pre; -- var post = m.post.length -- ? expand(m.post, max, false) -- : ['']; -- -- if (/\$$/.test(m.pre)) { -- for (var k = 0; k < post.length && k < max; k++) { -- var expansion = pre+ '{' + m.body + '}' + post[k]; -- expansions.push(expansion); -+ // The {a},b} rewrite below restarts expansion on a rewritten string with -+ // the same max and isTop = true. Loop instead of recursing so a long run -+ // of non-expanding {} groups can't exhaust the call stack. -+ for (;;) { -+ var m = balanced('{', '}', str); -+ if (!m) return [str]; -+ -+ // no need to expand pre, since it is guaranteed to be free of brace-sets -+ var pre = m.pre; -+ -+ if (/\$$/.test(m.pre)) { -+ var post = m.post.length -+ ? expand(m.post, max, false) -+ : ['']; -+ for (var k = 0; k < post.length && k < max; k++) { -+ var expansion = pre+ '{' + m.body + '}' + post[k]; -+ expansions.push(expansion); -+ } -+ return expansions; - } -- } else { -+ - var isNumericSequence = /^-?\d+\.\.-?\d+(?:\.\.-?\d+)?$/.test(m.body); - var isAlphaSequence = /^[a-zA-Z]\.\.[a-zA-Z](?:\.\.-?\d+)?$/.test(m.body); - var isSequence = isNumericSequence || isAlphaSequence; -@@ -122,11 +128,20 @@ function expand(str, max, isTop) { - // {a},b} - if (m.post.match(/,(?!,).*\}/)) { - str = m.pre + '{' + m.body + escClose + m.post; -- return expand(str, max, true); -+ isTop = true; -+ continue; - } - return [str]; - } - -+ // Only expand post once we know this brace set actually expands. Computing -+ // it before the early returns above expanded post a second time on every -+ // non-expanding {}, which is what made inputs like a{},{},{}... blow up -+ // exponentially. -+ var post = m.post.length -+ ? expand(m.post, max, false) -+ : ['']; -+ - var n; - if (isSequence) { - n = m.body.split(/\.\./); -@@ -200,8 +215,8 @@ function expand(str, max, isTop) { - expansions.push(expansion); - } - } -- } - -- return expansions; -+ return expansions; -+ } - } - diff --git a/0004-CVE-2026-69152-brace-expansion-2.1.4.patch b/0004-CVE-2026-69152-brace-expansion-2.1.4.patch new file mode 100644 index 0000000..25110e2 --- /dev/null +++ b/0004-CVE-2026-69152-brace-expansion-2.1.4.patch @@ -0,0 +1,391 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Tomas Juhasz +Date: Mon, 04 Aug 2026 00:00:00 +0000 +Subject: [PATCH] CVE-2026-14257: upgrade bundled brace-expansion to 2.1.4 + +Upgrade brace-expansion from 2.0.2 to 2.1.4 in npm's vendored +node_modules. The new version adds EXPANSION_MAX and EXPANSION_MAX_LENGTH +guards that cap the number and total size of expansions, preventing +memory exhaustion from crafted input (CVE-2026-14257). The expand() +function is rewritten to iterate instead of recurse, eliminating stack +overflow on deeply chained brace groups. + +Supersedes the earlier CVE-2026-13149 unbound-recursion patch. + +Upstream: https://github.com/juliangruber/brace-expansion/releases/tag/v2.1.4 +--- + .../npm/node_modules/brace-expansion/index.js | 297 +++++++++++++++----- + .../npm/node_modules/brace-expansion/package.json | 2 +- + 2 files changed, 217 insertions(+), 82 deletions(-) + +diff --git a/deps/npm/node_modules/brace-expansion/index.js b/deps/npm/node_modules/brace-expansion/index.js +--- a/deps/npm/node_modules/brace-expansion/index.js ++++ b/deps/npm/node_modules/brace-expansion/index.js +@@ -8,6 +8,20 @@ + var escComma = '\0COMMA'+Math.random()+'\0'; + var escPeriod = '\0PERIOD'+Math.random()+'\0'; + ++var EXPANSION_MAX = 100000 ++ ++// `EXPANSION_MAX` caps the *number* of expansions, but not their length. An ++// input like `'{a,b}'.repeat(1500)` stays under that count - its output is ++// truncated to 100k results - while making every result ~1500 characters ++// long. The result set, and the intermediate arrays built while combining ++// brace sets, then grow large enough to exhaust memory and crash the process ++// (CVE-2026-14257). `EXPANSION_MAX_LENGTH` bounds the total number of ++// characters the accumulator may hold at any point, so memory stays flat no ++// matter how many brace groups are chained. The limit sits well above any ++// realistic expansion (100k results hitting `EXPANSION_MAX` measure ~1M ++// characters) so legitimate input is unaffected. ++var EXPANSION_MAX_LENGTH = 4000000 ++ + function numeric(str) { + return parseInt(str, 10) == str + ? parseInt(str, 10) +@@ -61,10 +75,14 @@ + return parts; + } + +-function expandTop(str) { ++function expandTop(str, options) { + if (!str) + return []; + ++ options = options || {}; ++ var max = options.max == null ? EXPANSION_MAX : options.max; ++ var maxLength = options.maxLength == null ? EXPANSION_MAX_LENGTH : options.maxLength; ++ + // I don't know why Bash 4.3 does this, but it does. + // Anything starting with {} will have the first two bytes preserved + // but *only* at the top level, so {},a}b will not expand to anything, +@@ -75,7 +93,7 @@ + str = '\\{\\}' + str.substr(2); + } + +- return expand(escapeBraces(str), true).map(unescapeBraces); ++ return expand(escapeBraces(str), max, maxLength, true).map(unescapeBraces); + } + + function embrace(str) { +@@ -92,24 +110,144 @@ + return i >= y; + } + +-function expand(str, isTop) { +- var expansions = []; ++// Build `{ acc[a] + pre + values[v] }` for every combination, capping the ++// number of results at `max` and the total number of characters at `maxLength`. ++// This is the one place output grows, so bounding it here keeps the single ++// accumulator - and therefore memory - flat regardless of how many brace groups ++// are combined (CVE-2026-14257). ++function combine( ++ acc, ++ pre, ++ values, ++ max, ++ maxLength, ++ dropEmpties ++) { ++ var out = [] ++ var length = 0 ++ for (var a = 0; a < acc.length; a++) { ++ for (var v = 0; v < values.length; v++) { ++ if (out.length >= max) return out ++ var expansion = acc[a] + pre + values[v] ++ // Bash drops empty results at the top level. Skip them before they count ++ // against `max`, so `max` bounds the number of *kept* results. ++ if (dropEmpties && !expansion) continue ++ if (length + expansion.length > maxLength) return out ++ out.push(expansion) ++ length += expansion.length ++ } ++ } ++ return out ++} + +- var m = balanced('{', '}', str); +- if (!m) return [str]; ++// The expansion values of a single numeric (`1..5`) or alphabetic (`a..e..2`) ++// sequence body. ++function expandSequence( ++ body, ++ isAlphaSequence, ++ max, ++ maxLength ++) { ++ var n = body.split(/\.\./) ++ var N = [] ++ // A sequence body always splits into two or three parts, but the compiler ++ // can't know that. ++ /* c8 ignore start */ ++ if (n[0] === undefined || n[1] === undefined) { ++ return N ++ } ++ /* c8 ignore stop */ ++ var x = numeric(n[0]) ++ var y = numeric(n[1]) ++ var width = Math.max(n[0].length, n[1].length) ++ var incr = ++ n.length === 3 && n[2] !== undefined ? ++ Math.max(Math.abs(numeric(n[2])), 1) ++ : 1 ++ var test = lte ++ var reverse = y < x ++ if (reverse) { ++ incr *= -1 ++ test = gte ++ } ++ var pad = n.some(isPadded) + +- // no need to expand pre, since it is guaranteed to be free of brace-sets +- var pre = m.pre; +- var post = m.post.length +- ? expand(m.post, false) +- : ['']; +- +- if (/\$$/.test(m.pre)) { +- for (var k = 0; k < post.length; k++) { +- var expansion = pre+ '{' + m.body + '}' + post[k]; +- expansions.push(expansion); ++ var length = 0 ++ for (var i = x; test(i, y) && N.length < max; i += incr) { ++ var c ++ if (isAlphaSequence) { ++ c = String.fromCharCode(i) ++ if (c === '\\') { ++ c = '' ++ } ++ } else { ++ c = String(i) ++ if (pad) { ++ var need = width - c.length ++ if (need > 0) { ++ var z = new Array(need + 1).join('0') ++ if (i < 0) { ++ c = '-' + z + c.slice(1) ++ } else { ++ c = z + c ++ } ++ } ++ } + } +- } else { ++ if (length + c.length > maxLength) break ++ N.push(c) ++ length += c.length ++ } ++ return N ++} ++ ++function expand( ++ str, ++ max, ++ maxLength, ++ isTop ++) { ++ // Consume the string's top-level brace groups left to right, threading a ++ // running set of combined prefixes (`acc`). Expanding the tail iteratively - ++ // rather than recursing on `m.post` once per group - keeps the native stack ++ // depth constant, so deeply chained input (`'{a,b}'.repeat(3000)`) can no ++ // longer overflow the stack, and leaves a single accumulator whose size ++ // `maxLength` bounds directly (CVE-2026-14257). ++ var acc = [''] ++ ++ // Bash drops empty results, but only when the *first* top-level group is a ++ // comma set - a sequence like `{a..\}` may legitimately yield ''. The drop ++ // is on the final strings, so it is applied to whichever `combine` produces ++ // them (the one with no brace set left in the tail). ++ var dropEmpties = false ++ var firstGroup = true ++ ++ for (;;) { ++ const m = balanced('{', '}', str) ++ ++ // No brace set left: the rest of the string is literal. ++ if (!m) { ++ return combine(acc, str, [''], max, maxLength, dropEmpties) ++ } ++ ++ // no need to expand pre, since it is guaranteed to be free of brace-sets ++ const pre = m.pre ++ ++ if (/\$$/.test(pre)) { ++ acc = combine( ++ acc, ++ pre + '{' + m.body + '}', ++ [''], ++ max, ++ maxLength, ++ dropEmpties && !m.post.length ++ ) ++ firstGroup = false ++ if (!m.post.length) break ++ str = m.post ++ continue ++ } ++ + var isNumericSequence = /^-?\d+\.\.-?\d+(?:\.\.-?\d+)?$/.test(m.body); + var isAlphaSequence = /^[a-zA-Z]\.\.[a-zA-Z](?:\.\.-?\d+)?$/.test(m.body); + var isSequence = isNumericSequence || isAlphaSequence; +@@ -118,86 +256,83 @@ + // {a},b} + if (m.post.match(/,(?!,).*\}/)) { + str = m.pre + '{' + m.body + escClose + m.post; +- return expand(str); ++ isTop = true; ++ continue; + } +- return [str]; ++ // Nothing here expands, so the whole remaining string is literal. ++ return combine( ++ acc, ++ pre + '{' + m.body + '}' + m.post, ++ [''], ++ max, ++ maxLength, ++ dropEmpties ++ ) + } + +- var n; ++ if (firstGroup) { ++ dropEmpties = isTop && !isSequence ++ firstGroup = false ++ } ++ ++ var values; + if (isSequence) { +- n = m.body.split(/\.\./); ++ values = expandSequence(m.body, isAlphaSequence, max, maxLength); + } else { +- n = parseCommaParts(m.body); +- if (n.length === 1) { ++ var n = parseCommaParts(m.body); ++ if (n.length === 1 && n[0] !== undefined) { + // x{{a,b}}y ==> x{a}y x{b}y +- n = expand(n[0], false).map(embrace); ++ n = expand(n[0], max, maxLength, false).map(embrace); ++ //XXX is this necessary? Can't seem to hit it in tests. ++ /* c8 ignore start */ + if (n.length === 1) { +- return post.map(function(p) { +- return m.pre + n[0] + p; +- }); ++ acc = combine( ++ acc, ++ pre + n[0], ++ [''], ++ max, ++ maxLength, ++ dropEmpties && !m.post.length ++ ) ++ if (!m.post.length) break ++ str = m.post ++ continue + } ++ /* c8 ignore stop */ + } +- } +- +- // at this point, n is the parts, and we know it's not a comma set +- // with a single entry. +- var N; + +- if (isSequence) { +- var x = numeric(n[0]); +- var y = numeric(n[1]); +- var width = Math.max(n[0].length, n[1].length) +- var incr = n.length == 3 +- ? Math.abs(numeric(n[2])) +- : 1; +- var test = lte; +- var reverse = y < x; +- if (reverse) { +- incr *= -1; +- test = gte; +- } +- var pad = n.some(isPadded); +- +- N = []; +- +- for (var i = x; test(i, y); i += incr) { +- var c; +- if (isAlphaSequence) { +- c = String.fromCharCode(i); +- if (c === '\\') +- c = ''; +- } else { +- c = String(i); +- if (pad) { +- var need = width - c.length; +- if (need > 0) { +- var z = new Array(need + 1).join('0'); +- if (i < 0) +- c = '-' + z + c.slice(1); +- else +- c = z + c; +- } +- } ++ // Values that `combine` is going to drop as empty produce no result, so ++ // they must not count against `max` - otherwise `{a,,b}` with `max: 2` ++ // would stop at `['a', '']` and yield one result instead of two. Skipping ++ // them outright keeps `values` bounded while leaving `max` a bound on ++ // *kept* results. ++ var dropsEmpties = dropEmpties && !m.post.length && !pre ++ for (var d = 0; dropsEmpties && d < acc.length; d++) { ++ if (acc[d]) { ++ dropsEmpties = false + } +- N.push(c); + } +- } else { +- N = []; + +- for (var j = 0; j < n.length; j++) { +- N.push.apply(N, expand(n[j], false)); ++ values = [] ++ var valuesLength = 0 ++ outer: for (var j = 0; j < n.length; j++) { ++ var expanded = expand(n[j], max, maxLength, false) ++ for (var k = 0; k < expanded.length; k++) { ++ var v = expanded[k] ++ if (dropsEmpties && !v) continue ++ if (values.length >= max || valuesLength + v.length > maxLength) { ++ break outer ++ } ++ values.push(v) ++ valuesLength += v.length ++ } + } + } + +- for (var j = 0; j < N.length; j++) { +- for (var k = 0; k < post.length; k++) { +- var expansion = pre + N[j] + post[k]; +- if (!isTop || isSequence || expansion) +- expansions.push(expansion); +- } +- } ++ acc = combine(acc, pre, values, max, maxLength, dropEmpties && !m.post.length) ++ if (!m.post.length) break ++ str = m.post + } + +- return expansions; ++ return acc + } +- + +diff --git a/deps/npm/node_modules/brace-expansion/package.json b/deps/npm/node_modules/brace-expansion/package.json +--- a/deps/npm/node_modules/brace-expansion/package.json ++++ b/deps/npm/node_modules/brace-expansion/package.json +@@ -1,7 +1,7 @@ + { + "name": "brace-expansion", + "description": "Brace expansion as known from sh/bash", +- "version": "2.0.2", ++ "version": "2.1.4", + "repository": { + "type": "git", + "url": "git://github.com/juliangruber/brace-expansion.git" + diff --git a/0005-CVE-2026-69192-CVE-2026-54272-ip-address-10.4.0.patch b/0005-CVE-2026-69192-CVE-2026-54272-ip-address-10.4.0.patch new file mode 100644 index 0000000..c49ca99 --- /dev/null +++ b/0005-CVE-2026-69192-CVE-2026-54272-ip-address-10.4.0.patch @@ -0,0 +1,1994 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Tomas Juhasz +Date: Mon, 04 Aug 2026 00:00:00 +0000 +Subject: [PATCH] CVE-2026-69192, CVE-2026-54272: upgrade bundled ip-address to + 10.4.0 + +Upgrade ip-address from 10.1.0 to 10.4.0 in npm's vendored node_modules. + +CVE-2026-69192: ip-address used isInSubnet for special-use classifiers +(isLoopback, isPrivate, isLinkLocal, getType), which checked the network +address rather than the host address. An attacker could bypass SSRF +filters by appending a CIDR suffix (e.g. 127.0.0.1/24) that shifted the +network address out of the blocked range. + +CVE-2026-54272: HTML entities in error messages were not escaped, +enabling XSS when parse errors were rendered in a browser. + +The new version introduces isHostInSubnet (host-only check used by all +classifiers), prefixLengthFromMask, assertByteArray, and escapes HTML in +AddressError messages. + +Upstream: https://github.com/beaugunderson/ip-address/releases/tag/v10.4.0 +--- + deps/npm/node_modules/ip-address/dist/common.js | 70 ++ + deps/npm/node_modules/ip-address/dist/ipv4.js | 287 ++++++--- + deps/npm/node_modules/ip-address/dist/ipv6.js | 665 +++++++++++++++----- + .../node_modules/ip-address/dist/v4/constants.js | 6 +- + .../node_modules/ip-address/dist/v6/constants.js | 10 +- + .../npm/node_modules/ip-address/dist/v6/helpers.js | 15 +- + deps/npm/node_modules/ip-address/package.json | 86 +-- + 7 files changed, 850 insertions(+), 289 deletions(-) + +diff --git a/deps/npm/node_modules/ip-address/dist/common.js b/deps/npm/node_modules/ip-address/dist/common.js +--- a/deps/npm/node_modules/ip-address/dist/common.js ++++ b/deps/npm/node_modules/ip-address/dist/common.js +@@ -1,21 +1,47 @@ + "use strict"; + Object.defineProperty(exports, "__esModule", { value: true }); + exports.isInSubnet = isInSubnet; ++exports.isHostInSubnet = isHostInSubnet; + exports.isCorrect = isCorrect; ++exports.prefixLengthFromMask = prefixLengthFromMask; ++exports.assertByteArray = assertByteArray; + exports.numberToPaddedHex = numberToPaddedHex; + exports.stringToPaddedHex = stringToPaddedHex; + exports.testBit = testBit; ++const address_error_1 = require("./address-error"); ++/** ++ * Returns whether this address's *network* is contained within `address`, ++ * i.e. whether every address this one can represent also falls inside ++ * `address`. A network wider than `address` is not contained in it, so ++ * `10.0.0.0/8` is not in `10.0.0.0/16`. ++ * ++ * To ask whether the address itself falls inside a range, ignoring any CIDR ++ * suffix it was written with, use {@link isHostInSubnet} instead. That is the ++ * question the special-use classifiers ask. ++ */ + function isInSubnet(address) { + if (this.subnetMask < address.subnetMask) { + return false; + } +- if (this.mask(address.subnetMask) === address.mask()) { +- return true; +- } +- return false; ++ return isHostInSubnet.call(this, address); ++} ++/** ++ * Returns whether this address's host bits fall inside `address`, ignoring ++ * this address's own subnet mask. ++ * ++ * This is the primitive the special-use classifiers (`isLoopback`, ++ * `isPrivate`, `isLinkLocal`, `getType`, …) are built on: they answer a ++ * question about the address, so the answer must not change with the CIDR ++ * suffix the caller happened to write. Use this rather than ++ * {@link isInSubnet} when classifying a single address — notably when the ++ * address came from untrusted input and the result backs a trust-boundary ++ * decision such as an SSRF allow/deny filter. ++ */ ++function isHostInSubnet(address) { ++ return this.mask(address.subnetMask) === address.mask(); + } + function isCorrect(defaultBits) { +- return function () { ++ return function isCorrectForm() { + if (this.addressMinusSuffix !== this.correctForm()) { + return false; + } +@@ -25,6 +51,40 @@ + return this.parsedSubnet === String(this.subnetMask); + }; + } ++/** ++ * Returns the prefix length (number of leading 1 bits) of a contiguous ++ * subnet mask. Throws `AddressError` if the mask is non-contiguous (e.g. ++ * `255.0.255.0`). ++ */ ++function prefixLengthFromMask(value, totalBits) { ++ const binary = value.toString(2).padStart(totalBits, '0'); ++ if (binary.length > totalBits) { ++ throw new address_error_1.AddressError('Invalid subnet mask.'); ++ } ++ const firstZero = binary.indexOf('0'); ++ if (firstZero === -1) { ++ return totalBits; ++ } ++ if (binary.slice(firstZero).includes('1')) { ++ throw new address_error_1.AddressError('Invalid subnet mask.'); ++ } ++ return firstZero; ++} ++/** ++ * Throws `AddressError` unless `bytes` holds exactly `byteCount` integers, ++ * each from `minimum` to 255. Pass a `minimum` of `-128` where signed bytes ++ * are accepted and folded to unsigned, and `0` where they are not. ++ */ ++function assertByteArray(bytes, byteCount, family, minimum) { ++ if (bytes.length !== byteCount) { ++ throw new address_error_1.AddressError(`${family} addresses require exactly ${byteCount} bytes`); ++ } ++ for (let i = 0; i < bytes.length; i++) { ++ if (!Number.isInteger(bytes[i]) || bytes[i] < minimum || bytes[i] > 255) { ++ throw new address_error_1.AddressError(`All bytes must be integers between ${minimum} and 255`); ++ } ++ } ++} + function numberToPaddedHex(number) { + return number.toString(16).padStart(2, '0'); + } + +diff --git a/deps/npm/node_modules/ip-address/dist/ipv4.js b/deps/npm/node_modules/ip-address/dist/ipv4.js +--- a/deps/npm/node_modules/ip-address/dist/ipv4.js ++++ b/deps/npm/node_modules/ip-address/dist/ipv4.js +@@ -28,13 +28,14 @@ + const common = __importStar(require("./common")); + const constants = __importStar(require("./v4/constants")); + const address_error_1 = require("./address-error"); ++const isCorrect4 = common.isCorrect(constants.BITS); + /** + * Represents an IPv4 address +- * @class Address4 + * @param {string} address - An IPv4 address string + */ + class Address4 { + constructor(address) { ++ this.addressMinusSuffix = ''; + this.groups = constants.GROUPS; + this.parsedAddress = []; + this.parsedSubnet = ''; +@@ -43,18 +44,21 @@ + this.v4 = true; + /** + * Returns true if the address is correct, false otherwise +- * @memberof Address4 +- * @instance + * @returns {Boolean} + */ +- this.isCorrect = common.isCorrect(constants.BITS); ++ this.isCorrect = isCorrect4; + /** + * Returns true if the given address is in the subnet of the current address +- * @memberof Address4 +- * @instance + * @returns {boolean} + */ + this.isInSubnet = common.isInSubnet; ++ /** ++ * Returns true if this address's host bits fall inside the given subnet, ++ * ignoring this address's own subnet mask. See ++ * {@link common.isHostInSubnet}. ++ * @returns {boolean} ++ */ ++ this.isHostInSubnet = common.isHostInSubnet; + this.address = address; + const subnet = constants.RE_SUBNET_STRING.exec(address); + if (subnet) { +@@ -69,66 +73,145 @@ + this.addressMinusSuffix = address; + this.parsedAddress = this.parse(address); + } ++ /** ++ * Returns true if the given string is a valid IPv4 address (with optional ++ * CIDR subnet), false otherwise. Host bits in the subnet portion are ++ * allowed (e.g. `192.168.1.5/24` is valid); for strict network-address ++ * validation compare `correctForm()` to `startAddress().correctForm()`, ++ * or use `networkForm()`. ++ */ + static isValid(address) { + try { + // eslint-disable-next-line no-new + new Address4(address); + return true; + } +- catch (e) { ++ catch { + return false; + } + } +- /* +- * Parses a v4 address ++ /** ++ * Parses an IPv4 address string into its four octet groups and stores the ++ * result on `this.parsedAddress`. Called automatically by the constructor; ++ * you typically don't need to call it directly. Throws `AddressError` if ++ * the input is not a valid IPv4 address. + */ + parse(address) { + const groups = address.split('.'); ++ // Checked before the general match so the error names the actual problem. ++ // Address6 rejects the same notation on its v4-in-v6 path. ++ if (groups.some((group) => /^0\d/.test(group))) { ++ throw new address_error_1.AddressError("IPv4 addresses can't have leading zeroes."); ++ } + if (!address.match(constants.RE_ADDRESS)) { + throw new address_error_1.AddressError('Invalid IPv4 address.'); + } + return groups; + } + /** +- * Returns the correct form of an address +- * @memberof Address4 +- * @instance +- * @returns {String} ++ * Returns the address in correct form: octets joined with `.` and any ++ * leading zeros stripped (e.g. `192.168.1.1`). For IPv4 this matches the ++ * canonical dotted-decimal representation. + */ + correctForm() { + return this.parsedAddress.map((part) => parseInt(part, 10)).join('.'); + } + /** +- * Converts a hex string to an IPv4 address object +- * @memberof Address4 +- * @static ++ * Construct an `Address4` from an address and a dotted-decimal subnet ++ * mask given as separate strings (e.g. as returned by Node's ++ * `os.networkInterfaces()`). Throws `AddressError` if the mask is ++ * non-contiguous (e.g. `255.0.255.0`). ++ * @example ++ * var address = Address4.fromAddressAndMask('192.168.1.1', '255.255.255.0'); ++ * address.subnetMask; // 24 ++ */ ++ static fromAddressAndMask(address, mask) { ++ const bits = common.prefixLengthFromMask(new Address4(mask).bigInt(), constants.BITS); ++ return new Address4(`${address}/${bits}`); ++ } ++ /** ++ * Construct an `Address4` from an address and a Cisco-style wildcard mask ++ * given as separate strings (e.g. `0.0.0.255` for a `/24`). The wildcard ++ * mask is the bitwise inverse of the subnet mask. Throws `AddressError` ++ * if the mask is non-contiguous (e.g. `0.255.0.255`). ++ * @example ++ * var address = Address4.fromAddressAndWildcardMask('10.0.0.1', '0.0.0.255'); ++ * address.subnetMask; // 24 ++ */ ++ static fromAddressAndWildcardMask(address, wildcardMask) { ++ const wildcard = new Address4(wildcardMask).bigInt(); ++ const allOnes = (BigInt(1) << BigInt(constants.BITS)) - BigInt(1); ++ const mask = wildcard ^ allOnes; ++ const bits = common.prefixLengthFromMask(mask, constants.BITS); ++ return new Address4(`${address}/${bits}`); ++ } ++ /** ++ * Construct an `Address4` from a wildcard pattern with trailing `*` ++ * octets. The number of trailing wildcards determines the prefix ++ * length: each `*` represents 8 bits. ++ * ++ * Only trailing whole-octet wildcards are supported. Partial-octet ++ * wildcards (e.g. `192.168.0.1*`) and interior wildcards (e.g. ++ * `192.*.0.1`) throw `AddressError`. ++ * @example ++ * Address4.fromWildcard('192.168.0.*').subnet; // '/24' ++ * Address4.fromWildcard('192.168.*.*').subnet; // '/16' ++ * Address4.fromWildcard('*.*.*.*').subnet; // '/0' ++ */ ++ static fromWildcard(input) { ++ const groups = input.split('.'); ++ if (groups.length !== constants.GROUPS) { ++ throw new address_error_1.AddressError('Wildcard pattern must have 4 octets'); ++ } ++ let firstWildcard = -1; ++ for (let i = 0; i < groups.length; i++) { ++ if (groups[i] === '*') { ++ if (firstWildcard === -1) { ++ firstWildcard = i; ++ } ++ } ++ else if (firstWildcard !== -1) { ++ throw new address_error_1.AddressError('Wildcard `*` must only appear in trailing octets (e.g. `192.168.0.*`)'); ++ } ++ } ++ const trailing = firstWildcard === -1 ? 0 : groups.length - firstWildcard; ++ const replaced = groups.map((g) => (g === '*' ? '0' : g)); ++ const subnetBits = constants.BITS - trailing * 8; ++ return new Address4(`${replaced.join('.')}/${subnetBits}`); ++ } ++ /** ++ * Converts a hex string to an IPv4 address object. Accepts 8 hex digits ++ * with optional `:` separators (e.g. `'7f000001'` or `'7f:00:00:01'`). ++ * Throws `AddressError` for any other length or for non-hex characters. + * @param {string} hex - a hex string to convert + * @returns {Address4} + */ + static fromHex(hex) { +- const padded = hex.replace(/:/g, '').padStart(8, '0'); ++ const stripped = hex.replace(/:/g, ''); ++ if (!/^[0-9a-fA-F]{8}$/.test(stripped)) { ++ throw new address_error_1.AddressError('IPv4 hex must be exactly 8 hex digits'); ++ } + const groups = []; +- let i; +- for (i = 0; i < 8; i += 2) { +- const h = padded.slice(i, i + 2); +- groups.push(parseInt(h, 16)); ++ for (let i = 0; i < 8; i += 2) { ++ groups.push(parseInt(stripped.slice(i, i + 2), 16)); + } + return new Address4(groups.join('.')); + } + /** +- * Converts an integer into a IPv4 address object +- * @memberof Address4 +- * @static ++ * Converts an integer into a IPv4 address object. The integer must be a ++ * non-negative safe integer in the range `[0, 2**32 - 1]`; otherwise ++ * `AddressError` is thrown. + * @param {integer} integer - a number to convert + * @returns {Address4} + */ + static fromInteger(integer) { +- return Address4.fromHex(integer.toString(16)); ++ if (!Number.isInteger(integer) || integer < 0 || integer > 0xffffffff) { ++ throw new address_error_1.AddressError('IPv4 integer must be in the range 0 to 2**32 - 1'); ++ } ++ return Address4.fromHex(integer.toString(16).padStart(8, '0')); + } + /** + * Return an address from in-addr.arpa form +- * @memberof Address4 +- * @static + * @param {string} arpaFormAddress - an 'in-addr.arpa' form ipv4 address + * @returns {Adress4} + * @example +@@ -143,17 +226,15 @@ + } + /** + * Converts an IPv4 address object to a hex string +- * @memberof Address4 +- * @instance + * @returns {String} + */ + toHex() { + return this.parsedAddress.map((part) => common.stringToPaddedHex(part)).join(':'); + } + /** +- * Converts an IPv4 address object to an array of bytes +- * @memberof Address4 +- * @instance ++ * Converts an IPv4 address object to an array of bytes. ++ * ++ * To get a Node.js `Buffer`, wrap the result: `Buffer.from(address.toArray())`. + * @returns {Array} + */ + toArray() { +@@ -161,8 +242,6 @@ + } + /** + * Converts an IPv4 address object to an IPv6 address group +- * @memberof Address4 +- * @instance + * @returns {String} + */ + toGroup6() { +@@ -175,8 +254,6 @@ + } + /** + * Returns the address as a `bigint` +- * @memberof Address4 +- * @instance + * @returns {bigint} + */ + bigInt() { +@@ -184,8 +261,6 @@ + } + /** + * Helper function getting start address. +- * @memberof Address4 +- * @instance + * @returns {bigint} + */ + _startAddress() { +@@ -194,8 +269,6 @@ + /** + * The first address in the range given by this address' subnet. + * Often referred to as the Network Address. +- * @memberof Address4 +- * @instance + * @returns {Address4} + */ + startAddress() { +@@ -204,8 +277,6 @@ + /** + * The first host address in the range given by this address's subnet ie + * the first address after the Network Address +- * @memberof Address4 +- * @instance + * @returns {Address4} + */ + startAddressExclusive() { +@@ -214,8 +285,6 @@ + } + /** + * Helper function getting end address. +- * @memberof Address4 +- * @instance + * @returns {bigint} + */ + _endAddress() { +@@ -224,8 +293,6 @@ + /** + * The last address in the range given by this address' subnet + * Often referred to as the Broadcast +- * @memberof Address4 +- * @instance + * @returns {Address4} + */ + endAddress() { +@@ -234,8 +301,6 @@ + /** + * The last host address in the range given by this address's subnet ie + * the last address prior to the Broadcast Address +- * @memberof Address4 +- * @instance + * @returns {Address4} + */ + endAddressExclusive() { +@@ -243,38 +308,56 @@ + return Address4.fromBigInt(this._endAddress() - adjust); + } + /** +- * Converts a BigInt to a v4 address object +- * @memberof Address4 +- * @static ++ * The dotted-decimal form of the subnet mask, e.g. `255.255.240.0` for ++ * a `/20`. Returns an `Address4`; call `.correctForm()` for the string. ++ * @returns {Address4} ++ */ ++ subnetMaskAddress() { ++ return Address4.fromBigInt(BigInt(`0b${'1'.repeat(this.subnetMask)}${'0'.repeat(constants.BITS - this.subnetMask)}`)); ++ } ++ /** ++ * The Cisco-style wildcard mask, e.g. `0.0.0.255` for a `/24`. This is ++ * the bitwise inverse of `subnetMaskAddress()`. Returns an `Address4`; ++ * call `.correctForm()` for the string. ++ * @returns {Address4} ++ */ ++ wildcardMask() { ++ return Address4.fromBigInt(BigInt(`0b${'0'.repeat(this.subnetMask)}${'1'.repeat(constants.BITS - this.subnetMask)}`)); ++ } ++ /** ++ * The network address in CIDR string form, e.g. `192.168.1.0/24` for ++ * `192.168.1.5/24`. For an address with no explicit subnet the prefix is ++ * `/32`, e.g. `networkForm()` on `192.168.1.5` returns `192.168.1.5/32`. ++ * @returns {string} ++ */ ++ networkForm() { ++ return `${this.startAddress().correctForm()}/${this.subnetMask}`; ++ } ++ /** ++ * Converts a BigInt to a v4 address object. The value must be in the ++ * range `[0, 2**32 - 1]`; otherwise `AddressError` is thrown. + * @param {bigint} bigInt - a BigInt to convert + * @returns {Address4} + */ + static fromBigInt(bigInt) { +- return Address4.fromHex(bigInt.toString(16)); ++ if (bigInt < BigInt(0) || bigInt > BigInt(0xffffffff)) { ++ throw new address_error_1.AddressError('IPv4 BigInt must be in the range 0 to 2**32 - 1'); ++ } ++ return Address4.fromHex(bigInt.toString(16).padStart(8, '0')); + } + /** +- * Convert a byte array to an Address4 object +- * @memberof Address4 +- * @static ++ * Convert a byte array to an Address4 object. ++ * ++ * To convert from a Node.js `Buffer`, spread it: `Address4.fromByteArray([...buf])`. + * @param {Array} bytes - an array of 4 bytes (0-255) + * @returns {Address4} + */ + static fromByteArray(bytes) { +- if (bytes.length !== 4) { +- throw new address_error_1.AddressError('IPv4 addresses require exactly 4 bytes'); +- } +- // Validate that all bytes are within valid range (0-255) +- for (let i = 0; i < bytes.length; i++) { +- if (!Number.isInteger(bytes[i]) || bytes[i] < 0 || bytes[i] > 255) { +- throw new address_error_1.AddressError('All bytes must be integers between 0 and 255'); +- } +- } ++ common.assertByteArray(bytes, 4, 'IPv4', 0); + return this.fromUnsignedByteArray(bytes); + } + /** + * Convert an unsigned byte array to an Address4 object +- * @memberof Address4 +- * @static + * @param {Array} bytes - an array of 4 unsigned bytes (0-255) + * @returns {Address4} + */ +@@ -288,8 +371,6 @@ + /** + * Returns the first n bits of the address, defaulting to the + * subnet mask +- * @memberof Address4 +- * @instance + * @returns {String} + */ + mask(mask) { +@@ -300,8 +381,6 @@ + } + /** + * Returns the bits in the given range as a base-2 string +- * @memberof Address4 +- * @instance + * @returns {string} + */ + getBitsBase2(start, end) { +@@ -309,10 +388,8 @@ + } + /** + * Return the reversed ip6.arpa form of the address +- * @memberof Address4 + * @param {Object} options + * @param {boolean} options.omitSuffix - omit the "in-addr.arpa" suffix +- * @instance + * @returns {String} + */ + reverseForm(options) { +@@ -327,21 +404,62 @@ + } + /** + * Returns true if the given address is a multicast address +- * @memberof Address4 +- * @instance + * @returns {boolean} + */ + isMulticast() { +- return this.isInSubnet(new Address4('224.0.0.0/4')); ++ return this.isHostInSubnet(MULTICAST_V4); ++ } ++ /** ++ * Returns true if the address is in one of the [RFC 1918](https://datatracker.ietf.org/doc/html/rfc1918) private address ranges (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`). ++ * @returns {boolean} ++ */ ++ isPrivate() { ++ return PRIVATE_V4.some((subnet) => this.isHostInSubnet(subnet)); ++ } ++ /** ++ * Returns true if the address is in the loopback range `127.0.0.0/8` ([RFC 1122](https://datatracker.ietf.org/doc/html/rfc1122)). ++ * @returns {boolean} ++ */ ++ isLoopback() { ++ return this.isHostInSubnet(LOOPBACK_V4); ++ } ++ /** ++ * Returns true if the address is in the link-local range `169.254.0.0/16` ([RFC 3927](https://datatracker.ietf.org/doc/html/rfc3927)). ++ * @returns {boolean} ++ */ ++ isLinkLocal() { ++ return this.isHostInSubnet(LINK_LOCAL_V4); ++ } ++ /** ++ * Returns true if the address is the unspecified address `0.0.0.0`. ++ * @returns {boolean} ++ */ ++ isUnspecified() { ++ return this.isHostInSubnet(UNSPECIFIED_V4); ++ } ++ /** ++ * Returns true if the address is the limited broadcast address `255.255.255.255` ([RFC 919](https://datatracker.ietf.org/doc/html/rfc919)). ++ * @returns {boolean} ++ */ ++ isBroadcast() { ++ return this.isHostInSubnet(BROADCAST_V4); ++ } ++ /** ++ * Returns true if the address is in the carrier-grade NAT range `100.64.0.0/10` ([RFC 6598](https://datatracker.ietf.org/doc/html/rfc6598)). ++ * @returns {boolean} ++ */ ++ isCGNAT() { ++ return this.isHostInSubnet(CGNAT_V4); + } + /** + * Returns a zero-padded base-2 string representation of the address +- * @memberof Address4 +- * @instance + * @returns {string} + */ + binaryZeroPad() { +- return this.bigInt().toString(2).padStart(constants.BITS, '0'); ++ if (this._binaryZeroPad === undefined) { ++ this._binaryZeroPad = this.bigInt().toString(2).padStart(constants.BITS, '0'); ++ } ++ return this._binaryZeroPad; + } + /** + * Groups an IPv4 address for inclusion at the end of an IPv6 address +@@ -349,7 +467,7 @@ + */ + groupForV6() { + const segments = this.parsedAddress; +- return this.address.replace(constants.RE_ADDRESS, `${segments ++ return this.correctForm().replace(constants.RE_ADDRESS, `${segments + .slice(0, 2) + .join('.')}.${segments + .slice(2, 4) +@@ -357,4 +475,15 @@ + } + } + exports.Address4 = Address4; ++const MULTICAST_V4 = new Address4('224.0.0.0/4'); ++const PRIVATE_V4 = [ ++ new Address4('10.0.0.0/8'), ++ new Address4('172.16.0.0/12'), ++ new Address4('192.168.0.0/16'), ++]; ++const LOOPBACK_V4 = new Address4('127.0.0.0/8'); ++const LINK_LOCAL_V4 = new Address4('169.254.0.0/16'); ++const UNSPECIFIED_V4 = new Address4('0.0.0.0/32'); ++const BROADCAST_V4 = new Address4('255.255.255.255/32'); ++const CGNAT_V4 = new Address4('100.64.0.0/10'); + //# sourceMappingURL=ipv4.js.map +\ No newline at end of file + +diff --git a/deps/npm/node_modules/ip-address/dist/ipv6.js b/deps/npm/node_modules/ip-address/dist/ipv6.js +--- a/deps/npm/node_modules/ip-address/dist/ipv6.js ++++ b/deps/npm/node_modules/ip-address/dist/ipv6.js +@@ -34,6 +34,7 @@ + const regular_expressions_1 = require("./v6/regular-expressions"); + const address_error_1 = require("./address-error"); + const common_1 = require("./common"); ++const isCorrect6 = common.isCorrect(constants6.BITS); + function assert(condition) { + if (!condition) { + throw new Error('Assertion failed.'); +@@ -72,12 +73,10 @@ + return parseInt(octet, 16).toString(16).padStart(4, '0'); + } + function unsignByte(b) { +- // eslint-disable-next-line no-bitwise + return b & 0xff; + } + /** + * Represents an IPv6 address +- * @class Address6 + * @param {string} address - An IPv6 address string + * @param {number} [groups=8] - How many octets to parse + * @example +@@ -94,18 +93,21 @@ + // #region Attributes + /** + * Returns true if the given address is in the subnet of the current address +- * @memberof Address6 +- * @instance + * @returns {boolean} + */ + this.isInSubnet = common.isInSubnet; + /** ++ * Returns true if this address's host bits fall inside the given subnet, ++ * ignoring this address's own subnet mask. See ++ * {@link common.isHostInSubnet}. ++ * @returns {boolean} ++ */ ++ this.isHostInSubnet = common.isHostInSubnet; ++ /** + * Returns true if the address is correct, false otherwise +- * @memberof Address6 +- * @instance + * @returns {boolean} + */ +- this.isCorrect = common.isCorrect(constants6.BITS); ++ this.isCorrect = isCorrect6; + if (optionalGroups === undefined) { + this.groups = constants6.GROUPS; + } +@@ -125,7 +127,10 @@ + } + address = address.replace(constants6.RE_SUBNET_STRING, ''); + } +- else if (/\//.test(address)) { ++ // RE_SUBNET_STRING anchors on the end of the address, so it strips only ++ // the trailing suffix. A second one left behind (`::/0/1`) is malformed ++ // and must be rejected rather than parsed as an address group. ++ if (/\//.test(address)) { + throw new address_error_1.AddressError('Invalid subnet mask.'); + } + const zone = constants6.RE_ZONE_STRING.exec(address); +@@ -136,20 +141,26 @@ + this.addressMinusSuffix = address; + this.parsedAddress = this.parse(this.addressMinusSuffix); + } ++ /** ++ * Returns true if the given string is a valid IPv6 address (with optional ++ * CIDR subnet and zone identifier), false otherwise. Host bits in the ++ * subnet portion are allowed (e.g. `2001:db8::1/32` is valid); for strict ++ * network-address validation compare `correctForm()` to ++ * `startAddress().correctForm()`, or use `networkForm()`. ++ */ + static isValid(address) { + try { + // eslint-disable-next-line no-new + new Address6(address); + return true; + } +- catch (e) { ++ catch { + return false; + } + } + /** +- * Convert a BigInt to a v6 address object +- * @memberof Address6 +- * @static ++ * Convert a BigInt to a v6 address object. The value must be in the ++ * range `[0, 2**128 - 1]`; otherwise `AddressError` is thrown. + * @param {bigint} bigInt - a BigInt to convert + * @returns {Address6} + * @example +@@ -158,31 +169,36 @@ + * address.correctForm(); // '::e8:d4a5:1000' + */ + static fromBigInt(bigInt) { ++ if (bigInt < BigInt(0) || bigInt > (BigInt(1) << BigInt(constants6.BITS)) - BigInt(1)) { ++ throw new address_error_1.AddressError('IPv6 BigInt must be in the range 0 to 2**128 - 1'); ++ } + const hex = bigInt.toString(16).padStart(32, '0'); + const groups = []; +- let i; +- for (i = 0; i < constants6.GROUPS; i++) { ++ for (let i = 0; i < constants6.GROUPS; i++) { + groups.push(hex.slice(i * 4, (i + 1) * 4)); + } + return new Address6(groups.join(':')); + } + /** +- * Convert a URL (with optional port number) to an address object +- * @memberof Address6 +- * @static +- * @param {string} url - a URL with optional port number ++ * Parse a URL (with optional bracketed host and port) into an address and ++ * port. Returns either `{ address, port }` on success or ++ * `{ error, address: null, port: null }` if the URL could not be parsed. ++ * Ports are returned as numbers (or `null` if absent or out of range). + * @example + * var addressAndPort = Address6.fromURL('http://[ffff::]:8080/foo/'); + * addressAndPort.address.correctForm(); // 'ffff::' + * addressAndPort.port; // 8080 + */ + static fromURL(url) { ++ var _a; + let host; + let port = null; + let result; ++ // Remove the protocol prefix, if any ++ const stripped = url.replace(/^[a-z][a-z0-9+.-]*:\/\//i, ''); + // If we have brackets parse them and find a port +- if (url.indexOf('[') !== -1 && url.indexOf(']:') !== -1) { +- result = constants6.RE_URL_WITH_PORT.exec(url); ++ if (stripped.indexOf('[') !== -1 && stripped.indexOf(']:') !== -1) { ++ result = constants6.RE_URL_WITH_PORT.exec(stripped); + if (result === null) { + return { + error: 'failed to parse address with port', +@@ -192,13 +208,9 @@ + } + host = result[1]; + port = result[2]; +- // If there's a URL extract the address + } +- else if (url.indexOf('/') !== -1) { +- // Remove the protocol prefix +- url = url.replace(/^[a-z0-9]+:\/\//, ''); +- // Parse the address +- result = constants6.RE_URL.exec(url); ++ else { ++ result = constants6.RE_URL.exec(stripped); + if (result === null) { + return { + error: 'failed to parse address from URL', +@@ -206,17 +218,13 @@ + port: null, + }; + } +- host = result[1]; +- // Otherwise just assign the URL to the host and let the library parse it +- } +- else { +- host = url; ++ host = (_a = result[1]) !== null && _a !== void 0 ? _a : result[2]; + } + // If there's a port convert it to an integer + if (port) { + port = parseInt(port, 10); +- // squelch out of range ports +- if (port < 0 || port > 65536) { ++ // squelch out of range ports (valid ports are 0-65535) ++ if (port < 0 || port > 65535) { + port = null; + } + } +@@ -230,9 +238,90 @@ + }; + } + /** ++ * Construct an `Address6` from an address and a hex subnet mask given as ++ * separate strings (e.g. as returned by Node's `os.networkInterfaces()`). ++ * Throws `AddressError` if the mask is non-contiguous (e.g. ++ * `ffff::ffff`). ++ * @example ++ * var address = Address6.fromAddressAndMask('fe80::1', 'ffff:ffff:ffff:ffff::'); ++ * address.subnetMask; // 64 ++ */ ++ static fromAddressAndMask(address, mask) { ++ const bits = common.prefixLengthFromMask(new Address6(mask).bigInt(), constants6.BITS); ++ return new Address6(`${address}/${bits}`); ++ } ++ /** ++ * Construct an `Address6` from an address and a Cisco-style wildcard mask ++ * given as separate strings (e.g. `::ffff:ffff:ffff:ffff` for a `/64`). ++ * The wildcard mask is the bitwise inverse of the subnet mask. Throws ++ * `AddressError` if the mask is non-contiguous. ++ * @example ++ * var address = Address6.fromAddressAndWildcardMask('fe80::1', '::ffff:ffff:ffff:ffff'); ++ * address.subnetMask; // 64 ++ */ ++ static fromAddressAndWildcardMask(address, wildcardMask) { ++ const wildcard = new Address6(wildcardMask).bigInt(); ++ const allOnes = (BigInt(1) << BigInt(constants6.BITS)) - BigInt(1); ++ const mask = wildcard ^ allOnes; ++ const bits = common.prefixLengthFromMask(mask, constants6.BITS); ++ return new Address6(`${address}/${bits}`); ++ } ++ /** ++ * Construct an `Address6` from a wildcard pattern with trailing `*` ++ * groups. The number of trailing wildcards determines the prefix ++ * length: each `*` represents 16 bits. `::` is expanded to zero groups ++ * (not wildcards) before evaluating trailing wildcards. ++ * ++ * Only trailing whole-group wildcards are supported. Partial-group ++ * wildcards (e.g. `2001:db8::0*`) and interior wildcards (e.g. ++ * `*::1`) throw `AddressError`. ++ * @example ++ * Address6.fromWildcard('2001:db8:*:*:*:*:*:*').subnet; // '/32' ++ * Address6.fromWildcard('2001:db8::*').subnet; // '/112' ++ * Address6.fromWildcard('*:*:*:*:*:*:*:*').subnet; // '/0' ++ */ ++ static fromWildcard(input) { ++ if (input.includes('%') || input.includes('/')) { ++ throw new address_error_1.AddressError('Wildcard pattern must not include a zone or CIDR suffix'); ++ } ++ const halves = input.split('::'); ++ if (halves.length > 2) { ++ throw new address_error_1.AddressError("Wildcard pattern cannot contain more than one '::'"); ++ } ++ let groups; ++ if (halves.length === 2) { ++ const left = halves[0] === '' ? [] : halves[0].split(':'); ++ const right = halves[1] === '' ? [] : halves[1].split(':'); ++ const remaining = constants6.GROUPS - left.length - right.length; ++ if (remaining < 1) { ++ throw new address_error_1.AddressError("Wildcard pattern with '::' has too many groups"); ++ } ++ groups = [...left, ...new Array(remaining).fill('0'), ...right]; ++ } ++ else { ++ groups = input.split(':'); ++ } ++ if (groups.length !== constants6.GROUPS) { ++ throw new address_error_1.AddressError('Wildcard pattern must have 8 groups'); ++ } ++ let firstWildcard = -1; ++ for (let i = 0; i < groups.length; i++) { ++ if (groups[i] === '*') { ++ if (firstWildcard === -1) { ++ firstWildcard = i; ++ } ++ } ++ else if (firstWildcard !== -1) { ++ throw new address_error_1.AddressError('Wildcard `*` must only appear in trailing groups (e.g. `2001:db8:*:*:*:*:*:*`)'); ++ } ++ } ++ const trailing = firstWildcard === -1 ? 0 : groups.length - firstWildcard; ++ const replaced = groups.map((g) => (g === '*' ? '0' : g)); ++ const subnetBits = constants6.BITS - trailing * 16; ++ return new Address6(`${replaced.join(':')}/${subnetBits}`); ++ } ++ /** + * Create an IPv6-mapped address given an IPv4 address +- * @memberof Address6 +- * @static + * @param {string} address - An IPv4 address string + * @returns {Address6} + * @example +@@ -247,8 +336,6 @@ + } + /** + * Return an address from ip6.arpa form +- * @memberof Address6 +- * @static + * @param {string} arpaFormAddress - an 'ip6.arpa' form address + * @returns {Adress6} + * @example +@@ -273,8 +360,6 @@ + } + /** + * Return the Microsoft UNC transcription of the address +- * @memberof Address6 +- * @instance + * @returns {String} the Microsoft UNC transcription of the address + */ + microsoftTranscription() { +@@ -282,8 +367,6 @@ + } + /** + * Return the first n bits of the address, defaulting to the subnet mask +- * @memberof Address6 +- * @instance + * @param {number} [mask=subnet] - the number of bits to mask + * @returns {String} the first n bits of the address as a string + */ +@@ -292,8 +375,6 @@ + } + /** + * Return the number of possible subnets of a given size in the address +- * @memberof Address6 +- * @instance + * @param {number} [subnetSize=128] - the subnet size + * @returns {String} + */ +@@ -309,8 +390,6 @@ + } + /** + * Helper function getting start address. +- * @memberof Address6 +- * @instance + * @returns {bigint} + */ + _startAddress() { +@@ -319,8 +398,6 @@ + /** + * The first address in the range given by this address' subnet + * Often referred to as the Network Address. +- * @memberof Address6 +- * @instance + * @returns {Address6} + */ + startAddress() { +@@ -329,8 +406,6 @@ + /** + * The first host address in the range given by this address's subnet ie + * the first address after the Network Address +- * @memberof Address6 +- * @instance + * @returns {Address6} + */ + startAddressExclusive() { +@@ -339,8 +414,6 @@ + } + /** + * Helper function getting end address. +- * @memberof Address6 +- * @instance + * @returns {bigint} + */ + _endAddress() { +@@ -349,8 +422,6 @@ + /** + * The last address in the range given by this address' subnet + * Often referred to as the Broadcast +- * @memberof Address6 +- * @instance + * @returns {Address6} + */ + endAddress() { +@@ -359,8 +430,6 @@ + /** + * The last host address in the range given by this address's subnet ie + * the last address prior to the Broadcast Address +- * @memberof Address6 +- * @instance + * @returns {Address6} + */ + endAddressExclusive() { +@@ -368,36 +437,73 @@ + return Address6.fromBigInt(this._endAddress() - adjust); + } + /** +- * Return the scope of the address +- * @memberof Address6 +- * @instance ++ * The hex form of the subnet mask, e.g. `ffff:ffff:ffff:ffff::` for a ++ * `/64`. Returns an `Address6`; call `.correctForm()` for the string. ++ * @returns {Address6} ++ */ ++ subnetMaskAddress() { ++ return Address6.fromBigInt(BigInt(`0b${'1'.repeat(this.subnetMask)}${'0'.repeat(constants6.BITS - this.subnetMask)}`)); ++ } ++ /** ++ * The Cisco-style wildcard mask, e.g. `::ffff:ffff:ffff:ffff` for a ++ * `/64`. This is the bitwise inverse of `subnetMaskAddress()`. Returns ++ * an `Address6`; call `.correctForm()` for the string. ++ * @returns {Address6} ++ */ ++ wildcardMask() { ++ return Address6.fromBigInt(BigInt(`0b${'0'.repeat(this.subnetMask)}${'1'.repeat(constants6.BITS - this.subnetMask)}`)); ++ } ++ /** ++ * The network address in CIDR string form, e.g. `2001:db8::/32` for ++ * `2001:db8::1/32`. For an address with no explicit subnet the prefix ++ * is `/128`, e.g. `networkForm()` on `2001:db8::1` returns ++ * `2001:db8::1/128`. ++ * @returns {string} ++ */ ++ networkForm() { ++ return `${this.startAddress().correctForm()}/${this.subnetMask}`; ++ } ++ /** ++ * Return the scope of the address. The 4-bit scope field ++ * ([RFC 4291 §2.7](https://datatracker.ietf.org/doc/html/rfc4291#section-2.7)) ++ * is only defined for multicast addresses; for unicast addresses the scope ++ * is derived from the address type per ++ * [RFC 4007 §6](https://datatracker.ietf.org/doc/html/rfc4007#section-6). + * @returns {String} + */ + getScope() { +- let scope = constants6.SCOPES[parseInt(this.getBits(12, 16).toString(10), 10)]; +- if (this.getType() === 'Global unicast' && scope !== 'Link local') { +- scope = 'Global'; ++ const type = this.getType(); ++ if (type === 'Multicast' || type.startsWith('Multicast ')) { ++ const scope = constants6.SCOPES[parseInt(this.getBits(12, 16).toString(10), 10)]; ++ return scope || 'Unknown'; ++ } ++ // RFC 4291 §2.5.3: the loopback address is treated as having Link-Local ++ // scope. (Multicast scope 1, "Interface-Local", is a different concept ++ // used only for loopback transmission of multicast.) ++ if (type === 'Link-local unicast' || type === 'Loopback') { ++ return 'Link local'; ++ } ++ // RFC 4007 §6: the unspecified address has no scope. ++ if (type === 'Unspecified') { ++ return 'Unknown'; + } +- return scope || 'Unknown'; ++ return 'Global'; + } + /** + * Return the type of the address +- * @memberof Address6 +- * @instance + * @returns {String} + */ + getType() { +- for (const subnet of Object.keys(constants6.TYPES)) { +- if (this.isInSubnet(new Address6(subnet))) { +- return constants6.TYPES[subnet]; ++ for (let i = 0; i < TYPE_SUBNETS.length; i++) { ++ const entry = TYPE_SUBNETS[i]; ++ if (this.isHostInSubnet(entry[0])) { ++ return entry[1]; + } + } + return 'Global unicast'; + } + /** + * Return the bits in the given range as a BigInt +- * @memberof Address6 +- * @instance + * @returns {bigint} + */ + getBits(start, end) { +@@ -405,8 +511,6 @@ + } + /** + * Return the bits in the given range as a base-2 string +- * @memberof Address6 +- * @instance + * @returns {String} + */ + getBitsBase2(start, end) { +@@ -414,8 +518,6 @@ + } + /** + * Return the bits in the given range as a base-16 string +- * @memberof Address6 +- * @instance + * @returns {String} + */ + getBitsBase16(start, end) { +@@ -429,8 +531,6 @@ + } + /** + * Return the bits that are set past the subnet mask length +- * @memberof Address6 +- * @instance + * @returns {String} + */ + getBitsPastSubnet() { +@@ -438,10 +538,8 @@ + } + /** + * Return the reversed ip6.arpa form of the address +- * @memberof Address6 + * @param {Object} options + * @param {boolean} options.omitSuffix - omit the "ip6.arpa" suffix +- * @instance + * @returns {String} + */ + reverseForm(options) { +@@ -467,10 +565,10 @@ + return 'ip6.arpa.'; + } + /** +- * Return the correct form of the address +- * @memberof Address6 +- * @instance +- * @returns {String} ++ * Returns the address in correct form, per ++ * [RFC 5952](https://datatracker.ietf.org/doc/html/rfc5952): leading zeros ++ * stripped, the longest run of zero groups collapsed to `::`, and hex digits ++ * lowercased (e.g. `2001:db8::1`). This is the recommended form for display. + */ + correctForm() { + let i; +@@ -514,8 +612,6 @@ + } + /** + * Return a zero-padded base-2 string representation of the address +- * @memberof Address6 +- * @instance + * @returns {String} + * @example + * var address = new Address6('2001:4860:4001:803::1011'); +@@ -524,27 +620,58 @@ + * // 0000000000000000000000000000000000000000000000000001000000010001' + */ + binaryZeroPad() { +- return this.bigInt().toString(2).padStart(constants6.BITS, '0'); ++ if (this._binaryZeroPad === undefined) { ++ this._binaryZeroPad = this.bigInt().toString(2).padStart(constants6.BITS, '0'); ++ } ++ return this._binaryZeroPad; + } ++ /** ++ * Parses a v4-in-v6 string (e.g. `::ffff:192.168.0.1`) by extracting the ++ * trailing IPv4 address into `this.address4` / `this.parsedAddress4` and ++ * returning the address with the v4 portion converted to two v6 groups. ++ * Used internally by `parse()`. ++ */ + // TODO: Improve the semantics of this helper function + parse4in6(address) { ++ if (address.indexOf('.') === -1) { ++ return address; ++ } + const groups = address.split(':'); + const lastGroup = groups.slice(-1)[0]; ++ // RE_ADDRESS rejects octets with a leading zero, so a dotted-quad tail is ++ // matched permissively first: that way this notation still gets its own ++ // message with the offending octet highlighted, rather than falling ++ // through as an unrecognized group. ++ const v4Octets = lastGroup.split('.'); ++ if (v4Octets.length === constants4.GROUPS && ++ v4Octets.every((octet) => /^\d{1,3}$/.test(octet))) { ++ if (v4Octets.some((octet) => /^0\d/.test(octet))) { ++ // The prefix groups haven't been through the bad-character check ++ // yet, so escape them before including in the error HTML. ++ const highlighted = v4Octets.map(spanLeadingZeroes4).join('.'); ++ const prefix = groups.slice(0, -1).map(helpers.escapeHtml).join(':'); ++ const separator = groups.length > 1 ? ':' : ''; ++ throw new address_error_1.AddressError("IPv4 addresses can't have leading zeroes.", `${prefix}${separator}${highlighted}`); ++ } ++ } + const address4 = lastGroup.match(constants4.RE_ADDRESS); + if (address4) { + this.parsedAddress4 = address4[0]; +- this.address4 = new ipv4_1.Address4(this.parsedAddress4); +- for (let i = 0; i < this.address4.groups; i++) { +- if (/^0[0-9]+/.test(this.address4.parsedAddress[i])) { +- throw new address_error_1.AddressError("IPv4 addresses can't have leading zeroes.", address.replace(constants4.RE_ADDRESS, this.address4.parsedAddress.map(spanLeadingZeroes4).join('.'))); +- } +- } ++ const v4Suffix = this.subnetMask >= 96 ? `/${this.subnetMask - 96}` : ''; ++ this.address4 = new ipv4_1.Address4(`${this.parsedAddress4}${v4Suffix}`); + this.v4 = true; + groups[groups.length - 1] = this.address4.toGroup6(); + address = groups.join(':'); + } + return address; + } ++ /** ++ * Parses an IPv6 address string into its 8 hexadecimal groups (expanding ++ * any `::` elision and any trailing v4-in-v6 portion) and stores the result ++ * on `this.parsedAddress`. Called automatically by the constructor; you ++ * typically don't need to call it directly. Throws `AddressError` if the ++ * input is malformed. ++ */ + // TODO: Make private? + parse(address) { + address = this.parse4in6(address); +@@ -594,18 +721,16 @@ + return groups; + } + /** +- * Return the canonical form of the address +- * @memberof Address6 +- * @instance +- * @returns {String} ++ * Returns the canonical (fully expanded) form of the address: all 8 groups, ++ * each padded to 4 hex digits, with no `::` collapsing ++ * (e.g. `2001:0db8:0000:0000:0000:0000:0000:0001`). Useful for sorting and ++ * byte-exact comparison. + */ + canonicalForm() { + return this.parsedAddress.map(paddedHex).join(':'); + } + /** + * Return the decimal form of the address +- * @memberof Address6 +- * @instance + * @returns {String} + */ + decimal() { +@@ -613,17 +738,17 @@ + } + /** + * Return the address as a BigInt +- * @memberof Address6 +- * @instance + * @returns {bigint} + */ + bigInt() { + return BigInt(`0x${this.parsedAddress.map(paddedHex).join('')}`); + } + /** +- * Return the last two groups of this address as an IPv4 address string +- * @memberof Address6 +- * @instance ++ * Return the last two groups of this address as an IPv4 address string. ++ * If this address carries a CIDR prefix that covers the trailing 32 bits ++ * (i.e. `subnetMask >= 96`), the resulting `Address4` inherits the ++ * corresponding v4 prefix (`subnetMask - 96`); otherwise it defaults to ++ * `/32`. + * @returns {Address4} + * @example + * var address = new Address6('2001:4860:4001::1825:bf11'); +@@ -631,12 +756,21 @@ + */ + to4() { + const binary = this.binaryZeroPad().split(''); +- return ipv4_1.Address4.fromHex(BigInt(`0b${binary.slice(96, 128).join('')}`).toString(16)); ++ const hex = BigInt(`0b${binary.slice(96, 128).join('')}`) ++ .toString(16) ++ .padStart(8, '0'); ++ if (this.subnetMask >= 96) { ++ const v4Mask = this.subnetMask - 96; ++ const groups = []; ++ for (let i = 0; i < 8; i += 2) { ++ groups.push(parseInt(hex.slice(i, i + 2), 16)); ++ } ++ return new ipv4_1.Address4(`${groups.join('.')}/${v4Mask}`); ++ } ++ return ipv4_1.Address4.fromHex(hex); + } + /** + * Return the v4-in-v6 form of the address +- * @memberof Address6 +- * @instance + * @returns {String} + */ + to4in6() { +@@ -647,13 +781,13 @@ + if (!/:$/.test(correct)) { + infix = ':'; + } +- return correct + infix + address4.address; ++ return correct + infix + address4.correctForm(); + } + /** +- * Return an object containing the Teredo properties of the address +- * @memberof Address6 +- * @instance +- * @returns {Object} ++ * Decodes the Teredo tunneling fields embedded in this address. Returns the ++ * Teredo prefix, server IPv4, client IPv4, raw flag bits, cone-NAT flag, ++ * UDP port, and Microsoft-format flag breakdown (reserved, universal/local, ++ * group/individual, nonce). Only meaningful for addresses in `2001::/32`. + */ + inspectTeredo() { + /* +@@ -679,12 +813,10 @@ + */ + const prefix = this.getBitsBase16(0, 32); + const bitsForUdpPort = this.getBits(80, 96); +- // eslint-disable-next-line no-bitwise + const udpPort = (bitsForUdpPort ^ BigInt('0xffff')).toString(); + const server4 = ipv4_1.Address4.fromHex(this.getBitsBase16(32, 64)); + const bitsForClient4 = this.getBits(96, 128); +- // eslint-disable-next-line no-bitwise +- const client4 = ipv4_1.Address4.fromHex((bitsForClient4 ^ BigInt('0xffffffff')).toString(16)); ++ const client4 = ipv4_1.Address4.fromHex((bitsForClient4 ^ BigInt('0xffffffff')).toString(16).padStart(8, '0')); + const flagsBase2 = this.getBitsBase2(64, 80); + const coneNat = (0, common_1.testBit)(flagsBase2, 15); + const reserved = (0, common_1.testBit)(flagsBase2, 14); +@@ -707,10 +839,9 @@ + }; + } + /** +- * Return an object containing the 6to4 properties of the address +- * @memberof Address6 +- * @instance +- * @returns {Object} ++ * Decodes the 6to4 tunneling fields embedded in this address. Returns the ++ * 6to4 prefix and the embedded IPv4 gateway address. Only meaningful for ++ * addresses in `2002::/16`. + */ + inspect6to4() { + /* +@@ -726,8 +857,6 @@ + } + /** + * Return a v6 6to4 address from a v6 v4inv6 address +- * @memberof Address6 +- * @instance + * @returns {Address6} + */ + to6to4() { +@@ -744,15 +873,88 @@ + return new Address6(addr6to4); + } + /** +- * Return a byte array +- * @memberof Address6 +- * @instance ++ * Embed an IPv4 address into a NAT64 IPv6 address using the encoding ++ * defined by [RFC 6052](https://datatracker.ietf.org/doc/html/rfc6052). ++ * The default prefix is the well-known prefix `64:ff9b::/96`. The prefix ++ * length must be one of 32, 40, 48, 56, 64, or 96; for prefixes shorter ++ * than /64 the IPv4 octets are split around the reserved bits 64–71. ++ * @example ++ * Address6.fromAddress4Nat64('192.0.2.33').correctForm(); // '64:ff9b::c000:221' ++ * Address6.fromAddress4Nat64('192.0.2.33', '2001:db8::/32').correctForm(); // '2001:db8:c000:221::' ++ */ ++ static fromAddress4Nat64(address, prefix = '64:ff9b::/96') { ++ const v4 = new ipv4_1.Address4(address); ++ const prefix6 = new Address6(prefix); ++ const pl = prefix6.subnetMask; ++ if (pl !== 32 && pl !== 40 && pl !== 48 && pl !== 56 && pl !== 64 && pl !== 96) { ++ throw new address_error_1.AddressError('NAT64 prefix length must be 32, 40, 48, 56, 64, or 96'); ++ } ++ const prefixBits = prefix6.binaryZeroPad(); ++ const v4Bits = v4.binaryZeroPad(); ++ let bits; ++ if (pl === 96) { ++ bits = prefixBits.slice(0, 96) + v4Bits; ++ } ++ else { ++ const beforeU = 64 - pl; ++ bits = [ ++ prefixBits.slice(0, pl), ++ v4Bits.slice(0, beforeU), ++ // Bits 64 to 71 are the reserved u octet and are always zero. ++ '00000000', ++ v4Bits.slice(beforeU), ++ '0'.repeat(128 - 72 - (32 - beforeU)), ++ ].join(''); ++ } ++ const hex = BigInt(`0b${bits}`).toString(16).padStart(32, '0'); ++ const groups = []; ++ for (let i = 0; i < 8; i++) { ++ groups.push(hex.slice(i * 4, (i + 1) * 4)); ++ } ++ return new Address6(groups.join(':')); ++ } ++ /** ++ * Extract the embedded IPv4 address from a NAT64 IPv6 address using the ++ * encoding defined by [RFC 6052](https://datatracker.ietf.org/doc/html/rfc6052). ++ * The default prefix is the well-known prefix `64:ff9b::/96`. Returns ++ * `null` if this address is not contained within the given prefix. ++ * @example ++ * new Address6('64:ff9b::c000:221').toAddress4Nat64()!.correctForm(); // '192.0.2.33' ++ */ ++ toAddress4Nat64(prefix = '64:ff9b::/96') { ++ const prefix6 = new Address6(prefix); ++ const pl = prefix6.subnetMask; ++ if (pl !== 32 && pl !== 40 && pl !== 48 && pl !== 56 && pl !== 64 && pl !== 96) { ++ throw new address_error_1.AddressError('NAT64 prefix length must be 32, 40, 48, 56, 64, or 96'); ++ } ++ if (!this.isHostInSubnet(prefix6)) { ++ return null; ++ } ++ const bits = this.binaryZeroPad(); ++ let v4Bits; ++ if (pl === 96) { ++ v4Bits = bits.slice(96, 128); ++ } ++ else { ++ const beforeU = 64 - pl; ++ v4Bits = bits.slice(pl, pl + beforeU) + bits.slice(72, 72 + (32 - beforeU)); ++ } ++ const octets = []; ++ for (let i = 0; i < 4; i++) { ++ octets.push(parseInt(v4Bits.slice(i * 8, (i + 1) * 8), 2).toString()); ++ } ++ return new ipv4_1.Address4(octets.join('.')); ++ } ++ /** ++ * Return a byte array. ++ * ++ * To get a Node.js `Buffer`, wrap the result: `Buffer.from(address.toByteArray())`. + * @returns {Array} + */ + toByteArray() { +- const valueWithoutPadding = this.bigInt().toString(16); +- const leadingPad = '0'.repeat(valueWithoutPadding.length % 2); +- const value = `${leadingPad}${valueWithoutPadding}`; ++ const value = this.bigInt() ++ .toString(16) ++ .padStart(constants6.BITS / 4, '0'); + const bytes = []; + for (let i = 0, length = value.length; i < length; i += 2) { + bytes.push(parseInt(value.substring(i, i + 2), 16)); +@@ -760,30 +962,45 @@ + return bytes; + } + /** +- * Return an unsigned byte array +- * @memberof Address6 +- * @instance ++ * Return an unsigned byte array. ++ * ++ * To get a Node.js `Buffer`, wrap the result: `Buffer.from(address.toUnsignedByteArray())`. + * @returns {Array} + */ + toUnsignedByteArray() { ++ // toByteArray() emits 0 to 255, so unsigning it is an identity mapping and ++ // the two methods return equal arrays. 11.0.0 keeps one of them and makes ++ // this a deprecated alias; test/common-test.ts fails at that version. + return this.toByteArray().map(unsignByte); + } + /** +- * Convert a byte array to an Address6 object +- * @memberof Address6 +- * @static ++ * Convert a byte array to an Address6 object. ++ * ++ * Accepts unsigned bytes (0 to 255) or signed bytes (-128 to 127, as an ++ * `Int8Array` or a Java `byte[]` holds them), folding signed values to their ++ * unsigned equivalent. Throws `AddressError` unless given exactly 16 ++ * integers from -128 to 255. ++ * ++ * To convert from a Node.js `Buffer`, spread it: `Address6.fromByteArray([...buf])`. + * @returns {Address6} + */ + static fromByteArray(bytes) { ++ // Address4.fromByteArray takes unsigned bytes only. 11.0.0 aligns this ++ // method with it, at which point the -128 floor here, unsignByte, and the ++ // mapping below all go; test/common-test.ts fails at that version. ++ common.assertByteArray(bytes, 16, 'IPv6', -128); + return this.fromUnsignedByteArray(bytes.map(unsignByte)); + } + /** +- * Convert an unsigned byte array to an Address6 object +- * @memberof Address6 +- * @static ++ * Convert an unsigned byte array to an Address6 object. ++ * ++ * Throws `AddressError` unless given exactly 16 integers from 0 to 255. ++ * ++ * To convert from a Node.js `Buffer`, spread it: `Address6.fromUnsignedByteArray([...buf])`. + * @returns {Address6} + */ + static fromUnsignedByteArray(bytes) { ++ common.assertByteArray(bytes, 16, 'IPv6', 0); + const BYTE_MAX = BigInt('256'); + let result = BigInt('0'); + let multiplier = BigInt('1'); +@@ -795,8 +1012,6 @@ + } + /** + * Returns true if the address is in the canonical form, false otherwise +- * @memberof Address6 +- * @instance + * @returns {boolean} + */ + isCanonical() { +@@ -804,12 +1019,14 @@ + } + /** + * Returns true if the address is a link local address, false otherwise +- * @memberof Address6 +- * @instance + * @returns {boolean} + */ + isLinkLocal() { +- // Zeroes are required, i.e. we can't check isInSubnet with 'fe80::/10' ++ const embedded = this.embeddedIPv4(); ++ if (embedded) { ++ return embedded.isLinkLocal(); ++ } ++ // Zeroes are required, i.e. we can't check isHostInSubnet with 'fe80::/10' + if (this.getBitsBase2(0, 64) === + '1111111010000000000000000000000000000000000000000000000000000000') { + return true; +@@ -818,53 +1035,163 @@ + } + /** + * Returns true if the address is a multicast address, false otherwise +- * @memberof Address6 +- * @instance + * @returns {boolean} + */ + isMulticast() { +- return this.getType() === 'Multicast'; ++ const embedded = this.embeddedIPv4(); ++ if (embedded) { ++ return embedded.isMulticast(); ++ } ++ const type = this.getType(); ++ return type === 'Multicast' || type.startsWith('Multicast '); + } + /** +- * Returns true if the address is a v4-in-v6 address, false otherwise +- * @memberof Address6 +- * @instance ++ * Returns true if the address was written in v4-in-v6 dotted-quad notation ++ * (e.g. `::ffff:127.0.0.1`), false otherwise. This is a notation-level flag ++ * and does not reflect whether the address bits lie in the IPv4-mapped ++ * (`::ffff:0:0/96`) subnet — for that, see {@link isMapped4}. + * @returns {boolean} + */ + is4() { + return this.v4; + } + /** ++ * Returns true if the address is an IPv4-mapped IPv6 address in ++ * `::ffff:0:0/96` ([RFC 4291 §2.5.5.2](https://datatracker.ietf.org/doc/html/rfc4291#section-2.5.5.2)), ++ * false otherwise. Unlike {@link is4}, this checks the underlying address ++ * bits rather than the textual notation, so `::ffff:127.0.0.1` and ++ * `::ffff:7f00:1` both return true. ++ * @returns {boolean} ++ */ ++ isMapped4() { ++ return this.isHostInSubnet(IPV4_MAPPED_SUBNET); ++ } ++ /** ++ * If this address embeds a routable IPv4 address — i.e. it is IPv4-mapped ++ * (`::ffff:0:0/96`) or sits in the NAT64 well-known prefix (`64:ff9b::/96`, ++ * [RFC 6052](https://datatracker.ietf.org/doc/html/rfc6052)) — return that ++ * embedded address as an {@link Address4}; otherwise return null. ++ * ++ * The special-property checks (`isLoopback`, `isLinkLocal`, `isMulticast`, ++ * `isUnspecified`, `isPrivate`, `isCGNAT`, `isBroadcast`) call this first and ++ * delegate to the embedded {@link Address4} when present, so a literal such as ++ * `::ffff:127.0.0.1` is classified by what it actually reaches (loopback) ++ * rather than by its IPv6 wrapper (which `getType()` reports as IPv4-mapped). ++ * This matters wherever the checks back a trust-boundary decision (e.g. an ++ * SSRF allow/deny filter): without normalization, `::ffff:10.0.0.1`, ++ * `::ffff:169.254.169.254`, `64:ff9b::7f00:1`, etc. would all read as ++ * non-internal. ++ * @returns {Address4 | null} ++ */ ++ embeddedIPv4() { ++ if (this.isMapped4() || this.isHostInSubnet(NAT64_WELL_KNOWN_SUBNET)) { ++ return this.to4(); ++ } ++ return null; ++ } ++ /** + * Returns true if the address is a Teredo address, false otherwise +- * @memberof Address6 +- * @instance + * @returns {boolean} + */ + isTeredo() { +- return this.isInSubnet(new Address6('2001::/32')); ++ return this.isHostInSubnet(TEREDO_SUBNET); + } + /** + * Returns true if the address is a 6to4 address, false otherwise +- * @memberof Address6 +- * @instance + * @returns {boolean} + */ + is6to4() { +- return this.isInSubnet(new Address6('2002::/16')); ++ return this.isHostInSubnet(SIX_TO_FOUR_SUBNET); + } + /** + * Returns true if the address is a loopback address, false otherwise +- * @memberof Address6 +- * @instance + * @returns {boolean} + */ + isLoopback() { ++ const embedded = this.embeddedIPv4(); ++ if (embedded) { ++ return embedded.isLoopback(); ++ } + return this.getType() === 'Loopback'; + } ++ /** ++ * Returns true if the address is a Unique Local Address in `fc00::/7` ([RFC 4193](https://datatracker.ietf.org/doc/html/rfc4193)). ULAs are the IPv6 equivalent of IPv4 [RFC 1918](https://datatracker.ietf.org/doc/html/rfc1918) private addresses. ++ * @returns {boolean} ++ */ ++ isULA() { ++ return this.isHostInSubnet(ULA_SUBNET); ++ } ++ /** ++ * Returns true if the address is private, i.e. a Unique Local Address in ++ * `fc00::/7` ([RFC 4193](https://datatracker.ietf.org/doc/html/rfc4193)) or an ++ * IPv4-mapped / NAT64 address whose embedded IPv4 address is in one of the ++ * [RFC 1918](https://datatracker.ietf.org/doc/html/rfc1918) private ranges ++ * (e.g. `::ffff:10.0.0.1`). This is the IPv6 counterpart to ++ * {@link Address4.isPrivate}; use it instead of {@link isULA} when you need to ++ * catch mapped RFC 1918 addresses as well as native ULAs. ++ * @returns {boolean} ++ */ ++ isPrivate() { ++ const embedded = this.embeddedIPv4(); ++ if (embedded) { ++ return embedded.isPrivate(); ++ } ++ return this.isULA(); ++ } ++ /** ++ * Returns true if the address is an IPv4-mapped / NAT64 address whose embedded ++ * IPv4 address is in the carrier-grade NAT range `100.64.0.0/10` ++ * ([RFC 6598](https://datatracker.ietf.org/doc/html/rfc6598)), false ++ * otherwise. There is no native IPv6 CGNAT range, so this only ever returns ++ * true for an embedded IPv4 address (e.g. `::ffff:100.64.0.1`). ++ * @returns {boolean} ++ */ ++ isCGNAT() { ++ const embedded = this.embeddedIPv4(); ++ if (embedded) { ++ return embedded.isCGNAT(); ++ } ++ return false; ++ } ++ /** ++ * Returns true if the address is an IPv4-mapped / NAT64 address whose embedded ++ * IPv4 address is the limited broadcast address `255.255.255.255` ++ * ([RFC 919](https://datatracker.ietf.org/doc/html/rfc919)), false otherwise. ++ * There is no IPv6 broadcast, so this only ever returns true for an embedded ++ * IPv4 address (e.g. `::ffff:255.255.255.255`). ++ * @returns {boolean} ++ */ ++ isBroadcast() { ++ const embedded = this.embeddedIPv4(); ++ if (embedded) { ++ return embedded.isBroadcast(); ++ } ++ return false; ++ } ++ /** ++ * Returns true if the address is the unspecified address `::`. ++ * @returns {boolean} ++ */ ++ isUnspecified() { ++ const embedded = this.embeddedIPv4(); ++ if (embedded) { ++ return embedded.isUnspecified(); ++ } ++ return this.getType() === 'Unspecified'; ++ } ++ /** ++ * Returns true if the address is in the documentation prefix `2001:db8::/32` ([RFC 3849](https://datatracker.ietf.org/doc/html/rfc3849)). ++ * @returns {boolean} ++ */ ++ isDocumentation() { ++ return this.isHostInSubnet(DOCUMENTATION_SUBNET); ++ } + // #endregion + // #region HTML + /** +- * @returns {String} the address in link form with a default port of 80 ++ * Returns the address as an HTTP URL with the host bracketed, e.g. ++ * `http://[2001:db8::1]/`. If `optionalPort` is provided it is appended, ++ * e.g. `http://[2001:db8::1]:8080/`. + */ + href(optionalPort) { + if (optionalPort === undefined) { +@@ -876,7 +1203,12 @@ + return `http://[${this.correctForm()}]${optionalPort}/`; + } + /** +- * @returns {String} a link suitable for conveying the address via a URL hash ++ * Returns an HTML `` element whose `href` encodes the address in a URL ++ * hash fragment (default prefix `/#address=`). Useful for linking between ++ * pages of an address-inspector UI. ++ * @param options.className - CSS class for the rendered `` element ++ * @param options.prefix - hash prefix prepended to the address (default `/#address=`) ++ * @param options.v4 - when true, render the address in v4-in-v6 form + */ + link(options) { + if (!options) { +@@ -896,10 +1228,13 @@ + formFunction = this.to4in6; + } + const form = formFunction.call(this); ++ const safeHref = helpers.escapeHtml(`${options.prefix}${form}`); ++ const safeForm = helpers.escapeHtml(form); + if (options.className) { +- return `${form}`; ++ const safeClass = helpers.escapeHtml(options.className); ++ return `${safeForm}`; + } +- return `${form}`; ++ return `${safeForm}`; + } + /** + * Groups an address +@@ -908,13 +1243,13 @@ + group() { + if (this.elidedGroups === 0) { + // The simple case +- return helpers.simpleGroup(this.address).join(':'); ++ return helpers.simpleGroup(this.addressMinusSuffix).join(':'); + } + assert(typeof this.elidedGroups === 'number'); + assert(typeof this.elisionBegin === 'number'); + // The elided case + const output = []; +- const [left, right] = this.address.split('::'); ++ const [left, right] = this.addressMinusSuffix.split('::'); + if (left.length) { + output.push(...helpers.simpleGroup(left)); + } +@@ -944,8 +1279,6 @@ + /** + * Generate a regular expression string that can be used to find or validate + * all variations of this address +- * @memberof Address6 +- * @instance + * @param {boolean} substringSearch + * @returns {string} + */ +@@ -990,8 +1323,6 @@ + /** + * Generate a regular expression that can be used to find or validate all + * variations of this address. +- * @memberof Address6 +- * @instance + * @param {boolean} substringSearch + * @returns {RegExp} + */ +@@ -1000,4 +1331,14 @@ + } + } + exports.Address6 = Address6; ++const TYPE_SUBNETS = Object.keys(constants6.TYPES).map((subnet) => [ ++ new Address6(subnet), ++ constants6.TYPES[subnet], ++]); ++const TEREDO_SUBNET = new Address6('2001::/32'); ++const SIX_TO_FOUR_SUBNET = new Address6('2002::/16'); ++const ULA_SUBNET = new Address6('fc00::/7'); ++const DOCUMENTATION_SUBNET = new Address6('2001:db8::/32'); ++const IPV4_MAPPED_SUBNET = new Address6('::ffff:0:0/96'); ++const NAT64_WELL_KNOWN_SUBNET = new Address6('64:ff9b::/96'); + //# sourceMappingURL=ipv6.js.map +\ No newline at end of file + +diff --git a/deps/npm/node_modules/ip-address/dist/v4/constants.js b/deps/npm/node_modules/ip-address/dist/v4/constants.js +--- a/deps/npm/node_modules/ip-address/dist/v4/constants.js ++++ b/deps/npm/node_modules/ip-address/dist/v4/constants.js +@@ -3,6 +3,10 @@ + exports.RE_SUBNET_STRING = exports.RE_ADDRESS = exports.GROUPS = exports.BITS = void 0; + exports.BITS = 32; + exports.GROUPS = 4; +-exports.RE_ADDRESS = /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/g; ++// Each octet is 0-255 written without a leading zero. A leading zero is ++// octal to the WHATWG URL parser, inet_aton, and getaddrinfo, but decimal to ++// parseInt(part, 10), so accepting the notation would make this library ++// disagree with the network stack about which host a string names. ++exports.RE_ADDRESS = /^(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])$/g; + exports.RE_SUBNET_STRING = /\/\d{1,2}$/; + //# sourceMappingURL=constants.js.map +\ No newline at end of file + +diff --git a/deps/npm/node_modules/ip-address/dist/v6/constants.js b/deps/npm/node_modules/ip-address/dist/v6/constants.js +--- a/deps/npm/node_modules/ip-address/dist/v6/constants.js ++++ b/deps/npm/node_modules/ip-address/dist/v6/constants.js +@@ -44,8 +44,14 @@ + 'ff05::1:3/128': 'Multicast (All DHCP servers in this site)', + '::/128': 'Unspecified', + '::1/128': 'Loopback', ++ '::ffff:0:0/96': 'IPv4-mapped', + 'ff00::/8': 'Multicast', + 'fe80::/10': 'Link-local unicast', ++ 'fc00::/7': 'Unique local', ++ '2002::/16': '6to4', ++ '2001:db8::/32': 'Documentation', ++ '64:ff9b::/96': 'NAT64 (well-known)', ++ '64:ff9b:1::/48': 'NAT64 (local-use)', + }; + /** + * A regular expression that matches bad characters in an IPv6 address +@@ -71,6 +77,6 @@ + * @static + */ + exports.RE_ZONE_STRING = /%.*$/; +-exports.RE_URL = /^\[{0,1}([0-9a-f:]+)\]{0,1}/; +-exports.RE_URL_WITH_PORT = /\[([0-9a-f:]+)\]:([0-9]{1,5})/; ++exports.RE_URL = /^(?:\[([0-9a-f:.]+)\]|([0-9a-f:.]+))(?:[/?#].*)?$/i; ++exports.RE_URL_WITH_PORT = /^\[([0-9a-f:.]+)\]:([0-9]{1,5})(?:[/?#].*)?$/i; + //# sourceMappingURL=constants.js.map +\ No newline at end of file + +diff --git a/deps/npm/node_modules/ip-address/dist/v6/helpers.js b/deps/npm/node_modules/ip-address/dist/v6/helpers.js +--- a/deps/npm/node_modules/ip-address/dist/v6/helpers.js ++++ b/deps/npm/node_modules/ip-address/dist/v6/helpers.js +@@ -1,14 +1,23 @@ + "use strict"; + Object.defineProperty(exports, "__esModule", { value: true }); ++exports.escapeHtml = escapeHtml; + exports.spanAllZeroes = spanAllZeroes; + exports.spanAll = spanAll; + exports.spanLeadingZeroes = spanLeadingZeroes; + exports.simpleGroup = simpleGroup; ++function escapeHtml(s) { ++ return s ++ .replace(/&/g, '&') ++ .replace(//g, '>') ++ .replace(/"/g, '"') ++ .replace(/'/g, '''); ++} + /** + * @returns {String} the string with all zeroes contained in a + */ + function spanAllZeroes(s) { +- return s.replace(/(0+)/g, '$1'); ++ return escapeHtml(s).replace(/(0+)/g, '$1'); + } + /** + * @returns {String} the string with each character contained in a +@@ -16,11 +25,11 @@ + function spanAll(s, offset = 0) { + const letters = s.split(''); + return letters +- .map((n, i) => `${spanAllZeroes(n)}`) ++ .map((n, i) => `${spanAllZeroes(n)}`) + .join(''); + } + function spanLeadingZeroesSimple(group) { +- return group.replace(/^(0+)/, '$1'); ++ return escapeHtml(group).replace(/^(0+)/, '$1'); + } + /** + * @returns {String} the string with leading zeroes contained in a + +diff --git a/deps/npm/node_modules/ip-address/package.json b/deps/npm/node_modules/ip-address/package.json +--- a/deps/npm/node_modules/ip-address/package.json ++++ b/deps/npm/node_modules/ip-address/package.json +@@ -2,77 +2,89 @@ + "name": "ip-address", + "description": "A library for parsing IPv4 and IPv6 IP addresses in node and the browser.", + "keywords": [ +- "ipv6", ++ "ip", + "ipv4", +- "browser", +- "validation" ++ "ipv6", ++ "address", ++ "cidr", ++ "subnet", ++ "netmask", ++ "validate", ++ "validation", ++ "parse", ++ "arpa", ++ "bigint", ++ "browser" + ], +- "version": "10.1.0", ++ "version": "10.4.0", + "author": "Beau Gunderson (https://beaugunderson.com/)", + "license": "MIT", + "main": "dist/ip-address.js", + "types": "dist/ip-address.d.ts", + "scripts": { +- "docs": "documentation build --github --output docs --format html ./ip-address.js", ++ "docs": "tsx scripts/build-readme.ts", + "build": "rm -rf dist; mkdir dist; tsc", +- "prepack": "npm run build", +- "release": "release-it", +- "test-ci": "nyc mocha", ++ "prepack": "npm run docs && npm run build", ++ "prepare": "git config core.hooksPath hooks || true", ++ "lint": "prettier --check . && eslint . --ext .ts,.js --max-warnings 0", ++ "lint:fix": "prettier --write . && eslint . --ext .ts,.js --max-warnings 0 --fix", ++ "test-ci": "c8 --experimental-monocart mocha", + "test": "mocha", + "watch": "mocha --watch" + }, +- "nyc": { +- "extension": [ +- ".ts" ++ "c8": { ++ "include": [ ++ "src/**/*.ts" + ], + "exclude": [ + "**/*.d.ts", +- ".eslintrc.js", +- "coverage/", +- "dist/", +- "test/", +- "tmp/" ++ "src/ip-address.ts", ++ "src/v4/constants.ts", ++ "src/v6/constants.ts" + ], + "reporter": [ + "html", + "lcov", + "text" +- ], +- "all": true ++ ] + }, + "engines": { + "node": ">= 12" + }, ++ "sideEffects": false, + "files": [ +- "src", + "dist" + ], + "repository": { + "type": "git", +- "url": "git://github.com/beaugunderson/ip-address.git" ++ "url": "https://github.com/beaugunderson/ip-address.git" ++ }, ++ "overrides": { ++ "diff": "^8.0.3", ++ "serialize-javascript": "^7.0.5", ++ "@eslint/plugin-kit": "^0.7.1" + }, + "devDependencies": { +- "@types/chai": "^5.0.0", +- "@types/mocha": "^10.0.8", +- "@typescript-eslint/eslint-plugin": "^8.8.0", +- "@typescript-eslint/parser": "^8.8.0", +- "chai": "^5.1.1", +- "documentation": "^14.0.3", +- "eslint": "^8.50.0", ++ "@types/chai": "^5.2.3", ++ "@types/mocha": "^10.0.10", ++ "@typescript-eslint/eslint-plugin": "^8.59.1", ++ "@typescript-eslint/parser": "^8.59.1", ++ "c8": "^11.0.0", ++ "chai": "^6.2.2", ++ "eslint": "^8.57.1", + "eslint_d": "^14.0.4", +- "eslint-config-airbnb": "^19.0.4", +- "eslint-config-prettier": "^9.1.0", ++ "eslint-config-airbnb-base": "^15.0.0", ++ "eslint-config-prettier": "^10.1.8", + "eslint-plugin-filenames": "^1.3.2", +- "eslint-plugin-import": "^2.30.0", +- "eslint-plugin-jsx-a11y": "^6.10.0", +- "eslint-plugin-prettier": "^5.2.1", ++ "eslint-plugin-import": "^2.32.0", ++ "eslint-plugin-prettier": "^5.5.5", + "eslint-plugin-sort-imports-es6-autofix": "^0.6.0", +- "mocha": "^10.7.3", +- "nyc": "^17.1.0", +- "prettier": "^3.3.3", +- "release-it": "^17.6.0", ++ "mocha": "^11.7.5", ++ "monocart-coverage-reports": "^2.12.11", ++ "prettier": "^3.8.3", + "source-map-support": "^0.5.21", +- "tsx": "^4.19.1", ++ "tsx": "^4.21.0", ++ "typedoc": "^0.28.19", + "typescript": "<5.6.0" + } + } diff --git a/nodejs22.spec b/nodejs22.spec index a91f91d..6d7c4f1 100644 --- a/nodejs22.spec +++ b/nodejs22.spec @@ -2,7 +2,7 @@ ## (rpmautospec version 0.8.4) ## RPMAUTOSPEC: autorelease, autochangelog %define autorelease(e:s:pb:n) %{?-p:0.}%{lua: - release_number = 4; + release_number = 6; base_release_number = tonumber(rpm.expand("%{?-b*}%{!?-b:1}")); print(release_number + base_release_number - 1); }%{?-e:.%{-e*}}%{?-s:.%{-s*}}%{!?-n:%{?dist}} @@ -188,11 +188,10 @@ Source301: test-should-pass.txt Patch: 0001-Remove-unused-OpenSSL-config.patch Patch: 0001-fips-disable-options.patch -Patch: 0001-CVE-2026-25547-braces-expansion.patch # npm deps patches -Patch: 0002-CVE-2026-42338-npm-ip-address-security-fix.patch Patch: 0003-CVE-2026-59873-CVE-2026-59874-upgrade-bundled-tar-to-7.5.19.patch -Patch: 0004-CVE-2026-13149-brace-expansion-unbound-recursion.patch +Patch: 0004-CVE-2026-69152-brace-expansion-2.1.4.patch +Patch: 0005-CVE-2026-69192-CVE-2026-54272-ip-address-10.4.0.patch %if 0%{?nodejs_default} %global pkgname nodejs @@ -992,6 +991,12 @@ end %changelog ## START: Generated by rpmautospec +* Tue Aug 04 2026 tjuhasz - 1:22.23.1-6 +- Fix for CVE-2026-69152 + +* Tue Aug 04 2026 tjuhasz - 1:22.23.1-5 +- Fix for CVE-2026-69192 & CVE-2026-54272 : ip-address + * Mon Jul 20 2026 RHEL Packaging Agent - 1:22.23.1-4 - Fix CVE-2026-13149 (npm/brace-expansion)