A pnpm-based SaaS is harder to compromise when dependency changes are treated as reviewed operational changes, not as routine install output. Start with a committed lockfile and a frozen install. Then add a release-age delay, block unexpected Git or tarball sources, allow only reviewed dependency build scripts, and make every new package pass a short approval record. These controls reduce the chance of accepting a surprise; they do not prove a dependency is safe.
That distinction matters for a solo Next.js developer. A package update can change code that runs during installation, alter the resolved tree, or bring an unfamiliar transitive dependency into a production build. The useful goal is not “secure npm forever.” It is a dependency path you can inspect, reproduce, and stop when its assumptions change.
Use one policy to turn installs into an explicit gate
The current Frontend Accelerator product repository pins its package manager to pnpm 11.25.0 and keeps its install policy in pnpm-workspace.yaml. That is verified repository evidence, not a claim that every pnpm project has the same controls. Its policy requires new direct and transitive releases to be at least 1,440 minutes old, rejects transitive Git and tarball sources, prefers a frozen lockfile, verifies store integrity and package-content checks, disables automatic peer installation, and restricts dependency lifecycle scripts to a small named allowlist.
pnpm’s current security guidance explains the rationale for three of those choices: it recommends an explicit allowBuilds list rather than allowing all dependency builds; blockExoticSubdeps: true prevents transitive dependencies from using Git repositories or direct tarball URLs; and minimumReleaseAge delays newly published versions. The same guidance is clear about the limit: these are risk-reduction controls, not a substitute for reviewing updates and responding to compromised trusted packages.
# pnpm-workspace.yaml
packages:
- "."
minimumReleaseAge: 1440
blockExoticSubdeps: true
preferFrozenLockfile: true
verifyStoreIntegrity: true
strictStorePkgContentCheck: true
autoInstallPeers: false
engineStrict: true
allowBuilds:
"@swc/core": true
sharp: true
This is a policy shape, not a drop-in allowlist. The last two entries are examples of packages the repository has explicitly reviewed for build-time behavior. Copying a package name from another project would defeat the point. Review each build script and record why it is required before adding it.
What each control changes—and what it cannot decide
Freeze the resolution before CI installs it
A committed lockfile gives a specific dependency resolution a durable name. A frozen install makes CI fail when package.json and the lockfile disagree instead of quietly resolving a fresh tree. In the current product repository, pnpm install --frozen-lockfile completed against 1,001 lockfile entries and reported that the lockfile passed the configured supply-chain policies. That proves this checkout can reproduce its declared resolution under that policy at the time of the check. It does not audit every package’s source code or establish that future installs will have identical external conditions.
Delay novelty instead of trusting it immediately
A release-age rule creates a review window. The current policy uses one day. That is a practical default for a maintained SaaS where waiting until tomorrow is usually cheaper than installing a just-published version during an incident window. It is not the right delay for every team: a critical security fix may require an exception. When that happens, document the package, version, reason, reviewer, upstream advisory or release note, and the follow-up date. An exception with a receipt is safer than silently turning the safeguard off.
Keep transitive sources ordinary and visible
Registry packages are still third-party code, but a normal registry resolution is easier to inventory and compare than a transitive Git URL or direct archive. With blockExoticSubdeps: true, a new transitive source that bypasses that expectation is a stop signal. Do not solve the stop by disabling the setting globally. First identify which direct dependency introduced it, why the alternate source is necessary, whether a registry release exists, and who owns the exception.
Treat install scripts as executable changes
Build and postinstall scripts run code during dependency installation. The product policy lists only reviewed packages such as compiler, native-watcher, protocol, and image-processing dependencies that need an install-time build step. A new request to add a build-capable dependency should create two decisions: whether the dependency belongs in the product, and whether its lifecycle script is acceptable. Those decisions are related but not interchangeable.
Integrity and strict store-content checks add another boundary: they help detect inconsistency between what pnpm expects and what is present in its store. They cannot verify an author’s intent, validate a package’s business logic, or protect a developer who runs an unreviewed project script. Keep the language precise so the team knows which controls it can rely on.
Run this approval workflow before adding a package
- Name the product need. Write the feature, failure, or maintenance task the package solves. “An agent suggested it” is not a requirement.
- Check the existing tree. Search the current manifest, lockfile, and application code for an existing capability or a smaller dependency already in use.
- Inspect provenance and license. Use the package’s registry page and its maintained source repository. Record the exact version, publisher or organization, license, release notes, and any advisories relevant to the version.
- Review package behavior. Inspect declared files, install scripts, supported runtimes, peer dependencies, server/client boundary, and the code path your app will call.
- Resolve under policy. Add the dependency in an isolated change, keep the generated lockfile diff, and let the release-age, exotic-source, peer, and build-script rules fail closed.
- Run a clean frozen install. Confirm the committed lockfile installs without resolution drift. Treat a policy failure as a review event, not a command to bypass.
- Test the smallest product behavior. Add or run the test that proves the package is used only where intended. For a server-only SDK, verify it does not enter a Client Component bundle.
- Record the decision. Link the pull request to the reason, version, exception if any, test evidence, and owner for future updates.
Use the audit as a release checklist, not a one-time setup task
The following current-policy audit is the original asset for this guide. Use it on every dependency-changing pull request. A blank entry is a reason to pause the change, not an invitation to invent evidence.
Dependency policy audit
- Resolution: Does the pull request include only an intended manifest and lockfile diff? Does
pnpm install --frozen-lockfilesucceed? - Age: Did the policy delay newly released direct and transitive versions? If an exception is needed, is its reason and expiry recorded?
- Source: Are all transitive dependencies resolved through expected registries? If not, is the exceptional Git or tarball source explicitly approved?
- Scripts: Did a new dependency request a lifecycle build? Is it present in a reviewed
allowBuildsentry with a concrete reason? - Integrity: Are store-integrity and package-content checks enabled, and did the install pass them?
- Package evidence: Are version, provenance, license, advisory status, repository, maintainer activity, and runtime boundary recorded from primary sources?
- Product scope: Is the package’s use server-only, client-safe, or build-only—and does the implementation preserve that boundary?
- Ownership: Who will reassess the dependency when it updates, is deprecated, or triggers an advisory?
Failure modes worth rehearsing
“The lockfile is up to date, so the update is safe.” A lockfile makes the resolution repeatable; it does not assess the trustworthiness of a newly chosen version. Review the diff before committing it and keep an advisory response path.
“The install script is expected, so allow every build.” This turns one approved exception into a permanent broad permission. Keep the allowlist small and revisit it on each related update.
“The policy blocked a release, so production must wait indefinitely.” A security fix can justify a documented exception. Preserve the decision trail and restore the ordinary gate after the urgent change.
“A vulnerability scanner returned nothing.” Scanner coverage and parsing matter. pnpm notes that its lockfile can use two documents, so validate that the chosen scanner or SBOM tool reads the whole format before treating an empty report as evidence.
Where Frontend Accelerator fits
Frontend Accelerator gives a Next.js SaaS a current pnpm workspace policy and a locked package-manager version to start from. It also keeps agent guidance and verification commands in the repository, which makes a package decision easier to review than a one-off prompt result. Your product still owns package selection, exceptions, testing, secrets, deployment, and incident response. For a wider review loop, pair this checklist with the AI-ready codebase guide and the production-readiness checklist.
Build the feature your SaaS needs. Make the dependency path explainable enough that you can still answer what entered the repository, why it was allowed to run, and how you would change it safely.



