npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@wojciech_lesicki/security-lifecycle-demo

v1.0.0

Published

Educational demo of npm preinstall/postinstall lifecycle scripts - each hook fetches a package's registry metadata and logs the response, and reports how npm >=12's allowScripts blocking affects it.

Readme

npm-lifecycle-demo

A tiny, harmless demo package showing how npm's preinstall and postinstall lifecycle scripts work — and, just as importantly, when npm actually runs them at all. Install it as a dependency in a real project and it doubles as a quick probe for "does this environment execute lifecycle scripts, and if so, is the output even visible?" — useful when checking a CI setup, a teammate's machine, or your own npm config for how permissive it is toward install scripts.

What it does

When this package is installed, npm runs two scripts automatically (defined in package.json → scripts):

  • preinstall — runs before the package's files are placed in node_modules.
  • postinstall — runs after they are.

Both scripts call the same helper, scripts/check-registry.js, which does exactly one thing: it makes a single HTTPS GET request to the public npm registry's package metadata endpoint, e.g.

https://registry.npmjs.org/npm

and prints a few fields from the JSON response (latest version, description, number of published versions) to the console. This is a real, unauthenticated, read-only registry endpoint — no npm login or token is needed, and the response is actually interesting to look at (unlike /-/whoami, which just returns 401 Unauthorized unless you're logged in).

By default it looks up the npm package itself — fitting, since this whole demo is about npm's own lifecycle-script behavior — but you can point it at anything:

LIFECYCLE_DEMO_PKG=react npm install

The script does not:

  • read any files outside itself
  • read environment variables, credentials, or .npmrc tokens
  • write anything to disk
  • send any data anywhere other than that one GET request
  • do anything conditional on install failing/succeeding

It exists purely so you can see lifecycle scripts fire and watch a real (if trivial) network call happen during npm install.

Try it locally

npm install

You should see console output like:

[npm-lifecycle-demo] preinstall script running - GET https://registry.npmjs.org/npm
[npm-lifecycle-demo] preinstall: running under npm v10
[npm-lifecycle-demo] preinstall: HTTP 200
[npm-lifecycle-demo] preinstall: package      -> npm
[npm-lifecycle-demo] preinstall: latest ver.  -> 12.0.2
[npm-lifecycle-demo] preinstall: description  -> a package manager for JavaScript
[npm-lifecycle-demo] preinstall: # versions   -> 604
...
[npm-lifecycle-demo] postinstall script running - GET https://registry.npmjs.org/npm
[npm-lifecycle-demo] postinstall: running under npm v10
[npm-lifecycle-demo] postinstall: HTTP 200
[npm-lifecycle-demo] postinstall: package      -> npm
[npm-lifecycle-demo] postinstall: latest ver.  -> 12.0.2
...

Exact numbers will differ over time since it's a live lookup against the real registry.

You can also simulate installing it as a dependency into another project without publishing it anywhere:

npm pack
# creates npm-lifecycle-demo-1.0.0.tgz
cd /path/to/some-other-project
npm install /path/to/npm-lifecycle-demo-1.0.0.tgz

Console output isn't guaranteed

Whether you actually see the console.log lines above depends on how the package is installed:

  • As the root project you're standing in (cd npm-lifecycle-demo && npm install, which is what "Try it locally" above does) — npm always prints lifecycle script output live. This is the case in every example in this README.

  • As a dependency inside another project (npm install npm-lifecycle-demo from someone else's package.json) — since npm 7, output from a dependency's preinstall/install/postinstall is captured in the background and not printed at all by default, unless the script exits with an error. You can confirm this yourself:

    mkdir /tmp/dep-test && cd /tmp/dep-test && npm init -y
    npm install /path/to/npm-lifecycle-demo
    # -> silent, no [npm-lifecycle-demo] lines, even though the scripts ran
    
    rm -rf node_modules package-lock.json
    npm install /path/to/npm-lifecycle-demo --foreground-scripts
    # -> now you see the full [npm-lifecycle-demo] output live

This matters because it's exactly the gap real malicious packages exploit: as a dependency, their postinstall can phone home, drop a payload, whatever — completely silently, by default, with nothing in your terminal to tip you off. --foreground-scripts (or npm config set foreground-scripts true) is how you'd audit that.

npm v12: dependency scripts are blocked by default

npm v12 (released July 8, 2026) goes a step further than just hiding output — it stops running dependency lifecycle scripts at all by default:

  • The allowScripts policy defaults to off. preinstall, install, and postinstall scripts belonging to dependencies (including implicit node-gyp rebuilds) are skipped with a warning, not executed — the install still succeeds ("soft skip").
  • This only applies to scripts coming from node_modules dependencies. The root project's own lifecycle scripts (i.e. what npm install runs when you're standing inside this repo) are unaffected and still run normally — same as before.
  • To allowlist a package's scripts: npm install-scripts ls lists what's currently blocked, npm install-scripts approve <pkg> writes the approval into package.json's allowScripts (there's also npm install-scripts deny <pkg> to explicitly refuse). The approved scripts don't run immediately though — you then need npm rebuild (or a fresh npm install) to actually execute them. And even then, their output stays hidden unless you also pass --foreground-scripts — see the section above.
  • Warnings about this change start appearing in npm 11.16.0+; it becomes the default in npm 12.0.0.

This whole flow is verified against a real npm 12.0.2 install, end to end:

mkdir /tmp/dep-test && cd /tmp/dep-test && npm init -y
npm install /path/to/npm-lifecycle-demo
# npm warn install-scripts 1 package had install scripts blocked because
# they are not covered by allowScripts: [email protected] (...)
# npm warn install-scripts Run `npm install-scripts ls` to review, or
# `npm install-scripts approve <pkg>` to allow.

npm install-scripts ls
# 1 package has install scripts blocked ... [email protected] (...)

npm install-scripts approve npm-lifecycle-demo
# Approved ... — written into package.json's "allowScripts"

npm install --foreground-scripts
# now the scripts actually run AND their [npm-lifecycle-demo] output
# is visible, in one step

scripts/check-registry.js in this package detects the running npm's major version from the npm_config_user_agent env var (which npm sets for every lifecycle script — no subprocess needed) and prints a note when it's ≥ 12. If you install this package as a dependency under npm 12+ and do see its [npm-lifecycle-demo] output, that by itself tells you the script was explicitly allowlisted via npm install-scripts approve.

Sources: this package's own verified test above; GitHub Changelog — Upcoming breaking changes for npm v12; npm CLI v12 changelog. Note: a couple of secondary blog posts found while researching this got the approval command's name wrong (npm approve-scripts) — the command npm itself actually prints, confirmed above, is npm install-scripts approve <pkg>.

Why this matters

preinstall/postinstall scripts run arbitrary code on the installing machine with the same permissions as the user running npm install — that's exactly the mechanism abused by real supply-chain attacks (credential theft, crypto-miners, etc.). This package is a minimal, transparent, read-only example meant to illustrate that the mechanism exists and what it can reach (the network), without doing anything harmful, so you can reason about why --ignore-scripts (or, on npm 12+, the default-off allowScripts policy) and dependency auditing matter.

License

MIT