Back to Intelligence

Package Manager Integrity Gap: Defending Against Compromised Registries and Malicious Package Injection

SA
Security Arsenal Team
October 14, 2026
11 min read

A new research effort from Trail of Bits has quantified something many of us in incident response have suspected for years: the package management ecosystem — the foundation of virtually every modern software build — has a systemic integrity problem. After testing 25 of the most popular package managers, the researchers found that fewer than half use lockfiles that check for tampering by default, and only two verified the integrity of every package downloaded. If a package repository is compromised and an attacker injects malicious code into a legitimate package, most package managers will happily fetch and install it without raising an alarm.

For defenders, this reframes the supply-chain threat model. The industry has invested heavily in hardening developer accounts (mandatory MFA on npm and PyPI, trusted publishing, Sigstore signing). If those efforts succeed — and they should — attackers will pivot to the next weakest link: the transport and integrity layer between the repository and your build agents. This post breaks down the exposure, explains how a registry-compromise attack unfolds, and delivers concrete detection and hardening guidance for your SOC and platform engineering teams.

Technical Analysis

What the Research Found

Trail of Bits tested 25 widely used package managers against a simple, devastating scenario: a compromised package repository injecting malicious code into a legitimate package. The core question was whether the package manager would detect the tampering. The answer, in most cases, was no.

The key findings:

  • Fewer than half of the 25 package managers reviewed used lockfiles that checked for tampering by default.
  • Only two verified the integrity of every package they downloaded.
  • The weakness is architectural, not a single CVE: integrity verification exists (cryptographic hashes, signatures) but is either optional, disabled by default, or not enforced end-to-end.

How the Attack Works (Defender's View of the Kill Chain)

The attack chain for a repository-compromise scenario looks like this:

  1. Compromise of the repository or mirror. The attacker gains write access to a package registry, a mirror, an internal caching proxy (e.g., Artifactory/Nexus), or a poisoned upstream cache.
  2. Injection into a legitimate package. Rather than publishing a typosquatted new package, the attacker modifies an existing, trusted package tarball/wheel. This defeats reputation-based defenses entirely — the package name, version, and author all look correct.
  3. Client fetch without integrity enforcement. The victim's package manager resolves the dependency and downloads the tampered artifact. If no lockfile hash exists (or the lockfile check is bypassed, e.g., npm install --no-package-lock, pip install without --require-hashes), the malicious artifact installs silently.
  4. Execution. For ecosystems with install-time code execution (npm lifecycle scripts such as preinstall/postinstall, Python setup.py, Make-based builds), the payload runs immediately during install or build on the developer workstation or CI runner — often with access to source code, signing keys, and cloud credentials.

This is a fundamentally different exposure than dependency confusion or typosquatting. Those attacks abuse name resolution; this one abuses the absence of content verification. Every package manager in your environment that does not enforce hash pinning against a trusted lockfile is an unmonitored ingress point for arbitrary code execution.

Exploitation Status

No CVE has been assigned — this is a systemic design weakness across an ecosystem, not a discrete vulnerability in one product. There is no confirmed in-the-wild campaign tied to this specific research, and it is not in CISA KEV. However, the technique class (T1195.002, Compromise Software Supply Chain) is well-established in nation-state and criminal tradecraft, and the research is a warning that as developer-account attacks get harder, registry integrity becomes the attractive target. Treat this as a high-probability, high-impact architectural risk to your build pipeline rather than a theoretical exercise.

Detection & Response

The observables that matter here are behavioral: package managers executing install-time code, fetching from unexpected registries, or lockfiles changing without a corresponding developer action. The rules below are tuned to be high-signal in environments where you have baseline knowledge of your approved registries and CI infrastructure.

Sigma Rules

YAML
---
title: Package Manager Install-Time Script Execution Spawning Shell
description: Detects npm install spawning shell interpreters, a common pattern when install-time lifecycle scripts (preinstall/postinstall) contain malicious injected code from a compromised package or registry. Tune to your CI environment where lifecycle scripts are expected.
references:
  - https://blog.trailofbits.com/2026/10/14/package-managers-have-an-integrity-problem/
  - https://attack.mitre.org/techniques/T1195/002/
author: Security Arsenal
date: 2026/10/15
status: experimental
id: 8c2f1a4e-3b7d-4f9a-b1e6-9d5c2a7f4012
tags:
  - attack.initial_access
  - attack.t1195.002
logsource:
  category: process_creation
  product: windows
detection:
  selection_parent:
    ParentImage|endswith:
      - '\npm.exe'
      - '\npm.cmd'
      - '\node.exe'
      - '\yarn.exe'
      - '\pnpm.exe'
  selection_child:
    Image|endswith:
      - '\cmd.exe'
      - '\powershell.exe'
      - '\pwsh.exe'
      - '\wscript.exe'
      - '\cscript.exe'
  condition: selection_parent and selection_child
falsepositives:
  - Legitimate packages with native build steps (node-gyp) — baseline per repo/build agent
level: high
---
title: Lockfile Integrity Bypass Flag in Package Install Command
description: Detects package installation commands that explicitly disable lockfile or integrity verification, which defeats the primary defense against registry-injected malicious packages.
references:
  - https://blog.trailofbits.com/2026/10/14/package-managers-have-an-integrity-problem/
  - https://attack.mitre.org/techniques/T1195/002/
author: Security Arsenal
date: 2026/10/15
status: experimental
id: 2d7a9b31-6c4f-4e82-a593-0b1d8f3e6c27
tags:
  - attack.defense_evasion
  - attack.t1195.002
logsource:
  category: process_creation
  product: windows
detection:
  selection:
    CommandLine|contains:
      - '--no-package-lock'
      - '--ignore-scripts=false --no-audit --no-verify'
      - 'npm config set strict-ssl false'
      - '--trusted-host'
      - '--no-verify'
  condition: selection
falsepositives:
  - Rare developer troubleshooting — should be approved change activity, not routine
level: medium
---
title: Linux Package Manager Spawning Network-Capable Download Tools
description: Detects pip, npm, yarn, or pipenv spawning curl/wget, consistent with injected install-time code pulling second-stage payloads after a tampered package is installed from a compromised repository.
references:
  - https://blog.trailofbits.com/2026/10/14/package-managers-have-an-integrity-problem/
  - https://attack.mitre.org/techniques/T1195/002/
author: Security Arsenal
date: 2026/10/15
status: experimental
id: 5f1c8e02-9a3b-4d67-b804-2e6a9c1d7358
tags:
  - attack.command_and_control
  - attack.t1105
  - attack.t1195.002
logsource:
  category: process_creation
  product: linux
detection:
  selection_parent:
    ParentImage|endswith:
      - '/pip'
      - '/pip3'
      - '/npm'
      - '/yarn'
      - '/pnpm'
      - '/python'
      - '/python3'
  selection_child:
    Image|endswith:
      - '/curl'
      - '/wget'
      - '/bash'
      - '/sh'
  condition: selection_parent and selection_child
falsepositives:
  - Build scripts fetching toolchain components — baseline on CI runners by repo
level: high

KQL Hunt — Microsoft Sentinel / Defender

The following query hunts for package manager processes making network connections to destinations outside your approved registry list, or spawning shells/download tools. Populate the allowlist with your organization's registries (npmjs, PyPI, internal Artifactory/Nexus, etc.) before deploying.

KQL — Microsoft Sentinel / Defender
// Hunt: package managers connecting to non-approved registries or spawning shells
let ApprovedRegistries = dynamic(["registry.npmjs.org", "pypi.org", "files.pythonhosted.org", "artifactory.corp.local", "nexus.corp.local", "registry.yarnpkg.com", "crates.io", "proxy.golang.org"]);
let PkgManagers = dynamic(["npm.exe", "npm", "node.exe", "yarn.exe", "pnpm.exe", "pip.exe", "pip", "pip3", "python.exe", "python3", "pipenv", "poetry", "bundle", "gem.exe", "cargo", "go.exe", "mvn", "gradle", "dotnet.exe", "nuget.exe"]);
let SuspiciousChildren = dynamic(["cmd.exe", "powershell.exe", "pwsh.exe", "curl.exe", "curl", "wget.exe", "wget", "bash", "sh", "wscript.exe", "cscript.exe"]);
let ProcEvents =
    DeviceProcessEvents
    | where TimeGenerated > ago(7d)
    | where InitiatingProcessFileName in~ (PkgManagers)
    | where FileName in~ (SuspiciousChildren)
    | project TimeGenerated, DeviceName, AccountName,
              ParentProcess = InitiatingProcessFileName,
              ParentCmd = InitiatingProcessCommandLine,
              ChildProcess = FileName, ChildCmd = ProcessCommandLine;
let NetEvents =
    DeviceNetworkEvents
    | where TimeGenerated > ago(7d)
    | where InitiatingProcessFileName in~ (PkgManagers)
    | where not(RemoteUrl has_any (ApprovedRegistries) or RemoteIP has_any ("10.", "172.16.", "192.168."))
    | summarize Connections = count(), Destinations = make_set(RemoteUrl, 10), IPs = make_set(RemoteIP, 10)
        by TimeGenerated = bin(TimeGenerated, 1h), DeviceName, Process = InitiatingProcessFileName, InitiatingProcessCommandLine;
union ProcEvents, NetEvents
| order by TimeGenerated desc

For Linux fleets ingested via Syslog/CEF, adapt the process table:

KQL — Microsoft Sentinel / Defender
// Hunt: Linux package installs with suspicious child processes (Syslog ingestion)
Syslog
| where TimeGenerated > ago(7d)
| where Facility == "user" or SyslogMessage has_any ("npm", "pip", "yarn", "apt", "dnf")
| where SyslogMessage has_any ("postinstall", "preinstall", "setup.py", "--trusted-host", "--no-verify", "insecure")
| project TimeGenerated, Computer, ProcessName, SyslogMessage
| order by TimeGenerated desc

Velociraptor VQL

This hunt artifact identifies endpoints where package managers recently spawned shells or download tools, and pulls recently modified lockfiles for review against version control history.

VQL — Velociraptor
-- Hunt: Package manager process anomalies and recent lockfile modifications
-- Target: developer workstations and CI runners

LET proc_hits = SELECT Pid, Ppid, Name, Exe, CommandLine, Username, CreateTime
FROM pslist()
WHERE Exe =~ '(curl|wget|powershell|cmd|bash|sh|pwsh)'
  AND CommandLine =~ '(http://|https://|Invoke-|IEX|base64)'

LET lockfile_watch = SELECT FullPath, Mtime, Size
FROM glob(globs=[
  'C:/Users/*/*/package-lock.json',
  'C:/Users/*/*/yarn.lock',
  'C:/Users/*/*/pnpm-lock.yaml',
  '/home/*/*/package-lock.json',
  '/home/*/*/Pipfile.lock',
  '/home/*/*/poetry.lock',
  '/opt/*/*/package-lock.json'
])
WHERE Mtime > now() - 86400

SELECT * FROM proc_hits
UNION ALL
SELECT NULL, NULL, 'LOCKFILE_MODIFIED', FullPath, NULL, NULL, Mtime FROM lockfile_watch

Remediation / Hardening Script

The following Bash script audits a Linux build host (or developer workstation) for the most dangerous package manager configurations: disabled integrity checks, HTTP registries, missing lockfiles, and global installs outside policy.

Bash / Shell
#!/usr/bin/env bash
# Package manager integrity audit — Security Arsenal
# Run on developer workstations and CI runners. Requires npm/pip where applicable.
set -u

echo "=== [1/5] npm registry and SSL configuration ==="
if command -v npm >/dev/null 2>&1; then
  npm config get registry
  npm config get strict-ssl
  npm config get ignore-scripts
  # Flag any non-HTTPS registry
  REG=$(npm config get registry)
  case "$REG" in
    https://*) echo "OK: npm registry uses HTTPS" ;;
    *) echo "ALERT: npm registry is not HTTPS: $REG" ;;
  esac
  # Flag missing lockfiles in common project roots
  find /home /opt -maxdepth 3 -name package.json ! -path '*/node_modules/*' 2>/dev/null | while read -r f; do
    d=$(dirname "$f")
    if [ ! -f "$d/package-lock.json" ] && [ ! -f "$d/npm-shrinkwrap.json" ]; then
      echo "ALERT: no lockfile for $f"
    fi
  done
fi

echo "=== [2/5] pip configuration ==="
if command -v pip3 >/dev/null 2>&1; then
  pip3 config list 2>/dev/null || true
  # trusted-host entries disable TLS verification for that index
  if pip3 config list 2>/dev/null | grep -qi 'trusted-host'; then
    echo "ALERT: pip trusted-host configured — TLS verification weakened"
  fi
fi

echo "=== [3/5] Global pip.conf / npmrc inspection ==="
for f in /etc/pip.conf /usr/etc/npmrc /etc/npmrc "$HOME/.npmrc" "$HOME/.pip/pip.conf" "$HOME/.config/pip/pip.conf"; do
  if [ -f "$f" ]; then
    echo "--- $f ---"
    grep -Ei 'registry|index-url|trusted-host|strict-ssl|verify' "$f" || echo "(no integrity-relevant keys)"
  fi
done

echo "=== [4/5] Lockfile hash verification for current directory ==="
# npm ci strictly honors package-lock.json integrity hashes — fails on tampering
if [ -f package-lock.json ]; then
  npm ci --dry-run --ignore-scripts 2>&1 | tail -5
fi
# pip hash-checking mode — generate requirements with hashes first:
#   pip install pip-tools && pip-compile --generate-hashes requirements.in
if [ -f requirements.txt ] && grep -q -- '--hash=sha256' requirements.txt; then
  pip3 install --dry-run --require-hashes -r requirements.txt 2>&1 | tail -5
else
  echo "NOTE: requirements.txt lacks --hash entries; pip cannot verify package integrity"
fi

echo "=== [5/5] Recently modified lockfiles (last 24h) — reconcile with git history ==="
find /home /opt -maxdepth 4 \( -name 'package-lock.json' -o -name 'yarn.lock' -o -name 'Pipfile.lock' -o -name 'poetry.lock' \) -mtime -1 2>/dev/null

echo "=== Audit complete ==="

Remediation

There is no single patch to apply — this is an architectural hardening effort. Prioritize the following:

  1. Commit and enforce lockfiles everywhere. Require package-lock.json, yarn.lock, Pipfile.lock, poetry.lock, Cargo.lock, or equivalent in every repository. In CI, use integrity-strict install modes: npm ci (not npm install), pip install --require-hashes with hash-pinned requirements generated via pip-compile --generate-hashes, and yarn install --frozen-lockfile.

  2. Block lockfile bypass. Scan CI pipelines and developer tooling for --no-package-lock, --no-verify, --trusted-host, and strict-ssl false. Make any such usage a security-reviewed exception.

  3. Disable install-time code execution by default. Set ignore-scripts=true in .npmrc for CI, with an explicit allowlist for packages requiring native builds. For Python, prefer wheels over source distributions so setup.py never executes.

  4. Route all package traffic through a controlled proxy. Force all ecosystems through an internal artifact repository (Artifactory, Nexus, or a cloud equivalent) with upstream hash pinning and retention of known-good artifacts. Egress-filter build agents so they cannot reach public registries directly — this converts a silent registry-compromise scenario into a single choke point you monitor.

  5. Verify before you mirror. If you operate internal mirrors, validate upstream artifacts against published hashes/signatures at ingestion time, and alert on any artifact whose hash changes after initial caching — hash drift on an immutable version is a strong compromise signal.

  6. Monitor lockfile diffs. A lockfile change without a corresponding dependency change in the manifest is an anomaly. Gate PRs that modify lockfiles on automated diff review and a second approver.

  7. Track the ecosystem. Read the full Trail of Bits research at https://blog.trailofbits.com/2026/10/14/package-managers-have-an-integrity-problem/ and map your package managers against their findings. Where your toolchain is in the vulnerable majority, treat compensating controls (steps 1–5) as mandatory, not optional.

The uncomfortable truth this research surfaces is that "supply-chain security" has largely meant "developer account security" — and the layer underneath, the integrity of the artifacts themselves as they travel from repository to build, has been running on trust. Close that gap now, before the attackers the research predicts make the pivot.

Related Resources

Security Arsenal Penetration Testing Services AlertMonitor Platform Book a SOC Assessment vulnerability-management Intel Hub

Is your security operations ready?

Get a free SOC assessment or see how AlertMonitor cuts through alert noise with automated triage.