Skip to main content
AI/MLjmagly

pnpm-release-age-gate

Configure pnpm's minimumReleaseAge gate (7-day default, 10-day high-sensitivity) plus blockExoticSubdeps for workspace-scope dep-source enforcement. Includes Corepack detection and lockfile-caveat warning.

Stars
141
Source
jmagly/aiwg
Updated
2026-05-31
Slug
jmagly--aiwg--pnpm-release-age-gate
View on GitHubRaw SKILL.md

// install — copy + paste into any project

mkdir -p .claude/skills && curl -fsSL https://raw.githubusercontent.com/jmagly/aiwg/HEAD/agentic/code/frameworks/security-engineering/skills/pnpm-release-age-gate/SKILL.md -o .claude/skills/pnpm-release-age-gate.md

Drops the SKILL.md into .claude/skills/pnpm-release-age-gate.md. Works with Claude Code, Cursor, and any agent that loads SKILL.md files from .claude/skills/.

pnpm-release-age-gate

Use this skill when a user has chosen pnpm as their package manager and wants to apply the same release-age-gate hardening that npm-release-age-gate provides for npm. pnpm is often the preferred baseline for new projects because minimumReleaseAge AND blockExoticSubdeps can be enforced at the workspace level — npm has no equivalent for the latter.

Triggers

  • "pnpm release age gate"
  • "pnpm minimumReleaseAge"
  • "pnpm blockExoticSubdeps"
  • "pnpm supply chain hardening"
  • "configure pnpm 7 day gate"

Prerequisites

  • pnpm v9.0+ installed (pnpm --version)
  • package.json exists at repo root

If pnpm is below v9.0, the skill should refuse to proceed and direct the user to upgrade — minimumReleaseAge was introduced in v9.0 and older versions silently ignore it.

Configuration paths — pick one based on workspace shape

Option A — Single-package repo (most common)

Add the gate to .npmrc at repo root:

# Refuse dependency versions published less than 7 days ago.
# pnpm interprets the value in MINUTES. 10080 = 7 days; 14400 = 10 days.
# Defends against newly-published malicious versions.
minimum-release-age=10080

# Reject git+, tarball-URL, file:, link: sources — these bypass
# registry signature verification (companion to npm-supply-chain-audit
# dep-source policy).
block-exotic-subdeps=true

Option B — Multi-package workspace

Add the gate to pnpm-workspace.yaml:

packages:
  - 'packages/*'
  - 'apps/*'

# Workspace-scope release-age enforcement.
# Value is in MINUTES. 10080 = 7 days; 14400 = 10 days.
minimumReleaseAge: 10080

# Reject non-registry dep sources fleet-wide.
blockExoticSubdeps: true

Workspace-scope is the pnpm advantage over npm — every nested package inherits the gate without per-package config.

Option C — Operator-wide via pnpm global config

pnpm config set minimum-release-age 10080  # minutes — 7 days
pnpm config set block-exotic-subdeps true

Use this only when the entire dev machine should apply the gate across all projects. Per-repo config (Option A or B) is preferred — it travels with the repo and works in CI without machine setup.

Unit conversion reference

pnpm uses minutes for minimumReleaseAge:

Days Minutes value
1 1440
7 (recommended default) 10080
10 (high-sensitivity profile) 14400
14 20160
30 43200

This differs from npm (min-release-age in days) and Bun (install.minimumReleaseAge in seconds). Document the unit inline when committing the config so future readers don't misread it.

Lockfile caveat

The gate must be active before pnpm install or pnpm update resolves dependencies. If the existing pnpm-lock.yaml was generated without the gate, the gate is checked on the NEXT resolution pass — not retroactively against the lockfile.

To apply the gate retroactively:

# Force re-resolution with the gate active
rm pnpm-lock.yaml
pnpm install

This is destructive to existing pins and may pull in newer intermediate versions. Coordinate with the team before running.

Corepack detection

Check whether the project pins a pnpm version via Corepack:

node -p "require('./package.json').packageManager"

Output like pnpm@9.15.0 means Corepack will use that exact version in CI. The skill should:

  1. Confirm the pinned version is ≥ v9.0 (else flag — gate is silently ignored)
  2. Document the pinned version in the audit output
  3. Suggest a Corepack pin if the project doesn't have one yet:
    corepack use pnpm@latest
    # writes packageManager to package.json
    

Override policy

Genuine emergency overrides:

# One-off install bypassing the gate
pnpm install <pkg>@<version> --ignore-min-release-age

# Permanently allow a specific package (rare; record rationale in
# a SECURITY-OVERRIDE.md or equivalent)

Document every override with reason + sunset date. Overrides should be reviewed quarterly.

CI integration

Add a verification step to the publish/build workflow:

- name: Verify pnpm gate active
  run: |
    set -euo pipefail
    AGE=$(pnpm config get minimum-release-age 2>/dev/null || echo "0")
    EXOTIC=$(pnpm config get block-exotic-subdeps 2>/dev/null || echo "false")
    if [ "$AGE" -lt 10080 ] || [ "$EXOTIC" != "true" ]; then
      echo "✗ pnpm gate not configured to baseline (minimumReleaseAge≥10080, blockExoticSubdeps=true)"
      exit 1
    fi
    echo "✓ pnpm gate active: $AGE minutes, blockExoticSubdeps=$EXOTIC"

What to inspect during review

  • .npmrc OR pnpm-workspace.yaml for minimumReleaseAge and blockExoticSubdeps
  • package.json packageManager field for Corepack pin
  • CI workflow has the verification step above
  • pnpm-lock.yaml was generated AFTER the gate was committed (timestamp check)

Output format

When auditing an existing pnpm project, produce a structured report at .aiwg/security/working/pnpm-release-age-audit.md:

# pnpm Release-Age Gate Audit

**pnpm version**: <version>  (Corepack pinned: yes/no)
**Workspace shape**: single-package / multi-package
**Gate active**: yes (10080 min) / yes (custom: <value>) / no
**blockExoticSubdeps**: true / false / unset

## Findings

### <severity> — <description>

- File: <path>
- Issue: <what's wrong>
- Fix: <exact change>

## Clean Checks

- ...

## Recommendations

- ...

See Also

References