From 11d1086ef578c20574ba49751f903d341a7a2d12 Mon Sep 17 00:00:00 2001 From: Ned Wolpert Date: Sat, 13 Jun 2026 07:30:50 -0700 Subject: [PATCH] fix(sdk): replace base64url regexes with replaceAll (SonarQube S5852 + S7781) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SonarQube flagged `/=+$/` in encode() as a slow-regex hotspot (S5852, ReDoS/CWE-1333). It only ran on btoa() output where `=` is at most 2 trailing padding chars, so the quadratic input was unreachable — but rather than dismiss it, remove the regex entirely: convert all the global-regex replaces in encode() and decode() to replaceAll() with string literals (ES2022 target). This also satisfies S7781 ("prefer replaceAll over replace"), keeps the transforms linear and non-backtracking, and is more readable. Behavior-preserving: base64 only emits `=` as trailing padding, so stripping all `=` equals stripping the trailing run; the rest are exact equivalents. Output is byte-identical and the full vitest round-trip suite (76 tests) and tsc pass. Refs #60 Co-Authored-By: Claude Opus 4.8 (1M context) --- clients/passkeys-browser/src/base64url.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/clients/passkeys-browser/src/base64url.ts b/clients/passkeys-browser/src/base64url.ts index 568e4e4..8a24352 100644 --- a/clients/passkeys-browser/src/base64url.ts +++ b/clients/passkeys-browser/src/base64url.ts @@ -15,12 +15,16 @@ export function encode(input: ArrayBuffer | Uint8Array | ArrayBufferView): Base6 for (let i = 0; i < bytes.length; i++) { s += String.fromCharCode(bytes[i]!); } - return btoa(s).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, ""); + // base64url per RFC 4648 §5: map +/ to -_ and strip padding. Using replaceAll with + // string literals (no regexes) keeps this linear — it avoids the ReDoS shape + // SonarQube flags on `/=+$/`. `=` only ever appears as trailing base64 padding, so + // removing all of it is equivalent to stripping the trailing run. + return btoa(s).replaceAll("+", "-").replaceAll("/", "_").replaceAll("=", ""); } export function decode(input: Base64Url): Uint8Array { const pad = "=".repeat((4 - (input.length % 4)) % 4); - const normalized = input.replace(/-/g, "+").replace(/_/g, "/") + pad; + const normalized = input.replaceAll("-", "+").replaceAll("_", "/") + pad; const binary = atob(normalized); const bytes = new Uint8Array(binary.length); for (let i = 0; i < binary.length; i++) {