diff --git a/lib/confluence-client.js b/lib/confluence-client.js
index 84802bd..7d0d41c 100644
--- a/lib/confluence-client.js
+++ b/lib/confluence-client.js
@@ -1073,10 +1073,11 @@ class ConfluenceClient {
// so they appear as literal characters in the CDATA output
const decodedCode = code.replace(/\n$/, '')
.replace(/"/g, '"')
- .replace(/&/g, '&')
.replace(/</g, '<')
- .replace(/>/g, '>');
- return `${language}`;
+ .replace(/>/g, '>')
+ .replace(/&/g, '&'); // & last to avoid double-decoding
+ const safeCode = decodedCode.replace(/]]>/g, ']]]]>');
+ return `${language}`;
});
// Convert inline code
diff --git a/tests/confluence-client.test.js b/tests/confluence-client.test.js
index 5f40248..3ce55ec 100644
--- a/tests/confluence-client.test.js
+++ b/tests/confluence-client.test.js
@@ -327,6 +327,16 @@ describe('ConfluenceClient', () => {
expect(result).toContain('
Cell 1 | ');
});
+ test('should escape CDATA terminators in code blocks', () => {
+ const markdown = '```xml\n\n```';
+ const result = client.markdownToStorage(markdown);
+
+ expect(result).toContain('');
+ // The literal ]]> from user code should not appear unescaped
+ expect(result).not.toContain('some data]]>');
+ });
+
test('should convert links to smart link format on Cloud instances', () => {
const markdown = '[Example Link](https://example.com)';
const result = client.markdownToStorage(markdown);