From 0b91d4d1a0d5de3b4cdcb94656b90d1187693162 Mon Sep 17 00:00:00 2001 From: 4gray Date: Tue, 13 Oct 2026 21:54:04 +0200 Subject: [PATCH] wip: linear URL-cleaning regexes --- .../logging/src/lib/strip-url-credentials.spec.ts | 11 +++++++++++ libs/shared/logging/src/lib/strip-url-credentials.ts | 11 +++++++---- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/libs/shared/logging/src/lib/strip-url-credentials.spec.ts b/libs/shared/logging/src/lib/strip-url-credentials.spec.ts index 0fb45e2a1..d40fec631 100644 --- a/libs/shared/logging/src/lib/strip-url-credentials.spec.ts +++ b/libs/shared/logging/src/lib/strip-url-credentials.spec.ts @@ -96,6 +96,17 @@ describe('stripUrlCredentials', () => { expect(stripUrlCredentials(url)).not.toMatch(/hunter2/); }); + it('stays fast on long runs a backtracking pattern would retry', () => { + const run = 'a'.repeat(50_000); + const started = performance.now(); + for (const value of [run, `${run}.`.repeat(2), 'a.'.repeat(25_000)]) { + stripUrlCredentials( + `https://host.test/list?note=${encodeURIComponent(value)}` + ); + } + expect(performance.now() - started).toBeLessThan(1_000); + }); + it('returns null for text that is not an absolute URL', () => { expect(stripUrlCredentials('get.php?username=joe')).toBeNull(); expect(stripUrlCredentials('')).toBeNull(); diff --git a/libs/shared/logging/src/lib/strip-url-credentials.ts b/libs/shared/logging/src/lib/strip-url-credentials.ts index a759d84da..c05b43a6d 100644 --- a/libs/shared/logging/src/lib/strip-url-credentials.ts +++ b/libs/shared/logging/src/lib/strip-url-credentials.ts @@ -12,11 +12,14 @@ const SHORT_ACCOUNT_KEYS = new Set(['user', 'usr', 'pass', 'pas']); /** How deep URLs nested in URLs are cleaned; anything deeper is dropped. */ const MAX_NESTING = 4; -const ABSOLUTE_URL = /^[a-z][a-z0-9+.-]*:\/\//iu; -const EMBEDDED_URL = /[a-z][a-z0-9+.-]*:\/\/[^\s"'<>]+/giu; +// Linear on any input: a match starts only where a name can start (the +// lookbehinds), and every name part is bounded, so a long run of letters +// cannot make a scan retry from each position. +const ABSOLUTE_URL = /^[a-z][a-z0-9+.-]{0,31}:\/\//iu; +const EMBEDDED_URL = /(?]+/giu; /** `joe:hunter2@origin.test`: an authority's userinfo without a scheme. */ -const BARE_USERINFO = /(?:^|[\s/])[^\s/:@]+:[^\s/@]*@[^\s/@]/u; -const KEYED_PAIR = /\b([a-z][a-z0-9_.-]*)\s*[=:]/giu; +const BARE_USERINFO = /(?:^|[\s/])[^\s/:@]{1,256}:[^\s/@]{0,256}@[^\s/@]/u; +const KEYED_PAIR = /(?