From 5a895c80876eccfba203ce3ffdee4db40e8d38cc Mon Sep 17 00:00:00 2001 From: martinst06 Date: Thu, 13 Nov 2025 14:29:09 +0100 Subject: [PATCH 1/3] feat: baseurl validation checks --- package-lock.json | 91 +++++-------------- packages/spacecat-shared-utils/package.json | 3 +- .../spacecat-shared-utils/src/functions.js | 37 +++++++- 3 files changed, 62 insertions(+), 69 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7249f2dcc..bb0c06882 100644 --- a/package-lock.json +++ b/package-lock.json @@ -33,6 +33,7 @@ "nock": "14.0.10", "semantic-release": "25.0.2", "semantic-release-monorepo": "8.0.2", + "tldts": "7.0.17", "typescript": "5.9.3" }, "engines": { @@ -1772,7 +1773,6 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/client-dynamodb/-/client-dynamodb-3.927.0.tgz", "integrity": "sha512-ERWdl5Cp7Og2AekPgaH96G60PE1VM7gET4brPR6uYNAZxJnc7DH2Z7XYsfp/GGLxFijk69JM0+cipkSEPruFcA==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", @@ -3953,7 +3953,6 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.726.0.tgz", "integrity": "sha512-5JzTX9jwev7+y2Jkzjz0pd1wobB5JQfPOQF3N2DrJ5Pao0/k6uRYwE4NqB0p0HlGrMTDm7xNq7OSPPIPG575Jw==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", @@ -4415,7 +4414,6 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.726.1.tgz", "integrity": "sha512-qh9Q9Vu1hrM/wMBOBIaskwnE4GTFaZu26Q6WHwyWNfj7J8a40vBxpW16c2vYXHLBtwRKM1be8uRLkmDwghpiNw==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", @@ -5639,7 +5637,6 @@ "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", @@ -6618,7 +6615,6 @@ "integrity": "sha512-t54CUOsFMappY1Jbzb7fetWeO0n6K0k/4+/ZpkS+3Joz8I4VcvY9OiEBFRYISqaI2fq5sCiPtAjRDOzVYG8m+Q==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@octokit/auth-token": "^6.0.0", "@octokit/graphql": "^9.0.2", @@ -8738,7 +8734,6 @@ "integrity": "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/linkify-it": "^5", "@types/mdurl": "^2" @@ -8935,7 +8930,6 @@ "integrity": "sha512-6m1I5RmHBGTnUGS113G04DMu3CpSdxCAU/UvtjNWL4Nuf3MW9tQhiJqRlHzChIkhy6kZSAQmc+I1bcGjE3yNKg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.46.3", "@typescript-eslint/types": "8.46.3", @@ -9169,7 +9163,6 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -9560,7 +9553,6 @@ "resolved": "https://registry.npmjs.org/aws-xray-sdk-core/-/aws-xray-sdk-core-3.11.0.tgz", "integrity": "sha512-b7RRs3/twrsCxb113ZgycyaYcXJUQADFMKTiAfzRJu/2hBD2UZkyrjrh8BNTwQ5PUJJmHLoapv1uhpJFk3qKvQ==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@aws-sdk/types": "^3.4.1", "@aws/lambda-invoke-store": "^0.0.1", @@ -9811,7 +9803,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.8.19", "caniuse-lite": "^1.0.30001751", @@ -10023,7 +10014,6 @@ "integrity": "sha512-aUTnJc/JipRzJrNADXVvpVqi6CO0dn3nx4EVPxijri+fj3LUUDyZQOgVeW54Ob3Y1Xh9Iz8f+CgaCl8v0mn9bA==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=18" } @@ -11929,7 +11919,6 @@ "integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -15338,7 +15327,6 @@ "integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "argparse": "^2.0.1", "entities": "^4.4.0", @@ -15378,7 +15366,6 @@ "integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==", "dev": true, "license": "MIT", - "peer": true, "bin": { "marked": "bin/marked.js" }, @@ -19300,7 +19287,6 @@ "dev": true, "inBundle": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -20991,7 +20977,6 @@ "integrity": "sha512-3GuObel8h7Kqdjt0gxkEzaifHTqLVW56Y/bjN7PSQtkKr0w3V/QYSdt6QWYtd7A1xUtYQigtdUfgj1RvWVtorw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/estree": "1.0.8" }, @@ -21130,7 +21115,6 @@ "integrity": "sha512-6qGjWccl5yoyugHt3jTgztJ9Y0JVzyH8/Voc/D8PlLat9pwxQYXz7W1Dpnq5h0/G5GCYGUaDSlYcyk3AMh5A6g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@semantic-release/commit-analyzer": "^13.0.1", "@semantic-release/error": "^4.0.0", @@ -22136,7 +22120,6 @@ "integrity": "sha512-TOgRcwFPbfGtpqvZw+hyqJDvqfapr1qUlOizROIk4bBLjlsjlB00Pg6wMFXNtJRpu+eCZuVOaLatG7M8105kAw==", "dev": true, "license": "BSD-3-Clause", - "peer": true, "dependencies": { "@sinonjs/commons": "^3.0.1", "@sinonjs/fake-timers": "^13.0.5", @@ -22893,7 +22876,6 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -22901,6 +22883,26 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/tldts": { + "version": "7.0.17", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.17.tgz", + "integrity": "sha512-Y1KQBgDd/NUc+LfOtKS6mNsC9CCaH+m2P1RoIZy7RAPo3C3/t8X45+zgut31cRZtZ3xKPjfn3TkGTrctC2TQIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tldts-core": "^7.0.17" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "7.0.17", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.17.tgz", + "integrity": "sha512-DieYoGrP78PWKsrXr8MZwtQ7GLCUeLxihtjC1jZsW1DnvSMdKPitJSe8OSYDM2u5H6g3kWJZpePqkp43TfLh0g==", + "dev": true, + "license": "MIT" + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -23146,7 +23148,6 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -23258,7 +23259,6 @@ "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", "license": "MIT", - "peer": true, "dependencies": { "@types/unist": "^3.0.0", "bail": "^2.0.0", @@ -26584,7 +26584,6 @@ "resolved": "https://registry.npmjs.org/aws-xray-sdk-core/-/aws-xray-sdk-core-3.10.2.tgz", "integrity": "sha512-hAFEB+Stqm4FoQmIuyw5AzGVJh3BSfvLjK7IK4YYRXXLt1Oq9KS6pv2samYgRTTTXsxhmVpDjiYF3Xo/gfXIXA==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@aws-sdk/types": "^3.4.1", "@smithy/service-error-classification": "^2.0.4", @@ -26716,7 +26715,7 @@ }, "packages/spacecat-shared-athena-client": { "name": "@adobe/spacecat-shared-athena-client", - "version": "1.4.1", + "version": "1.5.0", "license": "Apache-2.0", "dependencies": { "@adobe/spacecat-shared-utils": "1.66.0", @@ -27760,7 +27759,6 @@ "packages/spacecat-shared-athena-client/node_modules/aws-xray-sdk-core": { "version": "3.10.2", "license": "Apache-2.0", - "peer": true, "dependencies": { "@aws-sdk/types": "^3.4.1", "@smithy/service-error-classification": "^2.0.4", @@ -27996,7 +27994,6 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/client-dynamodb/-/client-dynamodb-3.859.0.tgz", "integrity": "sha512-Bt840uICsGcn7IFewif8ARCF0CxtdTx9DX/LfUGRI+SVZcqyeEccmH2JJRRzThtEzKTXr+rCN6yaNB3c4RQY2g==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", @@ -31224,7 +31221,6 @@ "resolved": "https://registry.npmjs.org/aws-xray-sdk-core/-/aws-xray-sdk-core-3.10.2.tgz", "integrity": "sha512-hAFEB+Stqm4FoQmIuyw5AzGVJh3BSfvLjK7IK4YYRXXLt1Oq9KS6pv2samYgRTTTXsxhmVpDjiYF3Xo/gfXIXA==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@aws-sdk/types": "^3.4.1", "@smithy/service-error-classification": "^2.0.4", @@ -33409,7 +33405,6 @@ "resolved": "https://registry.npmjs.org/aws-xray-sdk-core/-/aws-xray-sdk-core-3.10.2.tgz", "integrity": "sha512-hAFEB+Stqm4FoQmIuyw5AzGVJh3BSfvLjK7IK4YYRXXLt1Oq9KS6pv2samYgRTTTXsxhmVpDjiYF3Xo/gfXIXA==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@aws-sdk/types": "^3.4.1", "@smithy/service-error-classification": "^2.0.4", @@ -34385,7 +34380,6 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/client-dynamodb/-/client-dynamodb-3.859.0.tgz", "integrity": "sha512-Bt840uICsGcn7IFewif8ARCF0CxtdTx9DX/LfUGRI+SVZcqyeEccmH2JJRRzThtEzKTXr+rCN6yaNB3c4RQY2g==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", @@ -36682,7 +36676,6 @@ "resolved": "https://registry.npmjs.org/aws-xray-sdk-core/-/aws-xray-sdk-core-3.10.2.tgz", "integrity": "sha512-hAFEB+Stqm4FoQmIuyw5AzGVJh3BSfvLjK7IK4YYRXXLt1Oq9KS6pv2samYgRTTTXsxhmVpDjiYF3Xo/gfXIXA==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@aws-sdk/types": "^3.4.1", "@smithy/service-error-classification": "^2.0.4", @@ -39698,7 +39691,6 @@ "resolved": "https://registry.npmjs.org/aws-xray-sdk-core/-/aws-xray-sdk-core-3.10.3.tgz", "integrity": "sha512-bltsLAr4juMJJ2tT5/L/CtwUGIvHihtPe6SO/z3jjOD73PHhOYxcuwCMFFyTbTy5S4WThJO32oZk7r+pg3ZoCQ==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@aws-sdk/types": "^3.4.1", "@smithy/service-error-classification": "^2.0.4", @@ -41237,7 +41229,6 @@ "resolved": "https://registry.npmjs.org/aws-xray-sdk-core/-/aws-xray-sdk-core-3.10.2.tgz", "integrity": "sha512-hAFEB+Stqm4FoQmIuyw5AzGVJh3BSfvLjK7IK4YYRXXLt1Oq9KS6pv2samYgRTTTXsxhmVpDjiYF3Xo/gfXIXA==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@aws-sdk/types": "^3.4.1", "@smithy/service-error-classification": "^2.0.4", @@ -41870,7 +41861,6 @@ "packages/spacecat-shared-content-client/node_modules/@aws-sdk/client-dynamodb": { "version": "3.859.0", "license": "Apache-2.0", - "peer": true, "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", @@ -43942,7 +43932,6 @@ "resolved": "https://registry.npmjs.org/aws-xray-sdk-core/-/aws-xray-sdk-core-3.10.3.tgz", "integrity": "sha512-bltsLAr4juMJJ2tT5/L/CtwUGIvHihtPe6SO/z3jjOD73PHhOYxcuwCMFFyTbTy5S4WThJO32oZk7r+pg3ZoCQ==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@aws-sdk/types": "^3.4.1", "@smithy/service-error-classification": "^2.0.4", @@ -44053,7 +44042,7 @@ }, "packages/spacecat-shared-data-access": { "name": "@adobe/spacecat-shared-data-access", - "version": "2.80.1", + "version": "2.81.0", "license": "Apache-2.0", "dependencies": { "@adobe/spacecat-shared-utils": "1.66.0", @@ -45104,7 +45093,6 @@ "packages/spacecat-shared-data-access/node_modules/aws-xray-sdk-core": { "version": "3.10.2", "license": "Apache-2.0", - "peer": true, "dependencies": { "@aws-sdk/types": "^3.4.1", "@smithy/service-error-classification": "^2.0.4", @@ -45574,7 +45562,6 @@ "packages/spacecat-shared-google-client/node_modules/@adobe/spacecat-shared-data-access/node_modules/@aws-sdk/client-sso-oidc": { "version": "3.716.0", "license": "Apache-2.0", - "peer": true, "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", @@ -45626,7 +45613,6 @@ "packages/spacecat-shared-google-client/node_modules/@adobe/spacecat-shared-data-access/node_modules/@aws-sdk/client-sts": { "version": "3.716.0", "license": "Apache-2.0", - "peer": true, "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", @@ -46729,7 +46715,6 @@ "packages/spacecat-shared-google-client/node_modules/@adobe/spacecat-shared-http-utils/node_modules/@aws-sdk/client-sso-oidc": { "version": "3.716.0", "license": "Apache-2.0", - "peer": true, "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", @@ -46781,7 +46766,6 @@ "packages/spacecat-shared-google-client/node_modules/@adobe/spacecat-shared-http-utils/node_modules/@aws-sdk/client-sts": { "version": "3.716.0", "license": "Apache-2.0", - "peer": true, "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", @@ -47938,7 +47922,6 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.726.0.tgz", "integrity": "sha512-5JzTX9jwev7+y2Jkzjz0pd1wobB5JQfPOQF3N2DrJ5Pao0/k6uRYwE4NqB0p0HlGrMTDm7xNq7OSPPIPG575Jw==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", @@ -47992,7 +47975,6 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.726.1.tgz", "integrity": "sha512-qh9Q9Vu1hrM/wMBOBIaskwnE4GTFaZu26Q6WHwyWNfj7J8a40vBxpW16c2vYXHLBtwRKM1be8uRLkmDwghpiNw==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", @@ -48893,7 +48875,6 @@ "packages/spacecat-shared-google-client/node_modules/@aws-sdk/client-dynamodb": { "version": "3.721.0", "license": "Apache-2.0", - "peer": true, "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", @@ -51317,7 +51298,6 @@ "packages/spacecat-shared-google-client/node_modules/@aws-sdk/client-sts": { "version": "3.721.0", "license": "Apache-2.0", - "peer": true, "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", @@ -54058,7 +54038,6 @@ "packages/spacecat-shared-google-client/node_modules/aws-xray-sdk-core": { "version": "3.10.2", "license": "Apache-2.0", - "peer": true, "dependencies": { "@aws-sdk/types": "^3.4.1", "@smithy/service-error-classification": "^2.0.4", @@ -54368,7 +54347,6 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/client-dynamodb/-/client-dynamodb-3.859.0.tgz", "integrity": "sha512-Bt840uICsGcn7IFewif8ARCF0CxtdTx9DX/LfUGRI+SVZcqyeEccmH2JJRRzThtEzKTXr+rCN6yaNB3c4RQY2g==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", @@ -56733,7 +56711,6 @@ "resolved": "https://registry.npmjs.org/aws-xray-sdk-core/-/aws-xray-sdk-core-3.10.3.tgz", "integrity": "sha512-bltsLAr4juMJJ2tT5/L/CtwUGIvHihtPe6SO/z3jjOD73PHhOYxcuwCMFFyTbTy5S4WThJO32oZk7r+pg3ZoCQ==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@aws-sdk/types": "^3.4.1", "@smithy/service-error-classification": "^2.0.4", @@ -57629,7 +57606,6 @@ "resolved": "https://registry.npmjs.org/aws-xray-sdk-core/-/aws-xray-sdk-core-3.10.2.tgz", "integrity": "sha512-hAFEB+Stqm4FoQmIuyw5AzGVJh3BSfvLjK7IK4YYRXXLt1Oq9KS6pv2samYgRTTTXsxhmVpDjiYF3Xo/gfXIXA==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@aws-sdk/types": "^3.4.1", "@smithy/service-error-classification": "^2.0.4", @@ -57774,7 +57750,6 @@ "resolved": "https://registry.npmjs.org/aws-xray-sdk-core/-/aws-xray-sdk-core-3.10.3.tgz", "integrity": "sha512-bltsLAr4juMJJ2tT5/L/CtwUGIvHihtPe6SO/z3jjOD73PHhOYxcuwCMFFyTbTy5S4WThJO32oZk7r+pg3ZoCQ==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@aws-sdk/types": "^3.4.1", "@smithy/service-error-classification": "^2.0.4", @@ -60326,7 +60301,6 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/client-dynamodb/-/client-dynamodb-3.859.0.tgz", "integrity": "sha512-Bt840uICsGcn7IFewif8ARCF0CxtdTx9DX/LfUGRI+SVZcqyeEccmH2JJRRzThtEzKTXr+rCN6yaNB3c4RQY2g==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", @@ -61947,7 +61921,6 @@ "resolved": "https://registry.npmjs.org/aws-xray-sdk-core/-/aws-xray-sdk-core-3.10.2.tgz", "integrity": "sha512-hAFEB+Stqm4FoQmIuyw5AzGVJh3BSfvLjK7IK4YYRXXLt1Oq9KS6pv2samYgRTTTXsxhmVpDjiYF3Xo/gfXIXA==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@aws-sdk/types": "^3.4.1", "@smithy/service-error-classification": "^2.0.4", @@ -62584,7 +62557,6 @@ "resolved": "https://registry.npmjs.org/aws-xray-sdk-core/-/aws-xray-sdk-core-3.10.2.tgz", "integrity": "sha512-hAFEB+Stqm4FoQmIuyw5AzGVJh3BSfvLjK7IK4YYRXXLt1Oq9KS6pv2samYgRTTTXsxhmVpDjiYF3Xo/gfXIXA==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@aws-sdk/types": "^3.4.1", "@smithy/service-error-classification": "^2.0.4", @@ -66071,7 +66043,6 @@ "resolved": "https://registry.npmjs.org/aws-xray-sdk-core/-/aws-xray-sdk-core-3.10.3.tgz", "integrity": "sha512-bltsLAr4juMJJ2tT5/L/CtwUGIvHihtPe6SO/z3jjOD73PHhOYxcuwCMFFyTbTy5S4WThJO32oZk7r+pg3ZoCQ==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@aws-sdk/types": "^3.4.1", "@smithy/service-error-classification": "^2.0.4", @@ -67725,7 +67696,6 @@ "resolved": "https://registry.npmjs.org/aws-xray-sdk-core/-/aws-xray-sdk-core-3.10.2.tgz", "integrity": "sha512-hAFEB+Stqm4FoQmIuyw5AzGVJh3BSfvLjK7IK4YYRXXLt1Oq9KS6pv2samYgRTTTXsxhmVpDjiYF3Xo/gfXIXA==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@aws-sdk/types": "^3.4.1", "@smithy/service-error-classification": "^2.0.4", @@ -67849,7 +67819,6 @@ "packages/spacecat-shared-http-utils/node_modules/@aws-sdk/client-dynamodb": { "version": "3.859.0", "license": "Apache-2.0", - "peer": true, "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", @@ -70397,7 +70366,6 @@ "resolved": "https://registry.npmjs.org/aws-xray-sdk-core/-/aws-xray-sdk-core-3.10.3.tgz", "integrity": "sha512-bltsLAr4juMJJ2tT5/L/CtwUGIvHihtPe6SO/z3jjOD73PHhOYxcuwCMFFyTbTy5S4WThJO32oZk7r+pg3ZoCQ==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@aws-sdk/types": "^3.4.1", "@smithy/service-error-classification": "^2.0.4", @@ -70523,7 +70491,7 @@ }, "packages/spacecat-shared-ims-client": { "name": "@adobe/spacecat-shared-ims-client", - "version": "1.9.2", + "version": "1.9.3", "license": "Apache-2.0", "dependencies": { "@adobe/fetch": "4.2.3", @@ -70637,7 +70605,6 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/client-dynamodb/-/client-dynamodb-3.859.0.tgz", "integrity": "sha512-Bt840uICsGcn7IFewif8ARCF0CxtdTx9DX/LfUGRI+SVZcqyeEccmH2JJRRzThtEzKTXr+rCN6yaNB3c4RQY2g==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", @@ -73821,7 +73788,6 @@ "resolved": "https://registry.npmjs.org/aws-xray-sdk-core/-/aws-xray-sdk-core-3.10.3.tgz", "integrity": "sha512-bltsLAr4juMJJ2tT5/L/CtwUGIvHihtPe6SO/z3jjOD73PHhOYxcuwCMFFyTbTy5S4WThJO32oZk7r+pg3ZoCQ==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@aws-sdk/types": "^3.4.1", "@smithy/service-error-classification": "^2.0.4", @@ -78418,7 +78384,6 @@ "resolved": "https://registry.npmjs.org/aws-xray-sdk-core/-/aws-xray-sdk-core-3.10.2.tgz", "integrity": "sha512-hAFEB+Stqm4FoQmIuyw5AzGVJh3BSfvLjK7IK4YYRXXLt1Oq9KS6pv2samYgRTTTXsxhmVpDjiYF3Xo/gfXIXA==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@aws-sdk/types": "^3.4.1", "@smithy/service-error-classification": "^2.0.4", @@ -79602,7 +79567,6 @@ "packages/spacecat-shared-rum-api-client/node_modules/aws-xray-sdk-core": { "version": "3.10.2", "license": "Apache-2.0", - "peer": true, "dependencies": { "@aws-sdk/types": "^3.4.1", "@smithy/service-error-classification": "^2.0.4", @@ -80386,7 +80350,6 @@ "resolved": "https://registry.npmjs.org/aws-xray-sdk-core/-/aws-xray-sdk-core-3.10.2.tgz", "integrity": "sha512-hAFEB+Stqm4FoQmIuyw5AzGVJh3BSfvLjK7IK4YYRXXLt1Oq9KS6pv2samYgRTTTXsxhmVpDjiYF3Xo/gfXIXA==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@aws-sdk/types": "^3.4.1", "@smithy/service-error-classification": "^2.0.4", @@ -80585,7 +80548,6 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/client-dynamodb/-/client-dynamodb-3.917.0.tgz", "integrity": "sha512-PPOyDwlg59ESbj/Ur8VKRvlW6GRViThykNCg5qjCuejiEQ8F1j+0yPxIa+H0x6iklDZF/+AiERtLpmZh3UjD0g==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", @@ -83739,7 +83701,6 @@ "resolved": "https://registry.npmjs.org/aws-xray-sdk-core/-/aws-xray-sdk-core-3.10.2.tgz", "integrity": "sha512-hAFEB+Stqm4FoQmIuyw5AzGVJh3BSfvLjK7IK4YYRXXLt1Oq9KS6pv2samYgRTTTXsxhmVpDjiYF3Xo/gfXIXA==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@aws-sdk/types": "^3.4.1", "@smithy/service-error-classification": "^2.0.4", @@ -86195,7 +86156,6 @@ "resolved": "https://registry.npmjs.org/aws-xray-sdk-core/-/aws-xray-sdk-core-3.10.2.tgz", "integrity": "sha512-hAFEB+Stqm4FoQmIuyw5AzGVJh3BSfvLjK7IK4YYRXXLt1Oq9KS6pv2samYgRTTTXsxhmVpDjiYF3Xo/gfXIXA==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@aws-sdk/types": "^3.4.1", "@smithy/service-error-classification": "^2.0.4", @@ -88703,7 +88663,6 @@ "resolved": "https://registry.npmjs.org/aws-xray-sdk-core/-/aws-xray-sdk-core-3.10.2.tgz", "integrity": "sha512-hAFEB+Stqm4FoQmIuyw5AzGVJh3BSfvLjK7IK4YYRXXLt1Oq9KS6pv2samYgRTTTXsxhmVpDjiYF3Xo/gfXIXA==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@aws-sdk/types": "^3.4.1", "@smithy/service-error-classification": "^2.0.4", @@ -89028,7 +88987,7 @@ }, "packages/spacecat-shared-utils": { "name": "@adobe/spacecat-shared-utils", - "version": "1.70.1", + "version": "1.72.0", "license": "Apache-2.0", "dependencies": { "@adobe/fetch": "4.2.3", diff --git a/packages/spacecat-shared-utils/package.json b/packages/spacecat-shared-utils/package.json index 7885395bb..3c4323416 100644 --- a/packages/spacecat-shared-utils/package.json +++ b/packages/spacecat-shared-utils/package.json @@ -63,6 +63,7 @@ "iso-639-3": "3.0.1", "validator": "^13.15.15", "world-countries": "5.1.0", - "zod": "^4.1.11" + "zod": "^4.1.11", + "tldts": "7.0.17" } } diff --git a/packages/spacecat-shared-utils/src/functions.js b/packages/spacecat-shared-utils/src/functions.js index 927471223..702060a35 100644 --- a/packages/spacecat-shared-utils/src/functions.js +++ b/packages/spacecat-shared-utils/src/functions.js @@ -11,6 +11,7 @@ */ import isEmail from 'validator/lib/isEmail.js'; +import { parse } from 'tldts'; // Precompile regular expressions const REGEX_ISO_DATE = /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z/; @@ -185,14 +186,46 @@ function isIsoTimeOffsetsDate(str) { /** * Validates whether the given string is a valid URL with http or https protocol. + * Validates that the URL is clean: no explicit ports, hash fragments, or query parameters. + * Paths are allowed for flexibility with other URL fields. * * @param {string} urlString - The string to validate. * @returns {boolean} True if the given string validates successfully. */ function isValidUrl(urlString) { try { - const url = new URL(urlString); - return url.protocol === 'http:' || url.protocol === 'https:'; + let url = urlString.trim().toLowerCase(); + + // reject control characters (LF, CR etc) + if ([...url].some((c) => { + const code = c.charCodeAt(0); + return code < 32 || code === 127; + })) return false; + + // only allow https + if (!/^https:\/\//i.test(url)) { + url = `https://${url}`; + } + + const hostInput = url.slice('https://'.length); + if (/[/\\?#:]/.test(hostInput)) return false; + + const urlObj = new URL(url); + + if (urlObj.pathname !== '/' || urlObj.search || urlObj.hash || urlObj.port) return false; + + // ensure the hostname is a valid registrable domain and not an ip + const domain = parse(urlObj.hostname, { allowPrivateDomains: true }); + if (!domain.domain || domain.isIp) return false; + if (!domain.isIcann && !domain.isPrivate) return false; + + // validate each label for length and allowed characters + for (const label of urlObj.hostname.split('.')) { + if (label.length === 0 || label.length > 63) return false; + if (!/^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/i.test(label)) return false; + } + + return true; } catch { return false; } From ae05c11393163e464a69c43892b35460b3e04295 Mon Sep 17 00:00:00 2001 From: martinst06 Date: Thu, 13 Nov 2025 15:04:32 +0100 Subject: [PATCH 2/3] chore: update package lock --- package-lock.json | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/package-lock.json b/package-lock.json index bb0c06882..cb69893e5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -33,7 +33,6 @@ "nock": "14.0.10", "semantic-release": "25.0.2", "semantic-release-monorepo": "8.0.2", - "tldts": "7.0.17", "typescript": "5.9.3" }, "engines": { @@ -22887,7 +22886,6 @@ "version": "7.0.17", "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.17.tgz", "integrity": "sha512-Y1KQBgDd/NUc+LfOtKS6mNsC9CCaH+m2P1RoIZy7RAPo3C3/t8X45+zgut31cRZtZ3xKPjfn3TkGTrctC2TQIQ==", - "dev": true, "license": "MIT", "dependencies": { "tldts-core": "^7.0.17" @@ -22900,7 +22898,6 @@ "version": "7.0.17", "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.17.tgz", "integrity": "sha512-DieYoGrP78PWKsrXr8MZwtQ7GLCUeLxihtjC1jZsW1DnvSMdKPitJSe8OSYDM2u5H6g3kWJZpePqkp43TfLh0g==", - "dev": true, "license": "MIT" }, "node_modules/to-regex-range": { @@ -24244,7 +24241,7 @@ }, "packages/spacecat-shared-ahrefs-client": { "name": "@adobe/spacecat-shared-ahrefs-client", - "version": "1.9.13", + "version": "1.10.0", "license": "Apache-2.0", "dependencies": { "@adobe/fetch": "4.2.3", @@ -26715,7 +26712,7 @@ }, "packages/spacecat-shared-athena-client": { "name": "@adobe/spacecat-shared-athena-client", - "version": "1.5.0", + "version": "1.6.0", "license": "Apache-2.0", "dependencies": { "@adobe/spacecat-shared-utils": "1.66.0", @@ -44042,7 +44039,7 @@ }, "packages/spacecat-shared-data-access": { "name": "@adobe/spacecat-shared-data-access", - "version": "2.81.0", + "version": "2.82.0", "license": "Apache-2.0", "dependencies": { "@adobe/spacecat-shared-utils": "1.66.0", @@ -70491,7 +70488,7 @@ }, "packages/spacecat-shared-ims-client": { "name": "@adobe/spacecat-shared-ims-client", - "version": "1.9.3", + "version": "1.10.0", "license": "Apache-2.0", "dependencies": { "@adobe/fetch": "4.2.3", @@ -88777,7 +88774,7 @@ }, "packages/spacecat-shared-tokowaka-client": { "name": "@adobe/spacecat-shared-tokowaka-client", - "version": "1.0.2", + "version": "1.0.3", "license": "Apache-2.0", "dependencies": { "@adobe/spacecat-shared-utils": "1.66.1", @@ -88987,7 +88984,7 @@ }, "packages/spacecat-shared-utils": { "name": "@adobe/spacecat-shared-utils", - "version": "1.72.0", + "version": "1.72.1", "license": "Apache-2.0", "dependencies": { "@adobe/fetch": "4.2.3", @@ -88999,6 +88996,7 @@ "date-fns": "4.1.0", "franc-min": "6.2.0", "iso-639-3": "3.0.1", + "tldts": "7.0.17", "validator": "^13.15.15", "world-countries": "5.1.0", "zod": "^4.1.11" From d6fb86fd52f5279a5ee7e40dd00ac0bd8f251e06 Mon Sep 17 00:00:00 2001 From: martinst06 Date: Thu, 13 Nov 2025 17:49:23 +0100 Subject: [PATCH 3/3] test: validation test cases --- .../spacecat-shared-utils/src/functions.js | 2 - .../test/functions.test.js | 212 +++++++++++++++++- 2 files changed, 204 insertions(+), 10 deletions(-) diff --git a/packages/spacecat-shared-utils/src/functions.js b/packages/spacecat-shared-utils/src/functions.js index 702060a35..b39bc071c 100644 --- a/packages/spacecat-shared-utils/src/functions.js +++ b/packages/spacecat-shared-utils/src/functions.js @@ -212,8 +212,6 @@ function isValidUrl(urlString) { const urlObj = new URL(url); - if (urlObj.pathname !== '/' || urlObj.search || urlObj.hash || urlObj.port) return false; - // ensure the hostname is a valid registrable domain and not an ip const domain = parse(urlObj.hostname, { allowPrivateDomains: true }); if (!domain.domain || domain.isIp) return false; diff --git a/packages/spacecat-shared-utils/test/functions.test.js b/packages/spacecat-shared-utils/test/functions.test.js index f89b940f3..1caa08ada 100644 --- a/packages/spacecat-shared-utils/test/functions.test.js +++ b/packages/spacecat-shared-utils/test/functions.test.js @@ -282,24 +282,220 @@ describe('Shared functions', () => { }); describe('isValidUrl', () => { - it('returns false for invalid Url', async () => { - const invalidUrls = [ + it('returns false for non-string inputs', async () => { + const nonStringInputs = [ null, undefined, 1234, true, + false, + {}, + [], + Symbol('test'), + ]; + + nonStringInputs.forEach((url) => expect(isValidUrl(url)).to.be.false); + }); + + it('returns false for empty or whitespace-only strings', async () => { + expect(isValidUrl('')).to.be.false; + expect(isValidUrl(' ')).to.be.false; + expect(isValidUrl('\t')).to.be.false; + }); + + it('returns false for strings with control characters', async () => { + expect(isValidUrl('example.com\u0000')).to.be.false; + expect(isValidUrl('example.com\u001F')).to.be.false; + expect(isValidUrl('example.com\u007F')).to.be.false; + expect(isValidUrl('example\u0000.com')).to.be.false; + expect(isValidUrl('https://example\u0000.com')).to.be.false; + }); + + it('returns false for URLs with http protocol', async () => { + expect(isValidUrl('http://example.com')).to.be.false; + expect(isValidUrl('http://abc.xyz')).to.be.false; + expect(isValidUrl('http://subdomain.example.com')).to.be.false; + }); + + it('returns false for URLs with non-http/https protocols', async () => { + const invalidProtocols = [ + 'ftp://example.com', + 'file://example.com', + 'mailto:test@example.com', + // eslint-disable-next-line no-script-url + 'javascript:alert(1)', + 'data:text/html,', + 'ws://example.com', + 'wss://example.com', + ]; + + invalidProtocols.forEach((url) => expect(isValidUrl(url)).to.be.false); + }); + + it('returns false for URLs with paths', async () => { + expect(isValidUrl('example.com/path')).to.be.false; + expect(isValidUrl('https://example.com/path')).to.be.false; + expect(isValidUrl('https://example.com/path/to/resource')).to.be.false; + expect(isValidUrl('http://example.com/path')).to.be.false; + }); + + it('returns false for URLs with query parameters', async () => { + expect(isValidUrl('https://example.com?param=value')).to.be.false; + expect(isValidUrl('https://example.com?foo=bar&baz=qux')).to.be.false; + expect(isValidUrl('example.com?param=value')).to.be.false; + }); + + it('returns false for URLs with hash fragments', async () => { + expect(isValidUrl('https://example.com#section')).to.be.false; + expect(isValidUrl('https://example.com#anchor')).to.be.false; + expect(isValidUrl('example.com#section')).to.be.false; + }); + + it('returns false for URLs with explicit ports', async () => { + expect(isValidUrl('https://example.com:0')).to.be.false; + expect(isValidUrl('https://example.com:80')).to.be.false; + expect(isValidUrl('https://example.com:443')).to.be.false; + expect(isValidUrl('https://example.com:8080')).to.be.false; + expect(isValidUrl('https://example.com:5')).to.be.false; + expect(isValidUrl('example.com:0')).to.be.false; + expect(isValidUrl('example.com:80')).to.be.false; + expect(isValidUrl('example.com:443')).to.be.false; + expect(isValidUrl('example.com:8080')).to.be.false; + }); + + it('returns false for IP addresses', async () => { + expect(isValidUrl('https://192.168.1.1')).to.be.false; + expect(isValidUrl('https://127.0.0.1')).to.be.false; + expect(isValidUrl('https://255.255.255.255')).to.be.false; + expect(isValidUrl('192.168.1.1')).to.be.false; + expect(isValidUrl('255.255.255.256')).to.be.false; + }); + + it('returns false for invalid domain labels', async () => { + expect(isValidUrl('https://-example.com')).to.be.false; + expect(isValidUrl('-example.com')).to.be.false; + + expect(isValidUrl('https://example-.com')).to.be.false; + expect(isValidUrl('example-.com')).to.be.false; + + expect(isValidUrl('https://example_.com')).to.be.false; + expect(isValidUrl('https://example@.com')).to.be.false; + + expect(isValidUrl('https://.example.com')).to.be.false; + expect(isValidUrl('https://example..com')).to.be.false; + expect(isValidUrl('.example.com')).to.be.false; + expect(isValidUrl('example..com')).to.be.false; + }); + + it('returns false for domain labels exceeding 63 characters', async () => { + const longLabel = 'a'.repeat(64); + expect(isValidUrl(`https://${longLabel}.com`)).to.be.false; + expect(isValidUrl(`${longLabel}.com`)).to.be.false; + }); + + it('returns false for invalid domains (not ICANN or private)', async () => { + expect(isValidUrl('https://invalid..domain')).to.be.false; + expect(isValidUrl('invalid..domain')).to.be.false; + }); + + it('returns false for malformed URLs that cause URL constructor to throw', async () => { + const malformedUrls = [ + 'https://', + 'https:////', + '://example.com', + ]; + + malformedUrls.forEach((url) => expect(isValidUrl(url)).to.be.false); + }); + + it('returns true for valid https URLs', async () => { + const validHttpsUrls = [ + 'https://abc.xyz', + 'https://example.com', + 'https://subdomain.example.com', + 'https://test.co.uk', + 'https://example.org', + ]; + + validHttpsUrls.forEach((url) => expect(isValidUrl(url)).to.be.true); + }); + + it('returns true for valid domains without protocol (auto-prepends https://)', async () => { + const validDomains = [ 'example.com', 'www.example.com', - '255.255.255.256', - 'ftp://abc.com', + 'subdomain.example.com', + 'abc.xyz', + 'test.co.uk', + 'example.org', ]; - invalidUrls.forEach((url) => expect(isValidUrl(url)).to.be.false); + validDomains.forEach((url) => expect(isValidUrl(url)).to.be.true); + }); + + it('handles case-insensitive protocol matching', async () => { + expect(isValidUrl('HTTPS://example.com')).to.be.true; + expect(isValidUrl('Https://example.com')).to.be.true; + expect(isValidUrl('https://EXAMPLE.COM')).to.be.true; + expect(isValidUrl('https://EXAmpLE.COM')).to.be.true; + }); + + it('handles URLs with leading/trailing whitespace', async () => { + expect(isValidUrl(' https://example.com ')).to.be.true; + expect(isValidUrl(' example.com ')).to.be.true; + }); + + it('returns true for valid domain labels at maximum length', async () => { + const maxLengthLabel = 'a'.repeat(63); + expect(isValidUrl(`https://${maxLengthLabel}.com`)).to.be.true; + expect(isValidUrl(`${maxLengthLabel}.com`)).to.be.true; + }); + + it('returns true for domains with hyphens in labels', async () => { + expect(isValidUrl('https://sub-domain.example.com')).to.be.true; + expect(isValidUrl('https://my-site.example.org')).to.be.true; + expect(isValidUrl('sub-domain.example.com')).to.be.true; + }); + + it('returns true for domains with numbers in labels', async () => { + expect(isValidUrl('https://test123.example.com')).to.be.true; + expect(isValidUrl('test123.example.com')).to.be.true; + }); + + it('returns false for URLs with backslashes', async () => { + expect(isValidUrl('https://example.com\\path')).to.be.false; + expect(isValidUrl('example.com\\path')).to.be.false; + }); + + it('returns false for URLs with anything past the TLD', async () => { + expect(isValidUrl('https://example.com/')).to.be.false; + expect(isValidUrl('https://example.com/.')).to.be.false; + expect(isValidUrl('https://example.com/..')).to.be.false; + expect(isValidUrl('https://example.com\\')).to.be.false; + expect(isValidUrl('https://example.com\\.')).to.be.false; + expect(isValidUrl('https://example.com\\..')).to.be.false; + expect(isValidUrl('https://example.com?')).to.be.false; + expect(isValidUrl('https://example.com#')).to.be.false; + expect(isValidUrl('https://example.com:')).to.be.false; + expect(isValidUrl('https://example.com/path')).to.be.false; + expect(isValidUrl('https://example.com?query=value')).to.be.false; + expect(isValidUrl('https://example.com#hash')).to.be.false; + expect(isValidUrl('https://example.com:8080')).to.be.false; + }); + + it('returns true for non ascii characters', async () => { + expect(isValidUrl('https://тест.example.com')).to.be.true; + expect(isValidUrl('https://тест.мкд')).to.be.true; + expect(isValidUrl('https://परीक्षा.संगठन')).to.be.true; + }); + + it('returns false for invalid TLDs', async () => { + expect(isValidUrl('https://example.c')).to.be.false; + expect(isValidUrl('https://example.qq')).to.be.false; }); - it('returns true for valid url', async () => { - expect(isValidUrl('http://abc.xyz')).to.be.true; - expect(isValidUrl('https://abc.xyz')).to.be.true; + it('returns false for invalid domain labels', async () => { + expect(isValidUrl('https://example.-ab.com')).to.be.false; }); });