Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

no-scripts

Package Overview
Dependencies
Maintainers
1
Versions
6
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

no-scripts - npm Package Compare versions

Comparing version 0.0.2 to 0.0.3

1

lib/analyzer.js

@@ -20,2 +20,3 @@ export async function analyzePackages(packages) {

return {
packageName: packageInfo.packageJson.name,
packageInfo,

@@ -22,0 +23,0 @@ messages

@@ -1,14 +0,60 @@

import { lockfile, packageJson } from "./index.js";
import yargs from "yargs";
import { hideBin } from "yargs/helpers";
import path from "node:path";
import { analyzeLockfile, analyzePackageJson } from "./index.js";
import { writeToConsole } from "./formatter.js";
let result;
if (process.argv[2] && process.argv[2] === "package-json") {
result = await packageJson({ cwd: process.cwd() });
}
else {
result = await lockfile({ cwd: process.cwd() });
}
writeToConsole(result);
if (result.numberOfFindings) {
process.exit(1);
}
await yargs(hideBin(process.argv))
.parserConfiguration({
"parse-numbers": false
})
.scriptName("no-scripts")
.command({
command: "$0 [projectDir]",
desc: "Check lockfile",
builder: (yargs) => {
yargs
.positional("projectDir", {
describe: "Project directory to scan. Defaults to the current working directory",
type: "string",
});
},
handler: async (argv) => {
const cwd = argv.projectDir ? path.resolve(argv.projectDir) : process.cwd();
const lockfileResults = await analyzeLockfile({ cwd, ignorePackages: argv.ignore || [] });
let packageJsonResult;
if (argv.includeLocal) {
console.log("");
packageJsonResult = await analyzePackageJson({ cwd, ignorePackages: argv.ignore || [] });
}
console.log("");
writeToConsole(lockfileResults, packageJsonResult);
if (lockfileResults.numberOfFindings || (packageJsonResult && packageJsonResult.numberOfFindings)) {
process.exit(1);
}
}
})
.option("ignore", {
describe: "Package names to ignore",
array: true,
string: true,
})
.option("include-local", {
describe: "Extend check to include local dependencies",
boolean: true,
})
.showHelpOnFail(true)
.strict(true)
.fail(function (msg, err) {
if (err) {
console.log(`Analysis Failed:`);
console.log(err.message);
console.log("");
console.log(err.stack);
}
else {
// Yargs parsing error
console.log(`Command failed: ${msg}`);
}
})
.parse();
//# sourceMappingURL=cli.js.map

@@ -1,3 +0,3 @@

export function writeToConsole(results) {
for (const packageResult of results.packages) {
export function writeToConsole(lockfileResults, packageJsonResults) {
for (const packageResult of lockfileResults.packages) {
if (packageResult.messages.length) {

@@ -11,4 +11,19 @@ console.log(`Findings for package ${packageResult.packageInfo.packageJson.name}:`);

}
console.log(`Findings: ${results.numberOfFindings}`);
console.log(`${lockfileResults.numberOfFindings} Findings`);
if (packageJsonResults) {
console.log("");
console.log("------------------------");
console.log("Local results:");
for (const packageResult of packageJsonResults.packages) {
if (packageResult.messages.length) {
console.log(`Findings for package ${packageResult.packageInfo.packageJson.name}:`);
for (const msg of packageResult.messages) {
console.log(` ${msg}`);
}
console.log("");
}
}
console.log(`${packageJsonResults.numberOfFindings} Findings`);
}
}
//# sourceMappingURL=formatter.js.map

@@ -10,8 +10,10 @@ import path from "node:path";

*/
export async function lockfile({ cwd }) {
export async function analyzeLockfile({ cwd, ignorePackages }) {
const rootPackageInfo = await getRootPackage(cwd);
let lockfile = await tryReadJson(path.join(rootPackageInfo.modulePath, `package-lock.json`));
let packageLock = true;
if (!lockfile) {
// If no package-lock.json is available, try npm-shrinkwrap.json
lockfile = await tryReadJson(path.join(rootPackageInfo.modulePath, `npm-shrinkwrap.json`));
packageLock = false;
}

@@ -23,11 +25,13 @@ if (!lockfile) {

}
console.log(`Analyzing using lockfile and remote registry`);
console.log(`Analyzing using ${packageLock ? "package-lock.json" : "npm-shrinkwrap.json"} and registry`);
const packages = await getPackagesFromLockfile(rootPackageInfo, lockfile);
removeIgnoredPackages(packages, ignorePackages);
return await analyzePackages(packages);
}
export async function packageJson({ cwd }) {
console.log(`Analyzing using locally installed dependencies`);
export async function analyzePackageJson({ cwd, ignorePackages }) {
console.log(`Analyzing using package.json and locally installed dependencies`);
const rootPackageInfo = await getRootPackage(cwd);
const depPackages = await getPackagesFromInstalledDependencies(rootPackageInfo);
return await analyzePackages(depPackages);
const packages = await getPackagesFromInstalledDependencies(rootPackageInfo);
removeIgnoredPackages(packages, ignorePackages);
return await analyzePackages(packages);
}

@@ -64,2 +68,19 @@ async function getRootPackage(cwd) {

}
function removeIgnoredPackages(packages, ignorePackages) {
const foundIgnoredPackages = new Set();
if (ignorePackages && ignorePackages.length) {
for (const [modulePath, packageInfo] of packages.entries()) {
if (ignorePackages.includes(packageInfo.packageJson.name)) {
packages.delete(modulePath);
foundIgnoredPackages.add(packageInfo.packageJson.name);
}
}
ignorePackages.forEach((ignoredPackageName) => {
if (!foundIgnoredPackages.has(ignoredPackageName)) {
throw new Error(`Failed to find ignored package: ${ignoredPackageName}`);
}
});
}
console.log(`(ignoring ${foundIgnoredPackages.size} packages)`);
}
//# sourceMappingURL=index.js.map

60

lib/installedDepsHandler.js

@@ -10,10 +10,22 @@ import { promisify } from "node:util";

}
function addDepsToArray(targetArray, depsToAdd, optional) {
for (const packageName of depsToAdd) {
targetArray.push({
packageName,
optional
});
}
}
function getPackageDependencies(pkg, rootPkg = false) {
const deps = Object.keys(pkg.dependencies || {});
if (pkg.optionalDependencies) {
deps.push(...Object.keys(pkg.optionalDependencies));
const deps = [];
addDepsToArray(deps, Object.keys(pkg.dependencies || {}), false);
addDepsToArray(deps, Object.keys(pkg.optionalDependencies || {}), true);
// TODO: Maybe add check whether peerDependenciesMeta flags it as optional
addDepsToArray(deps, Object.keys(pkg.peerDependencies || {}), true);
addDepsToArray(deps, Object.keys(pkg.bundleDependencies || {}), false);
addDepsToArray(deps, Object.keys(pkg.bundledDependencies || {}), false);
if (rootPkg) {
// Ignore devDependencies unless for the root package
addDepsToArray(deps, Object.keys(pkg.devDependencies || {}), false);
}
if (rootPkg && pkg.devDependencies) {
deps.push(...Object.keys(pkg.devDependencies));
}
return deps;

@@ -31,3 +43,3 @@ }

packageJson: await readPackageJson(modulePath),
modulePath: modulePath
modulePath
};

@@ -48,17 +60,27 @@ }

const depPackages = new Map();
async function collectDependencies(moduleName, parentPath) {
const res = await getPackageJson(moduleName, parentPath);
if (depPackages.has(res.modulePath)) {
// Deps already processed
return;
async function collectDependencies(moduleName, parentPath, optional = false) {
try {
const res = await getPackageJson(moduleName, parentPath);
if (depPackages.has(res.modulePath)) {
// Deps already processed
return;
}
depPackages.set(res.modulePath, res);
await Promise.all(getPackageDependencies(res.packageJson).map(({ packageName, optional }) => {
return collectDependencies(packageName, res.modulePath, optional);
}));
}
depPackages.set(res.modulePath, res);
return await Promise.all(getPackageDependencies(res.packageJson).map((depName) => {
return collectDependencies(depName, res.modulePath);
}));
catch (err) {
if (optional) {
// Do nothing
}
else {
throw err;
}
}
}
const rootDeps = getPackageDependencies(rootPackageInfo.packageJson, true);
const rootModulePath = path.dirname(rootPackageInfo.modulePath);
await Promise.all(rootDeps.map((depName) => {
return collectDependencies(depName, rootModulePath);
const rootModulePath = rootPackageInfo.modulePath;
await Promise.all(rootDeps.map(({ packageName, optional }) => {
return collectDependencies(packageName, rootModulePath, optional);
}));

@@ -65,0 +87,0 @@ return depPackages;

@@ -56,6 +56,9 @@ import path from "node:path";

}
if (packageDescriptor.link) {
// Ignore links
return;
}
if (!packageDescriptor.resolved) {
// Packages from other sources than an npm registry (i.e. local)
console.log(packageDescriptor);
throw new Error(`Failed to resolve lockfile entry for package: ${packageLocation}`);
return;
}

@@ -62,0 +65,0 @@ if (requests.has(packageDescriptor.resolved)) {

{
"name": "no-scripts",
"version": "0.0.2",
"version": "0.0.3",
"description": "",

@@ -9,4 +9,5 @@ "type": "module",

"build-watch": "tsc -w -p .",
"test": "npm run lint",
"lint": "eslint ."
"test": "npm run lint && npm run check",
"lint": "eslint .",
"check": "node --import tsx/esm src/cli.ts --ignore esbuild"
},

@@ -33,3 +34,4 @@ "keywords": [],

"resolve": "^1.22.8",
"rimraf": "^5.0.5"
"rimraf": "^5.0.5",
"yargs": "^17.7.2"
},

@@ -36,0 +38,0 @@ "devDependencies": {

@@ -15,16 +15,22 @@ # no-scripts

## Usage
There are two modes of operation, "Lockfile Mode" and "package.json Mode".
### Lockfile Mode
```sh
no-scripts lockfile
no-scripts
```
This mode will start by fetching the tarballs of all dependencies listed in the project's lockfile 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 a certain file.
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.
### package.json Mode
**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 them.
### Ignore Dependencies
```sh
no-scripts package-json
no-scripts --ignore esbuild
```
### Include Local Dependencies
```sh
no-scripts --include-local
```
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.

@@ -31,0 +37,0 @@

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 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

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc