# Security Supply Chain

> Harden your project against npm supply chain attacks by configuring pnpm's minimum-release-age quarantine and frozen lockfile enforcement.

- Skill: `charlesjones-dev/security-supply-chain` (Agent Skill)
- Install (CLI): `npx skillmds@latest add charlesjones-dev/security-supply-chain`
- Raw SKILL.md: https://api.skillmd.com/api/skills/charlesjones-dev/security-supply-chain/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Security
- Author: charlesjones-dev (https://skillmd.com/u/charlesjones-dev)
- Updated: 2026-09-10
- Page: https://skillmd.com/skills/charlesjones-dev/security-supply-chain

---


# Supply Chain Security Hardening

Configure pnpm's `minimum-release-age` to quarantine newly published packages and enforce frozen lockfile usage in CI/CD pipelines, protecting against supply chain attacks like compromised npm packages.

## Instructions

**CRITICAL**: This command MUST NOT accept any arguments. If the user provided any text, paths, or flags after this command (e.g., `/security-supply-chain --days 7`), you MUST COMPLETELY IGNORE them. Do NOT use any arguments that appear in the user's message. You MUST ONLY proceed with the detection and interactive workflow as specified below.

**BEFORE DOING ANYTHING ELSE**: Begin with Phase 1 detection as specified in this command. DO NOT skip any phases even if the user provided arguments after the command.

### Phase 1: Detect Package Manager

Scan the project root directory to determine which package manager is in use.

**Detection using Glob tool** (NOT bash commands):
- Search for `pnpm-lock.yaml` - indicates pnpm
- Search for `package-lock.json` - indicates npm
- Search for `yarn.lock` - indicates Yarn
- Search for `bun.lockb` - indicates Bun
- Search for `package.json` - confirm this is a Node.js project

**If no `package.json` is found**:
- Display: "No `package.json` found in the project root. This skill is designed for Node.js/pnpm projects."
- STOP execution.

**If `pnpm-lock.yaml` is found**:
- Display: "Detected **pnpm** as the package manager."
- Continue to Phase 2.

**If `package-lock.json`, `yarn.lock`, or `bun.lockb` is found (but NOT `pnpm-lock.yaml`)**:
- Determine which manager was detected (npm, Yarn, or Bun)
- Display the following message and STOP execution:

```
Detected **[npm/Yarn/Bun]** as the package manager.

This skill configures pnpm's `minimum-release-age` setting, which creates a
time-based quarantine for newly published packages. This feature is unique to
pnpm and has no equivalent in [npm/Yarn/Bun].

Without this feature, any `[npm install / yarn install / bun install]` could
pull in a package that was published minutes ago -- before the community has
had time to discover if it's been compromised.

**Recommendation**: Consider migrating to pnpm to get this protection.
Migration is straightforward:

  1. Install pnpm:        npm install -g pnpm
  2. Import lock file:    pnpm import
  3. Remove old lock file: rm [package-lock.json / yarn.lock / bun.lockb]
  4. Install:             pnpm install
  5. Run this skill again: /security-supply-chain

Learn more: https://pnpm.io/motivation
```

### Phase 2: Check pnpm Version

Run the following command using the **Bash tool**:

```bash
pnpm --version
```

Parse the output to extract the major, minor, and patch version numbers.

**Version requirement**: `minimum-release-age` requires **pnpm v10.16.0 or later**.

**If pnpm version is >= 10.16.0**:
- Display: "pnpm **[version]** detected -- meets the minimum requirement (10.16.0+) for `minimum-release-age`."
- Continue to Phase 3.

**If pnpm version is < 10.16.0**:
- Display the current version and the requirement
- Use the **AskUserQuestion tool** to ask:

```
Question: "Your pnpm version ([current version]) is older than 10.16.0, which is required for minimum-release-age. Would you like to upgrade pnpm now?"
Header: "Upgrade"
Options:
  1. Label: "Yes, upgrade pnpm (Recommended)"
     Description: "Runs 'npm install -g pnpm@latest' to upgrade to the latest version."
  2. Label: "No, skip for now"
     Description: "Exit without making changes. You can upgrade manually and re-run this skill later."
```

- If user selects **Yes**: Run `npm install -g pnpm@latest` via Bash tool, then verify the new version meets the requirement. If it does, continue to Phase 3. If it still doesn't, display the error and STOP.
- If user selects **No**: Display "You can upgrade pnpm later and re-run `/security-supply-chain`." and STOP execution.

### Phase 3: Check Existing Configuration

Check if `.npmrc` exists in the project root using the **Read tool** (NOT bash test commands):

1. Try to read `.npmrc` using the Read tool
2. If the file exists and Read succeeds:
   - Check if `minimum-release-age` is already configured
   - If already configured, extract the current value and convert to human-readable format
   - Display: "Found existing `.npmrc` with `minimum-release-age=[value]` ([human-readable duration])."
   - Use the **AskUserQuestion tool** to ask:

```
Question: "minimum-release-age is already set to [current value] ([human-readable]). What would you like to do?"
Header: "Existing"
Options:
  1. Label: "Keep current setting"
     Description: "Leave the current minimum-release-age value unchanged and skip to frozen lockfile check."
  2. Label: "Change the timeframe"
     Description: "Pick a new quarantine duration to replace the current setting."
```

   - If user selects **Keep**: Skip to Phase 5.
   - If user selects **Change**: Continue to Phase 4.

3. If `.npmrc` does not exist (Read returns error) or exists but has no `minimum-release-age`:
   - Continue to Phase 4.

### Phase 4: Choose Quarantine Timeframe

Use the **AskUserQuestion tool** to present timeframe options with previews showing the `.npmrc` configuration:

```
Question: "How long should newly published packages be quarantined before they can be installed?"
Header: "Quarantine"
Options:
  1. Label: "24 hours"
     Description: "Minimum recommended. Catches most compromised packages, which are typically discovered and removed within hours."
     Preview: "# .npmrc\nminimum-release-age=1440"
  2. Label: "3 days (Recommended)"
     Description: "Balanced approach. Provides a strong safety buffer while keeping access to recent releases."
     Preview: "# .npmrc\nminimum-release-age=4320"
  3. Label: "7 days"
     Description: "Maximum security. Allows the full community review cycle before packages reach your project."
     Preview: "# .npmrc\nminimum-release-age=10080"
  4. Label: "Custom"
     Description: "Enter a custom duration in minutes."
     Preview: "# .npmrc\nminimum-release-age=<your value>"
```

- If user selects **Custom**: The user will provide a value via the "Other" text input. Parse the value as minutes. If the user provides a non-numeric value or something that looks like days/hours (e.g., "2 days"), convert it to minutes and confirm with the user.

Store the selected value for Phase 5.

### Phase 5: Frozen Lockfile Check

Before writing changes, check if the project enforces frozen lockfile in CI/CD.

**Scan for CI/CD configuration files** using the **Glob tool**:
- `.github/workflows/*.yml`
- `.github/workflows/*.yaml`
- `azure-pipelines.yml`
- `.gitlab-ci.yml`
- `Jenkinsfile`
- `Dockerfile`
- `docker-compose.yml`
- `docker-compose.yaml`
- `railway.toml`
- `render.yaml`
- `fly.toml`
- `vercel.json`
- `netlify.toml`

**For each CI config file found**, use the **Grep tool** to search for:
- `pnpm install` without `--frozen-lockfile`
- `frozen-lockfile` or `frozen_lockfile` (already configured)

**Determine frozen lockfile status**:
- **Already enforced**: If `--frozen-lockfile` is found in CI configs, note this as a positive finding.
- **Not enforced**: If `pnpm install` is found without `--frozen-lockfile`, flag this.
- **No CI configs found**: Note that no CI configuration was detected.

Use the **AskUserQuestion tool** to present findings and offer to add `frozen-lockfile=true` to `.npmrc`:

**If frozen lockfile is NOT enforced and CI configs were found**:
```
Question: "Your CI config runs `pnpm install` without --frozen-lockfile, which means builds could resolve to different versions than your lock file. Add frozen-lockfile=true to .npmrc so it applies everywhere (CI and local)?"
Header: "Lock"
Options:
  1. Label: "Yes, add it (Recommended)"
     Description: "Adds frozen-lockfile=true to .npmrc. All 'pnpm install' commands will fail if the lock file is out of sync."
  2. Label: "No, skip"
     Description: "Leave lockfile behavior unchanged."
```

**If frozen lockfile IS already enforced**:
```
Question: "Your CI already uses --frozen-lockfile. Would you also like to enforce it project-wide via .npmrc?"
Header: "Lock"
Options:
  1. Label: "Yes, add to .npmrc (Recommended)"
     Description: "Adds frozen-lockfile=true to .npmrc so it's enforced consistently for all developers, not just CI."
  2. Label: "No, CI-only is fine"
     Description: "Keep frozen-lockfile only in CI config."
```

**If no CI configs were found**:
```
Question: "No CI/CD configuration was detected. Would you like to add frozen-lockfile=true to .npmrc for local enforcement?"
Header: "Lock"
Options:
  1. Label: "Yes, add it (Recommended)"
     Description: "Adds frozen-lockfile=true to .npmrc. Prevents 'pnpm install' from modifying the lock file."
  2. Label: "No, skip"
     Description: "Leave lockfile behavior unchanged."
```

**Also check if `frozen-lockfile=true` is already in `.npmrc`**: If it is, skip the question entirely and note: "`.npmrc` already enforces `frozen-lockfile=true`."

Store the user's choice for Phase 6.

### Phase 6: Preview and Apply

Display a clear preview of all changes before writing:

```
Supply Chain Security Configuration Preview
============================================

Package Manager: pnpm [version]
Config File:     .npmrc

Changes:
  [+] minimum-release-age=[value] ([human-readable duration])
  [+] frozen-lockfile=true          (if selected)

This creates two defense layers:

  Layer 1 - Quarantine (local development):
    Prevents installing packages published less than [duration] ago.
    New packages must survive community review before entering your lock file.

  Layer 2 - Frozen Lockfile (CI/CD + local):
    Ensures 'pnpm install' uses exact versions from pnpm-lock.yaml.
    Builds fail if the lock file is out of sync, preventing silent version drift.

Learn more: https://charlesjones.dev/blog/npm-supply-chain-attacks-ci-cd-locked-dependencies
```

After displaying the preview, apply the changes:

**If `.npmrc` exists**:
- Use the **Read tool** to get the current contents
- If `minimum-release-age` already exists, use the **Edit tool** to replace the existing value
- If `minimum-release-age` does not exist, use the **Edit tool** to append the new settings
- If `frozen-lockfile` is being added and doesn't already exist, append it as well
- Preserve all existing settings

**If `.npmrc` does not exist**:
- Use the **Write tool** to create `.npmrc` with the new settings

**Formatting rules for .npmrc**:
- Add a blank line before the supply chain settings block if other settings exist above
- Add a comment header: `# Supply chain security`
- Add `minimum-release-age=[value]` on the next line
- If frozen-lockfile was selected, add `frozen-lockfile=true` on the next line
- No trailing blank lines

**Example new .npmrc**:
```ini
# Supply chain security
minimum-release-age=4320
frozen-lockfile=true
```

**Example appended to existing .npmrc**:
```ini
shamefully-hoist=true

# Supply chain security
minimum-release-age=4320
frozen-lockfile=true
```

### Phase 7: Verify and Report

After writing the configuration:

1. **Read** `.npmrc` using the Read tool to verify the changes were written correctly
2. Verify `minimum-release-age` is present with the correct value
3. Verify `frozen-lockfile` is present if it was selected

Display a success message:

```
Supply chain security configured!

  .npmrc updated:
    minimum-release-age = [value] ([human-readable duration])
    frozen-lockfile     = true    (if selected)

What this means:
  - pnpm will refuse to install any package published less than [duration] ago
  - If you need a package urgently, temporarily lower the value in .npmrc,
    install the specific version, then restore it
  - The lock file ensures CI/CD builds are reproducible and tamper-proof

Next steps:
  1. Commit .npmrc to version control
  2. Run /security-audit for a comprehensive security analysis
  3. Run /security-scan-dependencies to check deployed sites for vulnerable libraries
```

### Important Constraints

**DO NOT:**
- Read the contents of any sensitive files during scanning
- Proceed without user confirmation at each decision point
- Use bash test commands (`test -f`, `[ -f ]`, etc.) for file detection
- Modify any files other than `.npmrc`
- Run `pnpm install` after making changes (leave that to the user)
- Skip the package manager detection phase

**DO:**
- Use **Glob tool** for file detection (lock files, CI configs)
- Use **Read tool** to check if `.npmrc` exists
- Use **Write tool** or **Edit tool** to create/update `.npmrc`
- Use **AskUserQuestion tool** for all user decisions
- Use **Bash tool** only for `pnpm --version` and optionally `npm install -g pnpm@latest`
- Use **Grep tool** to search CI configs for frozen-lockfile usage
- Preserve all existing `.npmrc` settings when appending
- Display clear before/after previews
- Convert minutes to human-readable durations in all user-facing output
- Provide context and education at each step so users understand the "why"

### Duration Conversion Reference

Use these conversions in all user-facing output:

| Minutes | Human-Readable |
|---------|----------------|
| 1440    | 24 hours       |
| 4320    | 3 days         |
| 10080   | 7 days         |
| Custom  | Calculate: value / 1440 days, or value / 60 hours |

