core: harden npm install against supply-chain attacks (#22245)

* core: add .npmrc baseline to block dependency lifecycle scripts

Set ignore-scripts=true at the repo root, plus engine-strict, save-exact,
audit, and prefer-offline. This neutralizes the dominant npm supply-chain
attack vector — postinstall scripts in transitive dependencies — at the
cost of requiring an explicit rebuild for the handful of packages that
legitimately need install scripts (esbuild, chromedriver, tree-sitter,
tree-sitter-json). The next commit wires that rebuild into the Makefile.

Co-Authored-By: Playpen Agent <279763771+playpen-agent@users.noreply.github.com>

* core: route node installs through make to retire website preinstall hook

Make docs-install depend on a new root-node-install so the root deps
are guaranteed before the website install runs, removing the need for
the website/preinstall lifecycle script. Rebuild the small audited list
of trusted packages (esbuild, chromedriver, tree-sitter, tree-sitter-json)
after the web install so ignore-scripts=true remains the only path that
needs maintenance. web/README documents the new workflow.

Co-Authored-By: Playpen Agent <279763771+playpen-agent@users.noreply.github.com>

* Clean up install scripts.

* Track .npmrc in CODEOWNERS

---------

Co-authored-by: Playpen Agent <279763771+playpen-agent@users.noreply.github.com>
This commit is contained in:
Teffen Ellis
2026-05-13 14:20:36 +02:00
committed by GitHub
parent a3c50ae92a
commit 2c3d11a4c3
6 changed files with 59 additions and 6 deletions
+20
View File
@@ -0,0 +1,20 @@
# Block lifecycle scripts (preinstall/install/postinstall/prepare) from dependencies.
# This neutralizes the dominant npm supply-chain attack vector.
#
# Packages that legitimately need a build step (e.g. esbuild, chromedriver, tree-sitter)
# must be rebuilt explicitly:
#
# npm rebuild --foreground-scripts esbuild chromedriver tree-sitter tree-sitter-json
ignore-scripts=true
# Fail fast if the active Node/npm doesn't match the "engines" field.
engine-strict=true
# Pin exact versions so `npm install <pkg>` writes "1.2.3" not "^1.2.3".
save-exact=true
# Surface CVE warnings during install; doesn't block.
audit=true
# Suppress funding banners.
fund=false
+1
View File
@@ -34,6 +34,7 @@ packages/django-channels-postgres @goauthentik/backend
packages/django-postgres-cache @goauthentik/backend packages/django-postgres-cache @goauthentik/backend
packages/django-dramatiq-postgres @goauthentik/backend packages/django-dramatiq-postgres @goauthentik/backend
# Web packages # Web packages
.npmrc @goauthentik/frontend
tsconfig.json @goauthentik/frontend tsconfig.json @goauthentik/frontend
package.json @goauthentik/frontend package.json @goauthentik/frontend
package-lock.json @goauthentik/frontend package-lock.json @goauthentik/frontend
+15 -3
View File
@@ -125,7 +125,7 @@ core-i18n-extract:
--ignore website \ --ignore website \
-l en -l en
install: node-install docs-install core-install ## Install all requires dependencies for `node`, `docs` and `core` install: node-install web-install core-install ## Install all requires dependencies for `node`, `web` and `core`
dev-drop-db: dev-drop-db:
$(eval pg_user := $(shell $(UV) run python -m authentik.lib.config postgresql.user 2>/dev/null)) $(eval pg_user := $(shell $(UV) run python -m authentik.lib.config postgresql.user 2>/dev/null))
@@ -228,14 +228,26 @@ gen-dev-config: ## Generate a local development config file
## Node.js ## Node.js
######################### #########################
# Packages whose install/postinstall scripts are required for correct
# operation (binary downloads, native bindings). The root .npmrc sets
# `ignore-scripts=true` to block dependency lifecycle scripts by default;
# this list is rebuilt explicitly with scripts re-enabled. Audit any
# additions: each entry runs arbitrary code at install time.
TRUSTED_INSTALL_SCRIPTS := esbuild chromedriver tree-sitter tree-sitter-json
node-install: ## Install the necessary libraries to build Node.js packages node-install: ## Install the necessary libraries to build Node.js packages
npm ci npm ci
npm ci --prefix web
######################### #########################
## Web ## Web
######################### #########################
web-install: ## Install the necessary libraries to build the Authentik UI
npm ci --prefix web
web-postinstall: ## Trigger postinstall scripts for packages with native bindings or binary downloads, which are blocked by default for security reasons.
npm rebuild --prefix web --ignore-scripts=false --foreground-scripts $(TRUSTED_INSTALL_SCRIPTS)
web-build: node-install ## Build the Authentik UI web-build: node-install ## Build the Authentik UI
npm run --prefix web build npm run --prefix web build
@@ -268,7 +280,7 @@ web-i18n-extract:
docs: docs-lint-fix docs-build ## Automatically fix formatting issues in the Authentik docs source code, lint the code, and compile it docs: docs-lint-fix docs-build ## Automatically fix formatting issues in the Authentik docs source code, lint the code, and compile it
docs-install: docs-install: node-install
npm ci --prefix website npm ci --prefix website
docs-lint-fix: lint-spellcheck docs-lint-fix: lint-spellcheck
+21
View File
@@ -3,6 +3,27 @@
This is the default UI for the authentik server. The documentation is going to be a little sparse This is the default UI for the authentik server. The documentation is going to be a little sparse
for awhile, but at least let's get started. for awhile, but at least let's get started.
# Setup
Install dependencies from the repo root with `make node-install` (or `make install` for the full
Python + web + docs bootstrap). This wraps `npm ci` and explicitly rebuilds the small set of
packages whose install scripts are required for the toolchain to function — currently `esbuild`,
`chromedriver`, `tree-sitter`, and `tree-sitter-json`.
The repo-root `.npmrc` sets `ignore-scripts=true` to neutralize the dominant npm supply-chain
attack vector. As a side effect, running `npm ci` directly in this directory will install
dependencies but skip those rebuilds, leaving `esbuild` and `chromedriver` in a non-functional
state. If you bypass `make`, run the rebuild step yourself:
```bash
npm rebuild --ignore-scripts=false --foreground-scripts \
esbuild chromedriver tree-sitter tree-sitter-json
```
New dependencies that ship install scripts must be audited and added to `TRUSTED_INSTALL_SCRIPTS`
in the repo-root `Makefile`. Each entry is arbitrary code that runs at install time, so the list
is intentionally small.
# The Theory of the authentik UI # The Theory of the authentik UI
In Peter Naur's 1985 essay [Programming as Theory In Peter Naur's 1985 essay [Programming as Theory
-1
View File
@@ -7,7 +7,6 @@
"": { "": {
"name": "@goauthentik/docs", "name": "@goauthentik/docs",
"version": "0.0.0", "version": "0.0.0",
"hasInstallScript": true,
"license": "MIT", "license": "MIT",
"workspaces": [ "workspaces": [
"vendored/*", "vendored/*",
+2 -2
View File
@@ -9,12 +9,12 @@
"build:integrations": "npm run build -w integrations", "build:integrations": "npm run build -w integrations",
"check-types": "tsc -b", "check-types": "tsc -b",
"docusaurus": "docusaurus", "docusaurus": "docusaurus",
"preinstall": "npm ci --prefix ..",
"lint": "eslint --fix .", "lint": "eslint --fix .",
"lint:lockfile": "echo 'Skipping lockfile linting'", "lint:lockfile": "echo 'Skipping lockfile linting'",
"lint-check": "eslint --max-warnings 0 .", "lint-check": "eslint --max-warnings 0 .",
"prettier": "prettier --write .", "prettier": "prettier --write .",
"prettier-check": "prettier --check .", "prettier-check": "npm run prettier-prepare && prettier --check .",
"prettier-prepare": "npm ci --prefix ../packages/prettier-config",
"start": "npm start -w docs", "start": "npm start -w docs",
"test": "node --test" "test": "node --test"
}, },