Sanitize chat messages to render in Obsidian, Desktop, Web apps

Use DOMPurify to escape any unsafe HTML in chat message before adding
it to DOM via innerHTML updates to a HTML element
This commit is contained in:
Debanjum Singh Solanky
2024-05-29 15:39:49 +05:30
parent 9f80c2ab76
commit b757ba664f
7 changed files with 680 additions and 135 deletions

File diff suppressed because one or more lines are too long

View File

@@ -8,6 +8,7 @@
<link rel="icon" type="image/png" sizes="128x128" href="./assets/icons/favicon-128x128.png"> <link rel="icon" type="image/png" sizes="128x128" href="./assets/icons/favicon-128x128.png">
<link rel="manifest" href="/static/khoj.webmanifest"> <link rel="manifest" href="/static/khoj.webmanifest">
</head> </head>
<script type="text/javascript" src="./assets/purify.min.js?v={{ khoj_version }}"></script>
<script type="text/javascript" src="./assets/markdown-it.min.js"></script> <script type="text/javascript" src="./assets/markdown-it.min.js"></script>
<script src="./utils.js"></script> <script src="./utils.js"></script>
@@ -282,6 +283,8 @@
// Render markdown // Render markdown
newHTML = raw ? newHTML : md.render(newHTML); newHTML = raw ? newHTML : md.render(newHTML);
// Sanitize the rendered markdown
newHTML = DOMPurify.sanitize(newHTML);
// Set rendered markdown to HTML DOM element // Set rendered markdown to HTML DOM element
let element = document.createElement('div'); let element = document.createElement('div');
element.innerHTML = newHTML; element.innerHTML = newHTML;

View File

@@ -17,6 +17,7 @@
"assistant" "assistant"
], ],
"devDependencies": { "devDependencies": {
"@types/dompurify": "^3.0.5",
"@types/node": "^16.11.6", "@types/node": "^16.11.6",
"@typescript-eslint/eslint-plugin": "5.29.0", "@typescript-eslint/eslint-plugin": "5.29.0",
"@typescript-eslint/parser": "5.29.0", "@typescript-eslint/parser": "5.29.0",
@@ -25,5 +26,8 @@
"obsidian": "latest", "obsidian": "latest",
"tslib": "2.4.0", "tslib": "2.4.0",
"typescript": "4.7.4" "typescript": "4.7.4"
},
"dependencies": {
"dompurify": "^3.1.4"
} }
} }

View File

@@ -1,4 +1,5 @@
import { MarkdownRenderer, WorkspaceLeaf, request, requestUrl, setIcon } from 'obsidian'; import { MarkdownRenderer, WorkspaceLeaf, request, requestUrl, setIcon } from 'obsidian';
import * as DOMPurify from 'dompurify';
import { KhojSetting } from 'src/settings'; import { KhojSetting } from 'src/settings';
import { KhojPaneView } from 'src/pane_view'; import { KhojPaneView } from 'src/pane_view';
import { KhojView, createCopyParentText, getLinkToEntry, pasteTextAtCursor } from 'src/utils'; import { KhojView, createCopyParentText, getLinkToEntry, pasteTextAtCursor } from 'src/utils';
@@ -303,6 +304,9 @@ export class KhojChatView extends KhojPaneView {
// Remove any text between <s>[INST] and </s> tags. These are spurious instructions for the AI chat model. // Remove any text between <s>[INST] and </s> tags. These are spurious instructions for the AI chat model.
rendered_msg = rendered_msg.replace(/<s>\[INST\].+(<\/s>)?/g, ''); rendered_msg = rendered_msg.replace(/<s>\[INST\].+(<\/s>)?/g, '');
// Sanitize the markdown to render
message = DOMPurify.sanitize(message);
// Render markdow to HTML DOM element // Render markdow to HTML DOM element
let chat_message_body_text_el = this.contentEl.createDiv(); let chat_message_body_text_el = this.contentEl.createDiv();
chat_message_body_text_el.className = "chat-message-text-response"; chat_message_body_text_el.className = "chat-message-text-response";
@@ -389,6 +393,10 @@ export class KhojChatView extends KhojPaneView {
let chat_message_body_el = chatMessageEl.createDiv(); let chat_message_body_el = chatMessageEl.createDiv();
chat_message_body_el.addClasses(["khoj-chat-message-text", sender]); chat_message_body_el.addClasses(["khoj-chat-message-text", sender]);
let chat_message_body_text_el = chat_message_body_el.createDiv(); let chat_message_body_text_el = chat_message_body_el.createDiv();
// Sanitize the markdown to render
message = DOMPurify.sanitize(message);
if (raw) { if (raw) {
chat_message_body_text_el.innerHTML = message; chat_message_body_text_el.innerHTML = message;
} else { } else {
@@ -436,6 +444,8 @@ export class KhojChatView extends KhojPaneView {
async renderIncrementalMessage(htmlElement: HTMLDivElement, additionalMessage: string) { async renderIncrementalMessage(htmlElement: HTMLDivElement, additionalMessage: string) {
this.result += additionalMessage; this.result += additionalMessage;
htmlElement.innerHTML = ""; htmlElement.innerHTML = "";
// Sanitize the markdown to render
this.result = DOMPurify.sanitize(this.result);
// @ts-ignore // @ts-ignore
await MarkdownRenderer.renderMarkdown(this.result, htmlElement, '', null); await MarkdownRenderer.renderMarkdown(this.result, htmlElement, '', null);
// Render action buttons for the message // Render action buttons for the message

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -27,6 +27,7 @@
<!-- To automatically render math in text elements, include the auto-render extension: --> <!-- To automatically render math in text elements, include the auto-render extension: -->
<script defer src="https://assets.khoj.dev/katex/auto-render.min.js" onload="renderMathInElement(document.body);"></script> <script defer src="https://assets.khoj.dev/katex/auto-render.min.js" onload="renderMathInElement(document.body);"></script>
</head> </head>
<script type="text/javascript" src="/static/assets/purify.min.js?v={{ khoj_version }}"></script>
<script type="text/javascript" src="/static/assets/utils.js?v={{ khoj_version }}"></script> <script type="text/javascript" src="/static/assets/utils.js?v={{ khoj_version }}"></script>
<script type="text/javascript" src="/static/assets/markdown-it.min.js?v={{ khoj_version }}"></script> <script type="text/javascript" src="/static/assets/markdown-it.min.js?v={{ khoj_version }}"></script>
<script> <script>
@@ -374,6 +375,9 @@ To get started, just start typing below. You can also type / to see a list of co
newHTML = newHTML.replace(/LEFTPAREN/g, '\\(').replace(/RIGHTPAREN/g, '\\)') newHTML = newHTML.replace(/LEFTPAREN/g, '\\(').replace(/RIGHTPAREN/g, '\\)')
.replace(/LEFTBRACKET/g, '\\[').replace(/RIGHTBRACKET/g, '\\]'); .replace(/LEFTBRACKET/g, '\\[').replace(/RIGHTBRACKET/g, '\\]');
// Sanitize the rendered markdown
newHTML = DOMPurify.sanitize(newHTML);
// Set rendered markdown to HTML DOM element // Set rendered markdown to HTML DOM element
let element = document.createElement('div'); let element = document.createElement('div');
element.innerHTML = newHTML; element.innerHTML = newHTML;