Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion src/core/file/truncateBase64.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Constants for base64 detection and truncation
const MIN_BASE64_LENGTH_DATA_URI = 40;
const MIN_BASE64_LENGTH_STANDALONE = 60;
const MIN_BASE64_LENGTH_STANDALONE = 256;
const TRUNCATION_LENGTH = 32;
const MIN_CHAR_DIVERSITY = 10;
const MIN_CHAR_TYPE_COUNT = 3;
Expand Down Expand Up @@ -69,6 +69,11 @@ function isLikelyBase64(str: string): boolean {
const hasLowerCase = /[a-z]/.test(str);
const hasSpecialChars = /[+/]/.test(str);

// Real base64 encoded binary data virtually always contains digits
if (!hasNumbers) {
return false;
}

const charTypeCount = [hasNumbers, hasUpperCase, hasLowerCase, hasSpecialChars].filter(Boolean).length;

return charTypeCount >= MIN_CHAR_TYPE_COUNT;
Expand Down
61 changes: 47 additions & 14 deletions tests/core/file/truncateBase64.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { describe, expect, it } from 'vitest';
import { truncateBase64Content } from '../../../src/core/file/truncateBase64.js';

// A realistic long base64 string (344 chars) with digits, upper, lower, and special chars
const longBase64 =
'DTJXfKHG6xA1Wn+kye4TOF2Cp8zxFjtgharP9Bk+Y4it0vccQWaLsNX6H0RpjrPY/SJHbJG22wAlSm+Uud4DKE1yl7zhBitQdZq/5AkuU3idwucMMVZ7oMXqDzRZfqPI7RI3XIGmy/AVOl+Eqc7zGD1ih6zR9htAZYqv1PkeQ2iNstf8IUZrkLXa/yRJbpO43QInTHGWu+AFKk90mb7jCC1Sd5zB5gswVXqfxOkOM1h9osfsETZbgKXK7xQ5XoOozfIXPGGGq9D1Gj9kia7T+B1CZ4yx1vsgRWqPtNn+I0htkrfcASZLcJW63wQpTnOYveIHLFF2m8DlCi9UeZ7D6A==';

describe('truncateBase64Content', () => {
it('should truncate data URI base64 strings', () => {
const input =
Expand All @@ -16,12 +20,19 @@ describe('truncateBase64Content', () => {
expect(result).toBe('src="data:image/svg+xml;charset=utf-8;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53..."');
});

it('should truncate standalone base64 strings', () => {
const base64String =
'VGhlIHF1aWNrIGJyb3duIGZveCBqdW1wcyBvdmVyIHRoZSBsYXp5IGRvZy4gVGhlIHF1aWNrIGJyb3duIGZveCBqdW1wcyBvdmVyIHRoZSBsYXp5IGRvZy4gVGhlIHF1aWNrIGJyb3duIGZveCBqdW1wcyBvdmVyIHRoZSBsYXp5IGRvZy4=';
const input = `const data = "${base64String}";`;
it('should truncate standalone base64 strings longer than 256 chars', () => {
const input = `const data = "${longBase64}";`;
const result = truncateBase64Content(input);
expect(result).toBe('const data = "VGhlIHF1aWNrIGJyb3duIGZveCBqdW1w...";');
expect(result).toBe('const data = "DTJXfKHG6xA1Wn+kye4TOF2Cp8zxFjtg...";');
});

it('should truncate standalone base64 strings at exactly 256 chars', () => {
// 192 bytes encodes to exactly 256 base64 chars with no padding
const exact256 =
'DTJXfKHG6xA1Wn+kye4TOF2Cp8zxFjtgharP9Bk+Y4it0vccQWaLsNX6H0RpjrPY/SJHbJG22wAlSm+Uud4DKE1yl7zhBitQdZq/5AkuU3idwucMMVZ7oMXqDzRZfqPI7RI3XIGmy/AVOl+Eqc7zGD1ih6zR9htAZYqv1PkeQ2iNstf8IUZrkLXa/yRJbpO43QInTHGWu+AFKk90mb7jCC1Sd5zB5gswVXqfxOkOM1h9osfsETZbgKXK7xQ5XoOo';
const input = `const data = "${exact256}";`;
const result = truncateBase64Content(input);
expect(result).toBe('const data = "DTJXfKHG6xA1Wn+kye4TOF2Cp8zxFjtg...";');
});

it('should preserve short base64 strings', () => {
Expand All @@ -37,30 +48,52 @@ describe('truncateBase64Content', () => {
expect(result).toBe(input);
});

it('should not truncate path-like or XPath strings (no digits)', () => {
// This was the false positive reported in #1298
const xpath = 'postTransactionAmounts/sharesOwnedFollowingTransaction/value';
const input = `const path = ".///${xpath}";`;
const result = truncateBase64Content(input);
expect(result).toBe(input);
});

it('should not truncate long path-like strings without digits', () => {
// Even if somehow longer than 256 chars, path-like strings without digits should be preserved
const longPath = 'abcdefghijklmnopqrstuvwxyz/ABCDEFGHIJKLMNOPQRSTUVWXYZ/'.repeat(6);
const input = `const path = "${longPath}";`;
const result = truncateBase64Content(input);
expect(result).toBe(input);
});

it('should handle multiple base64 occurrences in same content', () => {
const input = `
const img1 = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==";
const img2 = "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAYABgAAD/2wBDAAIBAQIBAQICAgICAgICAwUDAwMDAwYEBAMFBwYHBwcGBwcICQsJCAgKCAcHCg0KCgsMDAwMBwkODw0MDgsMDAz/wAALCAABAAEBAREA/8QAFAABAAAAAAAAAAAAAAAAAAAACf/EABQQAQAAAAAAAAAAAAAAAAAAAAD/2gAIAQEAAD8AKp//2Q==";
const data = "VGhlIHF1aWNrIGJyb3duIGZveCBqdW1wcyBvdmVyIHRoZSBsYXp5IGRvZy4gVGhlIHF1aWNrIGJyb3duIGZveCBqdW1wcyBvdmVyIHRoZSBsYXp5IGRvZy4=";
const data = "${longBase64}";
`;
const result = truncateBase64Content(input);
expect(result).toContain('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAAB...');
expect(result).toContain('data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAYABgAAD/2wBD...');
expect(result).toContain('VGhlIHF1aWNrIGJyb3duIGZveCBqdW1w...');
expect(result).toContain('DTJXfKHG6xA1Wn+kye4TOF2Cp8zxFjtg...');
});

it('should handle base64 with whitespace around it', () => {
const base64String =
'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==';
const input = `const data = \`\n ${base64String}\n\`;`;
const input = `const data = \`\n ${longBase64}\n\`;`;
const result = truncateBase64Content(input);
expect(result).toContain('iVBORw0KGgoAAAANSUhEUgAAAAEAAAAB...');
expect(result).toContain('DTJXfKHG6xA1Wn+kye4TOF2Cp8zxFjtg...');
});

it('should handle base64 strings with padding', () => {
const input =
'const paddedData = "VGhpcyBpcyBhIHRlc3Qgc3RyaW5nIHRoYXQgaXMgbG9uZyBlbm91Z2ggdG8gYmUgdHJ1bmNhdGVkIGJ5IHRoZSBmdW5jdGlvbi4gSXQgc2hvdWxkIGJlIHRydW5jYXRlZCBhZnRlciAzMiBjaGFyYWN0ZXJzLg==";';
// longBase64 already ends with '==' padding
const input = `const paddedData = "${longBase64}";`;
const result = truncateBase64Content(input);
expect(result).toBe('const paddedData = "VGhpcyBpcyBhIHRlc3Qgc3RyaW5nIHRo...";');
expect(result).toBe('const paddedData = "DTJXfKHG6xA1Wn+kye4TOF2Cp8zxFjtg...";');
});

it('should preserve medium-length base64-like strings under 256 chars', () => {
// 60-char string that previously would have been truncated
const mediumString = 'VGhlIHF1aWNrIGJyb3duIGZveCBqdW1wcyBvdmVyIHRoZSBsYXp5IGRvZy4=';
const input = `const data = "${mediumString}";`;
const result = truncateBase64Content(input);
expect(result).toBe(input);
});
});
Loading