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);