From 977235043529cc3fbc6a47e6d5c3ae7e52ea41ab Mon Sep 17 00:00:00 2001 From: Ben Weiss Date: Mon, 21 Oct 2024 11:53:25 -0700 Subject: [PATCH 1/2] Security review and update for chrome extension Add security measures to the Chrome extension. * **content.js**: Add validation and sanitization for `chatbotUrl` before embedding it in an iframe. Check if the URL is valid, uses HTTPS, and does not contain malicious scripts. * **manifest.json**: Add a content security policy to enhance security. * **options.js**: Add validation for `chatbotUrl` input to prevent XSS attacks. Check if the URL is valid and uses HTTPS. * **options.html**: Add security headers and content security policies. * **README.md**: Add a section on security considerations and best practices for using the extension. * **README_CN.md**: Add a section on security considerations and best practices for using the extension. --- For more details, open the [Copilot Workspace session](https://copilot-workspace.githubnext.com/langgenius/chatbot-chrome-extension?shareId=XXXX-XXXX-XXXX-XXXX). --- README.md | 18 +++++++++++++++++- README_CN.md | 18 +++++++++++++++++- content.js | 29 +++++++++++++++++++++++++++-- manifest.json | 5 ++++- options.html | 6 +++++- options.js | 24 +++++++++++++++++++++--- 6 files changed, 91 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 91ce5c9..b0c70df 100644 --- a/README.md +++ b/README.md @@ -30,4 +30,20 @@ - Restart the browser to ensure that all pages are refreshed successfully - Dify chatbot floating bar can be loaded normally on any page in Chrome, if you need to change the chatbot, just change the ChatBot URL -![img-4.png](images/img-4.png) \ No newline at end of file +![img-4.png](images/img-4.png) + +### Security Considerations and Best Practices + +When using the Dify Chatbot Chrome extension, it is important to follow these security considerations and best practices: + +1. **Validate and Sanitize URLs**: Ensure that the ChatBot URL is validated and sanitized before embedding it in an iframe. This helps prevent potential security vulnerabilities such as XSS attacks. + +2. **Use HTTPS**: Always use HTTPS for the ChatBot URL to ensure secure communication between the extension and the chatbot server. + +3. **Content Security Policy (CSP)**: The extension includes a content security policy to enhance security. Make sure to review and update the CSP as needed to protect against potential threats. + +4. **Security Headers**: The extension includes security headers such as X-Content-Type-Options, X-Frame-Options, and X-XSS-Protection. These headers help protect against common security vulnerabilities. + +5. **Regular Updates**: Keep the extension and its dependencies up to date to ensure that you have the latest security patches and improvements. + +By following these security considerations and best practices, you can help ensure the safe and secure use of the Dify Chatbot Chrome extension. diff --git a/README_CN.md b/README_CN.md index 55586da..ac47bca 100644 --- a/README_CN.md +++ b/README_CN.md @@ -30,4 +30,20 @@ - 保险起见重启浏览器确保所有分页刷新成功 - Chrome打开任意页面均可正常加载DIfy机器人浮动栏,后续如需更换机器人只需要变更ChatBot Url即可 -![img-4.png](images/img-4.png) \ No newline at end of file +![img-4.png](images/img-4.png) + +### 安全注意事项和最佳实践 + +在使用Dify Chatbot Chrome扩展程序时,遵循以下安全注意事项和最佳实践非常重要: + +1. **验证和清理URL**:确保在将ChatBot URL嵌入iframe之前对其进行验证和清理。这有助于防止潜在的安全漏洞,例如XSS攻击。 + +2. **使用HTTPS**:始终使用HTTPS作为ChatBot URL,以确保扩展程序与聊天机器人服务器之间的安全通信。 + +3. **内容安全策略(CSP)**:扩展程序包括内容安全策略以增强安全性。请确保根据需要审查和更新CSP以防范潜在威胁。 + +4. **安全标头**:扩展程序包括X-Content-Type-Options、X-Frame-Options和X-XSS-Protection等安全标头。这些标头有助于防止常见的安全漏洞。 + +5. **定期更新**:保持扩展程序及其依赖项的最新状态,以确保您拥有最新的安全补丁和改进。 + +通过遵循这些安全注意事项和最佳实践,您可以帮助确保安全使用Dify Chatbot Chrome扩展程序。 diff --git a/content.js b/content.js index 3f64f3b..a7b9da7 100644 --- a/content.js +++ b/content.js @@ -1,12 +1,37 @@ const storage = chrome.storage.sync; chrome.storage.sync.get(['chatbotUrl'], function(result) { window.difyChatbotConfig = { - chatbotUrl: result.chatbotUrl, + chatbotUrl: sanitizeUrl(result.chatbotUrl), }; }); document.body.onload = embedChatbot; +function sanitizeUrl(url) { + const parser = document.createElement('a'); + parser.href = url; + + // Check if the URL is valid + if (!parser.protocol || !parser.host) { + console.warn('Invalid URL'); + return ''; + } + + // Check if the URL uses HTTPS + if (parser.protocol !== 'https:') { + console.warn('URL must use HTTPS'); + return ''; + } + + // Check for malicious scripts + if (url.includes('javascript:') || url.includes(' - \ No newline at end of file + diff --git a/options.js b/options.js index 7049c11..b931c8d 100644 --- a/options.js +++ b/options.js @@ -1,11 +1,14 @@ - document.getElementById('save-button').addEventListener('click', function (e) { e.preventDefault(); const chatbotUrl = document.getElementById('chatbot-url').value; const errorTip = document.getElementById('error-tip'); if (chatbotUrl.trim() === "") { - errorTip.textContent = "Dify ChatBot URL cannot be empty."; + errorTip.textContent = "Dify ChatBot URL cannot be empty."; + } else if (!isValidUrl(chatbotUrl)) { + errorTip.textContent = "Invalid URL format."; + } else if (!isHttpsUrl(chatbotUrl)) { + errorTip.textContent = "URL must use HTTPS."; } else { errorTip.textContent = ""; @@ -25,4 +28,19 @@ chrome.storage.sync.get(['chatbotUrl'], function (result) { chatbotUrlInput.value = result.chatbotUrl; } -}); \ No newline at end of file +}); + +function isValidUrl(url) { + try { + new URL(url); + return true; + } catch (e) { + return false; + } +} + +function isHttpsUrl(url) { + const parser = document.createElement('a'); + parser.href = url; + return parser.protocol === 'https:'; +} From 80f6f6369c1741059528f212701f67775dc9bc92 Mon Sep 17 00:00:00 2001 From: Ben Weiss Date: Mon, 21 Oct 2024 12:03:31 -0700 Subject: [PATCH 2/2] Add data extraction and messaging functionality * **content.js** - Add code to extract key data points (headings, paragraphs, links, images, metadata) from the webpage - Add code to send extracted data to `background.js` using `chrome.runtime.sendMessage` * **manifest.json** - Add `activeTab` and `scripting` to the `permissions` array - Add `background.js` to the `background` property * **options.js** - Add code to save data extraction settings to `chrome.storage` - Add code to load data extraction settings from `chrome.storage` * **options.html** - Add input fields for configuring data extraction settings * **options.css** - Add styles for the new input fields in `options.html` * **background.js** - Add code to receive messages from `content.js` using `chrome.runtime.onMessage` - Add code to format and sanitize the extracted data - Add code to send the formatted data to the LLM --- background.js | 54 +++++++++++++++++++++++++++++++++++++++++++++++++++ content.js | 21 ++++++++++++++++++++ manifest.json | 5 ++++- options.css | 29 ++++++++++++++++++++++++++- options.html | 24 +++++++++++++++++++++++ options.js | 19 +++++++++++++++++- 6 files changed, 149 insertions(+), 3 deletions(-) create mode 100644 background.js diff --git a/background.js b/background.js new file mode 100644 index 0000000..a0123cc --- /dev/null +++ b/background.js @@ -0,0 +1,54 @@ +chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { + if (message.action === 'sendData') { + const formattedData = formatData(message.data); + sendDataToLLM(formattedData); + } +}); + +function formatData(data) { + // Sanitize and format the extracted data + return { + headings: data.headings.map(sanitizeText), + paragraphs: data.paragraphs.map(sanitizeText), + links: data.links.map(sanitizeUrl), + images: data.images.map(sanitizeUrl), + metadata: { + title: sanitizeText(data.metadata.title), + description: sanitizeText(data.metadata.description), + keywords: sanitizeText(data.metadata.keywords) + } + }; +} + +function sanitizeText(text) { + // Implement text sanitization logic + return text.replace(//g, ">"); +} + +function sanitizeUrl(url) { + // Implement URL sanitization logic + const parser = document.createElement('a'); + parser.href = url; + if (parser.protocol !== 'https:') { + return ''; + } + return url; +} + +function sendDataToLLM(data) { + // Implement the logic to send data to the LLM + fetch('https://your-llm-endpoint.com/api', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(data) + }) + .then(response => response.json()) + .then(result => { + console.log('Data sent to LLM:', result); + }) + .catch(error => { + console.error('Error sending data to LLM:', error); + }); +} diff --git a/content.js b/content.js index a7b9da7..8cd35e7 100644 --- a/content.js +++ b/content.js @@ -191,3 +191,24 @@ async function embedChatbot() { handleElementDrag(targetButton); } } + +// Function to extract key data points from the webpage +function extractData() { + const data = { + headings: Array.from(document.querySelectorAll('h1, h2, h3')).map(h => h.innerText), + paragraphs: Array.from(document.querySelectorAll('p')).map(p => p.innerText), + links: Array.from(document.querySelectorAll('a')).map(a => a.href), + images: Array.from(document.querySelectorAll('img')).map(img => img.src), + metadata: { + title: document.title, + description: document.querySelector('meta[name="description"]')?.content || '', + keywords: document.querySelector('meta[name="keywords"]')?.content || '' + } + }; + + // Send the extracted data to the background script + chrome.runtime.sendMessage({ action: 'sendData', data: data }); +} + +// Call the extractData function when the content script is loaded +extractData(); diff --git a/manifest.json b/manifest.json index bf2e871..f739dc2 100644 --- a/manifest.json +++ b/manifest.json @@ -9,7 +9,10 @@ "js": ["content.js"] } ], - "permissions": ["webRequest", "storage"], + "permissions": ["webRequest", "storage", "activeTab", "scripting"], + "background": { + "service_worker": "background.js" + }, "action": { "default_popup": "options.html", "default_icon": { diff --git a/options.css b/options.css index faa7179..b0295a9 100644 --- a/options.css +++ b/options.css @@ -16,4 +16,31 @@ label { input[type="text"] { width: 280px; padding: 6px; -} \ No newline at end of file +} + +input[type="checkbox"] { + margin-right: 10px; +} + +select { + width: 280px; + padding: 6px; +} + +#error-tip { + color: red; + font-size: 14px; +} + +button { + background-color: #4CAF50; + color: white; + padding: 10px 20px; + border: none; + border-radius: 5px; + cursor: pointer; +} + +button:hover { + background-color: #45a049; +} diff --git a/options.html b/options.html index 77cf185..618e5ed 100644 --- a/options.html +++ b/options.html @@ -26,6 +26,30 @@

Dify Chatbot Extension

+
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+
diff --git a/options.js b/options.js index b931c8d..95c0659 100644 --- a/options.js +++ b/options.js @@ -2,6 +2,8 @@ document.getElementById('save-button').addEventListener('click', function (e) { e.preventDefault(); const chatbotUrl = document.getElementById('chatbot-url').value; const errorTip = document.getElementById('error-tip'); + const dataExtraction = document.getElementById('data-extraction').checked; + const dataTypes = Array.from(document.getElementById('data-types').selectedOptions).map(option => option.value); if (chatbotUrl.trim() === "") { errorTip.textContent = "Dify ChatBot URL cannot be empty."; @@ -14,6 +16,8 @@ document.getElementById('save-button').addEventListener('click', function (e) { chrome.storage.sync.set({ 'chatbotUrl': chatbotUrl, + 'dataExtraction': dataExtraction, + 'dataTypes': dataTypes }, function () { alert('Save Success!'); }); @@ -21,13 +25,26 @@ document.getElementById('save-button').addEventListener('click', function (e) { }); // Load parameters from chrome.storage when the page loads -chrome.storage.sync.get(['chatbotUrl'], function (result) { +chrome.storage.sync.get(['chatbotUrl', 'dataExtraction', 'dataTypes'], function (result) { const chatbotUrlInput = document.getElementById('chatbot-url'); + const dataExtractionInput = document.getElementById('data-extraction'); + const dataTypesInput = document.getElementById('data-types'); if (result.chatbotUrl) { chatbotUrlInput.value = result.chatbotUrl; } + if (result.dataExtraction !== undefined) { + dataExtractionInput.checked = result.dataExtraction; + } + + if (result.dataTypes) { + Array.from(dataTypesInput.options).forEach(option => { + if (result.dataTypes.includes(option.value)) { + option.selected = true; + } + }); + } }); function isValidUrl(url) {