From 5ceeabbcb6776ea19fb239a5e28c9824eab5080a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Oct 2026 18:49:55 +0000 Subject: [PATCH 1/9] Bump sharp from 0.35.4 to 0.35.5 Bumps [sharp](https://github.com/lovell/sharp) from 0.35.4 to 0.35.5. - [Release notes](https://github.com/lovell/sharp/releases) - [Commits](https://github.com/lovell/sharp/compare/v0.35.4...v0.35.5) --- updated-dependencies: - dependency-name: sharp dependency-version: 0.35.5 dependency-type: indirect ... Signed-off-by: dependabot[bot] --- package-lock.json | 236 +++++++++++++++++++++++----------------------- 1 file changed, 118 insertions(+), 118 deletions(-) diff --git a/package-lock.json b/package-lock.json index 844dc8956..43950a6e3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1830,9 +1830,9 @@ } }, "node_modules/@img/sharp-darwin-arm64": { - "version": "0.35.4", - "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.35.4.tgz", - "integrity": "sha512-Uhfl4V4lhP2nbUVF9+hyH1+luj86f1gUFeo8ALYxFoULoU+G87D43BfeMP8XHsk9boxAnCY/bf2EHwhA7MuGsA==", + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.35.5.tgz", + "integrity": "sha512-QRUlFQ0WxvdWyqqG/WtI3iupfD5rBzmCHXSdPsY91sAtVtTo7Q4cb6zOccZ3gqEqkr0f1As1ehLqmEpDsRf+lg==", "cpu": [ "arm64" ], @@ -1848,13 +1848,13 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-darwin-arm64": "1.3.3" + "@img/sharp-libvips-darwin-arm64": "1.3.4" } }, "node_modules/@img/sharp-darwin-x64": { - "version": "0.35.4", - "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.35.4.tgz", - "integrity": "sha512-hWniXY3bG5qKpkKrAwPe4y+VTPmf086YQAnkxWh7uA1YrlRouWGa0M0Mxj3ZjnXFkv7/TD1bTy9lGUK26vRvWw==", + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.35.5.tgz", + "integrity": "sha512-+BR255RhDlpygUpOc/Jdt1nT6DQ3XG/ERo5wbcdOf5Q320dKtPCKPLR1LJs9VGXRaMa8l1uUa0tkCNOXiAxZUw==", "cpu": [ "x64" ], @@ -1870,20 +1870,20 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-darwin-x64": "1.3.3" + "@img/sharp-libvips-darwin-x64": "1.3.4" } }, "node_modules/@img/sharp-freebsd-wasm32": { - "version": "0.35.4", - "resolved": "https://registry.npmjs.org/@img/sharp-freebsd-wasm32/-/sharp-freebsd-wasm32-0.35.4.tgz", - "integrity": "sha512-lIsKw/BU+kjB4eZjxrYrZmwOJYi3Ajrv66iAlBmUPyKc3HpnloevB1g3wxGD9P/5BbQ1brBGl65VRRrCvQDEqA==", + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-freebsd-wasm32/-/sharp-freebsd-wasm32-0.35.5.tgz", + "integrity": "sha512-Y/z91nEZ4uIBX5X3nfTovjU9lHNKFYbL2lpHCLVNmXQK03VIZvXBBt0KxbPGp2SdGSF+2mQU4e+hQaWOt86iAw==", "license": "Apache-2.0", "optional": true, "os": [ "freebsd" ], "dependencies": { - "@img/sharp-wasm32": "0.35.4" + "@img/sharp-wasm32": "0.35.5" }, "engines": { "node": ">=20.9.0" @@ -1893,9 +1893,9 @@ } }, "node_modules/@img/sharp-libvips-darwin-arm64": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.3.3.tgz", - "integrity": "sha512-suTBPTDGrI9WodccaDdwZItTSaBYASlBk1NSfElSHrUfzu3szG6lvIF58+WiFvnfzuK8ZBFS5zE00PxqxnRiPg==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.3.4.tgz", + "integrity": "sha512-5R89nBYiRdUlSWJxPhO+GVtaXzXSxKnRu/xqMn3KTA3L9EB9Oy/P+Nn2f2vlhPuUdy/Zusb2DarbyTpGCfEDuw==", "cpu": [ "arm64" ], @@ -1909,9 +1909,9 @@ } }, "node_modules/@img/sharp-libvips-darwin-x64": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.3.3.tgz", - "integrity": "sha512-FVJZ5mITMobmXIz/hPDTw0EintTW5H3WfrxwLqEqjiIihlu+hVRyGrFQ60xl0Lxn7Bt3zdpevPaQi0HEzqz9fw==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.3.4.tgz", + "integrity": "sha512-iR2OKH80yi0U+dUplyh3/xdpFvps6YkCwsXenIJxqxR1v9o+xtKTGbS9H7cps+2Vxjc8B1j96p75NmTGjIhtpQ==", "cpu": [ "x64" ], @@ -1925,9 +1925,9 @@ } }, "node_modules/@img/sharp-libvips-linux-arm": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.3.3.tgz", - "integrity": "sha512-3rbU4vqXXc3hY/OiXdl52xZvT0F1yEngWfvqudtPJg/KkyiaQw2DRsFrNzpmLvfavbwOq3qXn36GP8obHRULQA==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.3.4.tgz", + "integrity": "sha512-LmRtTsOHuvM2+wlO2Db37dx5MiZhB0FvSunciw48YjdOkZz9KAiRbm8ujeMOA1INqmei5NapFxYEK1D1ZSidmw==", "cpu": [ "arm" ], @@ -1944,9 +1944,9 @@ } }, "node_modules/@img/sharp-libvips-linux-arm64": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.3.3.tgz", - "integrity": "sha512-0DaL0A6Xu6sQSQFwe4iVCrKWU2cCTItnRsYsCdxAMm9NF6twAA9BKnoqy4hqz4+azQ0JHuA26qiUKsf1XJ/v5A==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.3.4.tgz", + "integrity": "sha512-Y3dgX/6lE2QhQb+Gxy0WZxfg9MEm/JBjamZpS2IklP7xIQoKN4hzAm7KcMVGtaVDt3neE9OKBC7vAfonA/Lr1A==", "cpu": [ "arm64" ], @@ -1963,9 +1963,9 @@ } }, "node_modules/@img/sharp-libvips-linux-ppc64": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.3.3.tgz", - "integrity": "sha512-cdn1OvUBwsXhbC0zSzJnNzf5MZ/mTrobawDvNXBTxe8VtqKAm0sRuEY2Evzovb/w9JMk4TvRxqt1mekSuJz64w==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.3.4.tgz", + "integrity": "sha512-Le6boB8Tai0Nis+gIxIpKx68UDVVIqdR8Tin5Yf1z2LJJQLDJvCDRqRu+jC2qCoD+eIomonmOwB4smBRxfVpYQ==", "cpu": [ "ppc64" ], @@ -1982,9 +1982,9 @@ } }, "node_modules/@img/sharp-libvips-linux-riscv64": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.3.3.tgz", - "integrity": "sha512-HjPVx7yKz+0lqdhDlTw1tt90wamBoxhiXpvl1XZpJLiHH4RCJ5yDTqH+VlYPv2fwFs89JFw4c1IexYOcQUi4IQ==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.3.4.tgz", + "integrity": "sha512-aHkkIEHPRdQEegJN20MLmGtxYD9R2wQr3Cwpddnu5+YKMt6Uzax7S9h5gpZTo8wyrGuZSlfQ63OevL5mTyOC7Q==", "cpu": [ "riscv64" ], @@ -2001,9 +2001,9 @@ } }, "node_modules/@img/sharp-libvips-linux-s390x": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.3.3.tgz", - "integrity": "sha512-neWLh+3yCNThxnfy3c4BbVBeGgt9aftno+XbT56iK28RgeDs3UOFWviLWlUu0bArYVYJaFDK+RRohbicUNCm8Q==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.3.4.tgz", + "integrity": "sha512-ra/mB6MikESDUO7Yg+Mi95bFBb9GsObURuhnOv3OqknjGe9sZrG8tCe9q0xSIGrtLgvgw0gKnFWcK4blSgQOuQ==", "cpu": [ "s390x" ], @@ -2020,9 +2020,9 @@ } }, "node_modules/@img/sharp-libvips-linux-x64": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.3.3.tgz", - "integrity": "sha512-4vKmvAst9nrowcqquKFAyZJUDolUaIp8uRiN0mWFguJ1IplC9/pitXtlnnlU4aa/eJw3J7i67V+pwUL+wZGdsA==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.3.4.tgz", + "integrity": "sha512-GJ//SSXbnwSDes02umB3nDJLFcQzw8a18V8fyhqr6tV515tOEMdImjjxj1AoafMRz56F3PHgftnj1QEKSU1zkw==", "cpu": [ "x64" ], @@ -2039,9 +2039,9 @@ } }, "node_modules/@img/sharp-libvips-linuxmusl-arm64": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.3.3.tgz", - "integrity": "sha512-Y9kQaLMuNoB0bPYOOdcZMaseNrFpPodIWWMrx+CZyydf2xn68j9WYc6sWWRrDwNkzCQjKYfc68L7jKjGlHMibw==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.3.4.tgz", + "integrity": "sha512-hvulFwtjUcagsis6BBxHwGFwWoNZjgYmULGVrZcyfNbjA8hKILbRxGg15/7w5HDyXHXUos/j6baAWqnCyQ2DWA==", "cpu": [ "arm64" ], @@ -2058,9 +2058,9 @@ } }, "node_modules/@img/sharp-libvips-linuxmusl-x64": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.3.3.tgz", - "integrity": "sha512-fj8Mv0HHfD1Rr+4I68+3agJynxDWtBFgicTbSOb9Bke6pIwzGcJ+RX/yHjmiEGFMCavY/dxvem7MyNaJF+wDiw==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.3.4.tgz", + "integrity": "sha512-6zXKeE/p39I1AmA3cJG35eyBGNqNddLnUXjhwBnsGjFPWqf5VKkDBEqaEkPDoTEtkxwi2vv8Tcr2mDyP4So7Fg==", "cpu": [ "x64" ], @@ -2077,9 +2077,9 @@ } }, "node_modules/@img/sharp-linux-arm": { - "version": "0.35.4", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.35.4.tgz", - "integrity": "sha512-7OAS8gI0EReKGVN2HssHlM6umJgxF5VI3xN0p9FA91p/YO+ou5hiNghLdZ5BEHztwaaK5+bLKRf8x/o2L2nk9A==", + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.35.5.tgz", + "integrity": "sha512-LEaXK2WdXVK5ykcw0buWyPMsmLLL2vpHLD6yrNSW+JGEL3BZPA4tpKN6iaMc4AxTTAoaX/sU1rOL51lcIz48ZQ==", "cpu": [ "arm" ], @@ -2098,13 +2098,13 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-arm": "1.3.3" + "@img/sharp-libvips-linux-arm": "1.3.4" } }, "node_modules/@img/sharp-linux-arm64": { - "version": "0.35.4", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.35.4.tgz", - "integrity": "sha512-De4jpEnAU8Hd5oT0j1G3uL4ZvTuipVMn7YC6vPaJhy6/7EwEae0SVAoBrUMYQbkLGDm85taVWwuPc1a44LTzCQ==", + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.35.5.tgz", + "integrity": "sha512-LYVx5JTsOM2CBzmxreh+nl64/3H6Xb09iSLknqH47z2T2DFFxDeFLP5y4dJwe6H7uGQlHPyEEtIqyo3DYsRwdQ==", "cpu": [ "arm64" ], @@ -2123,13 +2123,13 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-arm64": "1.3.3" + "@img/sharp-libvips-linux-arm64": "1.3.4" } }, "node_modules/@img/sharp-linux-ppc64": { - "version": "0.35.4", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.35.4.tgz", - "integrity": "sha512-2oYZJeIl4kCcMGk4ouZVjnkCtFrpQFlNEtJ6GbxzhHQchwH0NH/qEb9ykmOl29dqwMq+JhFdZn+1ak2FKhI9fQ==", + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.35.5.tgz", + "integrity": "sha512-QVxAAq8evVRI9ia2vqgwrmWucn5Dfv+JdWzj75pD8omHLPSP7f8p20O8jxzjCcuCEQEOtYOZUmX1hkiZ0kdevA==", "cpu": [ "ppc64" ], @@ -2148,13 +2148,13 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-ppc64": "1.3.3" + "@img/sharp-libvips-linux-ppc64": "1.3.4" } }, "node_modules/@img/sharp-linux-riscv64": { - "version": "0.35.4", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.35.4.tgz", - "integrity": "sha512-cPbNChoRURAWdebDIHSenxRpgEdy7JkPydSnUxRm9VvKD7m0/xVaR/8Fzlu81pk5nHEvHH87UZUA7cTtwnbJSA==", + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.35.5.tgz", + "integrity": "sha512-LtdreXguaavKODPIfzJ4kffx7UNt1omwtK0rch4EBbbSTXPnxWmYSayXdLJw0fJzQ97kHt1gL/yh4tvU+nCyRQ==", "cpu": [ "riscv64" ], @@ -2173,13 +2173,13 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-riscv64": "1.3.3" + "@img/sharp-libvips-linux-riscv64": "1.3.4" } }, "node_modules/@img/sharp-linux-s390x": { - "version": "0.35.4", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.35.4.tgz", - "integrity": "sha512-RY0JFY8Fd6RonCBtHz+DvadaPkXDSI1AUn6yWL9TipqkZ1vY8w8evqdgyDFnkm4/K1ve1TvZiaePP5oSd4+WVQ==", + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.35.5.tgz", + "integrity": "sha512-UZasTOFiYzotTsGOCu42BfUzP6Tu6Do/947iRm1RsLKvlllxwGcn4RN27LibGWceix4Y+Pmw3jsnTcCQIgWjqA==", "cpu": [ "s390x" ], @@ -2198,13 +2198,13 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-s390x": "1.3.3" + "@img/sharp-libvips-linux-s390x": "1.3.4" } }, "node_modules/@img/sharp-linux-x64": { - "version": "0.35.4", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.35.4.tgz", - "integrity": "sha512-9qvvEAuk8k89TfWUoX2htWjbAMX8p+NxCppjpcg5k6xMsjhBQPTsoIh36h9Qde4WRuGpJeYnOjdosDn/cnv+OA==", + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.35.5.tgz", + "integrity": "sha512-SxFtLTeJInhAA9Q836kux2vZNeOBQEx658qvbboZScr0wIARym3IcGmW7KpVD5sbVg0Ojy+udFQdayYIZyoNog==", "cpu": [ "x64" ], @@ -2223,13 +2223,13 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-x64": "1.3.3" + "@img/sharp-libvips-linux-x64": "1.3.4" } }, "node_modules/@img/sharp-linuxmusl-arm64": { - "version": "0.35.4", - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.35.4.tgz", - "integrity": "sha512-KB5jxpfWQTr0nc3xdHtWChdbifHrBGsd2SM62Eyxrl8afikm+f5qGBU75SJIZBT/S1MC8XyacdlXBMSWq6OURA==", + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.35.5.tgz", + "integrity": "sha512-9HbMclmI1zlNkFRs3z9/eBtDjfD0sGlrX1z6b1qwmiFY5ElDLh4BC0LPBdVp7z1DXFiKlIcznf+ZlsuZzLxQqg==", "cpu": [ "arm64" ], @@ -2248,13 +2248,13 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linuxmusl-arm64": "1.3.3" + "@img/sharp-libvips-linuxmusl-arm64": "1.3.4" } }, "node_modules/@img/sharp-linuxmusl-x64": { - "version": "0.35.4", - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.35.4.tgz", - "integrity": "sha512-f+eZJZIQNEEd26RPSW+76chwOf1XtA2Y/O+5ocVyLliHkeih3e+jhLVBdNTd2rS3IbNXK8+ug93Vf5ZXtF5Lxg==", + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.35.5.tgz", + "integrity": "sha512-4KOphqB035HrVdqLZfCgMzzERrQkkzOwRhl4OAkRO1YCldbaFjySXMaK534Mo0V+LndnlJk+sbUyLeU0ULyD1A==", "cpu": [ "x64" ], @@ -2273,13 +2273,13 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linuxmusl-x64": "1.3.3" + "@img/sharp-libvips-linuxmusl-x64": "1.3.4" } }, "node_modules/@img/sharp-wasm32": { - "version": "0.35.4", - "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.35.4.tgz", - "integrity": "sha512-zQnl4Kwp7Q6NHsENtU2T/00Zi+w3AQNwz3+UaTyVBy2FpXrzXzGjndpK61onhZjRtRpQXxCTeqw19bVyXOh7jA==", + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.35.5.tgz", + "integrity": "sha512-Ptsga1su4tQx+LLF1ECS9U6nz5kmrXKo6XVbtR48Ke3ZRxxgaWBu7IDtEe1quo8hiupwm6WFqxVlXaSf7IINGQ==", "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", "optional": true, "dependencies": { @@ -2303,16 +2303,16 @@ } }, "node_modules/@img/sharp-webcontainers-wasm32": { - "version": "0.35.4", - "resolved": "https://registry.npmjs.org/@img/sharp-webcontainers-wasm32/-/sharp-webcontainers-wasm32-0.35.4.tgz", - "integrity": "sha512-ESfNkywmCfPNyaZjxooddJQiQ+l/nTpGEOGthxiLnIHXC/CmcBixnfwUleX9mCz9ovrUUvKMap/pm8RYbzfwaA==", + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-webcontainers-wasm32/-/sharp-webcontainers-wasm32-0.35.5.tgz", + "integrity": "sha512-hfhF/FmoQyTUkA0bIKFOtw536BQSeBMe6BF6QyWlrPxT754+TFLaZ7sKKTfvvM0yJgKgaYTwnFCIZ/GuDw5SUA==", "cpu": [ "wasm32" ], "license": "Apache-2.0", "optional": true, "dependencies": { - "@img/sharp-wasm32": "0.35.4" + "@img/sharp-wasm32": "0.35.5" }, "engines": { "node": ">=20.9.0" @@ -2322,9 +2322,9 @@ } }, "node_modules/@img/sharp-win32-arm64": { - "version": "0.35.4", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.35.4.tgz", - "integrity": "sha512-iNdlBX9gLVvqe2I3uIJSIKTq6wckP/DYxZtcqxm09x5Gi24DnFBmPAWZmr60ZyYMG0xlzo6goG3670ar+RXvRw==", + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.35.5.tgz", + "integrity": "sha512-X4t7g+7ZA5DKblCBEXGjUqqemj4vczING/5viFwAL8h4N3qYeyjwdCvRLHi4EdOUI+2Z7UFlp1VM+p/AuEtm6Q==", "cpu": [ "arm64" ], @@ -2341,9 +2341,9 @@ } }, "node_modules/@img/sharp-win32-ia32": { - "version": "0.35.4", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.35.4.tgz", - "integrity": "sha512-kqRsbaa5CS6KHlpxnN7WhE6vAAugXyZButpRdvDWetlv6Qv4N9WTcrWzF7tXfB9T7MsoadqdI8hmwLq6UlLvtw==", + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.35.5.tgz", + "integrity": "sha512-5Zm82LoBc43nhwNybZlG7Y1KO//Zhsn306fQl29ZOuStHLGTo3BWL83q3cznX0poxSAMuYL1On/BHBxkBeKr6A==", "cpu": [ "ia32" ], @@ -2360,9 +2360,9 @@ } }, "node_modules/@img/sharp-win32-x64": { - "version": "0.35.4", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.35.4.tgz", - "integrity": "sha512-XtmnYhBcrORsJ4XJngyzr/EWP0hRZLAZRFaApdKuviyqF78+ylxh2y06ZmtULAMOnObJ3ucpN0AcwSWnMowTRg==", + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.35.5.tgz", + "integrity": "sha512-x76eH0vEiHlcMQu8Y8IenntaACtddpT6W0wmXtWrnKcnKI7ME5DdgqhAD6SEWOEl1v2zDvkZDhFA9KnURwpfqg==", "cpu": [ "x64" ], @@ -16397,9 +16397,9 @@ "license": "ISC" }, "node_modules/sharp": { - "version": "0.35.4", - "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.35.4.tgz", - "integrity": "sha512-n++8XWcj+jCOr2IOl7h8LbKnGBDY4aPbmprMONBNFdn0ImXqpGVv5zliDs0V9HbmbCQLpbuo2ej9rAoOQTvMDA==", + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.35.5.tgz", + "integrity": "sha512-Ywn4OnzGukp7CDMrp08RQ50YKmuwG47brZgIVPTvBaaAfQlRlygrRqSrxdCiL9M+LlzLBiJ68IR1QqvzHyjC7g==", "license": "Apache-2.0", "optional": true, "dependencies": { @@ -16414,31 +16414,31 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-darwin-arm64": "0.35.4", - "@img/sharp-darwin-x64": "0.35.4", - "@img/sharp-freebsd-wasm32": "0.35.4", - "@img/sharp-libvips-darwin-arm64": "1.3.3", - "@img/sharp-libvips-darwin-x64": "1.3.3", - "@img/sharp-libvips-linux-arm": "1.3.3", - "@img/sharp-libvips-linux-arm64": "1.3.3", - "@img/sharp-libvips-linux-ppc64": "1.3.3", - "@img/sharp-libvips-linux-riscv64": "1.3.3", - "@img/sharp-libvips-linux-s390x": "1.3.3", - "@img/sharp-libvips-linux-x64": "1.3.3", - "@img/sharp-libvips-linuxmusl-arm64": "1.3.3", - "@img/sharp-libvips-linuxmusl-x64": "1.3.3", - "@img/sharp-linux-arm": "0.35.4", - "@img/sharp-linux-arm64": "0.35.4", - "@img/sharp-linux-ppc64": "0.35.4", - "@img/sharp-linux-riscv64": "0.35.4", - "@img/sharp-linux-s390x": "0.35.4", - "@img/sharp-linux-x64": "0.35.4", - "@img/sharp-linuxmusl-arm64": "0.35.4", - "@img/sharp-linuxmusl-x64": "0.35.4", - "@img/sharp-webcontainers-wasm32": "0.35.4", - "@img/sharp-win32-arm64": "0.35.4", - "@img/sharp-win32-ia32": "0.35.4", - "@img/sharp-win32-x64": "0.35.4" + "@img/sharp-darwin-arm64": "0.35.5", + "@img/sharp-darwin-x64": "0.35.5", + "@img/sharp-freebsd-wasm32": "0.35.5", + "@img/sharp-libvips-darwin-arm64": "1.3.4", + "@img/sharp-libvips-darwin-x64": "1.3.4", + "@img/sharp-libvips-linux-arm": "1.3.4", + "@img/sharp-libvips-linux-arm64": "1.3.4", + "@img/sharp-libvips-linux-ppc64": "1.3.4", + "@img/sharp-libvips-linux-riscv64": "1.3.4", + "@img/sharp-libvips-linux-s390x": "1.3.4", + "@img/sharp-libvips-linux-x64": "1.3.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.3.4", + "@img/sharp-libvips-linuxmusl-x64": "1.3.4", + "@img/sharp-linux-arm": "0.35.5", + "@img/sharp-linux-arm64": "0.35.5", + "@img/sharp-linux-ppc64": "0.35.5", + "@img/sharp-linux-riscv64": "0.35.5", + "@img/sharp-linux-s390x": "0.35.5", + "@img/sharp-linux-x64": "0.35.5", + "@img/sharp-linuxmusl-arm64": "0.35.5", + "@img/sharp-linuxmusl-x64": "0.35.5", + "@img/sharp-webcontainers-wasm32": "0.35.5", + "@img/sharp-win32-arm64": "0.35.5", + "@img/sharp-win32-ia32": "0.35.5", + "@img/sharp-win32-x64": "0.35.5" }, "peerDependenciesMeta": { "@types/node": { From dc579acc44de1690b3a4355ec8f3c1b36761e431 Mon Sep 17 00:00:00 2001 From: choksi2212 Date: Tue, 13 Oct 2026 06:52:56 +0530 Subject: [PATCH 2/9] fix(org): clean up every site when deleting an org deleteOrgById fetched the org's sites with .limit(1), so the per-site cleanup loop ran for at most one site: the WireGuard deletePeer call, the Newt terminate collection, and cleanupSiteAssociations were all skipped for every other site in the org. Drop the limit so every site is loaded, and mirror deleteSite.ts in the loop: run cleanupSiteAssociations for newt sites so clients of other orgs do not keep stale associations for the deleted sites. Covered by a standalone regression test that seeds two wireguard and two newt sites plus a stale association-cache row, runs deleteOrgById against a stubbed exit node, and asserts all four sites were cleaned. On the previous code the test fails with exactly the reported symptom: one peer removal instead of two. Fixes #3863 --- server/lib/deleteOrg.childTest.ts | 139 ++++++++++++++++++++++ server/lib/deleteOrg.test.ts | 185 ++++++++++++++++++++++++++++++ server/lib/deleteOrg.ts | 34 +++--- 3 files changed, 343 insertions(+), 15 deletions(-) create mode 100644 server/lib/deleteOrg.childTest.ts create mode 100644 server/lib/deleteOrg.test.ts diff --git a/server/lib/deleteOrg.childTest.ts b/server/lib/deleteOrg.childTest.ts new file mode 100644 index 000000000..b00a4f96d --- /dev/null +++ b/server/lib/deleteOrg.childTest.ts @@ -0,0 +1,139 @@ +// Test child for deleteOrg.test.ts. Boots the real sqlite driver against a +// fresh database in the temp workdir (cwd), seeds an org with two wireguard +// sites and two newt sites, runs deleteOrgById against a local HTTP stand-in +// for the exit node, and prints one JSON line of observations. +// The app boots with `import "./extendZod"` before anything else; zod +// schemas in the import chain use .openapi(), so do the same here. +import "@server/extendZod"; +import http from "http"; +import { deleteOrgById } from "@server/lib/deleteOrg"; +import { + db, + exitNodes, + orgs, + sites, + newts, + newtSessions, + clientSitesAssociationsCache +} from "@server/db"; +import { sql } from "drizzle-orm"; + +const peerRemovals: string[] = []; +const server = http.createServer((req, res) => { + let body = ""; + req.on("data", (c) => (body += c)); + req.on("end", () => { + if (req.method === "DELETE") { + if (!req.url) { + res.writeHead(200, { "Content-Type": "application/json" }); + res.end(JSON.stringify({ status: "ok" })); + return; + } + const url = new URL(req.url, "http://localhost"); + const pubKey = url.searchParams.get("public_key"); + if (pubKey !== null) { + peerRemovals.push(pubKey); + } + } + res.writeHead(200, { "Content-Type": "application/json" }); + res.end(JSON.stringify({ status: "ok" })); + }); +}); + +server.listen(0, "127.0.0.1", async () => { + const addr = server.address(); + if (addr === null || typeof addr === "string") { + throw new Error("failed to bind the stub exit node"); + } + const port = addr.port; + try { + await db.insert(exitNodes).values({ + exitNodeId: 1, + name: "en", + address: "10.0.0.1", + endpoint: "10.0.0.1:51820", + publicKey: "enKey", + listenPort: 51820, + reachableAt: "http://127.0.0.1:" + port, + type: "wireguard" + }); + await db.insert(orgs).values({ + orgId: "org1", + name: "Org", + settingsJitModeLimit: 1 + }); + for (let i = 1; i <= 2; i++) { + await db.insert(sites).values({ + siteId: i, + orgId: "org1", + niceId: String(i), + name: "wg" + i, + pubKey: "wgKey" + i, + exitNodeId: 1, + type: "wireguard" + }); + } + for (let i = 3; i <= 4; i++) { + await db.insert(sites).values({ + siteId: i, + orgId: "org1", + niceId: String(i), + name: "newt" + i, + pubKey: "newtPub" + i, + publicKey: "newtSession" + i, + type: "newt" + }); + await db.insert(newts).values({ + newtId: "newtId" + i, + secretHash: "h" + i, + dateCreated: "2026-01-01", + siteId: i + }); + await db.insert(newtSessions).values({ + sessionId: "sess" + i, + newtId: "newtId" + i, + expiresAt: 4102444800 + }); + } + // A stale association-cache row pinned to one of the newt sites, the + // kind cleanupSiteAssociations exists to clear. + await db.insert(clientSitesAssociationsCache).values({ + clientId: 99, + siteId: 3, + isRelayed: false + }); + + const result = await deleteOrgById("org1"); + + const row = (await db.get( + sql`select + (select count(*) from sites) as sites, + (select count(*) from "newt") as newts, + (select count(*) from "newtSession") as sessions, + (select count(*) from clientSitesAssociationsCache where siteId = 3) as assoc` + )) as { + sites: number; + newts: number; + sessions: number; + assoc: number; + }; + + console.log( + "OBSERVATIONS " + + JSON.stringify({ + peerRemovals: peerRemovals.slice().sort(), + terminatedNewtIds: result.deletedNewtIds.slice().sort(), + sitesLeft: Number(row.sites), + newtsLeft: Number(row.newts), + sessionsLeft: Number(row.sessions), + associationRowsLeft: Number(row.assoc) + }) + ); + server.close(); + process.exit(0); + } catch (e: any) { + console.error("CHILD_FAIL", e && e.message ? e.message : String(e)); + server.close(); + process.exit(1); + } +}); diff --git a/server/lib/deleteOrg.test.ts b/server/lib/deleteOrg.test.ts new file mode 100644 index 000000000..8ec46183b --- /dev/null +++ b/server/lib/deleteOrg.test.ts @@ -0,0 +1,185 @@ +import { execFileSync } from "child_process"; +import fs from "fs"; +import os from "os"; +import path from "path"; +import { fileURLToPath } from "url"; +import { assertEquals } from "@test/assert"; + +const here = path.dirname(fileURLToPath(import.meta.url)); +const repoRoot = path.resolve(here, "..", ".."); + +const CHILD_SCRIPT_PATH = path.join( + repoRoot, + "server", + "lib", + "deleteOrg.childTest.ts" +); + +function runChild(workdir: string): { + exitCode: number; + observations: any; + output: string; +} { + const tsconfig = ["tsconfig.json", "tsconfig.oss.json"] + .map((file) => path.join(repoRoot, file)) + .find((file) => fs.existsSync(file)); + if (!tsconfig) { + throw new Error("No tsconfig found for @server path aliases"); + } + const tsxCli = path.join( + repoRoot, + "node_modules", + "tsx", + "dist", + "cli.mjs" + ); + if (!fs.existsSync(tsxCli)) { + throw new Error("tsx is not installed; run npm ci first"); + } + try { + const output = execFileSync( + process.execPath, + [tsxCli, "--tsconfig", tsconfig, CHILD_SCRIPT_PATH], + { + cwd: workdir, + timeout: 120000, + encoding: "utf8", + env: { ...process.env, NODE_ENV: "test" } + } + ); + const m = output.match(/OBSERVATIONS (.*)/); + if (!m) { + throw new Error("child did not print observations: " + output); + } + return { exitCode: 0, observations: JSON.parse(m[1]), output }; + } catch (error: any) { + const output = String(error?.stdout ?? ""); + return { exitCode: 1, observations: null, output }; + } +} + +function createTestEnvironment(): string { + for (const generated of ["server/build.ts", "server/db/index.ts"]) { + if (!fs.existsSync(path.join(repoRoot, generated))) { + throw new Error( + `Missing ${generated}; run npm run set:oss && npm run set:sqlite first` + ); + } + } + const workdir = fs.mkdtempSync( + path.join(os.tmpdir(), "pangolin-org-cleanup-") + ); + fs.mkdirSync(path.join(workdir, "config", "db"), { recursive: true }); + fs.copyFileSync( + path.join(repoRoot, "config", "config.example.yml"), + path.join(workdir, "config", "config.yml") + ); + fs.symlinkSync( + path.join(repoRoot, "server"), + path.join(workdir, "server"), + process.platform === "win32" ? "junction" : "dir" + ); + fs.symlinkSync( + path.join(repoRoot, "test"), + path.join(workdir, "test"), + process.platform === "win32" ? "junction" : "dir" + ); + fs.symlinkSync( + path.join(repoRoot, "node_modules"), + path.join(workdir, "node_modules"), + process.platform === "win32" ? "junction" : "dir" + ); + return workdir; +} + +function applyMigrations(workdir: string) { + const tsconfig = ["tsconfig.json", "tsconfig.oss.json"] + .map((file) => path.join(repoRoot, file)) + .find((file) => fs.existsSync(file)); + if (!tsconfig) { + throw new Error("No tsconfig found for @server path aliases"); + } + const tsxCli = path.join( + repoRoot, + "node_modules", + "tsx", + "dist", + "cli.mjs" + ); + // CI applies migrations with `npm run db:push` before booting the app; + // server/db/migrate.ts is the drizzle migrator that script invokes. + const migrationsScript = path.join(repoRoot, "server", "db", "migrate.ts"); + execFileSync( + process.execPath, + [tsxCli, "--tsconfig", tsconfig, migrationsScript], + { + cwd: workdir, + timeout: 120000, + encoding: "utf8", + env: { ...process.env, NODE_ENV: "test" } + } + ); +} + +function testOrgCleanupCoversEverySite() { + console.log("Running deleteOrgById per-site cleanup test..."); + const workdir = createTestEnvironment(); + applyMigrations(workdir); + + const { exitCode, observations, output } = runChild(workdir); + if (exitCode !== 0) { + throw new Error("deleteOrgById child run failed: " + output); + } + + // The exact bug from the issue: with a single-row sites fetch, only one + // of the four sites gets its side effects. All wireguard peers must be + // removed from the exit node... + assertEquals( + JSON.stringify(observations.peerRemovals), + JSON.stringify(["wgKey1", "wgKey2"]), + "deleteOrgById must remove every wireguard site's peer from its exit node" + ); + // ...and every newt connector must be collected for the terminate message. + assertEquals( + JSON.stringify(observations.terminatedNewtIds), + JSON.stringify(["newtId3", "newtId4"]), + "deleteOrgById must collect every newt id so all connectors get the terminate message" + ); + // DB rows must still cascade to nothing left. + assertEquals(observations.sitesLeft, 0, "all site rows must be gone"); + assertEquals(observations.newtsLeft, 0, "all newt rows must be gone"); + assertEquals( + observations.sessionsLeft, + 0, + "all newt session rows must be gone" + ); + + // The gap the issue calls out separately: cleanupSiteAssociations must + // run for newt sites (deleteSite.ts already does this), so the stale + // association cache rows for these sites must be cleared. + assertEquals( + observations.associationRowsLeft, + 0, + "newt site cleanup must clear clientSitesAssociationsCache rows" + ); + + console.log("deleteOrgById per-site cleanup test passed"); +} + +function main() { + let failed = 0; + for (const test of [testOrgCleanupCoversEverySite]) { + try { + test(); + } catch (error) { + failed++; + console.error(`FAIL: ${(error as Error).message}`); + } + } + if (failed > 0) { + process.exit(1); + } + console.log("deleteOrg tests complete"); +} + +main(); diff --git a/server/lib/deleteOrg.ts b/server/lib/deleteOrg.ts index 43921e41b..131214aab 100644 --- a/server/lib/deleteOrg.ts +++ b/server/lib/deleteOrg.ts @@ -23,6 +23,7 @@ import { sendToClient } from "#dynamic/routers/ws"; import { deletePeer } from "@server/routers/gerbil/peers"; import { OlmErrorCodes } from "@server/routers/olm/error"; import { sendTerminateClient } from "@server/routers/client/terminate"; +import { cleanupSiteAssociations } from "@server/lib/rebuildClientAssociations"; import { usageService } from "./billing/usageService"; import { LimitId } from "./billing"; @@ -55,8 +56,7 @@ export async function deleteOrgById( const orgSites = await db .select() .from(sites) - .where(eq(sites.orgId, orgId)) - .limit(1); + .where(eq(sites.orgId, orgId)); const orgClients = await db .select() @@ -73,20 +73,24 @@ export async function deleteOrgById( await db.transaction(async (trx) => { for (const site of orgSites) { - if (site.pubKey) { - if (site.type == "wireguard") { + if (site.type == "wireguard") { + if (site.pubKey) { await deletePeer(site.exitNodeId!, site.pubKey); - } else if (site.type == "newt") { - const [deletedNewt] = await trx - .delete(newts) - .where(eq(newts.siteId, site.siteId)) - .returning(); - if (deletedNewt) { - deletedNewtIds.push(deletedNewt.newtId); - await trx - .delete(newtSessions) - .where(eq(newtSessions.newtId, deletedNewt.newtId)); - } + } + } else if (site.type == "newt") { + // Same per-site cleanup deleteSite.ts performs, so that + // removing an org does not leave other orgs' clients + // holding stale associations for these sites. + await cleanupSiteAssociations(site, trx); + const [deletedNewt] = await trx + .delete(newts) + .where(eq(newts.siteId, site.siteId)) + .returning(); + if (deletedNewt) { + deletedNewtIds.push(deletedNewt.newtId); + await trx + .delete(newtSessions) + .where(eq(newtSessions.newtId, deletedNewt.newtId)); } } logger.info(`Deleting site ${site.siteId}`); From 9242c2c1965b8363f5abbb57e6bf4e5f23a8b381 Mon Sep 17 00:00:00 2001 From: choksi2212 Date: Tue, 13 Oct 2026 23:11:28 +0530 Subject: [PATCH 3/9] fix(org): build the deleteOrg child test under the pg config deleteOrg.childTest.ts read its leftover counts with db.get(sql...), a better-sqlite3 driver method. The pg config type-checks the same file with the pg driver's types, where .get() does not exist, so build-postgres failed on PR #3895's head (TS2339 at deleteOrg.childTest.ts:108) and main has carried the break since the merge landed 40 seconds before the job reported red. Read the counts through the query builder instead - the same .select({ count: count() }) shape deleteOrg.ts itself uses, which compiles under both configs - keeping the observations JSON the parent test parses unchanged. Signed-off-by: Manas Choksi <12302040701105@mbit.edu.in> Signed-off-by: choksi2212 --- server/lib/deleteOrg.childTest.ts | 34 +++++++++++++++---------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/server/lib/deleteOrg.childTest.ts b/server/lib/deleteOrg.childTest.ts index b00a4f96d..15e0763e8 100644 --- a/server/lib/deleteOrg.childTest.ts +++ b/server/lib/deleteOrg.childTest.ts @@ -16,7 +16,7 @@ import { newtSessions, clientSitesAssociationsCache } from "@server/db"; -import { sql } from "drizzle-orm"; +import { count, eq } from "drizzle-orm"; const peerRemovals: string[] = []; const server = http.createServer((req, res) => { @@ -105,28 +105,28 @@ server.listen(0, "127.0.0.1", async () => { const result = await deleteOrgById("org1"); - const row = (await db.get( - sql`select - (select count(*) from sites) as sites, - (select count(*) from "newt") as newts, - (select count(*) from "newtSession") as sessions, - (select count(*) from clientSitesAssociationsCache where siteId = 3) as assoc` - )) as { - sites: number; - newts: number; - sessions: number; - assoc: number; - }; + // Read the leftovers through the query builder, not db.get(): the + // pg build type-checks this file against the pg driver too, and + // .get() exists only on the sqlite driver. + const [sitesRow] = await db.select({ count: count() }).from(sites); + const [newtsRow] = await db.select({ count: count() }).from(newts); + const [sessionsRow] = await db + .select({ count: count() }) + .from(newtSessions); + const [assocRow] = await db + .select({ count: count() }) + .from(clientSitesAssociationsCache) + .where(eq(clientSitesAssociationsCache.siteId, 3)); console.log( "OBSERVATIONS " + JSON.stringify({ peerRemovals: peerRemovals.slice().sort(), terminatedNewtIds: result.deletedNewtIds.slice().sort(), - sitesLeft: Number(row.sites), - newtsLeft: Number(row.newts), - sessionsLeft: Number(row.sessions), - associationRowsLeft: Number(row.assoc) + sitesLeft: sitesRow.count, + newtsLeft: newtsRow.count, + sessionsLeft: sessionsRow.count, + associationRowsLeft: assocRow.count }) ); server.close(); From c0dd4356e9039e5d836df91ad734adbec3bdafc4 Mon Sep 17 00:00:00 2001 From: Owen Date: Wed, 14 Oct 2026 14:16:07 -0400 Subject: [PATCH 4/9] Add enterprise to the saas build --- Makefile | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Makefile b/Makefile index da31b6e27..6e555698a 100644 --- a/Makefile +++ b/Makefile @@ -102,6 +102,13 @@ build-saas: --tag $(AWS_IMAGE):$(tag) \ --push . + docker buildx build \ + --build-arg BUILD=enterprise \ + --build-arg DATABASE=pg \ + --platform linux/arm64 \ + --tag $(AWS_IMAGE):$(tag)-ee \ + --push . + build-release-arm: @if [ -z "$(tag)" ]; then \ echo "Error: tag is required. Usage: make build-release-arm tag="; \ From b0bb4fed88c8a2a04e7f7e7889c9288d76c4b61d Mon Sep 17 00:00:00 2001 From: Owen Date: Wed, 14 Oct 2026 14:44:54 -0400 Subject: [PATCH 5/9] Dont delete maxmind --- Dockerfile | 9 +++------ Makefile | 1 - 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/Dockerfile b/Dockerfile index 0bf3a59fe..669221784 100644 --- a/Dockerfile +++ b/Dockerfile @@ -55,17 +55,14 @@ COPY server/db/ios_models.json ./dist/ios_models.json COPY server/db/mac_models.json ./dist/mac_models.json COPY public ./public -# Copy MaxMind databases for SaaS builds -ARG BUILD=oss - RUN mkdir -p ./maxmind -# Copy MaxMind databases (placeholders exist for oss builds, real files for saas) +# Copy MaxMind databases (empty placeholders unless real files were in the build context) COPY --from=builder-dev /app/GeoLite2-Country.mmdb ./maxmind/GeoLite2-Country.mmdb COPY --from=builder-dev /app/GeoLite2-ASN.mmdb ./maxmind/GeoLite2-ASN.mmdb -# Remove MaxMind databases for non-saas builds (keep only for saas) -RUN if [ "$BUILD" != "saas" ]; then rm -rf ./maxmind; fi +# Remove empty placeholder MaxMind databases (and the directory if nothing real is left) +RUN find ./maxmind -empty -delete # OCI Image Labels - Build Args for dynamic values ARG VERSION="dev" diff --git a/Makefile b/Makefile index 6e555698a..b04750ce2 100644 --- a/Makefile +++ b/Makefile @@ -101,7 +101,6 @@ build-saas: --platform linux/arm64 \ --tag $(AWS_IMAGE):$(tag) \ --push . - docker buildx build \ --build-arg BUILD=enterprise \ --build-arg DATABASE=pg \ From 3601ca17f176fec7235e338c55b56327b1c18e3d Mon Sep 17 00:00:00 2001 From: Owen Date: Wed, 14 Oct 2026 15:30:58 -0400 Subject: [PATCH 6/9] Manage cert enabled with leader election --- server/private/lib/certificates/index.ts | 26 +- server/private/lib/certificates/scheduler.ts | 10 +- server/private/lib/leaderElection.ts | 259 +++++++++++++++++++ 3 files changed, 287 insertions(+), 8 deletions(-) create mode 100644 server/private/lib/leaderElection.ts diff --git a/server/private/lib/certificates/index.ts b/server/private/lib/certificates/index.ts index 6097629a7..27dd24177 100644 --- a/server/private/lib/certificates/index.ts +++ b/server/private/lib/certificates/index.ts @@ -15,6 +15,23 @@ import logger from "@server/logger"; import { privateConfig } from "#private/lib/config"; import { acmeClientManager } from "./acme-client"; import { jobScheduler } from "./scheduler"; +import { LeaderElection } from "#private/lib/leaderElection"; + +// Only one instance sharing this Redis talks to the ACME server at a time. +// Every instance runs the election; whichever holds the lease runs issuance +// and renewal, and another takes over if it goes away. +const acmeLeaderElection = new LeaderElection("acme-client", { + onElected: async () => { + logger.info("Starting certificate management server..."); + + // Initialize ACME client + await acmeClientManager.initialize(); + + // Start certificate issuance/renewal jobs + await jobScheduler.start(); + }, + onDemoted: () => jobScheduler.stopCertJobs() +}); export async function startCertificateManager() { const acmeConfig = privateConfig.getRawPrivateConfig().acme; @@ -23,13 +40,7 @@ export async function startCertificateManager() { acmeConfig.cert_mode === "pangolin" && acmeConfig.enable_acme_client ) { - logger.info("Starting certificate management server..."); - - // Initialize ACME client - await acmeClientManager.initialize(); - - // Start certificate issuance/renewal jobs - await jobScheduler.start(); + acmeLeaderElection.start(); } if (privateConfig.getRawPrivateConfig().flags.use_pangolin_dns) { @@ -41,5 +52,6 @@ export async function startCertificateManager() { } export async function stopCertificateManager() { + await acmeLeaderElection.stop(); await jobScheduler.stop(); } diff --git a/server/private/lib/certificates/scheduler.ts b/server/private/lib/certificates/scheduler.ts index c46463008..4bd52a6eb 100644 --- a/server/private/lib/certificates/scheduler.ts +++ b/server/private/lib/certificates/scheduler.ts @@ -173,13 +173,21 @@ export class JobScheduler { logger.info("DNS validation job scheduler started successfully"); } - async stop(): Promise { + // Stops only issuance/renewal (e.g. when this instance stops being the + // ACME leader) and leaves the DNS jobs running. A batch already in + // flight finishes on its own; the per-domain cert locks keep it from + // overlapping with the new leader's work on the same certificate. + async stopCertJobs(): Promise { if (this.certRunning) { logger.info("Stopping certificate job scheduler"); this.certRunning = false; this.certIntervals.forEach((interval) => clearInterval(interval)); this.certIntervals = []; } + } + + async stop(): Promise { + await this.stopCertJobs(); if (this.dnsRunning) { logger.info("Stopping DNS validation job scheduler"); diff --git a/server/private/lib/leaderElection.ts b/server/private/lib/leaderElection.ts new file mode 100644 index 000000000..27f850da4 --- /dev/null +++ b/server/private/lib/leaderElection.ts @@ -0,0 +1,259 @@ +/* + * This file is part of a proprietary work. + * + * Copyright (c) 2025-2026 Fossorial, Inc. + * All rights reserved. + * + * This file is licensed under the Fossorial Commercial License. + * You may not use this file except in compliance with the License. + * Unauthorized use, copying, modification, or distribution is strictly prohibited. + * + * This file is not licensed under the AGPLv3. + */ + +import { v4 as uuidv4 } from "uuid"; +import redisManager from "#private/lib/redis"; +import { withTimeout } from "@server/lib/retry"; +import logger from "@server/logger"; + +// How long a lease lasts without being renewed. If the leader dies, the +// role sits empty for at most this long before a follower can take it. +const LEASE_TTL_MS = 30_000; + +// How often the leader renews its lease and a follower tries to take it. +const TICK_INTERVAL_MS = 10_000; + +// A leader that can't confirm its lease within this time steps down. Even +// when the last successful renewal was just before a tick, it stops within +// TICK_INTERVAL_MS + REDIS_TIMEOUT_MS (15s), well before the lease can run +// out (LEASE_TTL_MS) and another instance can take it. +const REDIS_TIMEOUT_MS = 5_000; + +const RENEW_SCRIPT = ` +if redis.call('GET', KEYS[1]) == ARGV[1] then + return redis.call('PEXPIRE', KEYS[1], ARGV[2]) +else + return 0 +end +`; + +const RELEASE_SCRIPT = ` +if redis.call('GET', KEYS[1]) == ARGV[1] then + return redis.call('DEL', KEYS[1]) +else + return 0 +end +`; + +type LeaderCallbacks = { + // Runs once each time this instance becomes leader. If it throws, the + // lease is given up and the next tick tries again (here or elsewhere). + onElected: () => Promise; + // Runs when leadership is lost or given up. Must be safe to call even if + // onElected only partly finished. + onDemoted: () => Promise; +}; + +/** + * Makes sure only one Pangolin instance sharing a Redis is running a given + * role at a time. + * + * Leadership is a Redis key, set with NX and a TTL, whose value is this + * instance's token. The leader renews it every tick and steps down as soon + * as a renewal fails or times out, so it has stopped before the lease + * can run out and another instance can take over. + * + * Talks to the Redis client directly rather than through lockManager: + * lockManager falls back to in-process locks while Redis is unreachable, + * which would let every instance become leader during an outage. Here an + * unreachable Redis means no instance is leader until it comes back. + * + * With Redis disabled, the deployment is assumed to be a single instance + * (Redis is required to run more than one), so this instance is always the + * leader. + */ +export class LeaderElection { + private readonly key: string; + private readonly callbacks: LeaderCallbacks; + private token: string | null = null; + private timer: NodeJS.Timeout | null = null; + private leader = false; + private running = false; + + constructor(name: string, callbacks: LeaderCallbacks) { + this.key = `leader:${name}`; + this.callbacks = callbacks; + } + + start(): void { + if (this.running) { + return; + } + this.running = true; + + if (!redisManager.getWriteClient()) { + logger.info( + `Redis is disabled - assuming a single instance and taking ${this.key}` + ); + void this.becomeLeader(); + return; + } + + logger.info(`Leader election started for ${this.key}`); + void this.tick(); + } + + async stop(): Promise { + if (!this.running) { + return; + } + this.running = false; + + if (this.timer) { + clearTimeout(this.timer); + this.timer = null; + } + + await this.stepDown("shutting down"); + } + + isLeader(): boolean { + return this.leader; + } + + // Ticks are chained with setTimeout rather than setInterval so a slow + // acquire/renew can never overlap with the next one. + private async tick(): Promise { + if (!this.running) { + return; + } + + try { + if (this.leader) { + await this.renew(); + } else { + await this.tryAcquire(); + } + } catch (error) { + logger.error(`Leader election error for ${this.key}:`, error); + await this.stepDown("leader election error"); + } + + if (this.running) { + this.timer = setTimeout(() => void this.tick(), TICK_INTERVAL_MS); + } + } + + private async tryAcquire(): Promise { + const client = redisManager.getWriteClient()!; + const token = uuidv4(); + + const result = await withTimeout( + client.set(this.key, token, "PX", LEASE_TTL_MS, "NX"), + REDIS_TIMEOUT_MS, + `acquire ${this.key}` + ); + + if (result !== "OK") { + logger.debug( + `Not leader for ${this.key} - another instance holds the lease` + ); + return; + } + + this.token = token; + + // stop() may have run while the SET was in flight. + if (!this.running) { + await this.release(); + return; + } + + await this.becomeLeader(); + } + + private async renew(): Promise { + if (!this.token) { + throw new Error("leader has no lease token"); + } + + const client = redisManager.getWriteClient()!; + const renewed = (await withTimeout( + client.eval( + RENEW_SCRIPT, + 1, + this.key, + this.token, + LEASE_TTL_MS.toString() + ), + REDIS_TIMEOUT_MS, + `renew ${this.key}` + )) as number; + + if (renewed !== 1) { + throw new Error("lease is no longer held by this instance"); + } + } + + private async becomeLeader(): Promise { + this.leader = true; + logger.info(`Elected leader for ${this.key}`); + + try { + await this.callbacks.onElected(); + } catch (error) { + logger.error( + `Failed to start ${this.key} after being elected leader:`, + error + ); + // Without Redis there's nobody to hand over to and no retry loop, + // so keep the role, just as it was before leader election. + if (this.token) { + await this.stepDown("startup failed"); + } + } + } + + private async stepDown(reason: string): Promise { + const wasLeader = this.leader; + this.leader = false; + + if (wasLeader) { + logger.warn(`Stepping down as leader for ${this.key}: ${reason}`); + try { + await this.callbacks.onDemoted(); + } catch (error) { + logger.error( + `Error stopping ${this.key} after stepping down:`, + error + ); + } + } + + // Only after onDemoted has stopped this instance from scheduling more + // work, so the next leader doesn't overlap with it. + await this.release(); + } + + // Best effort: if Redis is unreachable the lease just runs out after + // LEASE_TTL_MS instead. + private async release(): Promise { + const token = this.token; + this.token = null; + + const client = redisManager.getWriteClient(); + if (!token || !client) { + return; + } + + try { + await withTimeout( + client.eval(RELEASE_SCRIPT, 1, this.key, token), + REDIS_TIMEOUT_MS, + `release ${this.key}` + ); + } catch (error) { + logger.warn(`Failed to release ${this.key}:`, error); + } + } +} From c5c19871eb84e40181d46cafdea4bd2e906233b6 Mon Sep 17 00:00:00 2001 From: Owen Date: Thu, 15 Oct 2026 10:10:41 -0400 Subject: [PATCH 7/9] Prevent overlapping exit node address selections --- .../exitNodes/getUniqueSubnetForExitNode.ts | 81 ++++++++++++------- .../routers/newt/handleNewtRegisterMessage.ts | 26 +++--- .../routers/olm/handleOlmRegisterMessage.ts | 33 +++++--- 3 files changed, 89 insertions(+), 51 deletions(-) diff --git a/server/lib/exitNodes/getUniqueSubnetForExitNode.ts b/server/lib/exitNodes/getUniqueSubnetForExitNode.ts index 728049eb1..1c5f0f660 100644 --- a/server/lib/exitNodes/getUniqueSubnetForExitNode.ts +++ b/server/lib/exitNodes/getUniqueSubnetForExitNode.ts @@ -1,41 +1,62 @@ -import { db, ExitNode, Transaction, sites, clients } from "@server/db"; +import { ExitNode, primaryDb, sites, clients } from "@server/db"; import { eq } from "drizzle-orm"; import config from "@server/lib/config"; import { findNextAvailableCidr } from "@server/lib/ip"; import { lockManager } from "#dynamic/lib/lock"; +/** + * Picks the next free site/client subnet on the given exit node. This acquires + * a per-exit-node lock that the caller MUST release (via the returned + * `release`) only after the chosen subnet has been persisted to the database + * (or the operation has failed) — otherwise a concurrent caller on another + * node could pick the same subnet. If no subnet is available the lock is + * released before returning and `value` is null. + */ export async function getUniqueSubnetForExitNode( - exitNode: ExitNode, - trx: Transaction | typeof db = db -): Promise { + exitNode: ExitNode +): Promise<{ value: string | null; release: () => Promise }> { const lockKey = `subnet-allocation:${exitNode.exitNodeId}`; + const acquired = await lockManager.acquireLockWithRetry(lockKey, 6000); + if (!acquired) { + throw new Error(`Failed to acquire lock: ${lockKey}`); + } + const release = () => lockManager.releaseLock(lockKey, acquired); - return await lockManager.withLock( - lockKey, - async () => { - const [sitesQuery, clientsQuery] = await Promise.all([ - trx - .select({ subnet: sites.exitNodeSubnet }) - .from(sites) - .where(eq(sites.exitNodeId, exitNode.exitNodeId)), - trx - .select({ subnet: clients.exitNodeSubnet }) - .from(clients) - .where(eq(clients.exitNodeId, exitNode.exitNodeId)) - ]); + try { + // read from the primary so we see subnets committed by the previous lock holder + const [sitesQuery, clientsQuery] = await Promise.all([ + primaryDb + .select({ subnet: sites.exitNodeSubnet }) + .from(sites) + .where(eq(sites.exitNodeId, exitNode.exitNodeId)), + primaryDb + .select({ subnet: clients.exitNodeSubnet }) + .from(clients) + .where(eq(clients.exitNodeId, exitNode.exitNodeId)) + ]); - const blockSize = config.getRawConfig().gerbil.site_block_size; - const subnets = [...sitesQuery, ...clientsQuery] - .map((row) => row.subnet) - .filter( - (subnet): subnet is string => - !!subnet && - /^(\d{1,3}\.){3}\d{1,3}\/\d{1,2}$/.test(subnet) - ); - subnets.push(exitNode.address.replace(/\/\d+$/, `/${blockSize}`)); + const blockSize = config.getRawConfig().gerbil.site_block_size; + const subnets = [...sitesQuery, ...clientsQuery] + .map((row) => row.subnet) + .filter( + (subnet): subnet is string => + !!subnet && /^(\d{1,3}\.){3}\d{1,3}\/\d{1,2}$/.test(subnet) + ); + subnets.push(exitNode.address.replace(/\/\d+$/, `/${blockSize}`)); - return findNextAvailableCidr(subnets, blockSize, exitNode.address); - }, - 5000 // 5 second lock TTL - subnet allocation should be quick - ); + const subnet = findNextAvailableCidr( + subnets, + blockSize, + exitNode.address + ); + if (!subnet) { + await release(); + return { value: null, release: async () => {} }; + } + + return { value: subnet, release }; + } catch (e) { + await release(); + throw e; + } } diff --git a/server/routers/newt/handleNewtRegisterMessage.ts b/server/routers/newt/handleNewtRegisterMessage.ts index 4d90aa0aa..db21a6661 100644 --- a/server/routers/newt/handleNewtRegisterMessage.ts +++ b/server/routers/newt/handleNewtRegisterMessage.ts @@ -112,7 +112,8 @@ export const handleNewtRegisterMessage: MessageHandler = async (context) => { return; } - const newSubnet = await getUniqueSubnetForExitNode(exitNode); + const { value: newSubnet, release: releaseSubnetLock } = + await getUniqueSubnetForExitNode(exitNode); if (!newSubnet) { logger.error( @@ -124,15 +125,20 @@ export const handleNewtRegisterMessage: MessageHandler = async (context) => { siteSubnet = newSubnet; - await db - .update(sites) - .set({ - pubKey: publicKey, - exitNodeId: exitNodeId, - exitNodeSubnet: newSubnet - }) - .where(eq(sites.siteId, siteId)) - .returning(); + try { + await db + .update(sites) + .set({ + pubKey: publicKey, + exitNodeId: exitNodeId, + exitNodeSubnet: newSubnet + }) + .where(eq(sites.siteId, siteId)) + .returning(); + } finally { + // only release once the subnet is persisted so no other node can pick it + await releaseSubnetLock(); + } } else { await db .update(sites) diff --git a/server/routers/olm/handleOlmRegisterMessage.ts b/server/routers/olm/handleOlmRegisterMessage.ts index f583729ef..73078a4a6 100644 --- a/server/routers/olm/handleOlmRegisterMessage.ts +++ b/server/routers/olm/handleOlmRegisterMessage.ts @@ -314,6 +314,7 @@ export const handleOlmRegisterMessage: MessageHandler = async (context) => { } let clientSubnet = client.exitNodeSubnet; + let releaseSubnetLock: (() => Promise) | null = null; if ( exitNodeId && (client.exitNodeId !== exitNodeId || !client.exitNodeSubnet) @@ -341,7 +342,8 @@ export const handleOlmRegisterMessage: MessageHandler = async (context) => { // TODO: IF WE DO NOT HAVE AN INFERENCE RESOURCE DO WE NEED TO BE HOLDING A SUBNET ON THE CLIENT? - const newSubnet = await getUniqueSubnetForExitNode(exitNode); + const { value: newSubnet, release } = + await getUniqueSubnetForExitNode(exitNode); if (!newSubnet) { logger.error( @@ -353,6 +355,7 @@ export const handleOlmRegisterMessage: MessageHandler = async (context) => { } clientSubnet = newSubnet; + releaseSubnetLock = release; } if ( @@ -365,16 +368,21 @@ export const handleOlmRegisterMessage: MessageHandler = async (context) => { "[handleOlmRegisterMessage] Public key mismatch. Updating public key and clearing session info...", { orgId: client.orgId, clientId: client.clientId } ); - // Update the client's public key - await db - .update(clients) - .set({ - pubKey: publicKey, - archived: false, - exitNodeId: exitNodeId, // this can be undefined if no exit node was selected, which is fine just means we cant talk to the node or connect to it - exitNodeSubnet: clientSubnet - }) - .where(eq(clients.clientId, client.clientId)); + try { + // Update the client's public key + await db + .update(clients) + .set({ + pubKey: publicKey, + archived: false, + exitNodeId: exitNodeId, // this can be undefined if no exit node was selected, which is fine just means we cant talk to the node or connect to it + exitNodeSubnet: clientSubnet + }) + .where(eq(clients.clientId, client.clientId)); + } finally { + // only release once the subnet is persisted so no other node can pick it + await releaseSubnetLock?.(); + } // set isRelay to false for all of the client's sites to reset the connection metadata await db @@ -397,6 +405,9 @@ export const handleOlmRegisterMessage: MessageHandler = async (context) => { ); } + // safety net in case a subnet was locked but the update above was skipped (release is idempotent) + await releaseSubnetLock?.(); + if (client.pubKey && client.pubKey !== publicKey && client.exitNodeId) { // test the old client to see if its different then remove logger.info("Public key mismatch. Deleting old peer..."); From f0b8a594cf95dd58ac56f1ee94520182a4bc73df Mon Sep 17 00:00:00 2001 From: Owen Date: Thu, 15 Oct 2026 11:47:33 -0400 Subject: [PATCH 8/9] Fix deleting orgs --- server/lib/deleteOrg.ts | 22 ++++++++++++++++++++-- server/routers/auth/deleteMyAccount.ts | 9 ++++++++- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/server/lib/deleteOrg.ts b/server/lib/deleteOrg.ts index 131214aab..e396a56ee 100644 --- a/server/lib/deleteOrg.ts +++ b/server/lib/deleteOrg.ts @@ -70,6 +70,10 @@ export async function deleteOrgById( let siteCount: number | null = null; let userCount: number | null = null; let remoteExitNodeCount: number | null = null; + // billingOrgId has no FK, so the billing org may already be gone (e.g. + // a partially failed account deletion). Writing usage for it would + // violate the usage FK and abort the transaction. + let billingOrgExists = false; await db.transaction(async (trx) => { for (const site of orgSites) { @@ -161,7 +165,17 @@ export async function deleteOrgById( .where(inArray(domains.domainId, domainIdsToDelete)); } - await usageService.add(orgId, LimitId.ORGANIZATIONS, -1, trx); // here we are decreasing the org count BEFORE deleting the org because we need to still be able to get the org to get the billing org inside of here + const [billingOrg] = org.billingOrgId + ? await trx + .select({ orgId: orgs.orgId }) + .from(orgs) + .where(eq(orgs.orgId, org.billingOrgId)) + .limit(1) + : [org]; + billingOrgExists = !!billingOrg; + if (billingOrgExists) { + await usageService.add(orgId, LimitId.ORGANIZATIONS, -1, trx); // here we are decreasing the org count BEFORE deleting the org because we need to still be able to get the org to get the billing org inside of here + } await trx.delete(orgs).where(eq(orgs.orgId, orgId)); @@ -205,7 +219,11 @@ export async function deleteOrgById( } }); - if (org.billingOrgId) { + if ( + org.billingOrgId && + org.billingOrgId !== orgId && + billingOrgExists + ) { usageService.updateCount( org.billingOrgId, LimitId.DOMAINS, diff --git a/server/routers/auth/deleteMyAccount.ts b/server/routers/auth/deleteMyAccount.ts index 0a1f74f3e..5892e4ada 100644 --- a/server/routers/auth/deleteMyAccount.ts +++ b/server/routers/auth/deleteMyAccount.ts @@ -182,7 +182,14 @@ export async function deleteMyAccount( const allDeletedNewtIds: string[] = []; const allOlmsToTerminate: string[] = []; - for (const row of ownedOrgsRows) { + // Delete billing orgs last: deleting a child org decrements usage on + // its billing org, which fails (and aborts the transaction) if the + // billing org is already gone. + const orgsInDeleteOrder = [...ownedOrgsRows].sort( + (a, b) => Number(!!a.isBillingOrg) - Number(!!b.isBillingOrg) + ); + + for (const row of orgsInDeleteOrder) { try { const result = await deleteOrgById(row.orgId); allDeletedNewtIds.push(...result.deletedNewtIds); From bb9950a5e8a980facb68ad9977ab7ca4d010345f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 16 Oct 2026 01:32:54 +0000 Subject: [PATCH 9/9] Bump golang.org/x/term Bumps the go-install-dependencies group with 1 update in the /install directory: [golang.org/x/term](https://github.com/golang/term). Updates `golang.org/x/term` from 0.46.0 to 0.47.0 - [Commits](https://github.com/golang/term/compare/v0.46.0...v0.47.0) --- updated-dependencies: - dependency-name: golang.org/x/term dependency-version: 0.47.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: go-install-dependencies ... Signed-off-by: dependabot[bot] --- install/go.mod | 4 ++-- install/go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/install/go.mod b/install/go.mod index cdaa154e1..8507d7b99 100644 --- a/install/go.mod +++ b/install/go.mod @@ -5,7 +5,7 @@ go 1.26.0 require ( github.com/charmbracelet/huh v1.0.0 github.com/charmbracelet/lipgloss v1.1.0 - golang.org/x/term v0.46.0 + golang.org/x/term v0.47.0 gopkg.in/yaml.v3 v3.0.1 ) @@ -33,6 +33,6 @@ require ( github.com/rivo/uniseg v0.4.7 // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect golang.org/x/sync v0.15.0 // indirect - golang.org/x/sys v0.48.0 // indirect + golang.org/x/sys v0.49.0 // indirect golang.org/x/text v0.23.0 // indirect ) diff --git a/install/go.sum b/install/go.sum index cd7266fa1..a7388a521 100644 --- a/install/go.sum +++ b/install/go.sum @@ -69,10 +69,10 @@ golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8= golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.48.0 h1:bbX/i/6MgT9BVLM9RT1thmxL04yeTAhbEz4SyadbXoo= -golang.org/x/sys v0.48.0/go.mod h1:hNLxWAXmnKAxqDtdwIYC4bM9oQPEecfsnNMuSxOs3og= -golang.org/x/term v0.46.0 h1:3+OXuTbaKDgwk8jTi3aSLHRlmWqHEUDUtxnbFigO4YE= -golang.org/x/term v0.46.0/go.mod h1:+K02xbkittuwc0Am4abfA3Fc+XRGXkvBXNO88NCXPoc= +golang.org/x/sys v0.49.0 h1:XbzkgYJHdqh/8m2Uu0W/dQv8nktxx4BFHp1M0gROTXA= +golang.org/x/sys v0.49.0/go.mod h1:hNLxWAXmnKAxqDtdwIYC4bM9oQPEecfsnNMuSxOs3og= +golang.org/x/term v0.47.0 h1:cpyOwQkr2Nzxe2p6KnrTG7Ei2wtzjsuSjhpXXepZ/BM= +golang.org/x/term v0.47.0/go.mod h1:efAfAuG6RKABvg1ArGebRkvX6LXul14SM67o2e4mUCY= golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=