Skip to content

Verify auto-downloaded Node binary against nodejs.org SHASUMS256 #2

@jimmyshiau

Description

@jimmyshiau

Severity

Medium

Summary

scripts/build-sea.mjs downloads the official Node binary from nodejs.org/dist/... over HTTPS but does not verify the tarball/zip against the published SHASUMS256.txt. The Node binary is the largest, most-privileged piece of every shipped release — a tampered Node = full RCE for everyone who runs the CLI.

Affected code

scripts/build-sea.mjs:106-115:

process.stderr.write(`Downloading Node ${DEFAULT_NODE_VERSION} for ${osTag}-${arch}…\n`);
const url = nodeDownloadUrl(DEFAULT_NODE_VERSION, platform, arch);
if (platform === "win32") {
  const zipPath = resolve(cacheRoot, `${dirname}.zip`);
  run("curl", ["-fsSL", "-o", zipPath, url]);
  run("unzip", ["-q", "-o", zipPath, "-d", cacheRoot]);
} else {
  const tarPath = resolve(cacheRoot, `${dirname}.tar.gz`);
  run("curl", ["-fsSL", "-o", tarPath, url]);
  run("tar", ["xzf", tarPath, "-C", cacheRoot]);
}

Threat model

  • TLS protects against passive on-path attackers when the runner trusts the public-CA root store.
  • TLS does not protect against:
    • Compromise of nodejs.org's web infrastructure or CDN.
    • A future scenario where a CA mis-issues a cert for nodejs.org.
    • Local cache poisoning if dist-sea/cache/ is shared across builds.
  • A bad Node binary becomes part of every macOS/Linux/Windows release, signed by our Developer ID — users have no easy way to detect.

Suggested fix

Pin a known-good SHA-256 per (version, platform, arch) triple in the script (or fetch SHASUMS256.txt from the same nodejs.org URL and verify against it), then shasum -a 256 -c the downloaded archive before extracting.

Sketch:

async function fetchAndVerify(url, archivePath, expectedSha256) {
  run("curl", ["-fsSL", "-o", archivePath, url]);
  const actual = createHash("sha256")
    .update(readFileSync(archivePath))
    .digest("hex");
  if (actual !== expectedSha256) {
    throw new Error(`SHA-256 mismatch on ${url}: expected ${expectedSha256}, got ${actual}`);
  }
}

Or fetch SHASUMS256.txt once per DEFAULT_NODE_VERSION and look up the row matching the archive filename. Either approach makes a tampered upstream archive cause a hard build failure rather than shipping silently.

Found by

Internal pre-0.1.1 security review.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingsecuritySecurity-related issue

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions