run install scripts: init

add option: 'trustedDeps'. Only install scripts of explizit added dependencies are run.
In the future there may be wildcards to allow running all install scripts
This commit is contained in:
Johannes Kirschbauer 2024-01-11 17:01:09 +01:00 committed by mergify[bot]
parent 0af1c36d17
commit 57fd52adcd
3 changed files with 136 additions and 3 deletions

View File

@ -123,6 +123,7 @@
# TODO: Verify this is reasonable default;
source = builtins.dirOf cfg.packageLockFile;
makeNodeModules = ./build-node-modules.mjs;
installTrusted = ./install-trusted-modules.mjs;
in
if path == ""
then let
@ -144,14 +145,17 @@
name = entry.name + "-dist";
src = source;
buildInputs = with config.deps; [nodejs jq];
env = {
TRUSTED = builtins.toJSON cfg.trustedDeps;
};
configurePhase = ''
cp -r ${prepared-dev}/node_modules node_modules
# TODO: run installScripts of trusted dependencies
node ${installTrusted}
'';
buildPhase = ''
echo "BUILDING... $name"
if [ -n "$runBuild" ] && [ "$(jq '.scripts.build' ./package.json)" != "null" ]; then
if [ "$(jq '.scripts.build' ./package.json)" != "null" ]; then
echo "npm run build"
npm run build
fi;
'';

View File

@ -0,0 +1,90 @@
import fs from "fs";
import { abort } from "process";
import { execSync } from "child_process";
const { TRUSTED } = process.env;
/**@type {string[]} */
const trusted = JSON.parse(TRUSTED);
console.log({ trusted });
/**
* @type {fs.Dirent[]}*/
const packageJsonFiles = fs
.readdirSync(
"./node_modules",
{ recursive: true, withFileTypes: true },
(error) => {
console.error({ error });
abort();
}
)
.filter(
(/**@type {fs.Dirent}*/ entry) =>
entry.isFile() &&
entry.name === "package.json" &&
// Reduce the number of packageJson files that we need to parse.
// Note: The list may still have some false positives. They will be skipped later
trusted.some((trustedName) => entry.path.endsWith(trustedName))
);
// If a dependency is trusted
// Run the following scripts if present
//
// preinstall
// install
// postinstall
// prepublish
// preprepare
// prepare
// postprepare
//
// The lifecycle scripts run only after node_modules are completely initialized with ALL modules
//
// Lifecycle scripts can execute arbitrary code.
// They often violate isolation between packages which makes them potentially insecure.
const lifecycleScripts = [
"preinstall",
"install",
"postinstall",
"prepublish",
"preprepare",
"prepare",
"postprepare",
];
packageJsonFiles.forEach((pjs) => {
const content = fs.readFileSync(`${pjs.path}/${pjs.name}`);
/**@type {{scripts?: { [k: string]: string}, name: string }}*/
const info = JSON.parse(content.toString());
const { scripts, name: packageName } = info;
// Skip false positives
if (trusted.includes(packageName)) {
const run =
scripts &&
Object.entries(scripts).filter(([k]) =>
lifecycleScripts.some((s) => s === k)
);
if (run) {
run.forEach(([scriptName, command]) => {
console.log(`${packageName} - ${scriptName}: ${command}`);
try {
const result = execSync(command, { cwd: pjs.path });
console.log(result.toString());
} catch (err) {
console.error(
`Could not execute lifecycle script '${scriptName}' for ${packageName} (See Trusted Dependencies)`
);
console.error(err);
}
});
} else {
console.warn(
`Trusted package ${packageName} doesnt have any lifecycle scripts. This entry does not have any affect.`
);
}
}
});

View File

@ -14,11 +14,50 @@ in {
The package-lock.json file to use.
'';
};
packageLock = {
type = t.attrs;
description = "The content of the package-lock.json";
};
trustedDeps = {
type = t.listOf (t.str);
default = [];
example = ["@babel/core"];
description = ''
A list of trusted dependencies.
If a dependency is trusted.
Run the following scripts in order if present:
> All versions of a dependency are trusted if there are multiple versions.
preinstall
install
postinstall
prepublish
preprepare
prepare
postprepare
The lifecycle scripts run only after node_modules are completely initialized with ALL dependencies.
Lifecycle scripts can execute arbitrary code. Which makes them potentially insecure.
They often violate isolation between packages. Which makes them potentially insecure.
*TODO*:
Trust all dependencies:
trustedDeps [ "*" ]
Trust all dependencies starting with "@org"
trustedDeps [ "@org/*" ]
which is usefull if you want to add all dependendencies within an organization.
'';
};
inherit
(import ./types.nix {
inherit