diff --git a/lib/marshalls/typosquatting.marshall.js b/lib/marshalls/typosquatting.marshall.js new file mode 100644 index 0000000..6260115 --- /dev/null +++ b/lib/marshalls/typosquatting.marshall.js @@ -0,0 +1,50 @@ +'use strict' + +const BaseMarshall = require('./baseMarshall') +const { marshallCategories } = require('./constants') + +const path = require('path') +const levenshtein = require('fast-levenshtein') +const topPackagesRawJSON = require(path.join(__dirname, '../../data/top-packages.json')) + +const MARSHALL_NAME = 'typosquatting' + +class Marshall extends BaseMarshall { + constructor(options) { + super(options) + this.name = MARSHALL_NAME + this.categoryId = marshallCategories.PackageHealth.id + } + + title() { + return 'Checking for typosquatting' + } + + validate(pkg) { + let levenshteinDistance = null + let similarPackages = [] + return new Promise((resolve, reject) => { + for (const popularPackageNameInRepository of topPackagesRawJSON) { + levenshteinDistance = levenshtein.get(pkg.packageName, popularPackageNameInRepository) + + if (levenshteinDistance < 3) { + similarPackages.push(popularPackageNameInRepository) + } + } + + if (similarPackages.length > 0) { + return reject( + new Error( + `Package name could be a typosquatting attempt for popular package(s): ${similarPackages.join( + ', ' + )}` + ) + ) + } + + return resolve([]) + }) + } +} + +module.exports = Marshall diff --git a/package-lock.json b/package-lock.json index 63b19e7..c41b0f7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "0.0.0-development", "license": "Apache-2.0", "dependencies": { + "fast-levenshtein": "^3.0.0", "glob": "^10.3.10", "inquirer": "^8.2.6", "kleur": "^4.1.5", @@ -6415,10 +6416,20 @@ "dev": true }, "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-3.0.0.tgz", + "integrity": "sha512-hKKNajm46uNmTlhHSyZkmToAc56uZJwYq7yrciZjqOxnlfQwERDQJmHPUp7m1m9wx8vgOe8IaCKZ5Kv2k1DdCQ==", + "dependencies": { + "fastest-levenshtein": "^1.0.7" + } + }, + "node_modules/fastest-levenshtein": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", + "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", + "engines": { + "node": ">= 4.9.1" + } }, "node_modules/fastq": { "version": "1.15.0", @@ -10603,6 +10614,12 @@ "node": ">= 0.8.0" } }, + "node_modules/optionator/node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, "node_modules/ora": { "version": "5.4.1", "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", diff --git a/package.json b/package.json index a401c69..e7a7235 100644 --- a/package.json +++ b/package.json @@ -170,6 +170,7 @@ } }, "dependencies": { + "fast-levenshtein": "^3.0.0", "glob": "^10.3.10", "inquirer": "^8.2.6", "kleur": "^4.1.5",