Skip to content

Migrate from Node.js crypto/buffer built-ins to Web Crypto API for browser compatibility #379

@kevinelliott

Description

@kevinelliott

Summary

The @airframes/acars-decoder package currently depends on Node.js built-in modules (buffer, zlib/stream via minizlib) which require polyfills when used in browser environments. This forces downstream browser applications to include vite-plugin-node-polyfills (or equivalent Webpack polyfills), adding significant bundle size and introducing transitive security vulnerabilities that cannot be resolved by consumers.

Current Impact

In the airframesio/frontend-spa-vue2 frontend, @airframes/acars-decoder is the sole reason Node.js polyfills exist:

  • buffer — used via require("buffer") for Buffer.from() in ACARS message decompression
  • zlib/stream/string_decoder/events — used transitively via minizlib for zlib decompression
  • process/global — required by minipass (dependency of minizlib)

Security consequences

The polyfill chain pulls in node-stdlib-browsercrypto-browserifyelliptic, which has 12 known vulnerabilities (6 low, 6 moderate) that downstream consumers cannot fix or override — they are transitive dependencies locked by the polyfill infrastructure.

Bundle size consequences

The polyfills add ~50-80KB gzipped to the frontend bundle for functionality that modern browsers provide natively.

Proposed Migration

1. Replace Buffer.from() with Uint8Array + TextEncoder/TextDecoder

// Before
const buf = Buffer.from(data, 'base64');
const str = buf.toString('utf-8');

// After
const bytes = Uint8Array.from(atob(data), c => c.charCodeAt(0));
const str = new TextDecoder().decode(bytes);

2. Replace minizlib/zlib with DecompressionStream (Web Streams API)

// Before (Node zlib via minizlib)
const { Inflate } = require('minizlib');

// After (Web API — supported in all modern browsers and Node 18+)
async function decompress(compressed: Uint8Array): Promise<Uint8Array> {
  const stream = new Response(compressed).body!
    .pipeThrough(new DecompressionStream('deflate'));
  return new Uint8Array(await new Response(stream).arrayBuffer());
}

Alternatively, use the pako library (already used by many ACARS tools) which is a pure JavaScript zlib implementation with zero Node.js dependencies.

3. If any crypto operations exist, use Web Crypto API

// Before
const crypto = require('crypto');
const hash = crypto.createHash('md5').update(data).digest('hex');

// After
const hashBuffer = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(data));
const hash = Array.from(new Uint8Array(hashBuffer)).map(b => b.toString(16).padStart(2, '0')).join('');

Benefits

  • Eliminates all 12 transitive vulnerabilities in downstream browser consumers
  • Reduces bundle size by ~50-80KB gzipped (no more polyfills needed)
  • Simplifies consumer build configs — no need for vite-plugin-node-polyfills or Webpack node polyfill plugins
  • Works in all environments — Web APIs are available in modern browsers, Node 18+, Deno, Bun, and edge runtimes (Cloudflare Workers, etc.)
  • Future-proof — Node.js polyfill ecosystem is increasingly unmaintained

Environment Compatibility

All proposed replacement APIs are available in:

  • Chrome 80+, Firefox 113+, Safari 14.1+ (DecompressionStream)
  • Node.js 18+ (Web Streams, TextEncoder/TextDecoder, Web Crypto)
  • All current LTS Node.js versions

Context

This was identified during a comprehensive audit of the frontend-spa-vue2 codebase. The frontend has been fully modernized (Vite 8, TypeScript 6, ESLint 10, zero lint warnings) but the 12 remaining npm audit vulnerabilities are all caused by the Node.js polyfill chain required solely by this package.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions