From b152ee4a34ee3217835cd30659c935a1f46ffc1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8C=85=E5=91=A8=E6=B6=9B?= Date: Mon, 27 Jul 2026 05:10:33 -0700 Subject: [PATCH] fix(auth): spell isLikelyEmail ASCII range with printable bounds (no control char) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Mirror the objectui hygiene fix: `[^\x20-\x7e]` instead of `[^\x00-\x7f]` so the regex literal carries no control character (eslint no-control-regex). Same semantics — anything outside printable ASCII fails the email pre-filter. Co-Authored-By: Claude Opus 4.8 --- .changeset/isLikelyEmail-no-control-char.md | 11 +++++++++++ .../plugin-auth/src/admin-user-endpoints.ts | 14 ++++++++------ 2 files changed, 19 insertions(+), 6 deletions(-) create mode 100644 .changeset/isLikelyEmail-no-control-char.md diff --git a/.changeset/isLikelyEmail-no-control-char.md b/.changeset/isLikelyEmail-no-control-char.md new file mode 100644 index 0000000000..cd66aaed9b --- /dev/null +++ b/.changeset/isLikelyEmail-no-control-char.md @@ -0,0 +1,11 @@ +--- +"@objectstack/plugin-auth": patch +--- + +fix(auth): spell isLikelyEmail's ASCII guard with printable bounds (no control char) + +The non-ASCII guard added in framework#3566 was written as `[^\x00-\x7f]`, whose +regex literal embeds a control character (`\x00`). Rewrite it as `[^\x20-\x7e]` — +identical behaviour (anything outside printable ASCII fails the email +pre-filter), but the pattern no longer carries a control character (eslint +`no-control-regex`), and it matches the objectui side's `isPlausibleEmail`. diff --git a/packages/plugins/plugin-auth/src/admin-user-endpoints.ts b/packages/plugins/plugin-auth/src/admin-user-endpoints.ts index ab2bdd4a5e..b33b3da311 100644 --- a/packages/plugins/plugin-auth/src/admin-user-endpoints.ts +++ b/packages/plugins/plugin-auth/src/admin-user-endpoints.ts @@ -187,12 +187,14 @@ function badRequest(message: string): EndpointResult { */ export function isLikelyEmail(value: string): boolean { if (value.length === 0 || value.length > 254 || /\s/.test(value)) return false; - // Reject non-ASCII up front so obviously-invalid addresses (e.g. a Chinese - // domain like `x@柴仟.com`) fail this pre-filter — and therefore the import - // dry-run — instead of passing here only to be rejected later by - // better-auth's strict ASCII validator at real-import time (framework#3566). - // A single linear char-class test; no backtracking (see the ReDoS note above). - if (/[^\x00-\x7f]/.test(value)) return false; + // Reject anything outside printable ASCII up front so obviously-invalid + // addresses (e.g. a Chinese domain like `x@柴仟.com`) fail this pre-filter — + // and therefore the import dry-run — instead of passing here only to be + // rejected later by better-auth's strict ASCII validator at real-import time + // (framework#3566). A single linear char-class test; no backtracking (see the + // ReDoS note above). The range is spelled with printable bounds (0x20–0x7e) + // so the pattern itself carries no control character (eslint no-control-regex). + if (/[^\x20-\x7e]/.test(value)) return false; const at = value.indexOf('@'); if (at <= 0 || at !== value.lastIndexOf('@') || at === value.length - 1) return false; const domain = value.slice(at + 1);