diff --git a/packages/api/src/files/documents/html.spec.ts b/packages/api/src/files/documents/html.spec.ts
index ea9a81be57..5989952f5f 100644
--- a/packages/api/src/files/documents/html.spec.ts
+++ b/packages/api/src/files/documents/html.spec.ts
@@ -6,6 +6,7 @@ import {
csvToHtml,
excelSheetToHtml,
pptxToSlideListHtml,
+ sanitizeOfficeHtml,
wordDocToHtml,
} from './html';
@@ -217,4 +218,70 @@ describe('Office HTML producers', () => {
expect(html!).toContain('lc-docx');
});
});
+
+ describe('sanitizeOfficeHtml security', () => {
+ test('strips after
');
+ expect(out).not.toMatch(/">click',
+ );
+ expect(out).not.toContain('data:');
+ expect(out).not.toContain('script');
+ expect(out).toContain('click');
+ });
+
+ test('preserves data: URLs in
(mammoth inlines DOCX images as base64)', async () => {
+ const tinyPng =
+ 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=';
+ const out = await sanitizeOfficeHtml(`
`);
+ expect(out).toContain(tinyPng);
+ expect(out).toContain('alt="dot"');
+ });
+
+ test('preserves http(s) and mailto links on anchors', async () => {
+ const out = await sanitizeOfficeHtml(
+ 'site mail',
+ );
+ expect(out).toContain('https://example.com');
+ expect(out).toContain('mailto:a@b.test');
+ });
+
+ test('forces target=_blank rel=noopener on surviving anchors', async () => {
+ const out = await sanitizeOfficeHtml('link');
+ expect(out).toContain('target="_blank"');
+ expect(out).toContain('rel="noopener noreferrer"');
+ });
+
+ test('strips