diff --git a/.gitignore b/.gitignore index 8f8291cf7f3..a6f072c91e5 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,13 @@ out packages/*/dist/** **/.pnpm-store/** +# Vendored upstream sources are NOT build output. Anything under +# packages/*/src/vendor/*/upstream/ is a verbatim copy of an npm tarball and +# must ship with the package; the `dist` and `build` matchers above would +# otherwise silently exclude their `dist/` subdirs from git. +!packages/*/src/vendor/** + + # dependencies node_modules **/node_modules/** diff --git a/eslint.config.mjs b/eslint.config.mjs index 0624c8f75fe..9c9840e6547 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -351,6 +351,11 @@ export default tseslint.config([ message: "Please always import from '@clerk/shared/' instead of '@clerk/shared'.", name: '@clerk/shared', }, + { + name: 'base-64', + message: + "base-64 is vendored at packages/expo/src/vendor/base-64. Import { encode, decode } from '../vendor/base-64' instead. See packages/expo/src/vendor/base-64/README.md.", + }, ], patterns: [ { diff --git a/packages/expo/package.json b/packages/expo/package.json index 9a8d73454f0..28c1cbfa5c4 100644 --- a/packages/expo/package.json +++ b/packages/expo/package.json @@ -116,7 +116,6 @@ "@clerk/clerk-js": "workspace:^", "@clerk/react": "workspace:^", "@clerk/shared": "workspace:^", - "base-64": "^1.0.0", "react-native-url-polyfill": "2.0.0", "tslib": "catalog:repo" }, @@ -124,6 +123,7 @@ "@clerk/expo-passkeys": "workspace:*", "@expo/config-plugins": "^54.0.4", "@types/base-64": "^1.0.2", + "base-64": "^1.0.0", "esbuild": "^0.28.0", "expo-apple-authentication": "^7.2.4", "expo-auth-session": "^5.5.2", diff --git a/packages/expo/src/polyfills/base64Polyfill.ts b/packages/expo/src/polyfills/base64Polyfill.ts index 2d9bd89b610..d32a2161021 100644 --- a/packages/expo/src/polyfills/base64Polyfill.ts +++ b/packages/expo/src/polyfills/base64Polyfill.ts @@ -1,4 +1,8 @@ -import { decode, encode } from 'base-64'; +// Vendored from base-64@1.0.0 — see ../vendor/base-64/README.md for the +// supply-chain rationale (this code becomes global.btoa/global.atob inside +// every Clerk-Expo customer app, which makes it an unusually high-leverage +// upstream dep to leave externalized). +import { decode, encode } from '../vendor/base-64'; import { isHermes } from '../utils'; diff --git a/packages/expo/src/vendor/base-64/README.md b/packages/expo/src/vendor/base-64/README.md new file mode 100644 index 00000000000..e4f2f6da9d6 --- /dev/null +++ b/packages/expo/src/vendor/base-64/README.md @@ -0,0 +1,89 @@ +# Vendored: `base-64` + +- **Upstream:** https://github.com/mathiasbynens/base64 +- **Vendored version:** 1.0.0 (published 2020-12-12) +- **License:** MIT (see `upstream/LICENSE-MIT.txt`) +- **Maintainer (npm):** `mathias` (single) +- **Owner inside Clerk:** `@clerk/expo` maintainers + +## Why this is vendored + +`base-64` is the userland `atob`/`btoa` implementation that `@clerk/expo` polyfills onto `global` for Hermes engines that lack native versions (see `packages/expo/src/polyfills/base64Polyfill.ts`). When `@clerk/expo` is installed by a customer, the published tarball declares `base-64` as a runtime external. The customer's package manager resolves `^1.0.0` against the npm registry at install time and fetches `base-64` fresh — Clerk's own `pnpm-lock.yaml` is not in the published tarball and does not participate in the customer's install. + +That externality + the fact that `base-64`'s exports become **`global.btoa` and `global.atob` inside every Clerk-Expo customer's running app** makes this dependency a high-leverage supply-chain target. Two attack chains motivate vendoring: + +### Chain 1 — Publisher account compromise + +`base-64` is single-maintainer. If `mathias`'s npm account is compromised (phishing, token theft, hostile transfer, social engineering) and a malicious `base-64@1.0.1` is published, every customer install of `@clerk/expo` after the publish resolves `^1.0.0` to `1.0.1` and pulls the compromised bytes. The polyfill assigns the compromised `encode`/`decode` to `global.btoa`/`global.atob`. Every subsequent `btoa()` or `atob()` call anywhere in the customer's app — including third-party libraries and Clerk's own runtime — runs through the compromised implementation, silently. + +Historical precedents in this class: `event-stream` (2018), `ua-parser-js` (2021), `colors.js`/`faker.js` (2022), `xz-utils` (2024). Each was a maintainer-account compromise that published a malicious new version. + +Pinning to an exact version (`"base-64": "1.0.0"` instead of `"^1.0.0"`) would close Chain 1 for direct deps — the customer's resolver would never pick up `1.0.1`. But: + +### Chain 2 — Registry-level same-version substitution + +If the npm registry itself serves substituted bytes for `base-64@1.0.0` (registry compromise, malicious unpublish-then-republish, npm-internal account compromise), the customer's first install of `@clerk/expo` fetches the substituted bytes, computes their hash, and records it as the trusted reference in their lockfile. There is no prior hash to compare against. Future installs with `--frozen-lockfile` "verify" against the now-poisoned hash. + +Exact-pinning does not address Chain 2 — the resolver still routes through the registry for `1.0.0`, and whatever bytes the registry serves are what the customer gets. Vendoring is the only mechanism that closes both chains: the customer's resolver never fetches `base-64` from the npm registry because the bytes ship inside the `@clerk/expo` npm tarball. + +| | Caret range | Exact pin | Vendored | +|---|---|---|---| +| Chain 1 (new version) | ❌ | ✅ | ✅ | +| Chain 2 (same-version substitution, first install) | ❌ | ❌ | ✅ | + +See `Sessions/S161/PROPOSAL.md` for the broader proposal. + +## What's in `upstream/` + +`upstream/` is a **byte-for-byte copy of the published `base-64@1.0.0` npm tarball.** Nothing in that directory has been modified by Clerk. + +``` +upstream/ +├── base64.js (~164 lines, single-file UMD; exports {encode, decode, version}) +├── package.json (upstream's; see "inert fields" below) +├── LICENSE-MIT.txt (MIT) +└── README.md (upstream's README) +``` + +### Inert fields in `upstream/package.json` + +The upstream `package.json` is preserved so future refresh diffs against new `base-64` versions match byte-for-byte against `npm pack` output. These fields are **inert in this location** — they do nothing here: + +- `scripts.*` — not executed; no install lifecycle runs against vendored code. +- `main: "base64.js"` — bundlers do not walk inner `package.json` of nested `src/vendor/` directories for relative imports; the Clerk-side `index.ts` (in this directory) handles resolution explicitly. + +## How consumers import it + +Inside `@clerk/expo`: + +```ts +import { decode, encode } from '../vendor/base-64'; +``` + +The Clerk-side `index.ts` shim re-exports from `./upstream/base64.js` with typed signatures, abstracting the bundler-resolution detail (see `index.ts`). + +## Refreshing from upstream + +`upstream/` is intentionally frozen. Don't routinely sync. + +Refresh **only** when: + +- A CVE is reported against `mathiasbynens/base64` upstream, OR +- A spec-relevant bug is discovered. + +`base-64@1.0.0` has been the only release since 2014. Any new upstream release after 2020-12-12 should be treated as anomalous and investigated before adoption. + +Procedure: + +1. `npm pack base-64@` in a scratch directory; extract. +2. `diff -r` against `upstream/`. +3. Read every changed line. Apply Clerk's threat model — is this a fix you want, or a behavior change you don't? +4. If accepting: replace `upstream/` with the new tarball contents in one commit (no other changes). +5. Re-run `parity.spec.ts` to confirm behavioral equivalence still holds. +6. Update the vendored version in this README. + +## Tests + +`__tests__/parity.spec.ts` asserts byte-equivalent inputs produce identical outputs between the upstream npm package (kept as `@clerk/expo`'s `devDependency`) and this vendored copy. Covers RFC 4648 fixtures, `atob`/`btoa` cross-compatibility, and Unicode edge cases. + +The upstream `base-64` stays a `devDependency` of `@clerk/expo` for as long as this parity test exists. Removing the devDep would mean giving up the empirical comparator. diff --git a/packages/expo/src/vendor/base-64/__tests__/parity.spec.ts b/packages/expo/src/vendor/base-64/__tests__/parity.spec.ts new file mode 100644 index 00000000000..365a1c7900c --- /dev/null +++ b/packages/expo/src/vendor/base-64/__tests__/parity.spec.ts @@ -0,0 +1,135 @@ +/** + * Vendor parity test for base-64@1.0.0. + * + * Loads encode/decode from BOTH the upstream npm package (kept in + * @clerk/expo devDependencies for as long as this test exists) AND the + * vendored copy at ../upstream/base64.js. Asserts byte-equivalent inputs + * produce identical outputs. + * + * What this test buys: + * - The byte-equivalence check (tools/verify-vendor.sh) proves the bytes + * on disk match the upstream tarball. + * - This test proves that loading those bytes through our bundler / + * test runtime produces upstream's behavior — closing the gap between + * "the bytes are correct" and "the runtime does what upstream does." + * + * When to remove this test: + * - When the upstream `base-64` devDependency is removed from + * packages/expo/package.json, this file must be removed too (the + * `from 'base-64'` import would fail to resolve). Removing the devDep + * means losing the comparator; don't do that unless the vendoring + * approach is fully accepted. + * + * See packages/expo/src/vendor/base-64/README.md for the broader vendoring + * rationale and the customer-side attack chains that motivate it. + */ + +// eslint-disable-next-line no-restricted-imports -- intentional: comparator for vendor parity +import { decode as upstreamDecode, encode as upstreamEncode } from 'base-64'; +import { describe, expect, it } from 'vitest'; + +import { decode as vendoredDecode, encode as vendoredEncode } from '../'; + +/** + * RFC 4648 §10 test vectors — canonical base64 fixtures from the spec. + * Every base64 implementation should handle these identically. + */ +const RFC4648_VECTORS: Array<[plain: string, encoded: string]> = [ + ['', ''], + ['f', 'Zg=='], + ['fo', 'Zm8='], + ['foo', 'Zm9v'], + ['foob', 'Zm9vYg=='], + ['fooba', 'Zm9vYmE='], + ['foobar', 'Zm9vYmFy'], +]; + +/** + * Cases beyond the RFC vectors — the polyfill use case is hijacking + * global.btoa / global.atob, so the parity surface must cover everything + * an arbitrary caller (third-party library) might throw at it. + */ +const EXTRA_VECTORS: Array<[label: string, plain: string]> = [ + ['empty', ''], + ['single null byte', '\x00'], + ['Latin-1 high', '\xff'], + ['arbitrary binary', '\x00\x01\x02\x03\x04\xfd\xfe\xff'], + ['ASCII letters', 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'], + ['ASCII symbols', '!@#$%^&*()_+-=[]{}|;:,.<>?/~`\'"\\'], + ['long string', 'a'.repeat(1024)], + ['exactly 3 bytes', 'abc'], + ['exactly 4 bytes (forces padding=)', 'abcd'], + ['exactly 6 bytes (no padding)', 'abcdef'], + ['JSON shape', '{"foo":"bar","baz":[1,2,3]}'], +]; + +describe('base-64 vendor parity — RFC 4648 fixtures', () => { + it.each(RFC4648_VECTORS)('encode(%j) === %j', (plain, encoded) => { + expect(vendoredEncode(plain)).toBe(upstreamEncode(plain)); + expect(vendoredEncode(plain)).toBe(encoded); // canonical anchor + }); + + it.each(RFC4648_VECTORS)('decode(%j) === %j', (plain, encoded) => { + expect(vendoredDecode(encoded)).toBe(upstreamDecode(encoded)); + expect(vendoredDecode(encoded)).toBe(plain); // canonical anchor + }); +}); + +describe('base-64 vendor parity — extra fixtures', () => { + it.each(EXTRA_VECTORS)('encode/decode roundtrip: %s', (_label, plain) => { + const vEnc = vendoredEncode(plain); + const uEnc = upstreamEncode(plain); + expect(vEnc).toBe(uEnc); + expect(vendoredDecode(vEnc)).toBe(upstreamDecode(uEnc)); + expect(vendoredDecode(vEnc)).toBe(plain); + }); +}); + +describe('base-64 vendor parity — deterministic fuzz', () => { + it('matches upstream for 512 random binary strings of varying length', () => { + for (let seed = 0; seed < 512; seed++) { + const len = (seed * 37) % 256; + const chars: string[] = []; + for (let i = 0; i < len; i++) { + // Latin-1 range only (0-255) — what base-64 contracts on. + chars.push(String.fromCharCode((seed + i * 13) & 0xff)); + } + const plain = chars.join(''); + const vEnc = vendoredEncode(plain); + const uEnc = upstreamEncode(plain); + expect(vEnc, `seed=${seed}`).toBe(uEnc); + expect(vendoredDecode(vEnc), `seed=${seed}`).toBe(upstreamDecode(uEnc)); + } + }); +}); + +describe('base-64 vendor parity — error handling', () => { + // Both upstream and vendored should throw on invalid input. We don't pin + // the error message, just that they agree on which inputs throw. + const INVALID: Array<[label: string, invalid: string]> = [ + ['truncated padding', 'Zm9'], + ['invalid char', 'Zm$9v'], + ['stray padding', 'Zm9v='], + ]; + it.each(INVALID)('decode throws-or-matches on invalid input: %s', (_label, invalid) => { + let vErr: unknown = null; + let uErr: unknown = null; + let vResult: string | null = null; + let uResult: string | null = null; + try { + vResult = vendoredDecode(invalid); + } catch (e) { + vErr = e; + } + try { + uResult = upstreamDecode(invalid); + } catch (e) { + uErr = e; + } + // Either both threw or both produced the same output. + expect(Boolean(vErr)).toBe(Boolean(uErr)); + if (!vErr) { + expect(vResult).toBe(uResult); + } + }); +}); diff --git a/packages/expo/src/vendor/base-64/index.ts b/packages/expo/src/vendor/base-64/index.ts new file mode 100644 index 00000000000..a354e5bde02 --- /dev/null +++ b/packages/expo/src/vendor/base-64/index.ts @@ -0,0 +1,47 @@ +/** + * Clerk-side entry for the vendored `base-64` package. + * + * Why this file exists: + * - `upstream/` is a verbatim copy of the base-64@1.0.0 npm tarball and + * must not be modified (byte-equivalence is the security claim — see + * ./README.md). + * - The upstream ships as a UMD wrapper — its exports are assigned to + * `module.exports` inside an IIFE that TypeScript cannot trace + * statically. tsc therefore infers an empty export surface for the + * `.js` file even with `allowJs: true`. The cast below asserts the + * export shape; the parity test (`./__tests__/parity.spec.ts`) + * verifies the assertion empirically against the upstream npm + * package. + * - Consumers import from this file (`../vendor/base-64`); they should + * not reach into `upstream/` directly. + */ + +import * as upstreamModule from './upstream/base64.js'; + +interface Base64Module { + encode: (input: string) => string; + decode: (input: string) => string; + version: string; +} + +// tsc infers `typeof upstreamModule` as `{}` because base-64's UMD wrapper +// hides the module.exports assignment inside an IIFE. The shape asserted +// here is verified empirically by __tests__/parity.spec.ts against the +// upstream npm package (kept as a devDependency for this purpose). +const upstream: Base64Module = upstreamModule as unknown as Base64Module; + +/** + * Encode a binary-safe string to base64. Compatible with the WHATWG + * `btoa()` algorithm (RFC 4648 §4). Throws on non-Latin-1 input. + * + * Vendored from base-64@1.0.0 — see ./README.md. + */ +export const encode: (input: string) => string = upstream.encode; + +/** + * Decode a base64-encoded string back to a binary string. Compatible with + * the WHATWG `atob()` algorithm (RFC 4648 §4). Throws on invalid input. + * + * Vendored from base-64@1.0.0 — see ./README.md. + */ +export const decode: (input: string) => string = upstream.decode; diff --git a/packages/expo/src/vendor/base-64/upstream/LICENSE-MIT.txt b/packages/expo/src/vendor/base-64/upstream/LICENSE-MIT.txt new file mode 100644 index 00000000000..a41e0a7ef97 --- /dev/null +++ b/packages/expo/src/vendor/base-64/upstream/LICENSE-MIT.txt @@ -0,0 +1,20 @@ +Copyright Mathias Bynens + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/packages/expo/src/vendor/base-64/upstream/README.md b/packages/expo/src/vendor/base-64/upstream/README.md new file mode 100644 index 00000000000..ab0ef251e3f --- /dev/null +++ b/packages/expo/src/vendor/base-64/upstream/README.md @@ -0,0 +1,112 @@ +# base64 [![Build status](https://travis-ci.org/mathiasbynens/base64.svg?branch=master)](https://travis-ci.org/mathiasbynens/base64) [![Code coverage status](http://img.shields.io/coveralls/mathiasbynens/base64/master.svg)](https://coveralls.io/r/mathiasbynens/base64) + +_base64_ is a robust base64 encoder/decoder that is fully compatible with [`atob()` and `btoa()`](https://html.spec.whatwg.org/multipage/webappapis.html#atob), written in JavaScript. The base64-encoding and -decoding algorithms it uses are fully [RFC 4648](https://tools.ietf.org/html/rfc4648#section-4) compliant. + +## Installation + +Via [npm](https://www.npmjs.com/): + +```bash +npm install base-64 +``` + +In a browser: + +```html + +``` + +In [Narwhal](http://narwhaljs.org/), [Node.js](https://nodejs.org/), and [RingoJS](http://ringojs.org/): + +```js +var base64 = require('base-64'); +``` + +In [Rhino](http://www.mozilla.org/rhino/): + +```js +load('base64.js'); +``` + +Using an AMD loader like [RequireJS](http://requirejs.org/): + +```js +require( + { + 'paths': { + 'base64': 'path/to/base64' + } + }, + ['base64'], + function(base64) { + console.log(base64); + } +); +``` + +## API + +### `base64.version` + +A string representing the semantic version number. + +### `base64.encode(input)` + +This function takes a byte string (the `input` parameter) and encodes it according to base64. The input data must be in the form of a string containing only characters in the range from U+0000 to U+00FF, each representing a binary byte with values `0x00` to `0xFF`. The `base64.encode()` function is designed to be fully compatible with [`btoa()` as described in the HTML Standard](https://html.spec.whatwg.org/multipage/webappapis.html#dom-windowbase64-btoa). + +```js +var encodedData = base64.encode(input); +``` + +To base64-encode any Unicode string, [encode it as UTF-8 first](https://github.com/mathiasbynens/utf8.js#utf8encodestring): + +```js +var base64 = require('base-64'); +var utf8 = require('utf8'); + +var text = 'foo © bar 𝌆 baz'; +var bytes = utf8.encode(text); +var encoded = base64.encode(bytes); +console.log(encoded); +// → 'Zm9vIMKpIGJhciDwnYyGIGJheg==' +``` + +### `base64.decode(input)` + +This function takes a base64-encoded string (the `input` parameter) and decodes it. The return value is in the form of a string containing only characters in the range from U+0000 to U+00FF, each representing a binary byte with values `0x00` to `0xFF`. The `base64.decode()` function is designed to be fully compatible with [`atob()` as described in the HTML Standard](https://html.spec.whatwg.org/multipage/webappapis.html#dom-windowbase64-atob). + +```js +var decodedData = base64.decode(encodedData); +``` + +To base64-decode UTF-8-encoded data back into a Unicode string, [UTF-8-decode it](https://github.com/mathiasbynens/utf8.js#utf8decodebytestring) after base64-decoding it: + +```js +var encoded = 'Zm9vIMKpIGJhciDwnYyGIGJheg=='; +var bytes = base64.decode(encoded); +var text = utf8.decode(bytes); +console.log(text); +// → 'foo © bar 𝌆 baz' +``` + +## Support + +_base64_ is designed to work in at least Node.js v0.10.0, Narwhal 0.3.2, RingoJS 0.8-0.9, PhantomJS 1.9.0, Rhino 1.7RC4, as well as old and modern versions of Chrome, Firefox, Safari, Opera, and Internet Explorer. + +## Unit tests & code coverage + +After cloning this repository, run `npm install` to install the dependencies needed for development and testing. You may want to install Istanbul _globally_ using `npm install istanbul -g`. + +Once that’s done, you can run the unit tests in Node using `npm test` or `node tests/tests.js`. To run the tests in Rhino, Ringo, Narwhal, and web browsers as well, use `grunt test`. + +To generate the code coverage report, use `grunt cover`. + +## Author + +| [![twitter/mathias](https://gravatar.com/avatar/24e08a9ea84deb17ae121074d0f17125?s=70)](https://twitter.com/mathias "Follow @mathias on Twitter") | +|---| +| [Mathias Bynens](https://mathiasbynens.be/) | + +## License + +_base64_ is available under the [MIT](https://mths.be/mit) license. diff --git a/packages/expo/src/vendor/base-64/upstream/base64.js b/packages/expo/src/vendor/base-64/upstream/base64.js new file mode 100644 index 00000000000..0b335e5c051 --- /dev/null +++ b/packages/expo/src/vendor/base-64/upstream/base64.js @@ -0,0 +1,164 @@ +/*! https://mths.be/base64 v1.0.0 by @mathias | MIT license */ +;(function(root) { + + // Detect free variables `exports`. + var freeExports = typeof exports == 'object' && exports; + + // Detect free variable `module`. + var freeModule = typeof module == 'object' && module && + module.exports == freeExports && module; + + // Detect free variable `global`, from Node.js or Browserified code, and use + // it as `root`. + var freeGlobal = typeof global == 'object' && global; + if (freeGlobal.global === freeGlobal || freeGlobal.window === freeGlobal) { + root = freeGlobal; + } + + /*--------------------------------------------------------------------------*/ + + var InvalidCharacterError = function(message) { + this.message = message; + }; + InvalidCharacterError.prototype = new Error; + InvalidCharacterError.prototype.name = 'InvalidCharacterError'; + + var error = function(message) { + // Note: the error messages used throughout this file match those used by + // the native `atob`/`btoa` implementation in Chromium. + throw new InvalidCharacterError(message); + }; + + var TABLE = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; + // http://whatwg.org/html/common-microsyntaxes.html#space-character + var REGEX_SPACE_CHARACTERS = /[\t\n\f\r ]/g; + + // `decode` is designed to be fully compatible with `atob` as described in the + // HTML Standard. http://whatwg.org/html/webappapis.html#dom-windowbase64-atob + // The optimized base64-decoding algorithm used is based on @atk’s excellent + // implementation. https://gist.github.com/atk/1020396 + var decode = function(input) { + input = String(input) + .replace(REGEX_SPACE_CHARACTERS, ''); + var length = input.length; + if (length % 4 == 0) { + input = input.replace(/==?$/, ''); + length = input.length; + } + if ( + length % 4 == 1 || + // http://whatwg.org/C#alphanumeric-ascii-characters + /[^+a-zA-Z0-9/]/.test(input) + ) { + error( + 'Invalid character: the string to be decoded is not correctly encoded.' + ); + } + var bitCounter = 0; + var bitStorage; + var buffer; + var output = ''; + var position = -1; + while (++position < length) { + buffer = TABLE.indexOf(input.charAt(position)); + bitStorage = bitCounter % 4 ? bitStorage * 64 + buffer : buffer; + // Unless this is the first of a group of 4 characters… + if (bitCounter++ % 4) { + // …convert the first 8 bits to a single ASCII character. + output += String.fromCharCode( + 0xFF & bitStorage >> (-2 * bitCounter & 6) + ); + } + } + return output; + }; + + // `encode` is designed to be fully compatible with `btoa` as described in the + // HTML Standard: http://whatwg.org/html/webappapis.html#dom-windowbase64-btoa + var encode = function(input) { + input = String(input); + if (/[^\0-\xFF]/.test(input)) { + // Note: no need to special-case astral symbols here, as surrogates are + // matched, and the input is supposed to only contain ASCII anyway. + error( + 'The string to be encoded contains characters outside of the ' + + 'Latin1 range.' + ); + } + var padding = input.length % 3; + var output = ''; + var position = -1; + var a; + var b; + var c; + var buffer; + // Make sure any padding is handled outside of the loop. + var length = input.length - padding; + + while (++position < length) { + // Read three bytes, i.e. 24 bits. + a = input.charCodeAt(position) << 16; + b = input.charCodeAt(++position) << 8; + c = input.charCodeAt(++position); + buffer = a + b + c; + // Turn the 24 bits into four chunks of 6 bits each, and append the + // matching character for each of them to the output. + output += ( + TABLE.charAt(buffer >> 18 & 0x3F) + + TABLE.charAt(buffer >> 12 & 0x3F) + + TABLE.charAt(buffer >> 6 & 0x3F) + + TABLE.charAt(buffer & 0x3F) + ); + } + + if (padding == 2) { + a = input.charCodeAt(position) << 8; + b = input.charCodeAt(++position); + buffer = a + b; + output += ( + TABLE.charAt(buffer >> 10) + + TABLE.charAt((buffer >> 4) & 0x3F) + + TABLE.charAt((buffer << 2) & 0x3F) + + '=' + ); + } else if (padding == 1) { + buffer = input.charCodeAt(position); + output += ( + TABLE.charAt(buffer >> 2) + + TABLE.charAt((buffer << 4) & 0x3F) + + '==' + ); + } + + return output; + }; + + var base64 = { + 'encode': encode, + 'decode': decode, + 'version': '1.0.0' + }; + + // Some AMD build optimizers, like r.js, check for specific condition patterns + // like the following: + if ( + typeof define == 'function' && + typeof define.amd == 'object' && + define.amd + ) { + define(function() { + return base64; + }); + } else if (freeExports && !freeExports.nodeType) { + if (freeModule) { // in Node.js or RingoJS v0.8.0+ + freeModule.exports = base64; + } else { // in Narwhal or RingoJS v0.7.0- + for (var key in base64) { + base64.hasOwnProperty(key) && (freeExports[key] = base64[key]); + } + } + } else { // in Rhino or a web browser + root.base64 = base64; + } + +}(this)); diff --git a/packages/expo/src/vendor/base-64/upstream/package.json b/packages/expo/src/vendor/base-64/upstream/package.json new file mode 100644 index 00000000000..479b0a180c4 --- /dev/null +++ b/packages/expo/src/vendor/base-64/upstream/package.json @@ -0,0 +1,43 @@ +{ + "name": "base-64", + "version": "1.0.0", + "description": "A robust base64 encoder/decoder that is fully compatible with `atob()` and `btoa()`, written in JavaScript.", + "homepage": "https://mths.be/base64", + "main": "base64.js", + "keywords": [ + "codec", + "decoder", + "encoder", + "base64", + "atob", + "btoa" + ], + "license": "MIT", + "author": { + "name": "Mathias Bynens", + "url": "https://mathiasbynens.be/" + }, + "repository": { + "type": "git", + "url": "https://github.com/mathiasbynens/base64.git" + }, + "bugs": "https://github.com/mathiasbynens/base64/issues", + "files": [ + "LICENSE-MIT.txt", + "base64.js" + ], + "scripts": { + "test": "mocha tests/tests.js", + "build": "grunt build" + }, + "devDependencies": { + "coveralls": "^2.11.4", + "grunt": "^0.4.5", + "grunt-cli": "^1.3.2", + "grunt-shell": "^1.1.2", + "grunt-template": "^0.2.3", + "istanbul": "^0.4.0", + "mocha": "^6.2.0", + "regenerate": "^1.2.1" + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0f62ecad1c4..4194cb88e13 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -541,9 +541,6 @@ importers: '@clerk/shared': specifier: workspace:^ version: link:../shared - base-64: - specifier: ^1.0.0 - version: 1.0.0 expo: specifier: '>=53 <56' version: 54.0.23(@babel/core@7.29.0)(@modelcontextprotocol/sdk@1.26.0(@cfworker/json-schema@4.1.1)(zod@3.25.76))(bufferutil@4.1.0)(graphql@16.13.2)(react-native@0.85.2(@babel/core@7.29.0)(@react-native-community/cli@12.3.7(bufferutil@4.1.0)(utf-8-validate@5.0.10))(@types/react@18.3.28)(bufferutil@4.1.0)(react@18.3.1)(utf-8-validate@5.0.10))(react@18.3.1)(utf-8-validate@5.0.10) @@ -569,6 +566,9 @@ importers: '@types/base-64': specifier: ^1.0.2 version: 1.0.2 + base-64: + specifier: ^1.0.0 + version: 1.0.0 esbuild: specifier: ^0.28.0 version: 0.28.0