From c939dcaf3c3987ca3b4e73c124b84e60e1e41975 Mon Sep 17 00:00:00 2001 From: Mike Jensen Date: Wed, 16 Jul 2025 15:04:04 -0600 Subject: [PATCH 1/3] fix: prevent ReDoS vulnerability in HTML payload unwrapping regex Fixed a polynomial Regular Expression Denial of Service (ReDoS) vulnerability in the HTML payload unwrapping function. The base64 capture group in the regex pattern was changed from greedy (`+`) to non-greedy (`+?`) to prevent exponential backtracking when processing malicious input. **Security Impact:** - Prevents potential DoS attacks through crafted HTML input that could cause excessive CPU usage - Maintains the same functional behavior for legitimate base64 payload extraction **Technical Details:** - Modified regex pattern in `lib/tdf3/src/utils/unwrap.ts` line 6 - Changed `([a-zA-Z0-9+/=]+)` to `([a-zA-Z0-9+/=]+?)` to use non-greedy matching - This eliminates catastrophic backtracking scenarios while preserving correct base64 extraction --- lib/tdf3/src/utils/unwrap.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/tdf3/src/utils/unwrap.ts b/lib/tdf3/src/utils/unwrap.ts index 36be954f2..4c4e0d735 100644 --- a/lib/tdf3/src/utils/unwrap.ts +++ b/lib/tdf3/src/utils/unwrap.ts @@ -3,7 +3,7 @@ import { InvalidFileError } from '../../../src/errors.js'; export function unwrapHtml(htmlPayload: Uint8Array): Uint8Array { const html = new TextDecoder().decode(htmlPayload); - const payloadRe = /]*?value=['"]?([a-zA-Z0-9+/=]+)['"]?/; + const payloadRe = /]*?value=['"]?([a-zA-Z0-9+/=]+?)['"]?/; const reResult = payloadRe.exec(html); if (!reResult) { throw new InvalidFileError('Payload is missing'); From 6f28cbd370089d972fd614328d6da756bb68d112 Mon Sep 17 00:00:00 2001 From: Mike Jensen Date: Fri, 18 Jul 2025 09:18:49 -0600 Subject: [PATCH 2/3] Update lib/tdf3/src/utils/unwrap.ts Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- lib/tdf3/src/utils/unwrap.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/tdf3/src/utils/unwrap.ts b/lib/tdf3/src/utils/unwrap.ts index 4c4e0d735..e9e6286a9 100644 --- a/lib/tdf3/src/utils/unwrap.ts +++ b/lib/tdf3/src/utils/unwrap.ts @@ -3,7 +3,7 @@ import { InvalidFileError } from '../../../src/errors.js'; export function unwrapHtml(htmlPayload: Uint8Array): Uint8Array { const html = new TextDecoder().decode(htmlPayload); - const payloadRe = /]*?value=['"]?([a-zA-Z0-9+/=]+?)['"]?/; + const payloadRe = /]*?value=['"]?([a-zA-Z0-9+/=\-_]+?)['"]?/; const reResult = payloadRe.exec(html); if (!reResult) { throw new InvalidFileError('Payload is missing'); From c1f7f08ea101f08363dbc9153864bb4e72802a1f Mon Sep 17 00:00:00 2001 From: Mike Jensen Date: Fri, 18 Jul 2025 09:59:53 -0600 Subject: [PATCH 3/3] regex DoS improvements and testing --- lib/tdf3/src/utils/unwrap.ts | 3 ++- lib/tests/mocha/unit/unwrap.spec.ts | 42 +++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/lib/tdf3/src/utils/unwrap.ts b/lib/tdf3/src/utils/unwrap.ts index e9e6286a9..a6e442549 100644 --- a/lib/tdf3/src/utils/unwrap.ts +++ b/lib/tdf3/src/utils/unwrap.ts @@ -3,7 +3,8 @@ import { InvalidFileError } from '../../../src/errors.js'; export function unwrapHtml(htmlPayload: Uint8Array): Uint8Array { const html = new TextDecoder().decode(htmlPayload); - const payloadRe = /]*?value=['"]?([a-zA-Z0-9+/=\-_]+?)['"]?/; + const payloadRe = + /]*id=(?:['"]?)data-input(?:['"]?)[^>]*value=(?:['"]?)([a-zA-Z0-9+/=\-_]+)(?:['"]?)/; const reResult = payloadRe.exec(html); if (!reResult) { throw new InvalidFileError('Payload is missing'); diff --git a/lib/tests/mocha/unit/unwrap.spec.ts b/lib/tests/mocha/unit/unwrap.spec.ts index ce49eb65e..5c1d84ebb 100644 --- a/lib/tests/mocha/unit/unwrap.spec.ts +++ b/lib/tests/mocha/unit/unwrap.spec.ts @@ -25,4 +25,46 @@ describe('unwrapHtml', () => { 'There was a problem extracting the TDF3 payload' ); }); + + describe('regex pattern variations', () => { + it('should handle double quotes', () => { + const htmlPayload = new TextEncoder().encode( + '' + ); + const result = unwrapHtml(htmlPayload); + expect(new TextDecoder().decode(result)).to.equal('Hello World'); + }); + + it('should handle single quotes', () => { + const htmlPayload = new TextEncoder().encode( + "" + ); + const result = unwrapHtml(htmlPayload); + expect(new TextDecoder().decode(result)).to.equal('Hello World'); + }); + + it('should handle no quotes', () => { + const htmlPayload = new TextEncoder().encode( + '' + ); + const result = unwrapHtml(htmlPayload); + expect(new TextDecoder().decode(result)).to.equal('Hello World'); + }); + + it('should handle URL-safe base64 characters', () => { + const htmlPayload = new TextEncoder().encode( + '' + ); + const result = unwrapHtml(htmlPayload); + expect(new TextDecoder().decode(result)).to.equal('Hello-World?'); + }); + + it('should handle additional attributes', () => { + const htmlPayload = new TextEncoder().encode( + '' + ); + const result = unwrapHtml(htmlPayload); + expect(new TextDecoder().decode(result)).to.equal('Hello World'); + }); + }); });