From 0638624a5fddcb3d50ee0d1ebbb25505dd66129f Mon Sep 17 00:00:00 2001 From: "[Armaansaxena]" Date: Sat, 29 Nov 2025 01:05:32 +0530 Subject: [PATCH 1/7] feat: Status Page Custom CSS/JS/HTML overrides (Fixes #2863) --- client/package-lock.json | 619 +++++++++++++++++- .../Create/Components/Tabs/Settings.jsx | 111 ++++ .../src/Pages/v1/StatusPage/Status/index.jsx | 341 ++++++---- client/src/locales/en.json | 11 + server/package-lock.json | 85 +-- server/src/db/v1/models/StatusPage.js | 12 + server/src/validation/joi.js | 67 +- 7 files changed, 1016 insertions(+), 230 deletions(-) diff --git a/client/package-lock.json b/client/package-lock.json index 7cf0e8631..30034ddc8 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -496,6 +496,262 @@ "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==", "license": "MIT" }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.5.tgz", + "integrity": "sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.5.tgz", + "integrity": "sha512-AdJKSPeEHgi7/ZhuIPtcQKr5RQdo6OO2IL87JkianiMYMPbCtot9fxPbrMiBADOWWm3T2si9stAiVsGbTQFkbA==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.5.tgz", + "integrity": "sha512-VGzGhj4lJO+TVGV1v8ntCZWJktV7SGCs3Pn1GRWI1SBFtRALoomm8k5E9Pmwg3HOAal2VDc2F9+PM/rEY6oIDg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.5.tgz", + "integrity": "sha512-D2GyJT1kjvO//drbRT3Hib9XPwQeWd9vZoBJn+bu/lVsOZ13cqNdDeqIF/xQ5/VmWvMduP6AmXvylO/PIc2isw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.5.tgz", + "integrity": "sha512-GtaBgammVvdF7aPIgH2jxMDdivezgFu6iKpmT+48+F8Hhg5J/sfnDieg0aeG/jfSvkYQU2/pceFPDKlqZzwnfQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.5.tgz", + "integrity": "sha512-1iT4FVL0dJ76/q1wd7XDsXrSW+oLoquptvh4CLR4kITDtqi2e/xwXwdCVH8hVHU43wgJdsq7Gxuzcs6Iq/7bxQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.5.tgz", + "integrity": "sha512-nk4tGP3JThz4La38Uy/gzyXtpkPW8zSAmoUhK9xKKXdBCzKODMc2adkB2+8om9BDYugz+uGV7sLmpTYzvmz6Sw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.5.tgz", + "integrity": "sha512-PrikaNjiXdR2laW6OIjlbeuCPrPaAl0IwPIaRv+SMV8CiM8i2LqVUHFC1+8eORgWyY7yhQY+2U2fA55mBzReaw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.5.tgz", + "integrity": "sha512-cPzojwW2okgh7ZlRpcBEtsX7WBuqbLrNXqLU89GxWbNt6uIg78ET82qifUy3W6OVww6ZWobWub5oqZOVtwolfw==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.5.tgz", + "integrity": "sha512-Z9kfb1v6ZlGbWj8EJk9T6czVEjjq2ntSYLY2cw6pAZl4oKtfgQuS4HOq41M/BcoLPzrUbNd+R4BXFyH//nHxVg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.5.tgz", + "integrity": "sha512-sQ7l00M8bSv36GLV95BVAdhJ2QsIbCuCjh/uYrWiMQSUuV+LpXwIqhgJDcvMTj+VsQmqAHL2yYaasENvJ7CDKA==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.5.tgz", + "integrity": "sha512-0ur7ae16hDUC4OL5iEnDb0tZHDxYmuQyhKhsPBV8f99f6Z9KQM02g33f93rNH5A30agMS46u2HP6qTdEt6Q1kg==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.5.tgz", + "integrity": "sha512-kB/66P1OsHO5zLz0i6X0RxlQ+3cu0mkxS3TKFvkb5lin6uwZ/ttOkP3Z8lfR9mJOBk14ZwZ9182SIIWFGNmqmg==", + "cpu": [ + "mips64el" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.5.tgz", + "integrity": "sha512-UZCmJ7r9X2fe2D6jBmkLBMQetXPXIsZjQJCjgwpVDz+YMcS6oFR27alkgGv3Oqkv07bxdvw7fyB71/olceJhkQ==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.5.tgz", + "integrity": "sha512-kTxwu4mLyeOlsVIFPfQo+fQJAV9mh24xL+y+Bm6ej067sYANjyEw1dNHmvoqxJUCMnkBdKpvOn0Ahql6+4VyeA==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.5.tgz", + "integrity": "sha512-K2dSKTKfmdh78uJ3NcWFiqyRrimfdinS5ErLSn3vluHNeHVnBAFWC8a4X5N+7FgVE1EjXS1QDZbpqZBjfrqMTQ==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@esbuild/linux-x64": { "version": "0.25.5", "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.5.tgz", @@ -512,6 +768,134 @@ "node": ">=18" } }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.5.tgz", + "integrity": "sha512-pwHtMP9viAy1oHPvgxtOv+OkduK5ugofNTVDilIzBLpoWAM16r7b/mxBvfpuQDpRQFMfuVr5aLcn4yveGvBZvw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.5.tgz", + "integrity": "sha512-WOb5fKrvVTRMfWFNCroYWWklbnXH0Q5rZppjq0vQIdlsQKuw6mdSihwSo4RV/YdQ5UCKKvBy7/0ZZYLBZKIbwQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.5.tgz", + "integrity": "sha512-7A208+uQKgTxHd0G0uqZO8UjK2R0DDb4fDmERtARjSHWxqMTye4Erz4zZafx7Di9Cv+lNHYuncAkiGFySoD+Mw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.5.tgz", + "integrity": "sha512-G4hE405ErTWraiZ8UiSoesH8DaCsMm0Cay4fsFWOOUcz8b8rC6uCvnagr+gnioEjWn0wC+o1/TAHt+It+MpIMg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.5.tgz", + "integrity": "sha512-l+azKShMy7FxzY0Rj4RCt5VD/q8mG/e+mDivgspo+yL8zW7qEwctQ6YqKX34DTEleFAvCIUviCFX1SDZRSyMQA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.5.tgz", + "integrity": "sha512-O2S7SNZzdcFG7eFKgvwUEZ2VG9D/sn/eIiz8XRZ1Q/DO5a3s76Xv0mdBzVM5j5R639lXQmPmSo0iRpHqUUrsxw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.5.tgz", + "integrity": "sha512-onOJ02pqs9h1iMJ1PQphR+VZv8qBMQ77Klcsqv9CNW2w6yLqoURLcgERAIurY6QE63bbLuqgP9ATqajFLK5AMQ==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.5.tgz", + "integrity": "sha512-TXv6YnJ8ZMVdX+SXWVBo/0p8LTcrUYngpWjvm91TMjjBQii7Oz11Lw5lbDV5Y0TzuhSJHwiH4hEtC1I42mMS0g==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.7.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz", @@ -1696,6 +2080,123 @@ } } }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.41.0.tgz", + "integrity": "sha512-KxN+zCjOYHGwCl4UCtSfZ6jrq/qi88JDUtiEFk8LELEHq2Egfc/FgW+jItZiOLRuQfb/3xJSgFuNPC9jzggX+A==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.41.0.tgz", + "integrity": "sha512-yDvqx3lWlcugozax3DItKJI5j05B0d4Kvnjx+5mwiUpWramVvmAByYigMplaoAQ3pvdprGCTCE03eduqE/8mPQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.41.0.tgz", + "integrity": "sha512-2KOU574vD3gzcPSjxO0eyR5iWlnxxtmW1F5CkNOHmMlueKNCQkxR6+ekgWyVnz6zaZihpUNkGxjsYrkTJKhkaw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.41.0.tgz", + "integrity": "sha512-gE5ACNSxHcEZyP2BA9TuTakfZvULEW4YAOtxl/A/YDbIir/wPKukde0BNPlnBiP88ecaN4BJI2TtAd+HKuZPQQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.41.0.tgz", + "integrity": "sha512-GSxU6r5HnWij7FoSo7cZg3l5GPg4HFLkzsFFh0N/b16q5buW1NAWuCJ+HMtIdUEi6XF0qH+hN0TEd78laRp7Dg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.41.0.tgz", + "integrity": "sha512-KGiGKGDg8qLRyOWmk6IeiHJzsN/OYxO6nSbT0Vj4MwjS2XQy/5emsmtoqLAabqrohbgLWJ5GV3s/ljdrIr8Qjg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.41.0.tgz", + "integrity": "sha512-46OzWeqEVQyX3N2/QdiU/CMXYDH/lSHpgfBkuhl3igpZiaB3ZIfSjKuOnybFVBQzjsLwkus2mjaESy8H41SzvA==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.41.0.tgz", + "integrity": "sha512-lfgW3KtQP4YauqdPpcUZHPcqQXmTmH4nYU0cplNeW583CMkAGjtImw4PKli09NFi2iQgChk4e9erkwlfYem6Lg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.41.0.tgz", + "integrity": "sha512-nn8mEyzMbdEJzT7cwxgObuwviMx6kPRxzYiOl6o/o+ChQq23gfdlZcUNnt89lPhhz3BYsZ72rp0rxNqBSfqlqw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, "node_modules/@rollup/rollup-linux-arm64-musl": { "version": "4.41.0", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.41.0.tgz", @@ -1709,6 +2210,71 @@ "linux" ] }, + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.41.0.tgz", + "integrity": "sha512-WbnJaxPv1gPIm6S8O/Wg+wfE/OzGSXlBMbOe4ie+zMyykMOeqmgD1BhPxZQuDqwUN+0T/xOFtL2RUWBspnZj3w==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.41.0.tgz", + "integrity": "sha512-eRDWR5t67/b2g8Q/S8XPi0YdbKcCs4WQ8vklNnUYLaSWF+Cbv2axZsp4jni6/j7eKvMLYCYdcsv8dcU+a6QNFg==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.41.0.tgz", + "integrity": "sha512-TWrZb6GF5jsEKG7T1IHwlLMDRy2f3DPqYldmIhnA2DVqvvhY2Ai184vZGgahRrg8k9UBWoSlHv+suRfTN7Ua4A==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.41.0.tgz", + "integrity": "sha512-ieQljaZKuJpmWvd8gW87ZmSFwid6AxMDk5bhONJ57U8zT77zpZ/TPKkU9HpnnFrM4zsgr4kiGuzbIbZTGi7u9A==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.41.0.tgz", + "integrity": "sha512-/L3pW48SxrWAlVsKCN0dGLB2bi8Nv8pr5S5ocSM+S0XCn5RCVCXqi8GVtHFsOBBCSeR+u9brV2zno5+mg3S4Aw==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, "node_modules/@rollup/rollup-linux-x64-gnu": { "version": "4.41.0", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.41.0.tgz", @@ -1735,6 +2301,45 @@ "linux" ] }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.41.0.tgz", + "integrity": "sha512-4yodtcOrFHpbomJGVEqZ8fzD4kfBeCbpsUy5Pqk4RluXOdsWdjLnjhiKy2w3qzcASWd04fp52Xz7JKarVJ5BTg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.41.0.tgz", + "integrity": "sha512-tmazCrAsKzdkXssEc65zIE1oC6xPHwfy9d5Ta25SRCDOZS+I6RypVVShWALNuU9bxIfGA0aqrmzlzoM5wO5SPQ==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.41.0.tgz", + "integrity": "sha512-h1J+Yzjo/X+0EAvR2kIXJDuTuyT7drc+t2ALY0nIcGPbTatNOf0VWdhEA2Z4AAjv6X1NJV7SYo5oCTYRJhSlVA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/@sideway/address": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz", @@ -6744,20 +7349,6 @@ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", "license": "ISC" }, - "node_modules/yaml": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.0.tgz", - "integrity": "sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==", - "license": "ISC", - "optional": true, - "peer": true, - "bin": { - "yaml": "bin.mjs" - }, - "engines": { - "node": ">= 14.6" - } - }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/client/src/Pages/v1/StatusPage/Create/Components/Tabs/Settings.jsx b/client/src/Pages/v1/StatusPage/Create/Components/Tabs/Settings.jsx index a668a9db8..11dca7ed0 100644 --- a/client/src/Pages/v1/StatusPage/Create/Components/Tabs/Settings.jsx +++ b/client/src/Pages/v1/StatusPage/Create/Components/Tabs/Settings.jsx @@ -8,6 +8,7 @@ import Search from "@/Components/v1/Inputs/Search/index.jsx"; import ImageUpload from "@/Components/v1/Inputs/ImageUpload/index.jsx"; import ColorPicker from "@/Components/v1/Inputs/ColorPicker/index.jsx"; import Progress from "../Progress/index.jsx"; +import TextField from "@mui/material/TextField"; // Utils import { useTheme } from "@emotion/react"; @@ -168,6 +169,116 @@ const TabSettings = ({ /> + {/* --- CUSTOM CSS SECTION --- */} + + + + {t("customCSS")} + + + {t("statusPageCreateCustomCSSDescription")} + + + + + + + + {/* --- CUSTOM HEADER HTML SECTION --- */} + + + + {t("customHeaderHTML")} + + + {t("statusPageCreateCustomHeaderHTMLDescription")} + + + + + + + + {/* --- CUSTOM FOOTER HTML SECTION --- */} + + + + {t("customFooterHTML")} + + + {t("statusPageCreateCustomFooterHTMLDescription")} + + + + + + + + {/* --- CUSTOM JAVASCRIPT SECTION --- */} + + + + {t("customJavaScript")} + + + {t("statusPageCreateCustomJavaScriptDescription")} + + + + + + ); diff --git a/client/src/Pages/v1/StatusPage/Status/index.jsx b/client/src/Pages/v1/StatusPage/Status/index.jsx index e700bbbd9..1238aebdb 100644 --- a/client/src/Pages/v1/StatusPage/Status/index.jsx +++ b/client/src/Pages/v1/StatusPage/Status/index.jsx @@ -16,151 +16,204 @@ import { useIsAdmin } from "../../../../Hooks/v1/useIsAdmin.js"; import { useLocation } from "react-router-dom"; import { useParams } from "react-router-dom"; import { useTranslation } from "react-i18next"; +// --- CHANGE 1: Import useEffect --- +import { useEffect } from "react"; const PublicStatus = () => { - const { url } = useParams(); - - // Utils - const theme = useTheme(); - const { t } = useTranslation(); - const location = useLocation(); - const isAdmin = useIsAdmin(); - - const [statusPage, monitors, isLoading, networkError] = useStatusPageFetch(false, url); - - // Breadcrumbs - const crumbs = [ - { name: t("statusBreadCrumbsStatusPages"), path: "/status" }, - { name: t("statusBreadCrumbsDetails"), path: `/status/uptime/${statusPage?.url}` }, - ]; - - // Setup - let sx = { paddingLeft: theme.spacing(20), paddingRight: theme.spacing(20) }; - let link = undefined; - const isPublic = location.pathname.startsWith("/status/uptime/public"); - // Public status page - if (isPublic && statusPage && statusPage.showAdminLoginLink === true) { - sx = { - paddingTop: theme.spacing(20), - paddingLeft: "20vw", - paddingRight: "20vw", - }; - link = ; - } - - // Loading - if (isLoading) { - return ; - } - - if (monitors?.length === 0) { - return ( - - - {"Theres nothing here yet"} - - {isAdmin && ( - - )} - - ); - } - - // Error fetching data - if (networkError === true) { - return ( - - - {t("common.toasts.networkError")} - - {t("common.toasts.checkConnection")} - - ); - } - - // Public status page fallback - if (!isLoading && typeof statusPage === "undefined" && isPublic) { - return ( - - - - {t("statusPageStatus")} - - {t("statusPageStatusContactAdmin")} - - - ); - } - - // Finished loading, but status page is not public - if (!isLoading && isPublic && statusPage.isPublished === false) { - return ( - - - - {t("statusPageStatusNotPublic")} - - {t("statusPageStatusContactAdmin")} - - - ); - } - - // Status page doesn't exist - if (!isLoading && typeof statusPage === "undefined") { - return ( - - - {t("statusPageStatusNoPage")} - - {t("statusPageStatusContactAdmin")} - - ); - } - - return ( - - {!isPublic && } - - {t("statusPageStatusServiceStatus")} - - - {link} - - ); + const { url } = useParams(); + + // Utils + const theme = useTheme(); + const { t } = useTranslation(); + const location = useLocation(); + const isAdmin = useIsAdmin(); + + const [statusPage, monitors, isLoading, networkError] = useStatusPageFetch(false, url); + + // --- CHANGE 2: Inject Custom CSS and JS --- + useEffect(() => { + if (!statusPage) return; + + // Inject Custom CSS + if (statusPage.customCSS) { + const style = document.createElement('style'); + style.id = 'custom-status-css'; + style.innerHTML = statusPage.customCSS; + document.head.appendChild(style); + } + + // Inject Custom JS + if (statusPage.customJavaScript) { + try { + const script = document.createElement('script'); + script.id = 'custom-status-js'; + // Wrap in IIFE to avoid global scope pollution + script.text = `(function() { ${statusPage.customJavaScript} })();`; + document.body.appendChild(script); + } catch (e) { + console.error("Custom JS execution failed:", e); + } + } + + // Cleanup on unmount + return () => { + const css = document.getElementById('custom-status-css'); + const js = document.getElementById('custom-status-js'); + if (css) css.remove(); + if (js) js.remove(); + }; + }, [statusPage]); + // ------------------------------------------ + + // Breadcrumbs + const crumbs = [ + { name: t("statusBreadCrumbsStatusPages"), path: "/status" }, + { name: t("statusBreadCrumbsDetails"), path: `/status/uptime/${statusPage?.url}` }, + ]; + + // Setup + let sx = { paddingLeft: theme.spacing(20), paddingRight: theme.spacing(20) }; + let link = undefined; + const isPublic = location.pathname.startsWith("/status/uptime/public"); + + // Public status page + if (isPublic && statusPage && statusPage.showAdminLoginLink === true) { + sx = { + paddingTop: theme.spacing(20), + paddingLeft: "20vw", + paddingRight: "20vw", + }; + link = ; + } + + // Loading + if (isLoading) { + return ; + } + + if (monitors?.length === 0) { + return ( + + + {"Theres nothing here yet"} + + {isAdmin && ( + + )} + + ); + } + + // Error fetching data + if (networkError === true) { + return ( + + + {t("common.toasts.networkError")} + + {t("common.toasts.checkConnection")} + + ); + } + + // Public status page fallback + if (!isLoading && typeof statusPage === "undefined" && isPublic) { + return ( + + + + {t("statusPageStatus")} + + {t("statusPageStatusContactAdmin")} + + + ); + } + + // Finished loading, but status page is not public + if (!isLoading && isPublic && statusPage.isPublished === false) { + return ( + + + + {t("statusPageStatusNotPublic")} + + {t("statusPageStatusContactAdmin")} + + + ); + } + + // Status page doesn't exist + if (!isLoading && typeof statusPage === "undefined") { + return ( + + + {t("statusPageStatusNoPage")} + + {t("statusPageStatusContactAdmin")} + + ); + } + + return ( + + {!isPublic && } + + {/* --- CHANGE 3: Custom Header Logic --- */} + {statusPage?.headerHTML ? ( +
+ ) : ( + + )} + {/* ------------------------------------- */} + + {t("statusPageStatusServiceStatus")} + + + + {link} + + {/* --- CHANGE 4: Custom Footer Logic --- */} + {statusPage?.footerHTML && ( +
+ )} + {/* ------------------------------------- */} + + ); }; -export default PublicStatus; +export default PublicStatus; \ No newline at end of file diff --git a/client/src/locales/en.json b/client/src/locales/en.json index e2e1a4faf..8e5a45389 100644 --- a/client/src/locales/en.json +++ b/client/src/locales/en.json @@ -1,4 +1,15 @@ { + "customCSS": "Custom CSS", + "statusPageCreateCustomCSSDescription": "Override the default styling with your own CSS.", + "statusPageCreateCustomCSSPlaceholder": ".body { background: #000; }", + "customHeaderHTML": "Custom Header HTML", + "statusPageCreateCustomHeaderHTMLDescription": "Replace the default header with your own HTML code.", + "statusPageCreateCustomHeaderHTMLPlaceholder": "
My Custom Header
", + "customFooterHTML": "Custom Footer HTML", + "statusPageCreateCustomFooterHTMLDescription": "Replace the default footer with your own HTML code.", + "customJavaScript": "Custom JavaScript", + "statusPageCreateCustomJavaScriptDescription": "Execute custom JavaScript code on the public status page.", + "statusPageCreateCustomJavaScriptWarning": "Warning: This code executes on the public page.", "submit": "Submit", "title": "Title", "distributedStatusHeaderText": "Real-time, real-device coverage", diff --git a/server/package-lock.json b/server/package-lock.json index 8de5cfc0c..c5d92d8bb 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -13,6 +13,7 @@ "axios": "^1.7.2", "bcryptjs": "3.0.2", "bullmq": "5.41.2", + "cacheable-lookup": "7.0.0", "compression": "1.8.1", "cookie-parser": "^1.4.7", "cors": "^2.8.5", @@ -39,7 +40,7 @@ "ping": "0.4.4", "sharp": "0.33.5", "ssl-checker": "2.0.10", - "super-simple-scheduler": "1.4.4", + "super-simple-scheduler": "1.4.5", "swagger-ui-express": "5.0.1", "winston": "^3.13.0" }, @@ -9325,6 +9326,40 @@ "postcss": "^8.4.32" } }, + "node_modules/postcss-svgo/node_modules/commander": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", + "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", + "license": "MIT", + "engines": { + "node": ">=16" + } + }, + "node_modules/postcss-svgo/node_modules/svgo": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-4.0.0.tgz", + "integrity": "sha512-VvrHQ+9uniE+Mvx3+C9IEe/lWasXCU0nXMY2kZeLrHNICuRiC8uMPyM14UEaMOFA5mhyQqEkB02VoQ16n3DLaw==", + "license": "MIT", + "dependencies": { + "commander": "^11.1.0", + "css-select": "^5.1.0", + "css-tree": "^3.0.1", + "css-what": "^6.1.0", + "csso": "^5.0.5", + "picocolors": "^1.1.1", + "sax": "^1.4.1" + }, + "bin": { + "svgo": "bin/svgo.js" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/svgo" + } + }, "node_modules/postcss-unique-selectors": { "version": "7.0.4", "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-7.0.4.tgz", @@ -9774,10 +9809,10 @@ "license": "MIT" }, "node_modules/sax": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz", - "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==", - "license": "ISC" + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.3.tgz", + "integrity": "sha512-yqYn1JhPczigF94DMS+shiDMjDowYO6y9+wB/4WgO0Y19jWYk0lQ4tuG5KI7kj4FTp1wxPj5IFfcrz/s1c3jjQ==", + "license": "BlueOak-1.0.0" }, "node_modules/saxes": { "version": "6.0.0", @@ -10397,9 +10432,9 @@ } }, "node_modules/super-simple-scheduler": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/super-simple-scheduler/-/super-simple-scheduler-1.4.4.tgz", - "integrity": "sha512-o99zHFrrFECwj1FFE+ns4su5hKSYif+SUNB3fUJbaRWGkwLLoj19TRuRJYp72TYt9eDiCii68UBuKiLF4tlkMg==", + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/super-simple-scheduler/-/super-simple-scheduler-1.4.5.tgz", + "integrity": "sha512-xHR3a+pLywY7HtwjWj/+OaANdEwAieQhckC/i0s6OoHtVJQ4+VLO1VfkKUbxp+oSROGXcaZet8PTGONVdKNUUw==", "license": "MIT", "dependencies": { "uuid": "11.1.0", @@ -10466,40 +10501,6 @@ "node": ">=8" } }, - "node_modules/svgo": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/svgo/-/svgo-4.0.0.tgz", - "integrity": "sha512-VvrHQ+9uniE+Mvx3+C9IEe/lWasXCU0nXMY2kZeLrHNICuRiC8uMPyM14UEaMOFA5mhyQqEkB02VoQ16n3DLaw==", - "license": "MIT", - "dependencies": { - "commander": "^11.1.0", - "css-select": "^5.1.0", - "css-tree": "^3.0.1", - "css-what": "^6.1.0", - "csso": "^5.0.5", - "picocolors": "^1.1.1", - "sax": "^1.4.1" - }, - "bin": { - "svgo": "bin/svgo.js" - }, - "engines": { - "node": ">=16" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/svgo" - } - }, - "node_modules/svgo/node_modules/commander": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", - "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", - "license": "MIT", - "engines": { - "node": ">=16" - } - }, "node_modules/swagger-ui-dist": { "version": "5.30.0", "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.30.0.tgz", diff --git a/server/src/db/v1/models/StatusPage.js b/server/src/db/v1/models/StatusPage.js index 621749fb8..63217bf7e 100755 --- a/server/src/db/v1/models/StatusPage.js +++ b/server/src/db/v1/models/StatusPage.js @@ -78,6 +78,18 @@ const StatusPageSchema = mongoose.Schema( type: String, default: "", }, + customJavaScript: { + type: String, + default: "", + }, + headerHTML: { + type: String, + default: "", + }, + footerHTML: { + type: String, + default: "", + }, }, { timestamps: true } ); diff --git a/server/src/validation/joi.js b/server/src/validation/joi.js index bc88d4567..0cedf83b6 100755 --- a/server/src/validation/joi.js +++ b/server/src/validation/joi.js @@ -449,36 +449,43 @@ const getStatusPageQueryValidation = joi.object({ }); const createStatusPageBodyValidation = joi.object({ - type: joi.string().valid("uptime").required(), - companyName: joi.string().required(), - url: joi - .string() - .pattern(/^[a-zA-Z0-9_-]+$/) // Only allow alphanumeric, underscore, and hyphen - .required() - .messages({ - "string.pattern.base": "URL can only contain letters, numbers, underscores, and hyphens", - }), - timezone: joi.string().optional(), - color: joi.string().optional(), - monitors: joi - .array() - .items(joi.string().pattern(/^[0-9a-fA-F]{24}$/)) - .required() - .messages({ - "string.pattern.base": "Must be a valid monitor ID", - "array.base": "Monitors must be an array", - "array.empty": "At least one monitor is required", - "any.required": "Monitors are required", - }), - subMonitors: joi - .array() - .items(joi.string().pattern(/^[0-9a-fA-F]{24}$/)) - .optional(), - deleteSubmonitors: joi.boolean().optional(), - isPublished: joi.boolean(), - showCharts: joi.boolean().optional(), - showUptimePercentage: joi.boolean(), - showAdminLoginLink: joi.boolean().optional(), + type: joi.string().valid("uptime").required(), + companyName: joi.string().required(), + url: joi + .string() + .pattern(/^[a-zA-Z0-9_-]+$/) + .required() + .messages({ + "string.pattern.base": "URL can only contain letters, numbers, underscores, and hyphens", + }), + timezone: joi.string().optional(), + color: joi.string().optional(), + monitors: joi + .array() + .items(joi.string().pattern(/^[0-9a-fA-F]{24}$/)) + .required() + .messages({ + "string.pattern.base": "Must be a valid monitor ID", + "array.base": "Monitors must be an array", + "array.empty": "At least one monitor is required", + "any.required": "Monitors are required", + }), + subMonitors: joi + .array() + .items(joi.string().pattern(/^[0-9a-fA-F]{24}$/)) + .optional(), + deleteSubmonitors: joi.boolean().optional(), + isPublished: joi.boolean(), + showCharts: joi.boolean().optional(), + showUptimePercentage: joi.boolean(), + showAdminLoginLink: joi.boolean().optional(), + + // --- ADD THESE 4 LINES --- + customCSS: joi.string().allow("").optional(), + customJavaScript: joi.string().allow("").optional(), + headerHTML: joi.string().allow("").optional(), + footerHTML: joi.string().allow("").optional(), + // ------------------------- }); const imageValidation = joi From f2f2469347acfb68688ae77953408620a91179e6 Mon Sep 17 00:00:00 2001 From: Armaansaxena <143505186+Armaansaxena@users.noreply.github.com> Date: Sat, 29 Nov 2025 14:56:13 +0530 Subject: [PATCH 2/7] Update server/src/validation/joi.js Co-authored-by: llamapreview[bot] <184758061+llamapreview[bot]@users.noreply.github.com> --- server/src/validation/joi.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/server/src/validation/joi.js b/server/src/validation/joi.js index 0cedf83b6..fa99c312f 100755 --- a/server/src/validation/joi.js +++ b/server/src/validation/joi.js @@ -481,10 +481,10 @@ const createStatusPageBodyValidation = joi.object({ showAdminLoginLink: joi.boolean().optional(), // --- ADD THESE 4 LINES --- - customCSS: joi.string().allow("").optional(), - customJavaScript: joi.string().allow("").optional(), - headerHTML: joi.string().allow("").optional(), - footerHTML: joi.string().allow("").optional(), +customCSS: joi.string().allow("").max(10000).optional(), +customJavaScript: joi.string().allow("").max(5000).optional(), +headerHTML: joi.string().allow("").max(5000).optional(), +footerHTML: joi.string().allow("").max(5000).optional(), // ------------------------- }); From 9e7f44efcb8e9bcb23a86ae6dc8e16bfdb993ca9 Mon Sep 17 00:00:00 2001 From: "[Armaansaxena]" Date: Mon, 1 Dec 2025 00:00:01 +0530 Subject: [PATCH 3/7] fix: address security review - safe useEffect cleanup, dompurify sanitization, and validation limits --- client/package-lock.json | 31 +- client/package.json | 3 +- .../Create/Components/Tabs/Settings.jsx | 2 + .../src/Pages/v1/StatusPage/Status/index.jsx | 376 +++++++++--------- client/src/locales/en.json | 2 +- server/src/db/v1/models/StatusPage.js | 24 +- server/src/db/v1/modules/statusPageModule.js | 28 +- server/src/validation/joi.js | 72 ++-- 8 files changed, 300 insertions(+), 238 deletions(-) diff --git a/client/package-lock.json b/client/package-lock.json index 30034ddc8..022a70b8e 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -20,6 +20,7 @@ "@reduxjs/toolkit": "2.7.0", "axios": "^1.7.4", "dayjs": "1.11.13", + "dompurify": "^3.3.0", "flag-icons": "7.3.2", "html2canvas": "^1.4.1", "human-interval": "2.0.1", @@ -53,7 +54,7 @@ "eslint-plugin-react-refresh": "^0.4.6", "prettier": "^3.3.3", "typescript": "5.9.3", - "vite": "6.3.6" + "vite": "^6.4.1" } }, "node_modules/@ampproject/remapping": { @@ -2783,6 +2784,13 @@ "@types/react": "*" } }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "license": "MIT", + "optional": true + }, "node_modules/@types/use-sync-external-store": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz", @@ -3658,6 +3666,15 @@ "csstype": "^3.0.2" } }, + "node_modules/dompurify": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.3.0.tgz", + "integrity": "sha512-r+f6MYR1gGN1eJv0TVQbhA7if/U7P87cdPl3HN5rikqaBSBxLiCb/b9O+2eG0cxz0ghyU+mU1QkbsOwERMYlWQ==", + "license": "(MPL-2.0 OR Apache-2.0)", + "optionalDependencies": { + "@types/trusted-types": "^2.0.7" + } + }, "node_modules/dot-case": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", @@ -5290,9 +5307,9 @@ "license": "MIT" }, "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", "license": "MIT", "dependencies": { "argparse": "^2.0.1" @@ -7125,9 +7142,9 @@ } }, "node_modules/vite": { - "version": "6.3.6", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.6.tgz", - "integrity": "sha512-0msEVHJEScQbhkbVTb/4iHZdJ6SXp/AvxL2sjwYQFfBqleHtnCqv1J3sa9zbWz/6kW1m9Tfzn92vW+kZ1WV6QA==", + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz", + "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==", "license": "MIT", "dependencies": { "esbuild": "^0.25.0", diff --git a/client/package.json b/client/package.json index 376671942..77d6dcbce 100644 --- a/client/package.json +++ b/client/package.json @@ -25,6 +25,7 @@ "@reduxjs/toolkit": "2.7.0", "axios": "^1.7.4", "dayjs": "1.11.13", + "dompurify": "^3.3.0", "flag-icons": "7.3.2", "html2canvas": "^1.4.1", "human-interval": "2.0.1", @@ -58,6 +59,6 @@ "eslint-plugin-react-refresh": "^0.4.6", "prettier": "^3.3.3", "typescript": "5.9.3", - "vite": "6.3.6" + "vite": "^6.4.1" } } diff --git a/client/src/Pages/v1/StatusPage/Create/Components/Tabs/Settings.jsx b/client/src/Pages/v1/StatusPage/Create/Components/Tabs/Settings.jsx index 11dca7ed0..45f17e90b 100644 --- a/client/src/Pages/v1/StatusPage/Create/Components/Tabs/Settings.jsx +++ b/client/src/Pages/v1/StatusPage/Create/Components/Tabs/Settings.jsx @@ -269,6 +269,8 @@ const TabSettings = ({ { - const { url } = useParams(); + const { url } = useParams(); - // Utils - const theme = useTheme(); - const { t } = useTranslation(); - const location = useLocation(); - const isAdmin = useIsAdmin(); + // Utils + const theme = useTheme(); + const { t } = useTranslation(); + const location = useLocation(); + const isAdmin = useIsAdmin(); - const [statusPage, monitors, isLoading, networkError] = useStatusPageFetch(false, url); + const [statusPage, monitors, isLoading, networkError] = useStatusPageFetch(false, url); - // --- CHANGE 2: Inject Custom CSS and JS --- - useEffect(() => { + // --- CHANGE 2: Inject Custom CSS and JS --- + useEffect(() => { if (!statusPage) return; - // Inject Custom CSS + // --- 1. Inject Custom CSS --- + let styleElement = null; if (statusPage.customCSS) { - const style = document.createElement('style'); - style.id = 'custom-status-css'; - style.innerHTML = statusPage.customCSS; - document.head.appendChild(style); + styleElement = document.createElement("style"); + styleElement.id = "custom-status-css"; + styleElement.innerHTML = statusPage.customCSS; + document.head.appendChild(styleElement); } - // Inject Custom JS + // --- 2. Inject Custom JS --- + let scriptElement = null; if (statusPage.customJavaScript) { try { - const script = document.createElement('script'); - script.id = 'custom-status-js'; - // Wrap in IIFE to avoid global scope pollution - script.text = `(function() { ${statusPage.customJavaScript} })();`; - document.body.appendChild(script); + scriptElement = document.createElement("script"); + scriptElement.id = "custom-status-js"; + + // Wrap in IIFE (Immediately Invoked Function Expression) with internal try/catch + // This ensures that if the user writes bad JS, it errors to console but doesn't crash the UI + scriptElement.textContent = ` + (function() { + try { + ${statusPage.customJavaScript} + } catch(e) { + console.error('Custom Status Page Script Error:', e); + } + })(); + `; + + document.body.appendChild(scriptElement); } catch (e) { - console.error("Custom JS execution failed:", e); + console.error("Failed to inject custom JS element:", e); } } - // Cleanup on unmount + // --- 3. Safe Cleanup --- return () => { - const css = document.getElementById('custom-status-css'); - const js = document.getElementById('custom-status-js'); - if (css) css.remove(); - if (js) js.remove(); + // Clean up CSS using the direct reference + if (styleElement && document.head.contains(styleElement)) { + document.head.removeChild(styleElement); + } + + // Clean up JS using the direct reference + if (scriptElement && document.body.contains(scriptElement)) { + document.body.removeChild(scriptElement); + } }; }, [statusPage]); - // ------------------------------------------ - - // Breadcrumbs - const crumbs = [ - { name: t("statusBreadCrumbsStatusPages"), path: "/status" }, - { name: t("statusBreadCrumbsDetails"), path: `/status/uptime/${statusPage?.url}` }, - ]; - - // Setup - let sx = { paddingLeft: theme.spacing(20), paddingRight: theme.spacing(20) }; - let link = undefined; - const isPublic = location.pathname.startsWith("/status/uptime/public"); - - // Public status page - if (isPublic && statusPage && statusPage.showAdminLoginLink === true) { - sx = { - paddingTop: theme.spacing(20), - paddingLeft: "20vw", - paddingRight: "20vw", - }; - link = ; - } - - // Loading - if (isLoading) { - return ; - } - - if (monitors?.length === 0) { - return ( - - - {"Theres nothing here yet"} - - {isAdmin && ( - - )} - - ); - } - - // Error fetching data - if (networkError === true) { - return ( - - - {t("common.toasts.networkError")} - - {t("common.toasts.checkConnection")} - - ); - } - - // Public status page fallback - if (!isLoading && typeof statusPage === "undefined" && isPublic) { - return ( - - - - {t("statusPageStatus")} - - {t("statusPageStatusContactAdmin")} - - - ); - } - - // Finished loading, but status page is not public - if (!isLoading && isPublic && statusPage.isPublished === false) { - return ( - - - - {t("statusPageStatusNotPublic")} - - {t("statusPageStatusContactAdmin")} - - - ); - } - - // Status page doesn't exist - if (!isLoading && typeof statusPage === "undefined") { - return ( - - - {t("statusPageStatusNoPage")} - - {t("statusPageStatusContactAdmin")} - - ); - } - - return ( - - {!isPublic && } - - {/* --- CHANGE 3: Custom Header Logic --- */} - {statusPage?.headerHTML ? ( -
- ) : ( - - )} - {/* ------------------------------------- */} - - {t("statusPageStatusServiceStatus")} - - - - {link} - - {/* --- CHANGE 4: Custom Footer Logic --- */} - {statusPage?.footerHTML && ( -
- )} - {/* ------------------------------------- */} - - ); + // ------------------------------------------ + + // Breadcrumbs + const crumbs = [ + { name: t("statusBreadCrumbsStatusPages"), path: "/status" }, + { name: t("statusBreadCrumbsDetails"), path: `/status/uptime/${statusPage?.url}` }, + ]; + + // Setup + let sx = { paddingLeft: theme.spacing(20), paddingRight: theme.spacing(20) }; + let link = undefined; + const isPublic = location.pathname.startsWith("/status/uptime/public"); + + // Public status page + if (isPublic && statusPage && statusPage.showAdminLoginLink === true) { + sx = { + paddingTop: theme.spacing(20), + paddingLeft: "20vw", + paddingRight: "20vw", + }; + link = ; + } + + // Loading + if (isLoading) { + return ; + } + + if (monitors?.length === 0) { + return ( + + + {"Theres nothing here yet"} + + {isAdmin && ( + + )} + + ); + } + + // Error fetching data + if (networkError === true) { + return ( + + + {t("common.toasts.networkError")} + + {t("common.toasts.checkConnection")} + + ); + } + + // Public status page fallback + if (!isLoading && typeof statusPage === "undefined" && isPublic) { + return ( + + + + {t("statusPageStatus")} + + {t("statusPageStatusContactAdmin")} + + + ); + } + + // Finished loading, but status page is not public + if (!isLoading && isPublic && statusPage.isPublished === false) { + return ( + + + + {t("statusPageStatusNotPublic")} + + {t("statusPageStatusContactAdmin")} + + + ); + } + + // Status page doesn't exist + if (!isLoading && typeof statusPage === "undefined") { + return ( + + + {t("statusPageStatusNoPage")} + + {t("statusPageStatusContactAdmin")} + + ); + } + + return ( + + {!isPublic && } + + {/* --- CHANGE 3: Custom Header Logic --- */} + {statusPage?.headerHTML ? ( +
+ ) : ( + + )} + {/* ------------------------------------- */} + + {t("statusPageStatusServiceStatus")} + + + + {link} + + {/* --- CHANGE 4: Custom Footer Logic --- */} + {statusPage?.footerHTML && ( +
+ )} + {/* ------------------------------------- */} + + ); }; -export default PublicStatus; \ No newline at end of file +export default PublicStatus; diff --git a/client/src/locales/en.json b/client/src/locales/en.json index 8e5a45389..164a576ee 100644 --- a/client/src/locales/en.json +++ b/client/src/locales/en.json @@ -1,7 +1,7 @@ { "customCSS": "Custom CSS", "statusPageCreateCustomCSSDescription": "Override the default styling with your own CSS.", - "statusPageCreateCustomCSSPlaceholder": ".body { background: #000; }", + "statusPageCreateCustomCSSPlaceholder": "body { background: #000; }", "customHeaderHTML": "Custom Header HTML", "statusPageCreateCustomHeaderHTMLDescription": "Replace the default header with your own HTML code.", "statusPageCreateCustomHeaderHTMLPlaceholder": "
My Custom Header
", diff --git a/server/src/db/v1/models/StatusPage.js b/server/src/db/v1/models/StatusPage.js index 63217bf7e..bd77e0f23 100755 --- a/server/src/db/v1/models/StatusPage.js +++ b/server/src/db/v1/models/StatusPage.js @@ -79,17 +79,19 @@ const StatusPageSchema = mongoose.Schema( default: "", }, customJavaScript: { - type: String, - default: "", - }, - headerHTML: { - type: String, - default: "", - }, - footerHTML: { - type: String, - default: "", - }, + type: String, + default: "", + }, + headerHTML: { + type: String, + default: "", + maxLength: 10000, + }, + footerHTML: { + type: String, + default: "", + maxLength: 10000, + }, }, { timestamps: true } ); diff --git a/server/src/db/v1/modules/statusPageModule.js b/server/src/db/v1/modules/statusPageModule.js index aadde0c81..9ee459863 100755 --- a/server/src/db/v1/modules/statusPageModule.js +++ b/server/src/db/v1/modules/statusPageModule.js @@ -97,8 +97,24 @@ class StatusPageModule { } if (!preliminaryStatusPage.monitors || preliminaryStatusPage.monitors.length === 0) { - const { _id, color, companyName, isPublished, logo, originalMonitors, showCharts, showUptimePercentage, timezone, showAdminLoginLink, url } = - preliminaryStatusPage; + const { + _id, + color, + companyName, + isPublished, + logo, + originalMonitors, + showCharts, + showUptimePercentage, + timezone, + showAdminLoginLink, + url, + customCSS, + customJavaScript, + headerHTML, + footerHTML, + } = preliminaryStatusPage; + return { statusPage: { _id, @@ -112,6 +128,10 @@ class StatusPageModule { timezone, showAdminLoginLink, url, + customCSS, + customJavaScript, + headerHTML, + footerHTML, }, monitors: [], }; @@ -218,6 +238,10 @@ class StatusPageModule { timezone: 1, showAdminLoginLink: 1, url: 1, + customCSS: 1, + customJavaScript: 1, + headerHTML: 1, + footerHTML: 1, }, monitors: { _id: 1, diff --git a/server/src/validation/joi.js b/server/src/validation/joi.js index 0cedf83b6..dfd7449cf 100755 --- a/server/src/validation/joi.js +++ b/server/src/validation/joi.js @@ -449,43 +449,41 @@ const getStatusPageQueryValidation = joi.object({ }); const createStatusPageBodyValidation = joi.object({ - type: joi.string().valid("uptime").required(), - companyName: joi.string().required(), - url: joi - .string() - .pattern(/^[a-zA-Z0-9_-]+$/) - .required() - .messages({ - "string.pattern.base": "URL can only contain letters, numbers, underscores, and hyphens", - }), - timezone: joi.string().optional(), - color: joi.string().optional(), - monitors: joi - .array() - .items(joi.string().pattern(/^[0-9a-fA-F]{24}$/)) - .required() - .messages({ - "string.pattern.base": "Must be a valid monitor ID", - "array.base": "Monitors must be an array", - "array.empty": "At least one monitor is required", - "any.required": "Monitors are required", - }), - subMonitors: joi - .array() - .items(joi.string().pattern(/^[0-9a-fA-F]{24}$/)) - .optional(), - deleteSubmonitors: joi.boolean().optional(), - isPublished: joi.boolean(), - showCharts: joi.boolean().optional(), - showUptimePercentage: joi.boolean(), - showAdminLoginLink: joi.boolean().optional(), - - // --- ADD THESE 4 LINES --- - customCSS: joi.string().allow("").optional(), - customJavaScript: joi.string().allow("").optional(), - headerHTML: joi.string().allow("").optional(), - footerHTML: joi.string().allow("").optional(), - // ------------------------- + type: joi.string().valid("uptime").required(), + companyName: joi.string().required(), + url: joi + .string() + .pattern(/^[a-zA-Z0-9_-]+$/) + .required() + .messages({ + "string.pattern.base": "URL can only contain letters, numbers, underscores, and hyphens", + }), + timezone: joi.string().optional(), + color: joi.string().optional(), + monitors: joi + .array() + .items(joi.string().pattern(/^[0-9a-fA-F]{24}$/)) + .required() + .messages({ + "string.pattern.base": "Must be a valid monitor ID", + "array.base": "Monitors must be an array", + "array.empty": "At least one monitor is required", + "any.required": "Monitors are required", + }), + subMonitors: joi + .array() + .items(joi.string().pattern(/^[0-9a-fA-F]{24}$/)) + .optional(), + deleteSubmonitors: joi.boolean().optional(), + isPublished: joi.boolean(), + showCharts: joi.boolean().optional(), + showUptimePercentage: joi.boolean(), + showAdminLoginLink: joi.boolean().optional(), + + customCSS: joi.string().allow("").max(10000).optional(), + customJavaScript: joi.string().allow("").max(5000).optional(), + headerHTML: joi.string().allow("").max(5000).optional(), + footerHTML: joi.string().allow("").max(5000).optional(), }); const imageValidation = joi From ef79ad9a3cc5310aed745e3b9ba8f3873f2254c6 Mon Sep 17 00:00:00 2001 From: "[Armaansaxena]" Date: Mon, 1 Dec 2025 00:30:00 +0530 Subject: [PATCH 4/7] fix: apply critical security fixes (sanitization, props, schema limits) --- .../Create/Components/Tabs/Settings.jsx | 1 - .../src/Pages/v1/StatusPage/Status/index.jsx | 73 +++++++++++-------- server/src/db/v1/models/StatusPage.js | 1 + 3 files changed, 42 insertions(+), 33 deletions(-) diff --git a/client/src/Pages/v1/StatusPage/Create/Components/Tabs/Settings.jsx b/client/src/Pages/v1/StatusPage/Create/Components/Tabs/Settings.jsx index 45f17e90b..b6934f50e 100644 --- a/client/src/Pages/v1/StatusPage/Create/Components/Tabs/Settings.jsx +++ b/client/src/Pages/v1/StatusPage/Create/Components/Tabs/Settings.jsx @@ -277,7 +277,6 @@ const TabSettings = ({ onChange={handleFormChange} fullWidth margin="normal" - helperText={t("statusPageCreateCustomJavaScriptWarning")} /> diff --git a/client/src/Pages/v1/StatusPage/Status/index.jsx b/client/src/Pages/v1/StatusPage/Status/index.jsx index aa8df0389..cc03e12f5 100644 --- a/client/src/Pages/v1/StatusPage/Status/index.jsx +++ b/client/src/Pages/v1/StatusPage/Status/index.jsx @@ -8,6 +8,7 @@ import StatusBar from "./Components/StatusBar/index.jsx"; import MonitorsList from "./Components/MonitorsList/index.jsx"; import Breadcrumbs from "@/Components/v1/Breadcrumbs/index.jsx"; import TextLink from "@/Components/v1/TextLink/index.jsx"; +import DOMPurify from "dompurify"; // Utils import { useStatusPageFetch } from "./Hooks/useStatusPageFetch.jsx"; @@ -199,38 +200,46 @@ const PublicStatus = () => { return ( - {!isPublic && } - - {/* --- CHANGE 3: Custom Header Logic --- */} - {statusPage?.headerHTML ? ( -
- ) : ( - - )} - {/* ------------------------------------- */} - - {t("statusPageStatusServiceStatus")} - - - - {link} - - {/* --- CHANGE 4: Custom Footer Logic --- */} - {statusPage?.footerHTML && ( -
- )} - {/* ------------------------------------- */} - + gap={theme.spacing(10)} + sx={{ ...sx, position: "relative" }} + > + {!isPublic && } + + {/* --- CHANGE 3: Custom Header Logic (SANITIZED) --- */} + {statusPage?.headerHTML ? ( +
+ ) : ( + + )} + {/* ------------------------------------- */} + + {t("statusPageStatusServiceStatus")} + + + + {link} + + {/* --- CHANGE 4: Custom Footer Logic (SANITIZED) --- */} + {statusPage?.footerHTML && ( +
+ )} + {/* ------------------------------------- */} + ); }; diff --git a/server/src/db/v1/models/StatusPage.js b/server/src/db/v1/models/StatusPage.js index bd77e0f23..c4f716c8a 100755 --- a/server/src/db/v1/models/StatusPage.js +++ b/server/src/db/v1/models/StatusPage.js @@ -81,6 +81,7 @@ const StatusPageSchema = mongoose.Schema( customJavaScript: { type: String, default: "", + maxLength: 5000, }, headerHTML: { type: String, From 8cba51826b4bed55458afcd58c12f2b32e05f4c1 Mon Sep 17 00:00:00 2001 From: "[Armaansaxena]" Date: Tue, 2 Dec 2025 18:46:41 +0530 Subject: [PATCH 5/7] fix: sanitize custom CSS and enforce JS risk acceptance --- .../Create/Components/Tabs/Settings.jsx | 54 ++++++++++++------- .../src/Pages/v1/StatusPage/Status/index.jsx | 10 +++- 2 files changed, 45 insertions(+), 19 deletions(-) diff --git a/client/src/Pages/v1/StatusPage/Create/Components/Tabs/Settings.jsx b/client/src/Pages/v1/StatusPage/Create/Components/Tabs/Settings.jsx index b6934f50e..2b7511b2b 100644 --- a/client/src/Pages/v1/StatusPage/Create/Components/Tabs/Settings.jsx +++ b/client/src/Pages/v1/StatusPage/Create/Components/Tabs/Settings.jsx @@ -1,5 +1,5 @@ // Components -import { Stack, Typography } from "@mui/material"; +import { Stack, Typography, Checkbox as MuiCheckbox, FormControlLabel } from "@mui/material"; import { TabPanel } from "@mui/lab"; import ConfigBox from "@/Components/v1/ConfigBox/index.jsx"; import Checkbox from "@/Components/v1/Inputs/Checkbox/index.jsx"; @@ -15,7 +15,7 @@ import { useTheme } from "@emotion/react"; import timezones from "../../../../../../Utils/timezones.json"; import PropTypes from "prop-types"; import { useTranslation } from "react-i18next"; -import { useMemo, useState, useCallback } from "react"; +import { useMemo, useState, useCallback, useEffect } from "react"; const TabSettings = ({ isCreate, @@ -31,6 +31,7 @@ const TabSettings = ({ const theme = useTheme(); const { t } = useTranslation(); const [rawInput, setRawInput] = useState(""); + const [riskAccepted, setRiskAccepted] = useState(false); const selectedTimezone = useMemo( () => timezones.find((tz) => tz._id === form.timezone) ?? null, @@ -50,6 +51,12 @@ const TabSettings = ({ [handleFormChange] ); + useEffect(() => { + if (form.customJavaScript && form.customJavaScript.length > 0) { + setRiskAccepted(true); + } + }, [form.customJavaScript]); + return ( @@ -254,29 +261,40 @@ const TabSettings = ({ {/* --- CUSTOM JAVASCRIPT SECTION --- */} - - - {t("customJavaScript")} - - - {t("statusPageCreateCustomJavaScriptDescription")} - - - + + {t("customJavaScript")} + + setRiskAccepted(e.target.checked)} + color="error" + /> + } + label={ + + {t("Security Risk Warning") || + "I understand that adding custom JavaScript poses a security risk and I accept responsibility."} + + } + /> + diff --git a/client/src/Pages/v1/StatusPage/Status/index.jsx b/client/src/Pages/v1/StatusPage/Status/index.jsx index cc03e12f5..a317f998d 100644 --- a/client/src/Pages/v1/StatusPage/Status/index.jsx +++ b/client/src/Pages/v1/StatusPage/Status/index.jsx @@ -40,7 +40,15 @@ const PublicStatus = () => { if (statusPage.customCSS) { styleElement = document.createElement("style"); styleElement.id = "custom-status-css"; - styleElement.innerHTML = statusPage.customCSS; + + // SECURITY FIX: Remove dangerous CSS patterns (url, import, expression) + const safeCSS = statusPage.customCSS + .replace(/url\s*\(/gi, '') // Block external data/images + .replace(/@import/gi, '') // Block external stylesheets + .replace(/expression\s*\(/gi, '') // Block IE scripts + .replace(/behavior:/gi, ''); // Block IE behaviors + + styleElement.textContent = safeCSS; // Use textContent instead of innerHTML for extra safety document.head.appendChild(styleElement); } From f99222cf61c0b0ebbc9b99b4d93ec8aa7cb5cbac Mon Sep 17 00:00:00 2001 From: "[Armaansaxena]" Date: Tue, 2 Dec 2025 18:53:17 +0530 Subject: [PATCH 6/7] fix: harden css sanitization regex per coderabbit review --- client/src/Pages/v1/StatusPage/Status/index.jsx | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/client/src/Pages/v1/StatusPage/Status/index.jsx b/client/src/Pages/v1/StatusPage/Status/index.jsx index a317f998d..582eb7e17 100644 --- a/client/src/Pages/v1/StatusPage/Status/index.jsx +++ b/client/src/Pages/v1/StatusPage/Status/index.jsx @@ -43,12 +43,14 @@ const PublicStatus = () => { // SECURITY FIX: Remove dangerous CSS patterns (url, import, expression) const safeCSS = statusPage.customCSS - .replace(/url\s*\(/gi, '') // Block external data/images - .replace(/@import/gi, '') // Block external stylesheets - .replace(/expression\s*\(/gi, '') // Block IE scripts - .replace(/behavior:/gi, ''); // Block IE behaviors + .replace(/url\s*\(/gi, 'blocked(') // Block url(...) + .replace(/@import/gi, '/* blocked */') // Block @import + .replace(/expression\s*\(/gi, 'blocked(') // Block IE expression(...) + .replace(/behavior\s*:/gi, 'blocked:') // Block behavior: + .replace(/-moz-binding\s*:/gi, 'blocked:') // Block Firefox XBL + .replace(/javascript\s*:/gi, 'blocked:'); // Block javascript: protocols - styleElement.textContent = safeCSS; // Use textContent instead of innerHTML for extra safety + styleElement.textContent = safeCSS; document.head.appendChild(styleElement); } From 63b5912602ca9bd0c8fea9c76aae51458e4c2935 Mon Sep 17 00:00:00 2001 From: "[Armaansaxena]" Date: Wed, 3 Dec 2025 09:29:03 +0530 Subject: [PATCH 7/7] fix: correct i18n fallback syntax for security warning --- .../Pages/v1/StatusPage/Create/Components/Tabs/Settings.jsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/client/src/Pages/v1/StatusPage/Create/Components/Tabs/Settings.jsx b/client/src/Pages/v1/StatusPage/Create/Components/Tabs/Settings.jsx index 2b7511b2b..1b8513979 100644 --- a/client/src/Pages/v1/StatusPage/Create/Components/Tabs/Settings.jsx +++ b/client/src/Pages/v1/StatusPage/Create/Components/Tabs/Settings.jsx @@ -278,8 +278,7 @@ const TabSettings = ({ color="error" sx={{ fontWeight: "bold" }} > - {t("Security Risk Warning") || - "I understand that adding custom JavaScript poses a security risk and I accept responsibility."} + {t("securityRiskWarning", { defaultValue: "I understand that adding custom JavaScript poses a security risk and I accept responsibility." })} } />