Renaming onlyBuiltDependencies and ignoredBuiltDependencies in pnpm v11
Contribution
- [ ] I'd be willing to implement this feature (contributing guide)
Describe the user story
We currently have 3 settings related to dependency build script rules. Initially we had only "neverBuiltDependencies", which was an array of dependency names that are prohibited from running build scripts. Later we have decided to add an allowlist instead thus "onlyBuiltDependencies" was introduced. These two settings cannot be used together. A project should either use an allowlist or a disallow list. A third setting is also available called ignoredBuiltDependencies, which is used together with onlyBuiltDependencies.
So, a project either uses onlyBuiltDependencies and ignoredBuiltDependencies, or it uses neverBuiltDependencies.
Describe the solution you'd like
I think we should deprecate all the 3 settings in pnpm v11 and have one setting instead: allowBuilds (or allowScripts). This new setting would be a dictionary, where the name is the package descriptor and the value is a boolean or "warn". E.g.:
allowBuilds:
esbuild: true
core-js: false
ghooks: 'warn'
This would allow us to prepopulate this setting during installation. E.g.:
allowBuilds:
esbuild: 'warn'
core-js: 'warn'
ghooks: 'warn'
The user will then have a choice to either manually change the values or to run pnpm approve-builds
Describe the drawbacks of your solution
No response
Describe alternatives you've considered
No response
Prior art
https://www.npmjs.com/package/@lavamoat/allow-scripts
cc @pnpm/collaborators
It looks good. This configuration is much simpler.
👍 LGTM
I like the idea!
There was also a suggestion to make strictDepBuilds true in v11. So, warn might be a misleading value in that case. Maybe it should be something else, like needsApproval.
Good idea! But I have two small points about the design.
On whether needsApproval / warn are actually needed
These look equivalent to simply not listing the package.
I’m not sure when someone would explicitly set foo: 'needsApproval' instead of just omitting it.
Is there a concrete use case?
On supporting version ranges
It might be useful if allowBuilds accepted semver ranges, not only exact versions:
allowBuilds:
esbuild: true
[email protected]: true
core-js@^0.10.1: false
This would allow blocking unsafe ranges while keeping safe ones.
Is there a concrete use case?
I think we could prepopulate this setting with the string placeholders. Users could then just edit pnpm-workspace.yaml and replace the placeholders with false and true. Of course, the pnpm approve-builds would work too.
It might be useful if allowBuilds accepted semver ranges
I think it isn't a good idea to allow ranges (although we do allow the * range currently) because when a package get hijacked, any version can be published. So someone with access to the package could release a version that will satisfy core-js@^0.10.1
Hi pnpm team! 👋🏻
First off, congratulations on all the attention pnpm has been getting lately—I'm sure it's been quite a whirlwind. I wanted to take a moment to thank you for the incredible work you're doing, particularly around supply chain security controls.
Who We Are
I'm Ryan Sobol, Principal Software Engineer at the Seattle Times. We're currently piloting pnpm as part of our broader initiative to secure our npm supply chain.
We've been using npm as our default package manager for what feels like forever. There was some experimentation with Yarn that never really got off the ground. But we're evaluating pnpm specifically for its client-side security controls that complement the great registry-level improvements npm has been rolling out.
Despite the organizational inertia that comes with sticking to npm, we think pnpm actually has a real chance of succeeding here. The fact that it's a true drop-in replacement (same commands, same workflows) makes the transition feel achievable in a way that previous alternatives didn't.
Why We're Sharing Our Story
We see npm's recent security improvements (OIDC publishing, provenance attestations, granular tokens) and pnpm's client-side controls as complementary pieces of a defense-in-depth strategy. npm is making it harder to publish malicious packages; pnpm is making it harder to consume them. Both are essential.
As we work through our pilot, I wanted to share our experience—not as a polished case study, but as a real-world data point from a team that's just starting to figure this out. The challenges we're encountering, the decisions we're making, and how we're thinking about these controls might be useful context as you consider improvements for v11.
Our Pilot Experience
We've successfully implemented pnpm's security controls in one of our backend services as a proof of concept. This included:
- strictDepBuilds: true - Requiring explicit allow/deny decisions for all lifecycle scripts
- minimumReleaseAge - Enforcing a cooldown period for newly published packages
- trustPolicy: no-downgrade - Blocking installation when package trust levels degrade
The pilot went smoothly. With pnpm's help, we identified a handful of packages with lifecycle scripts, researched what each script did, and determined none were necessary for our use case. Total setup time: about 1 hour. No functionality was impacted.
Our Mental Model
One thing that's become clear through this process: we expect to need exceptions, and that's okay.
There are legitimate use cases where packages need lifecycle scripts (native extensions, database drivers, compilation/linking steps). There are legitimate reasons to install newer packages immediately (critical security vulnerabilities like the recent React compromise that the ecosystem is grappling with). There are legitimate trust downgrades (CI/CD migrations, maintainer changes, emergency hotfixes).
This is why having escape hatches for each control is so valuable. The real world is messy. The fact that each setting (onlyBuiltDependencies, minimumReleaseAgeExclude, trustPolicyExclude) provides a way to enumerate specific exceptions means these controls are practical, not theoretical.
The key insight is that these controls work together as layers of defense. When we need to make an exception for one control (e.g., allowing a package's lifecycle scripts because it genuinely needs to compile native code, or exempting a critical security patch from the release cooldown), the other layers continue protecting us. No single point of failure.
This layered approach means:
- We can be pragmatic about exceptions when they're justified
- We still have robust protection against supply chain attacks like Shai-Hulud
- We're making conscious, documented decisions rather than implicitly trusting everything
Why This Naming Discussion Matters
That's why I'm interested in this issue about improving the naming and semantics of these controls for v11. As someone brand new to these concepts, I can share what was confusing during our pilot with pnpm 10:
The current names (onlyBuiltDependencies / ignoredBuiltDependencies) initially felt backwards to me. When I see "ignored," my instinct is "this package is being ignored/excluded from some process." But actually, ignoredBuiltDependencies means "the lifecycle scripts are being ignored/blocked"—the package itself installs just fine.
I found myself constantly translating:
onlyBuiltDependencies→ "packages whose scripts are ALLOWED to run"ignoredBuiltDependencies→ "packages whose scripts are BLOCKED from running"
Why We Use strictDepBuilds: true
One thing worth mentioning: we deliberately set strictDepBuilds: true to make installation fail immediately rather than just warn. Here's our reasoning:
By default, pnpm blocks lifecycle scripts and shows a warning, but installation continues. The problem is warnings are easy to ignore—especially when everything appears to work initially. But some packages genuinely need their lifecycle scripts to function correctly. Without those scripts running, you might encounter subtle bugs or inconsistent behavior later.
More importantly, there's a risk that to make warnings go away, teams might automatically approve lifecycle scripts without proper review—exactly the opposite of what these security controls are trying to achieve.
With strictDepBuilds: true, installation fails immediately, forcing us to:
- Identify which packages have lifecycle scripts
- Research what each script actually does
- Make a conscious, documented decision about whether to allow or block it
This ensures packages work as intended while maintaining the security benefit of requiring explicit review. We'd rather be forced to confront the decision upfront than have packages potentially misbehave later or fall into a pattern of approving scripts just to clear warnings.
Whatever improvements you make for v11, I'd encourage maintaining this "fail-fast" philosophy as an option—clarity around "what exactly is being allowed/blocked" combined with immediate feedback helps teams like ours adopt these controls more confidently.
What's Next For Us
Based on this successful pilot, we're planning to migrate our React Native mobile app to pnpm in Q1 2026. That's a much more complex codebase and will give us a better sense of how these controls scale to applications with larger dependency graphs. From there, we'll evaluate whether to make pnpm our standard across all Node.js projects.
Thank You
I wanted to share this anecdotal data point from a team in the wild that's just starting to wrap their heads around client-side supply chain security. You're building something genuinely valuable here, and it's clear a lot of thought has gone into making these controls both powerful and practical.
Thanks for all your work on this. Looking forward to seeing where pnpm v11 takes these features!
Ryan
@ryansobol thanks for the feedback. Could you submit this as an article to our blog here: https://github.com/pnpm/pnpm.io/tree/main/blog ? I think it showcases well how the different new features of pnpm can help with these supply chain security.
we're planning to migrate our React Native mobile app to pnpm in Q1 2025
I assume you meant 2026 here.
Regarding strictDepBuilds, we do plan to change it to true by default in v11 (related discussion.
Regarding minimumReleaseAge, I was considering to change the default value for it as well but not everyone likes this idea. I was also considering to add something like "loose minimum release age" where a new version will be allowed if none of the mature versions satisfy the range. Maybe that could be more acceptable as a default.
I found myself constantly translating: onlyBuiltDependencies → "packages whose scripts are ALLOWED to run" ignoredBuiltDependencies → "packages whose scripts are BLOCKED from running"
Maybe we should also use the term "scripts" instead of "built". So, allowBuilds=>allowScripts. pnpm approve-builds=>pnpm approve-scripts.
I am not sure about this. A script can be used for anything really but I think the only legitimate reason for a lifecycle script to exist is for building the package. Maybe even allowBuildScripts could work.
@zkochan Thanks for the interest in having this as a blog article! I'd be happy to work on that. I'll need to run it by my team to double-check they're comfortable with what gets shared more prominently, and I can handle any needed redactions from there.
And yes, good catch on the timeline—I meant 2026, not 2025. I've updated my original comment to fix the confusion.
On strictDepBuilds: true as default: That's great to hear you're planning to make that the default in v11. I think that's the right call. Better error messaging about why it's failing and what to do about it would definitely help with adoption—guidance on the recommended approach for evaluating whether to allow or block scripts.
On minimumReleaseAge default: This one's tough. Setting any default creates potential liability if a vulnerability slips through within that window, which invites backlash. You'd probably need to be pretty conservative, which then conflicts with the industry mindset of "newest is best."
The challenge is scenarios like the recent critical React vulnerability—teams will want the hotfix immediately, but then they'll need to add an exception to override the cooldown. That friction might be harder to justify for a default setting.
I suspect this is one of those things where each organization needs to decide their own risk tolerance. But if you're serious about exploring a default, it might be worth consulting with security experts who specialize in supply chain security. Given pnpm's influence in the ecosystem, you could probably find someone willing to weigh in.
On naming (bikeshedding alert 🚲): If we're throwing out suggestions, a few options come to mind:
allowDepBuildsorallowDepBuildsFor- mirrors the existingstrictDepBuildsnaming, creating a clear parallelallowBuildScriptsorallowBuildScriptsFor- more explicit about what you're allowing (the actual scripts)allowLifecycleScriptsorallowLifecycleScriptsFor- technically accurate since npm and pnpm both call these "lifecycle scripts," though that term is broader and includes things like prepack, publish, etc. beyond just install-time scripts
I lean toward the "build" terminology since it connects with strictDepBuilds and we're really talking about build scripts that run during installation. But just throwing these out there—you all will know what resonates with the community!
Thanks again for all the work you're putting into these security features. Really excited to see where v11 goes with this.
@zkochan Everything's been signed off on my end. I've submitted the blog article in pnpm/pnpm.io#728—expanded it a bit to provide more guidance for other teams. Let me know if you need anything else!