Comparing version 0.1.0 to 0.2.0
@@ -11,3 +11,4 @@ import yargs from "yargs"; | ||
.scriptName("no-scripts") | ||
.command("$0 [projectDir]", "Checks all npm dependencies and fail if any of them define automatically executed npm lifecycle scripts", (yargs) => { | ||
.command("$0 [projectDir]", "Check all installed npm dependencies of the current project and fail if any of them " + | ||
"define automatically executed npm lifecycle scripts", (yargs) => { | ||
yargs | ||
@@ -19,5 +20,9 @@ .positional("projectDir", { | ||
}, async (argv) => { | ||
if (argv.includeLocal && !argv.online) { | ||
console.log(`Parameter '--include-local' must only be used with '--online'`); | ||
process.exit(1); | ||
} | ||
const cwd = argv.projectDir ? path.resolve(argv.projectDir) : process.cwd(); | ||
let lockfileResults; | ||
if (!argv.offline) { | ||
if (argv.online) { | ||
lockfileResults = await analyzeLockfile({ cwd, ignorePackages: argv.ignore || [] }); | ||
@@ -28,3 +33,3 @@ writeResultsToConsole(lockfileResults); | ||
let packageJsonResult; | ||
if (argv.includeLocal || argv.offline) { | ||
if (!argv.online || argv.includeLocal) { | ||
packageJsonResult = await analyzePackageJson({ cwd, ignorePackages: argv.ignore || [] }); | ||
@@ -90,7 +95,8 @@ writeResultsToConsole(packageJsonResult); | ||
.option("include-local", { | ||
describe: "Extend check to include local dependencies", | ||
describe: "Only relevant when using `--online`. Extend check to include local dependencies", | ||
boolean: true, | ||
}) | ||
.option("offline", { | ||
describe: "Only scan local dependencies. Implies '--include-local'", | ||
.option("online", { | ||
describe: "Instead of analyzing local dependencies, " + | ||
"fetch all dependencies independently from the registry as an extra level of safety", | ||
boolean: true, | ||
@@ -97,0 +103,0 @@ }) |
@@ -5,2 +5,3 @@ import path from "node:path"; | ||
import { getPackagesFromInstalledDependencies } from "./installedDepsHandler.js"; | ||
import mapWorkspaces from "@npmcli/map-workspaces"; | ||
import { getPackagesFromLockfile } from "./lockfileHandler.js"; | ||
@@ -25,3 +26,4 @@ import { analyzePackages } from "./analyzer.js"; | ||
} | ||
console.log(`Analyzing using ${packageLock ? "package-lock.json" : "npm-shrinkwrap.json"} and registry`); | ||
console.log(`Analyzing ${packageLock ? "package-lock.json" : "npm-shrinkwrap.json"} ` + | ||
`of package "${rootPackageInfo.packageJson.name}" and fetching packages from the registry...`); | ||
const packages = await getPackagesFromLockfile(rootPackageInfo, lockfile); | ||
@@ -32,5 +34,26 @@ removeIgnoredPackages(packages, ignorePackages); | ||
export async function analyzePackageJson({ cwd, ignorePackages }) { | ||
console.log(`Analyzing using package.json and locally installed dependencies`); | ||
const rootPackageInfo = await getRootPackage(cwd); | ||
console.log(`Analyzing package.json of package "${rootPackageInfo.packageJson.name}" ` + | ||
`and locally installed dependencies...`); | ||
const packages = await getPackagesFromInstalledDependencies(rootPackageInfo); | ||
// Check for workspace config | ||
if (rootPackageInfo.packageJson.workspaces) { | ||
const workspacePaths = await mapWorkspaces({ | ||
cwd, | ||
pkg: rootPackageInfo.packageJson | ||
}); | ||
if (workspacePaths.size) { | ||
// Read packages in workspaces concurrently | ||
const paths = Array.from(workspacePaths.values()); | ||
await Promise.all(paths.map(async (workspacePath) => { | ||
const workspacePackageInfo = await getRootPackage(workspacePath); | ||
const workspacePackages = await getPackagesFromInstalledDependencies(workspacePackageInfo); | ||
workspacePackages.forEach((pkgInfo, modulePath) => { | ||
if (!packages.has(modulePath)) { | ||
packages.set(modulePath, pkgInfo); | ||
} | ||
}); | ||
})); | ||
} | ||
} | ||
removeIgnoredPackages(packages, ignorePackages); | ||
@@ -37,0 +60,0 @@ return await analyzePackages(packages); |
@@ -6,2 +6,3 @@ import path from "node:path"; | ||
import { rimraf } from "rimraf"; | ||
import pThrottle from "p-throttle"; | ||
import { readPackageJson } from "./util.js"; | ||
@@ -37,3 +38,3 @@ async function retrieveTarball(url, targetPath, cacheDir, expectedIntegrity) { | ||
if (![2, 3].includes(lockfile.lockfileVersion)) { | ||
throw new Error(`This tool requires lockfile version '2' or '3'. ` + | ||
throw new Error(`no-scripts requires lockfile version '2' or '3'. ` + | ||
`For details, see https://docs.npmjs.com/cli/configuring-npm/package-lock-json#lockfileversion`); | ||
@@ -76,4 +77,10 @@ } | ||
console.log(`Fetching ${requests.size} tarballs...`); | ||
// Slightly throttle the number of concurrent requests to 100 requests per 100 ms to hopefully prevent errors like: | ||
// "Client network socket disconnected before secure TLS connection was established" | ||
const throttle = pThrottle({ | ||
limit: 100, | ||
interval: 100 | ||
}); | ||
await Promise.all(Array.from(requests.entries()) | ||
.map(async ([url, { integrity, packageLocation }]) => { | ||
.map(throttle(async ([url, { integrity, packageLocation }]) => { | ||
const targetRelPath = packageLocation.replace("node_modules/", ""); | ||
@@ -88,3 +95,3 @@ const targetPath = path.join(packagesDir, targetRelPath); | ||
}); | ||
})); | ||
}))); | ||
await rimraf(workDir); | ||
@@ -91,0 +98,0 @@ return packages; |
{ | ||
"name": "no-scripts", | ||
"version": "0.1.0", | ||
"version": "0.2.0", | ||
"description": "", | ||
@@ -29,3 +29,5 @@ "type": "module", | ||
"dependencies": { | ||
"@npmcli/map-workspaces": "^3.0.4", | ||
"@npmcli/package-json": "^5.0.0", | ||
"p-throttle": "^6.1.0", | ||
"pacote": "^17.0.4", | ||
@@ -39,2 +41,3 @@ "read-package-up": "^11.0.0", | ||
"@types/node": "^20.8.10", | ||
"@types/npmcli__map-workspaces": "^3.0.4", | ||
"@types/pacote": "^11.1.8", | ||
@@ -41,0 +44,0 @@ "@types/resolve": "^1.20.5", |
@@ -11,7 +11,7 @@ # no-scripts | ||
Also make sure to update your CI jobs to run `npm ci --ignore-scripts` and `npm publish --ignore-scripts` (as also suggested by [snyk](https://snyk.io/blog/github-actions-to-securely-publish-npm-packages/) for example) | ||
Also make sure to update your CI jobs to run `npm ci --ignore-scripts` and `npm publish --ignore-scripts` (as also suggested [here](https://snyk.io/blog/github-actions-to-securely-publish-npm-packages/)) | ||
Note however that this disables **any** [pre- and post-scripts](https://docs.npmjs.com/cli/v10/using-npm/scripts#pre--post-scripts), such as for example the commonly used `pretest` script. | ||
Also, in case one of your dependencies does need an install script to run, you will have to manually execute [`npm rebuild <package name>`](https://docs.npmjs.com/cli/v10/commands/npm-rebuild) to run those after install. | ||
Also, in case one of your dependencies does need an install script to run, you will have to manually execute [`npm rebuild <package name>`](https://docs.npmjs.com/cli/v10/commands/npm-rebuild) to run those after install or manually reset the configuration option mentioned before. | ||
@@ -21,3 +21,3 @@ ## Install | ||
```sh | ||
npm install --global no-scripts | ||
npm i -g no-scripts | ||
``` | ||
@@ -31,8 +31,8 @@ | ||
By default, no-scripts will start by fetching the tarballs of all dependencies listed in the project's [lockfile](https://docs.npmjs.com/cli/v10/configuring-npm/package-lock-json) from the corresponding npm registry. It then evaluates the `package.json` files of every package, applying normalization using [`@npmcli/package-json`](https://www.npmjs.com/package/@npmcli/package-json) in order to detect implicit install-scripts such as `node-gyp rebuild` which would be executed if a package contains `*.gyp` files. | ||
By default, no-scripts will analyze all **locally installed dependencies** of the project in the current working directory. For this, it evaluates the `package.json` file of every package, applying normalization using [`@npmcli/package-json`](https://www.npmjs.com/package/@npmcli/package-json) in order to also detect _implicit_ install-scripts such as `node-gyp rebuild` which would be executed if a [package contains `*.gyp` files](https://docs.npmjs.com/cli/v9/using-npm/scripts#npm-install). | ||
If any install-scripts have been found, the tools exit code will be set to `1`. | ||
**If any install-scripts have been found, the tool's exit code will be set to `1` and a list of the affected packages is shown.** | ||
**Note:** Local dependencies which are referenced via links or workspaces are not analyzed. You can use the [`--include-local`](#include-local-dependencies) option to additionally check those. | ||
### Ignore Dependencies | ||
@@ -46,15 +46,12 @@ | ||
### Include Local Dependencies | ||
### Online Mode | ||
```sh | ||
no-scripts --include-local | ||
no-scripts --online | ||
``` | ||
This mode operates fully offline and is therefore rather fast. However, since a malicious package that has already been installed *could* have altered it's own package.json during the `postinstall` phase, this mode might be fooled into thinking that a package has no scripts even though they were already executed. I have not yet evaluated the feasability of such an attack though. | ||
This mode operates fully online and is therefore a little slower. Instead of analyzing already installed dependencies, it starts by independently fetching the tarballs of all dependencies listed in the project's [lockfile](https://docs.npmjs.com/cli/v10/configuring-npm/package-lock-json) from the corresponding npm registry. | ||
### Offline | ||
```sh | ||
no-scripts --offline | ||
``` | ||
Since a malicious package that has already been installed *could* altered it's own package.json during the `postinstall` phase, this mode might add an extra level of safety, making it harder for such packages to fool `no-scripts` compared to the default, local scan. I have not yet evaluated the feasibility of such an attack though. | ||
This option implies [`--include-local`](#include-local-dependencies) as it completely skips the default behavior of resolving the packages listed in the lockfile using a remote registry. | ||
**Note:** Local dependencies which are referenced via links or workspaces are not analyzed in this mode. You can use the [`--include-local`](#include-local-dependencies) option to additionally check those. | ||
@@ -61,0 +58,0 @@ ## Similar Projects |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
181721
4685
8
10
57
+ Addedp-throttle@^6.1.0
+ Added@npmcli/map-workspaces@3.0.4(transitive)
+ Added@npmcli/name-from-folder@2.0.0(transitive)
+ Addedp-throttle@6.1.0(transitive)