NPM CI Lifecycle Script Security Controls
Overview
Party Bus pipelines now enforce --ignore-scripts and --allow-git=none for npm ci to reduce supply chain attack risk. This document describes the impact of running these flags on npm projects, the required configuration changes, and the approved exception mechanisms.
New File Requirement For NPM Pipelines
REMINDER
To provide better security for npm pipelines, a new file (even if empty) named .npm-allow-scripts is required in all npm repos. Please read the Transitive Lifecycle Scripts section below for more details on the contents of this file.
Pipeline Behavior Change
Moving forward, the npm ci command that runs in Party Bus pipelines will include the --ignore-scripts and --allow-git=none flags. We recommend you set ignore-scripts=true and allow-git=none in your projects' .npmrc as well to keep this consistent in local development.
This disables:
- Automatic execution of project lifecycle scripts during package install
- Dependency lifecycle script execution during package install
- Dependency installation for packages that are hosted in a Git repository and resolved using Git (not published to the npm registry)
These changes are motivated by the increase in supply chain attacks targeting dependencies in the Node.js ecosystem. Many of these supply chain attacks have relied on the use of install-time lifecycle scripts (prepare, preinstall, install, and postinstall) that run during the installation of a compromised dependency when running npm install or npm ci. Attackers have also relied on dependencies that resolve via Git to inject malicious code.
Unfortunately, utilizing the ignore-scripts setting blocks both transitive scripts that would run as a result of a dependency install and explicit user-defined scripts specified in the project's package.json.
Impact On Your Projects
Given that customer-defined lifecycle scripts (explicitly defined prepare, preinstall, install, and postinstall in the package.json) will no longer automatically run during the npm-ci job, we recommend that you move towards utilizing custom build scripts defined in your package.json.
INFO
If your project requires a project-level npm lifecycle script to run before the npm ci step (this should be rare), you must submit a P1 Help Desk ticket . The MDO team will review the request and, if approved, configure a pipeline override for the affected job.
For customers using a preinstall script to run npx force-resolutions, please switch to utilizing the overrides field in your package.json. This tells npm to enforce the versions specified across all transitive dependencies. The overrides field is npm's built-in replacement available as of npm v8.3.0 (released with Node.js 16). All of Party Bus's current pipeline images use npm version 10.x or later.
If you need to run an additional script after package install, we recommend the following:
- Optional: Change the script you wish to run in the
scriptsfield of yourpackage.jsonfrompostinstallto a custom name that works for your team to avoid confusion. - Run the script during the
build-imagejob by specifyingRUN npm run <script-name>in your Dockerfile.
If your project requires a dependency that resolves via Git, we recommend finding an alternative package.
INFO
If moving away from the use of a dependency that resolves via Git is not possible, you must submit a P1 Help Desk ticket . The MDO team will review the request and, if approved, configure a pipeline override for the affected job.
Note: Due to limitations of npm, this cannot currently be done on a per-package basis.
Transitive Lifecycle Scripts
Transitive lifecycle scripts that run during dependency installation are discouraged due to the risks they pose. It is becoming standard practice to avoid these scripts and disable them by default, which modern package managers such as pnpm and yarn already do.
Despite this, your project will likely have at least one dependency that utilizes a lifecycle script. While we highly recommend that you explore alternative options to fulfill the dependency requirement, we understand that it may not be possible for some customers.
REMINDER
The new standard expected behavior is that no lifecycle scripts will run by default during the npm ci command due to the added --ignore-scripts flag.
Allowing Required Dependency Lifecycle Scripts
If you need specific transitive lifecycle scripts to run, use the following exception path:
Create the
.npm-allow-scriptsexceptions file- If you use npm and your project has dependencies that run install scripts, create a file named
.npm-allow-scriptsin the root of your project. - This file lists approved script exceptions. If no exceptions are needed, you can leave the file empty.
- If you use npm and your project has dependencies that run install scripts, create a file named
Add allowed dependencies (if needed)
- If a dependency requires its lifecycle scripts to run, add it to the
.npm-allow-scriptsfile. - Add one dependency per line and only include dependencies you trust and require for your build.
- If a dependency requires its lifecycle scripts to run, add it to the
Commit required files
Ensure both of the following files are committed to your repository:
.npm-allow-scriptspackage-lock.json
WARNING
If either file is missing, your pipeline will fail.
Pipeline validation (
npm-scripts-check)- During the pipeline, a job called
npm-scripts-checkwill:- Verify the
.npm-allow-scriptsfile exists, - Analyze your dependencies for lifecycle scripts, and
- Provide recommendations if scripts are detected.
- Verify the
- During the pipeline, a job called
Script execution behavior
- After
npm cicompletes:- Only dependencies listed in
.npm-allow-scriptswill have their lifecycle scripts executed. - This is done via a controlled npm rebuild step.
- Only dependencies listed in
- After