@@ -564,10 +564,23 @@ <h2 id="creditsDialogTitle">Credits</h2>
564564 } catch ( e ) { return false ; }
565565 } ;
566566
567- const tryBase64Decode = ( str ) => {
567+ const tryBase64DecodeBytes = ( str ) => {
568568 let padded = str . replace ( / - / g, '+' ) . replace ( / _ / g, '/' ) ;
569569 while ( padded . length % 4 ) padded += '=' ;
570- try { return atob ( padded ) ; } catch ( e ) { return null ; }
570+ try {
571+ const decoded = atob ( padded ) ;
572+ const bytes = new Uint8Array ( decoded . length ) ;
573+ for ( let i = 0 ; i < decoded . length ; i ++ ) bytes [ i ] = decoded . charCodeAt ( i ) ;
574+ return bytes ;
575+ } catch ( e ) { return null ; }
576+ } ;
577+
578+ const tryBase64Decode = ( str ) => {
579+ const bytes = tryBase64DecodeBytes ( str ) ;
580+ if ( ! bytes ) return null ;
581+ let decoded = '' ;
582+ for ( let i = 0 ; i < bytes . length ; i ++ ) decoded += String . fromCharCode ( bytes [ i ] ) ;
583+ return decoded ;
571584 } ;
572585
573586 const findJWTInString = ( str ) => {
@@ -579,28 +592,157 @@ <h2 id="creditsDialogTitle">Credits</h2>
579592 return null ;
580593 } ;
581594
595+ const escapeHtml = ( value ) => value . replace ( / [ & < > " ' ] / g, ( ch ) => ( {
596+ '&' : '&' , '<' : '<' , '>' : '>' , '"' : '"' , "'" : '''
597+ } ) [ ch ] ) ;
598+
599+ const bytesToUtf8 = ( bytes ) => {
600+ try { return new TextDecoder ( 'utf-8' , { fatal : true } ) . decode ( bytes ) ; } catch ( e ) { return null ; }
601+ } ;
602+
603+ const readUint32BE = ( bytes , offset ) => (
604+ ( ( bytes [ offset ] << 24 ) >>> 0 ) + ( bytes [ offset + 1 ] << 16 ) + ( bytes [ offset + 2 ] << 8 ) + bytes [ offset + 3 ]
605+ ) >>> 0 ;
606+
607+ const CRC32_TABLE = ( ( ) => {
608+ const table = new Uint32Array ( 256 ) ;
609+ for ( let n = 0 ; n < table . length ; n ++ ) {
610+ let c = n ;
611+ for ( let k = 0 ; k < 8 ; k ++ ) c = ( c & 1 ) ? ( 0xedb88320 ^ ( c >>> 1 ) ) : ( c >>> 1 ) ;
612+ table [ n ] = c >>> 0 ;
613+ }
614+ return table ;
615+ } ) ( ) ;
616+
617+ const crc32 = ( bytes ) => {
618+ let crc = 0xffffffff ;
619+ for ( let i = 0 ; i < bytes . length ; i ++ ) crc = CRC32_TABLE [ ( crc ^ bytes [ i ] ) & 0xff ] ^ ( crc >>> 8 ) ;
620+ return ( crc ^ 0xffffffff ) >>> 0 ;
621+ } ;
622+
623+ const unwrapElasticChecksum = ( bytes ) => {
624+ if ( bytes . length < 8 ) return null ;
625+ const separatorOffset = bytes . length - 8 ;
626+ if (
627+ bytes [ separatorOffset ] !== 0 || bytes [ separatorOffset + 1 ] !== 0 ||
628+ bytes [ separatorOffset + 2 ] !== 0 || bytes [ separatorOffset + 3 ] !== 0
629+ ) {
630+ return null ;
631+ }
632+
633+ const payload = bytes . subarray ( 0 , separatorOffset ) ;
634+ return crc32 ( payload ) === readUint32BE ( bytes , bytes . length - 4 ) ? payload : null ;
635+ } ;
636+
637+ const readLz4Length = ( bytes , state , initialLength ) => {
638+ let length = initialLength ;
639+ if ( length !== 15 ) return length ;
640+
641+ let next ;
642+ do {
643+ if ( state . offset >= bytes . length ) throw new Error ( 'Invalid LZ4 length' ) ;
644+ next = bytes [ state . offset ++ ] ;
645+ length += next ;
646+ } while ( next === 255 ) ;
647+ return length ;
648+ } ;
649+
650+ const decompressLz4BlockWithLength = ( bytes ) => {
651+ if ( bytes . length < 4 ) return null ;
652+
653+ const outputLength = readUint32BE ( bytes , 0 ) ;
654+ if ( outputLength === 0 || outputLength > 1024 * 1024 ) return null ;
655+
656+ const output = new Uint8Array ( outputLength ) ;
657+ const state = { offset : 4 } ;
658+ let outputOffset = 0 ;
659+
660+ try {
661+ while ( state . offset < bytes . length ) {
662+ const token = bytes [ state . offset ++ ] ;
663+ const literalLength = readLz4Length ( bytes , state , token >> 4 ) ;
664+ if ( state . offset + literalLength > bytes . length || outputOffset + literalLength > outputLength ) return null ;
665+
666+ output . set ( bytes . subarray ( state . offset , state . offset + literalLength ) , outputOffset ) ;
667+ state . offset += literalLength ;
668+ outputOffset += literalLength ;
669+
670+ if ( state . offset >= bytes . length ) break ;
671+ if ( state . offset + 2 > bytes . length ) return null ;
672+
673+ const matchOffset = bytes [ state . offset ] | ( bytes [ state . offset + 1 ] << 8 ) ;
674+ state . offset += 2 ;
675+ if ( matchOffset === 0 || matchOffset > outputOffset ) return null ;
676+
677+ const matchLength = readLz4Length ( bytes , state , token & 0x0f ) + 4 ;
678+ if ( outputOffset + matchLength > outputLength ) return null ;
679+
680+ for ( let i = 0 ; i < matchLength ; i ++ ) {
681+ output [ outputOffset + i ] = output [ outputOffset - matchOffset + i ] ;
682+ }
683+ outputOffset += matchLength ;
684+ }
685+ } catch ( e ) {
686+ return null ;
687+ }
688+
689+ return outputOffset === outputLength ? output : null ;
690+ } ;
691+
692+ const decodeElasticWrappedJWT = ( encoded ) => {
693+ const bytes = tryBase64DecodeBytes ( encoded ) ;
694+ if ( ! bytes ) return null ;
695+
696+ const checksumPayload = unwrapElasticChecksum ( bytes ) ;
697+ if ( ! checksumPayload ) return null ;
698+
699+ const decompressed = decompressLz4BlockWithLength ( checksumPayload ) ;
700+ const decodedPayload = decompressed ? bytesToUtf8 ( decompressed ) : bytesToUtf8 ( checksumPayload ) ;
701+ if ( ! decodedPayload ) return null ;
702+
703+ const jwt = findJWTInString ( decodedPayload ) ;
704+ if ( ! jwt ) return null ;
705+
706+ const transforms = [ 'Base64-decoded' , 'Verified Elastic checksum' ] ;
707+ if ( decompressed ) transforms . push ( 'LZ4-decompressed' ) ;
708+ transforms . push ( 'Extracted JWT from Elastic token' ) ;
709+ return { jwt, transforms } ;
710+ } ;
711+
582712 // Unwraps Bearer prefixes, opaque session tokens, and base64-wrapped envelopes
583713 // around a real JWT, so users can paste whatever their HTTP tooling shows.
584714 const extractJWT = ( input ) => {
585715 input = input . trim ( ) ;
586716 if ( ! input ) return null ;
587717
718+ const bearerMatch = input . match ( / ^ B e a r e r \s + ( .+ ) $ / i) ;
719+ if ( bearerMatch ) {
720+ const result = extractJWT ( bearerMatch [ 1 ] ) ;
721+ if ( result ) return { jwt : result . jwt , transforms : [ 'Stripped <code>Bearer</code> prefix' , ...result . transforms ] } ;
722+ }
723+
588724 if ( isValidJWT ( input ) ) return { jwt : input , transforms : [ ] } ;
589725
590726 const segments = input . split ( '_' ) ;
591727 for ( let i = 1 ; i < segments . length && i <= 5 ; i ++ ) {
592728 const remainder = segments . slice ( i ) . join ( '_' ) ;
593729 const prefix = segments . slice ( 0 , i ) . join ( '_' ) + '_' ;
730+ const prefixLabel = `<code>${ escapeHtml ( prefix ) } </code>` ;
594731
595732 if ( isValidJWT ( remainder ) ) {
596- return { jwt : remainder , transforms : [ `Stripped prefix <code>${ prefix } </code>` ] } ;
733+ return { jwt : remainder , transforms : [ `Stripped prefix ${ prefixLabel } ` ] } ;
734+ }
735+
736+ const elasticWrapped = decodeElasticWrappedJWT ( remainder ) ;
737+ if ( elasticWrapped ) {
738+ return { jwt : elasticWrapped . jwt , transforms : [ `Stripped prefix ${ prefixLabel } ` , ...elasticWrapped . transforms ] } ;
597739 }
598740
599741 const decoded = tryBase64Decode ( remainder ) ;
600742 if ( decoded ) {
601743 const jwt = findJWTInString ( decoded ) ;
602744 if ( jwt ) {
603- return { jwt, transforms : [ `Stripped prefix <code> ${ prefix } </code> ` , 'Base64-decoded' , 'Extracted JWT from decoded payload' ] } ;
745+ return { jwt, transforms : [ `Stripped prefix ${ prefixLabel } ` , 'Base64-decoded' , 'Extracted JWT from decoded payload' ] } ;
604746 }
605747 }
606748 }
0 commit comments