mirror of
https://github.com/khoaliber/khoj.git
synced 2026-03-07 21:29:13 +00:00
Merge pull request from GHSA-h2q2-vch3-72qm
Add CSP and sanitize chat messages in Obsidian, Desktop, Web apps
This commit is contained in:
3
src/interface/desktop/assets/purify.min.js
vendored
Normal file
3
src/interface/desktop/assets/purify.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
@@ -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;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
const { app, BrowserWindow, ipcMain, Tray, Menu, nativeImage, shell } = require('electron');
|
const { app, BrowserWindow, ipcMain, Tray, Menu, nativeImage, shell, session } = require('electron');
|
||||||
const todesktop = require("@todesktop/runtime");
|
const todesktop = require("@todesktop/runtime");
|
||||||
const khojPackage = require('./package.json');
|
const khojPackage = require('./package.json');
|
||||||
|
|
||||||
@@ -401,6 +401,33 @@ async function getUserInfo() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function addCSPHeaderToSession () {
|
||||||
|
// Get hostURL from store or use default
|
||||||
|
const hostURL = store.get('hostURL') || KHOJ_URL;
|
||||||
|
|
||||||
|
// Construct Content Security Policy
|
||||||
|
const defaultDomains = `'self' ${hostURL} https://app.khoj.dev https://assets.khoj.dev`;
|
||||||
|
const default_src = `default-src ${defaultDomains};`;
|
||||||
|
const script_src = `script-src ${defaultDomains} 'unsafe-inline';`;
|
||||||
|
const connect_src = `connect-src ${hostURL} https://ipapi.co/json;`;
|
||||||
|
const style_src = `style-src ${defaultDomains} 'unsafe-inline' https://fonts.googleapis.com;`;
|
||||||
|
const img_src = `img-src ${defaultDomains} data: https://*.khoj.dev https://*.googleusercontent.com;`;
|
||||||
|
const font_src = `font-src https://fonts.gstatic.com;`;
|
||||||
|
const child_src = `child-src 'none';`;
|
||||||
|
const objectSrc = `object-src 'none';`;
|
||||||
|
const csp = `${default_src} ${script_src} ${connect_src} ${style_src} ${img_src} ${font_src} ${child_src} ${objectSrc}`;
|
||||||
|
|
||||||
|
// Add Content Security Policy to all web requests
|
||||||
|
session.defaultSession.webRequest.onHeadersReceived((details, callback) => {
|
||||||
|
callback({
|
||||||
|
responseHeaders: {
|
||||||
|
...details.responseHeaders,
|
||||||
|
'Content-Security-Policy': [csp]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
let firstRun = true;
|
let firstRun = true;
|
||||||
let win = null;
|
let win = null;
|
||||||
let titleBarStyle = process.platform === 'win32' ? 'default' : 'hidden';
|
let titleBarStyle = process.platform === 'win32' ? 'default' : 'hidden';
|
||||||
@@ -480,6 +507,8 @@ const createWindow = (tab = 'chat.html') => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
app.whenReady().then(() => {
|
app.whenReady().then(() => {
|
||||||
|
addCSPHeaderToSession();
|
||||||
|
|
||||||
ipcMain.on('set-title', handleSetTitle);
|
ipcMain.on('set-title', handleSetTitle);
|
||||||
|
|
||||||
ipcMain.handle('handleFileOpen', (event, type) => {
|
ipcMain.handle('handleFileOpen', (event, type) => {
|
||||||
|
|||||||
@@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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';
|
||||||
@@ -80,6 +81,20 @@ export class KhojChatView extends KhojPaneView {
|
|||||||
|
|
||||||
super.onOpen();
|
super.onOpen();
|
||||||
|
|
||||||
|
// Construct Content Security Policy
|
||||||
|
let defaultDomains = `'self' ${this.setting.khojUrl} https://app.khoj.dev https://assets.khoj.dev`;
|
||||||
|
const defaultSrc = `default-src ${defaultDomains};`;
|
||||||
|
const scriptSrc = `script-src ${defaultDomains} 'unsafe-inline';`;
|
||||||
|
const connectSrc = `connect-src ${this.setting.khojUrl} https://ipapi.co/json;`;
|
||||||
|
const styleSrc = `style-src ${defaultDomains} 'unsafe-inline';`;
|
||||||
|
const imgSrc = `img-src ${defaultDomains} data: https://*.khoj.dev https://*.googleusercontent.com;`;
|
||||||
|
const childSrc = `child-src 'none';`;
|
||||||
|
const objectSrc = `object-src 'none';`;
|
||||||
|
const csp = `${defaultSrc} ${scriptSrc} ${connectSrc} ${styleSrc} ${imgSrc} ${childSrc} ${objectSrc}`;
|
||||||
|
|
||||||
|
// Add CSP meta tag to the Khoj Chat modal
|
||||||
|
document.head.createEl("meta", { attr: { "http-equiv": "Content-Security-Policy", "content": `${csp}` } });
|
||||||
|
|
||||||
// Create area for chat logs
|
// Create area for chat logs
|
||||||
let chatBodyEl = contentEl.createDiv({ attr: { id: "khoj-chat-body", class: "khoj-chat-body" } });
|
let chatBodyEl = contentEl.createDiv({ attr: { id: "khoj-chat-body", class: "khoj-chat-body" } });
|
||||||
|
|
||||||
@@ -289,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";
|
||||||
@@ -375,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 {
|
||||||
@@ -422,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
3
src/khoj/interface/web/assets/purify.min.js
vendored
Normal file
3
src/khoj/interface/web/assets/purify.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
@@ -4,6 +4,15 @@
|
|||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0 maximum-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0 maximum-scale=1.0">
|
||||||
<meta property="og:image" content="https://assets.khoj.dev/khoj_hero.png">
|
<meta property="og:image" content="https://assets.khoj.dev/khoj_hero.png">
|
||||||
|
<meta http-equiv="Content-Security-Policy"
|
||||||
|
content="default-src 'self' https://assets.khoj.dev;
|
||||||
|
script-src 'self' https://assets.khoj.dev 'unsafe-inline';
|
||||||
|
connect-src 'self' https://ipapi.co/json;
|
||||||
|
style-src 'self' https://assets.khoj.dev 'unsafe-inline' https://fonts.googleapis.com;
|
||||||
|
img-src 'self' data: https://*.khoj.dev https://*.googleusercontent.com;
|
||||||
|
font-src https://assets.khoj.dev https://fonts.gstatic.com;
|
||||||
|
child-src 'none';
|
||||||
|
object-src 'none';">
|
||||||
<title>Khoj - Chat</title>
|
<title>Khoj - Chat</title>
|
||||||
|
|
||||||
<link rel="stylesheet" href="/static/assets/khoj.css?v={{ khoj_version }}">
|
<link rel="stylesheet" href="/static/assets/khoj.css?v={{ khoj_version }}">
|
||||||
@@ -18,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>
|
||||||
@@ -365,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;
|
||||||
|
|||||||
Reference in New Issue
Block a user