Skip to content

fix(playground): authenticate, confine, and byte-bound npm archives #979

Description

@samchon

Problem

The Playground's browser-side npm installer trusts downloaded archive bytes and entry paths too early.

It does not verify registry dist.integrity or dist.shasum, accepts tar entries that escape the canonical package root, and buffers compressed and decompressed archives without byte limits. maxPackages bounds dependency count but does not bound network or memory consumption per package.

This is the trust boundary that feeds the wasm compiler MemFS, Monaco declarations, and the Execute runtime pack.

Evidence

Real npm install metadata for [email protected] includes:

  • a SHA-512 SRI dist.integrity;
  • a SHA-1 dist.shasum;
  • the tarball URL.

INpmVersionMetadata.dist currently retains only tarball. downloadTarball returns response.arrayBuffer(), and unpackNpmTarball immediately decompresses it.

A controlled registry response with deliberately false integrity and shasum values still installed successfully.

Tar extraction uses stripTarRoot, which removes the first component without validating the remaining segments. A regular entry named:

package/../../outside.js

produced these mounted keys:

node_modules/fixture/../../outside.js
fixture/../../outside.js

The compiler MemFS handoff later filters .., but the returned install result, Execute runtime pack, editor map, package file counts, and intermediate maps already contain the unconfined key.

The download and decompression paths also have no byte counters:

  • response.arrayBuffer() buffers the complete compressed response;
  • new Response(decompressionStream).arrayBuffer() buffers the complete expanded archive;
  • tar entry sizes are trusted without a safe bounds check.

An authenticated package can still be malicious or compromised, so integrity verification does not replace resource and path confinement.

Consequence surface

  • Corrupt or substituted tarballs can be executed after a successful metadata request.
  • Parent, absolute, drive-prefixed, backslash, PAX, and GNU long-path entries can escape the intended package key space.
  • Highly compressed or simply oversized packages can exhaust a browser tab before the package-count cap applies.
  • Optional dependencies must not silently convert an integrity or archive-format failure into a skip. Only registry absence retains optional semantics.
  • Normal npm package/ archives, Unicode PAX paths, long paths, JSON, declarations, and runtime JavaScript must continue to install.
  • Aborted installs must stop download, verification, decompression, and parsing promptly.

Approach

Treat registry metadata and archive bytes as one authenticated, bounded handoff:

  1. Preserve dist.integrity and dist.shasum.
  2. After download and before decompression, verify the strongest supported SRI algorithm. Use shasum only when SRI is absent.
  3. Stream the response and decompression output through configurable compressed and expanded byte budgets with conservative browser-safe defaults.
  4. Validate tar sizes and record bounds before slicing.
  5. Require regular files to live below the canonical package/ root after slash normalization. Reject empty, absolute, drive-prefixed, dot, parent, and other escaping segments. Apply the same rule to header, PAX, and GNU long paths.
  6. Mount files only after the complete archive passes.

Errors must identify the package and version at the installer boundary without logging archive contents.

Acceptance and verification

  • A tarball matching SHA-512 SRI installs.
  • A changed byte, malformed SRI, or mismatched strongest digest fails before decompression.
  • SHA-1 shasum is used only when integrity is absent.
  • A package with neither field follows an explicit compatibility policy covered by a test.
  • Compressed and decompressed limits fail deterministically, including when Content-Length is absent or false.
  • Truncated, overflowing, or out-of-bounds tar entries fail.
  • Header, PAX, and GNU long paths containing absolute or parent traversal fail without mounting any file.
  • Normal Unicode PAX and long-path archives retain coverage.
  • Abort behavior and optional dependency semantics remain stable.
  • Public documentation describes authenticity and size controls.
  • Run:
pnpm --filter @ttsc/playground build
pnpm --filter @ttsc/test-playground start -- --include=npm
pnpm --filter @ttsc/test-playground start

Coordination

The package-target issue begins after a valid runtime pack is mounted. This issue owns the earlier registry metadata, download, decompression, tar parsing, and mount-key boundary. They can share one campaign pull request but require separate acceptance evidence.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions