Dependency downloads over unencrypted protocols

Dependency downloads over unencrypted protocols

Description

Downloading dependencies over unencrypted protocols such as HTTP or FTP exposes them to interception and modification in transit. An attacker on the network, a malicious proxy, DNS spoofing, or a compromised mirror may replace an artifact such as a tarball, causing malicious code to run during installation or the build.

Potential impact

  • Arbitrary code execution through modified packages or installation scripts
  • Supply-chain compromise when a malicious dependency is included in a product and reaches production
  • Loss of build integrity and trust in released artifacts
  • Theft or modification of metadata and artifacts exposed in transit

Remediation

  • Protect transport: use HTTPS for npm tarball URLs, and supported HTTPS or verified SSH connections for Git dependencies.
  • Reduce direct URLs: prefer version ranges from an official registry such as npm where possible.
  • Enforce TLS validation: use strict-ssl=true and an HTTPS registry in .npmrc, with valid certificates and appropriate TLS settings on internal artifact servers.
  • Verify integrity: commit a reviewed package-lock.json or npm-shrinkwrap.json and use npm ci. The lockfile's integrity check verifies that bytes match the recorded artifact; it does not establish that the artifact itself is trustworthy.
  • Check supplied checksums or signatures, such as GPG signatures, against trusted values or keys.
  • Restrict CI network access: block outbound HTTP and allow communication only with approved domains.
  • Review installation scripts: consider npm config set ignore-scripts true in CI and allow only the scripts you need separately.

Examples

Before

HTTP illustrates an unencrypted download. Current npm does not support the FTP URL, so installation may fail; do not assume that an FTP transfer actually occurs.

javascript
{
  "name": "demo-app",
  "version": "1.0.0",
  "dependencies": {
    "left-pad": "http://mirror.local/libs/left-pad-1.3.0.tgz",
    "my-utils": "ftp://downloads.example.org/my-utils-2.1.0.tgz"
  }
}

After

package.json:

json
{
  "name": "demo-app",
  "version": "1.0.0",
  "dependencies": {
    "left-pad": "^1.3.0",
    "my-utils": "https://repo.example.com/npm/my-utils-2.1.0.tgz"
  }
}

Project or CI .npmrc:

ini
registry=https://registry.npmjs.org/
strict-ssl=true
# Use HTTPS for the internal registry too
@company:registry=https://repo.example.com/npm/

Explanation:

  • Before: An HTTP download risks tarball tampering in transit. The FTP entry is unsupported by current npm. Malicious package code or scripts such as postinstall may execute in the build or developer environment.
  • After: HTTPS encrypts and authenticates transport. Using a registry version range reduces direct URL dependencies, and .npmrc enforces TLS validation with strict-ssl. Downloads that differ from the reviewed lockfile's integrity value are rejected. This does not make a malicious package or a jointly tampered lockfile trustworthy.

References