Skip to content

@smithy/core@3.24.0: "Buffer is not defined" in browser — toUint8Array relative import bypasses browser field after serde consolidation #2025

Description

@gogakoreli

Bug Report

Summary

@smithy/core@3.24.0 causes ReferenceError: Buffer is not defined in browser environments during AWS SDK request signing. The core/serde package consolidation (PR #1985, commit 8963b91) moved @smithy/util-utf8 into @smithy/core as a submodule but lost the per-file browser field mappings that previously redirected Node.js-specific code to browser-safe alternatives.

This is a sibling regression to #2022 (crypto not defined on Node 18) — both stem from the same consolidation effort losing environment-specific code paths.

Stack Trace

ReferenceError: Buffer is not defined
    at fromString (buffer-from.js:12:1)
    at fromUtf8 (fromUtf8.js:3:1)
    at toUint8Array (toUint8Array.js:4:1)
    at SignatureV4.createStringToSign (SignatureV4Base.js:33:1)
    at SignatureV4.getSignature (SignatureV4.js:132:1)
    at SignatureV4.signRequest (SignatureV4.js:123:1)
    at async AwsSdkSigV4Signer.sign (AwsSdkSigV4Signer.js:42:1)
    at async httpSigningMiddleware.js:19:1

Affected Versions

  • Broken: @smithy/core@3.24.0 (published May 8, 2026) and @smithy/core@3.24.1
  • Last working: @smithy/core@3.23.17
  • Bundler: webpack 5 (likely affects all bundlers that rely on the browser field)

Root Cause Analysis

The call chain

toUint8Array.ts
  └─ import { fromUtf8 } from "./fromUtf8"       ← relative import
       └─ fromUtf8.ts
            └─ import { fromString } from "../util-buffer-from/buffer-from"
                 └─ Buffer.from(input, encoding)   ← 💥 doesn't exist in browser

What was lost during consolidation

Before — standalone @smithy/util-utf8@4.2.0 had per-file browser mappings in its package.json:

"browser": {
  "./dist-es/fromUtf8": "./dist-es/fromUtf8.browser",
  "./dist-es/toUtf8": "./dist-es/toUtf8.browser"
}

These told webpack: "when resolving ./fromUtf8 inside this package, use fromUtf8.browser.js (which uses TextEncoder) instead of fromUtf8.js (which uses Buffer.from())."

After@smithy/core@3.24.0 only has entry-point level mappings:

"browser": {
  "./dist-es/submodules/serde/index.js": "./dist-es/submodules/serde/index.browser.js"
}

This correctly routes the top-level import (@smithy/core/serdeindex.browser.js), and index.browser.js correctly exports from fromUtf8.browser.ts. However, toUint8Array.ts is the same file in both entry points, and it has a relative import:

// packages/core/src/submodules/serde/util-utf8/toUint8Array.ts
import { fromUtf8 } from "./fromUtf8";  // always resolves to Node.js version

Once webpack is inside the package following relative imports, the entry-point browser mapping has no effect. The relative ./fromUtf8 resolves to fromUtf8.js (Node.js, Buffer.from()) — never fromUtf8.browser.js (browser, TextEncoder).

Why PR #2009 didn't fix this

PR #2009 (commit 7ec62a0, "fix browser bundler metadata") restructured the approach to use variant index files for browser/native. The index.browser.ts correctly imports from ./util-utf8/fromUtf8.browser. But it didn't address the internal cross-referencetoUint8Array.ts still imports from ./fromUtf8 (the Node version), and this file is shared by both index.ts and index.browser.ts.

Regression Cross-References

Date Event Reference
Apr 29 Consolidation tracking issue opened #1980
May 1 core/serde consolidation PR merged #1985 (commit 8963b91)
May 6 Browser metadata fix PR merged (entry-point only) #2009 (commit 7ec62a0)
May 8 @smithy/core@3.24.0 published — regression goes live npm
May 8 Sibling bug reported: crypto is not defined on Node 18 #2022
May 11 @smithy/core@3.24.1 published — uuid fix only, Buffer bug persists npm

Suggested Fixes

Option A — Add per-file browser mappings to @smithy/core's package.json:

"browser": {
  "./dist-es/submodules/serde/util-utf8/fromUtf8.js": "./dist-es/submodules/serde/util-utf8/fromUtf8.browser.js",
  "./dist-es/submodules/serde/util-utf8/toUtf8.js": "./dist-es/submodules/serde/util-utf8/toUtf8.browser.js",
  "./dist-es/submodules/serde/util-buffer-from/buffer-from.js": false
}

Option B — Create a browser-specific toUint8Array.browser.ts that imports from ./fromUtf8.browser and export it from index.browser.ts.

Option C — Refactor toUint8Array.ts to use TextEncoder directly (no dependency on fromUtf8 at all), since TextEncoder is available in all modern browsers and Node.js 16+:

export const toUint8Array = (data: string | ArrayBuffer | ArrayBufferView): Uint8Array => {
  if (typeof data === "string") {
    return new TextEncoder().encode(data);
  }
  // ...
};

Current Workaround

For webpack users, inject Buffer globally via ProvidePlugin:

const webpack = require('webpack');

module.exports = {
  plugins: [
    new webpack.ProvidePlugin({
      Buffer: ['buffer', 'Buffer'],
    }),
  ],
  resolve: {
    fallback: {
      buffer: require.resolve('buffer/'),
    },
  },
};

Environment

  • Bundler: webpack 5
  • Target: browser (ES2020)
  • Framework: React 18
  • AWS SDK: @aws-sdk/client-* (latest, depends on @smithy/core@^3.24.0)

Metadata

Metadata

Assignees

Labels

bugThis issue is a bug.closing-soonThis issue will automatically close in 2 days unless further comments are made.releasedThe change has been released and there is a comment with the release version.

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions