From 0ee6dbb511aa926f6d490293282060abf3bab37f Mon Sep 17 00:00:00 2001 From: Tal Date: Thu, 23 Apr 2026 16:30:58 +0300 Subject: [PATCH] fix(filters): support Buffer input in base64_encode to prevent binary data corruption (#881) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: support Buffer input in base64_encode filter When binary data (e.g. images, PDFs) is passed through the template context as a Node.js Buffer, the base64_encode filter would call stringify() on it first, which internally does String(value). This triggers Buffer.toString() with the default 'utf-8' encoding, which is a lossy conversion for non-UTF-8 byte sequences — invalid bytes get replaced with U+FFFD, permanently destroying the original data. The fix checks for Buffer.isBuffer() before stringify, and calls buffer.toString('base64') directly, bypassing the lossy UTF-8 intermediate step. String inputs continue through the existing path unchanged. Made-with: Cursor * fix: handle Buffer in filter layer to fix browser build Move Buffer handling from base64-impl.ts (which gets swapped for the browser impl at build time) into base64.ts (the filter layer). This avoids a type error during the browser rollup build where the browser impl only accepts string. Also guard Buffer.isBuffer() with typeof Buffer !== 'undefined' for safety in browser environments. Made-with: Cursor --- src/filters/base64.ts | 6 ++++- test/integration/filters/base64.spec.ts | 29 ++++++++++++++++++++++++- 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/src/filters/base64.ts b/src/filters/base64.ts index c0e1a7993..29ef84e52 100644 --- a/src/filters/base64.ts +++ b/src/filters/base64.ts @@ -8,7 +8,11 @@ import { FilterImpl } from '../template' import { stringify } from '../util' import { base64Encode, base64Decode } from './base64-impl' -export function base64_encode (this: FilterImpl, value: string): string { +export function base64_encode (this: FilterImpl, value: string | Buffer): string { + if (typeof Buffer !== 'undefined' && Buffer.isBuffer(value)) { + this.context.memoryLimit.use(value.byteLength) + return value.toString('base64') + } const str = stringify(value) this.context.memoryLimit.use(str.length) return base64Encode(str) diff --git a/test/integration/filters/base64.spec.ts b/test/integration/filters/base64.spec.ts index cf54fc4f6..67eab977f 100644 --- a/test/integration/filters/base64.spec.ts +++ b/test/integration/filters/base64.spec.ts @@ -1,4 +1,4 @@ -import { test } from '../../stub/render' +import { test, liquid } from '../../stub/render' describe('filters/base64', function () { describe('base64_encode', function () { @@ -65,6 +65,33 @@ describe('filters/base64', function () { }) }) + describe('base64_encode with Buffer input', function () { + it('should encode a Buffer to base64 without data corruption', async () => { + const buf = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0xff, 0xfe]) + const result = await liquid.parseAndRender('{{ data | base64_encode }}', { data: buf }) + expect(result).toBe(buf.toString('base64')) + }) + + it('should preserve bytes that are invalid UTF-8', async () => { + const buf = Buffer.from([0x80, 0xff, 0xfe, 0x00, 0x01]) + const result = await liquid.parseAndRender('{{ data | base64_encode }}', { data: buf }) + const decoded = Buffer.from(result, 'base64') + expect(decoded).toEqual(buf) + }) + + it('should handle an empty Buffer', async () => { + const buf = Buffer.alloc(0) + const result = await liquid.parseAndRender('{{ data | base64_encode }}', { data: buf }) + expect(result).toBe('') + }) + + it('should handle a Buffer containing valid UTF-8 text', async () => { + const buf = Buffer.from('Hello World', 'utf8') + const result = await liquid.parseAndRender('{{ data | base64_encode }}', { data: buf }) + expect(result).toBe(Buffer.from('Hello World').toString('base64')) + }) + }) + describe('base64 round-trip', function () { it('should encode and decode back to original', () => { return test('{{ "Hello, World!" | base64_encode | base64_decode }}', 'Hello, World!')