@@ -118,13 +118,21 @@ const ESCAPED_ENV_ASSIGNMENT_REDACT_PATTERN = String.raw`/\b[A-Z0-9_]*(?:KEY|TOK
118118// quotes still mask like plain values instead of escaping both patterns.
119119const STANDALONE_ASSIGNMENT_QUOTED_REDACT_PATTERN = String . raw `(^|[\s,;])(?:${ STANDALONE_ASSIGNMENT_SECRET_KEYS } )=(["'\x60])((?:(?!\2)[^\r\n])+)\2` ;
120120const STANDALONE_ASSIGNMENT_REDACT_PATTERN = String . raw `(^|[\s,;])(?:${ STANDALONE_ASSIGNMENT_SECRET_KEYS } )=(["'\x60]?[^\s&#"'\x60<>]+)` ;
121+ // Pure-base64-alphabet token prefixes: require a non-alphanumeric left boundary (URL/path
122+ // delimiters like `/` and `=` still qualify) but skip explicit `;base64,` payload spans, so
123+ // data-URL media is never corrupted while tokens in URL paths or assignments still redact.
124+ const BASE64_SAFE_TOKEN_BOUNDARY = String . raw `(^|[^A-Za-z0-9])(?<!;base64,[A-Za-z0-9+/=]*)` ;
121125const SHELL_REFERENCE_PRESERVING_PATTERN_SOURCES = new Set ( [
122126 ENV_ASSIGNMENT_REDACT_PATTERN ,
123127 ESCAPED_ENV_ASSIGNMENT_REDACT_PATTERN ,
124128 STANDALONE_ASSIGNMENT_QUOTED_REDACT_PATTERN ,
125129 STANDALONE_ASSIGNMENT_REDACT_PATTERN ,
126130] ) ;
127131const shellReferencePreservingPatterns = new WeakSet < RegExp > ( ) ;
132+ // Patterns whose left-context assertions (BASE64_SAFE_TOKEN_BOUNDARY) break under chunked
133+ // replacement: a chunk start satisfies `^` and hides the `;base64,` container from the
134+ // lookbehind, so these must always run against the full string.
135+ const chunkUnsafePatterns = new WeakSet < RegExp > ( ) ;
128136
129137const DEFAULT_REDACT_PATTERNS : string [ ] = [
130138 // ENV-style assignments. Keep this case-sensitive so diagnostics like
@@ -183,7 +191,9 @@ const DEFAULT_REDACT_PATTERNS: string[] = [
183191 String . raw `(fal_[A-Za-z0-9_-]{10,})` ,
184192 String . raw `(fc-[A-Za-z0-9]{10,})` ,
185193 String . raw `(bb_live_[A-Za-z0-9_-]{10,})` ,
186- String . raw `(gAAAA[A-Za-z0-9_=-]{20,})` ,
194+ // Prefixes made only of standard-base64 characters need a non-base64 left boundary so they
195+ // do not fire inside unrelated base64 blobs (e.g. data-URL media), corrupting the payload.
196+ String . raw `${ BASE64_SAFE_TOKEN_BOUNDARY } (gAAAA[A-Za-z0-9_=-]{20,})` ,
187197 String . raw `(sk_live_[A-Za-z0-9]{10,})` ,
188198 String . raw `(sk_test_[A-Za-z0-9]{10,})` ,
189199 String . raw `(rk_live_[A-Za-z0-9]{10,})` ,
@@ -200,15 +210,15 @@ const DEFAULT_REDACT_PATTERNS: string[] = [
200210 String . raw `(bkua_[a-z0-9]{40})` ,
201211 String . raw `(CCIPAT_[A-Za-z0-9]{22}_[A-Fa-f0-9]{40})` ,
202212 String . raw `(sbp_[a-z0-9]{40})` ,
203- String . raw `(dapi[0-9a-f]{32}(?:-\d)?)` ,
213+ String . raw `${ BASE64_SAFE_TOKEN_BOUNDARY } (dapi[0-9a-f]{32}(?:-\d)?)` ,
204214 String . raw `(dd[pw]_[A-Za-z0-9]{36})` ,
205215 String . raw `(glsa_[A-Za-z0-9_]{41})` ,
206216 String . raw `(glc_eyJ[A-Za-z0-9+/=]{60,160})` ,
207217 String . raw `(nfp_[A-Za-z0-9_]{36})` ,
208218 String . raw `(CFPAT-[A-Za-z0-9_\-]{40,})` ,
209- String . raw `(ATCTT3xFfG[A-Za-z0-9+/=_-]+=[A-Za-z0-9]{8})` ,
210- String . raw `(ATATT[A-Za-z0-9+/=_-]+=[A-Za-z0-9]{8})` ,
211- String . raw `(ATBB[A-Za-z0-9_=.-]{16,})` ,
219+ String . raw `${ BASE64_SAFE_TOKEN_BOUNDARY } (ATCTT3xFfG[A-Za-z0-9+/=_-]+=[A-Za-z0-9]{8})` ,
220+ String . raw `${ BASE64_SAFE_TOKEN_BOUNDARY } (ATATT[A-Za-z0-9+/=_-]+=[A-Za-z0-9]{8})` ,
221+ String . raw `${ BASE64_SAFE_TOKEN_BOUNDARY } (ATBB[A-Za-z0-9_=.-]{16,})` ,
212222 String . raw `(BBDC-[A-Za-z0-9+/@_-]{40,50})` ,
213223 String . raw `(HRKU-AA[A-Za-z0-9_-]{20,})` ,
214224 String . raw `(pat-(?:eu|na)1-[A-Za-z0-9]{8}\-[A-Za-z0-9]{4}\-[A-Za-z0-9]{4}\-[A-Za-z0-9]{4}\-[A-Za-z0-9]{12})` ,
@@ -226,8 +236,8 @@ const DEFAULT_REDACT_PATTERNS: string[] = [
226236 String . raw `(brv_[A-Za-z0-9]{10,})` ,
227237 String . raw `(xai-[A-Za-z0-9]{30,})` ,
228238 // Additional access-key and token-style prefixes.
229- String . raw `(AKIA[A-Z0-9]{16})` ,
230- String . raw `(ASIA[A-Z0-9]{16})` ,
239+ String . raw `${ BASE64_SAFE_TOKEN_BOUNDARY } (AKIA[A-Z0-9]{16})` ,
240+ String . raw `${ BASE64_SAFE_TOKEN_BOUNDARY } (ASIA[A-Z0-9]{16})` ,
231241 String . raw `(AKID[A-Za-z0-9]{10,})` ,
232242 String . raw `(LTAI[A-Za-z0-9]{10,})` ,
233243 String . raw `(hf_[A-Za-z0-9]{10,})` ,
@@ -303,6 +313,9 @@ function parsePattern(raw: RedactPattern): RegExp | null {
303313 if ( pattern && typeof raw === "string" && SHELL_REFERENCE_PRESERVING_PATTERN_SOURCES . has ( raw ) ) {
304314 shellReferencePreservingPatterns . add ( pattern ) ;
305315 }
316+ if ( pattern && typeof raw === "string" && raw . startsWith ( BASE64_SAFE_TOKEN_BOUNDARY ) ) {
317+ chunkUnsafePatterns . add ( pattern ) ;
318+ }
306319 return pattern ;
307320}
308321
@@ -837,7 +850,7 @@ function redactText(
837850 next = redactFormBody ( next ) ;
838851 }
839852 for ( const pattern of patterns ) {
840- next = replacePatternBounded ( next , pattern , ( ...args : unknown [ ] ) => {
853+ const replacer = ( ...args : unknown [ ] ) => {
841854 const hasNamedGroups =
842855 args . length > 0 &&
843856 typeof args [ args . length - 1 ] === "object" &&
@@ -851,7 +864,10 @@ function redactText(
851864 const offset = typeof args [ offsetIndex ] === "number" ? args [ offsetIndex ] : - 1 ;
852865 const input = typeof args [ inputIndex ] === "string" ? args [ inputIndex ] : "" ;
853866 return redactMatch ( match , groups , pattern , { input, offset } ) ;
854- } ) ;
867+ } ;
868+ next = chunkUnsafePatterns . has ( pattern )
869+ ? next . replace ( pattern , replacer )
870+ : replacePatternBounded ( next , pattern , replacer ) ;
855871 }
856872 return next ;
857873}
0 commit comments