Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion example/chat_demo/widget_demo.html
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,8 @@ <h3>🔧 Implementation</h3>
style="position:fixed;bottom:0;right:0;width:100px;height:100px;border:none;background:transparent;z-index:9999;opacity:0;transition:opacity 0.2s ease"
frameborder="0" allow="microphone;camera"></iframe>
<script>

const newTabLink = true;
window.addEventListener('message',e=>{
if(e.origin!=='http://localhost:9222')return;
if(e.data.type==='WIDGET_READY'){
Expand All @@ -147,7 +149,11 @@ <h3>🔧 Implementation</h3>
setTimeout(() => w.style.display='none', 200);
}
}
}else if(e.data.type==='SCROLL_PASSTHROUGH')window.scrollBy(0,e.data.deltaY);
}else if(e.data.type==='SCROLL_PASSTHROUGH')window.scrollBy(0,e.data.deltaY);
else if(e.data.type==='CHAT_LINK_CLICK'&&e.data.url){
if(newTabLink)window.open(e.data.url,'_blank');
else window.location.href=e.data.url;
}
});
</script>
</body>
Expand Down
36 changes: 32 additions & 4 deletions web/src/components/embed-dialog/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,7 @@ function EmbedDialog({
return src.toString();
}, [beta, from, token, values]);

const text = useMemo(() => {
const generateEmbedCode = useCallback(() => {
const iframeSrc = generateIframeSrc();
const { embedType } = values;

Expand All @@ -248,7 +248,9 @@ function EmbedDialog({
allow="microphone;camera"
></iframe>
<script>
const newTabLink = true;
window.addEventListener('message',e=>{
console.log(e)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Remove debug logging from the generated embed script.

Line 253 logs all message events and payloads in host pages; this can leak user/session data into browser logs and creates noise in production.

Suggested fix
-  console.log(e)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
console.log(e)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/src/components/embed-dialog/index.tsx` at line 253, Remove the debug
console logging inside the message event handler that currently calls
console.log(e); locate the window.addEventListener('message', ...) callback (or
the message event handler function) in the embed script and delete the
console.log(e) call, or replace it with a safe, environment-gated logger (e.g.,
only log in development via a debug flag or use a secure logger method) so
message payloads are not emitted to host page browser consoles in production.

if(e.origin!=='${location.origin.replace(/:\d+/, ':9222')}')return;
if(e.data.type==='CREATE_CHAT_WINDOW'){
if(document.getElementById('chat-win'))return;
Expand All @@ -261,6 +263,10 @@ window.addEventListener('message',e=>{
const w=document.getElementById('chat-win');
if(w)w.style.display=e.data.isOpen?'block':'none';
}else if(e.data.type==='SCROLL_PASSTHROUGH')window.scrollBy(0,e.data.deltaY);
else if(e.data.type==='CHAT_LINK_CLICK'&&e.data.url){
if(newTabLink)window.open(e.data.url,'_blank');
else window.location.href=e.data.url;
}
Comment on lines +266 to +269
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail
file="web/src/components/embed-dialog/index.tsx"
# show line numbers around 240-330
python3 - <<'PY'
import itertools,sys
path="web/src/components/embed-dialog/index.tsx"
start=220; end=330
with open(path,'r',encoding='utf-8') as f:
    for i,line in enumerate(f,1):
        if start<=i<=end:
            print(f"{i:4d}:{line.rstrip()}")
PY
# also search for CHAT_LINK_CLICK occurrences and any window.open with noopener usage
rg -n "CHAT_LINK_CLICK" web/src/components/embed-dialog/index.tsx web/src/components/embed-dialog -S || true
rg -n "window\.open\(|noopener|noreferrer" web/src/components/embed-dialog/index.tsx -S || true
# find where newTabLink is defined and whether e.data.url is derived/validated earlier in the postMessage pipeline
rg -n "newTabLink" web/src/components/embed-dialog/index.tsx -n -S || true
rg -n "postMessage|e\.data\.url|data\.url" web/src/components/embed-dialog/index.tsx -S || true

Repository: infiniflow/ragflow

Length of output: 4989


🏁 Script executed:

#!/bin/bash
set -euo pipefail
file="web/src/components/embed-dialog/index.tsx"
# show line numbers around 240-330
python3 - <<'PY'
import itertools,sys
path="web/src/components/embed-dialog/index.tsx"
start=220; end=330
with open(path,'r',encoding='utf-8') as f:
    for i,line in enumerate(f,1):
        if start<=i<=end:
            print(f"{i:4d}:{line.rstrip()}")
PY
# also search for CHAT_LINK_CLICK occurrences and any window.open with noopener usage
rg -n "CHAT_LINK_CLICK" web/src/components/embed-dialog/index.tsx web/src/components/embed-dialog -S || true
rg -n "window\.open\(|noopener|noreferrer" web/src/components/embed-dialog/index.tsx -S || true
# find where newTabLink is defined and whether e.data.url is derived/validated earlier in the postMessage pipeline
rg -n "newTabLink" web/src/components/embed-dialog/index.tsx -n -S || true
rg -n "postMessage|e\.data\.url|data\.url" web/src/components/embed-dialog/index.tsx -S || true

Repository: infiniflow/ragflow

Length of output: 4989


Sanitize e.data.url in generated CHAT_LINK_CLICK handler and add noopener,noreferrer

  • web/src/components/embed-dialog/index.tsx generates a widget postMessage handler where CHAT_LINK_CLICK directly uses e.data.url in window.open(e.data.url,'_blank') (with newTabLink = true) and does not validate the URL protocol/format; also missing noopener,noreferrer, enabling tabnabbing and potential javascript:/data: navigation injection.
  • console.log(e) in the message handler adds noisy debug output for embedded widgets.
Suggested fix
-  else if(e.data.type==='CHAT_LINK_CLICK'&&e.data.url){
-    if(newTabLink)window.open(e.data.url,'_blank');
-    else window.location.href=e.data.url;
-  }
+  else if(e.data.type==='CHAT_LINK_CLICK'&&e.data.url){
+    let parsed;
+    try { parsed = new URL(e.data.url, window.location.href); } catch { return; }
+    if (!['http:', 'https:', 'mailto:', 'tel:'].includes(parsed.protocol)) return;
+    const safeUrl = parsed.toString();
+    if (newTabLink) window.open(safeUrl, '_blank', 'noopener,noreferrer');
+    else window.location.assign(safeUrl);
+  }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
else if(e.data.type==='CHAT_LINK_CLICK'&&e.data.url){
if(newTabLink)window.open(e.data.url,'_blank');
else window.location.href=e.data.url;
}
else if(e.data.type==='CHAT_LINK_CLICK'&&e.data.url){
let parsed;
try { parsed = new URL(e.data.url, window.location.href); } catch { return; }
if (!['http:', 'https:', 'mailto:', 'tel:'].includes(parsed.protocol)) return;
const safeUrl = parsed.toString();
if (newTabLink) window.open(safeUrl, '_blank', 'noopener,noreferrer');
else window.location.assign(safeUrl);
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/src/components/embed-dialog/index.tsx` around lines 266 - 269, In the
postMessage handler's CHAT_LINK_CLICK branch (the code that checks
e.data.type==='CHAT_LINK_CLICK' and uses newTabLink), remove the noisy
console.log(e), validate and sanitize e.data.url by constructing a URL and
allowing only http: or https: protocols (reject javascript:, data:, about:
etc.), and then open it safely: if newTabLink call window.open(sanitizedUrl,
'_blank', 'noopener,noreferrer') and otherwise set window.location.href =
sanitizedUrl; if URL parsing/validation fails, do nothing or log a harmless
warning via process logger instead.

});
</script>
`;
Expand All @@ -274,10 +280,32 @@ window.addEventListener('message',e=>{
}
}, [generateIframeSrc, values]);

const text = useMemo(() => generateEmbedCode(), [generateEmbedCode]);

const handleOpenInNewTab = useCallback(() => {
const iframeSrc = generateIframeSrc();
window.open(iframeSrc, '_blank');
}, [generateIframeSrc]);
if (values.embedType === 'widget') {
const html = `<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>RAGFlow Chat Widget Preview</title>
</head>
<body style="margin:0;min-height:100vh;">
${generateEmbedCode()}
</body>
</html>`;

const blob = new Blob([html], { type: 'text/html' });
const previewUrl = URL.createObjectURL(blob);

window.open(previewUrl, '_blank', 'noopener,noreferrer');
window.setTimeout(() => URL.revokeObjectURL(previewUrl), 60_000);
return;
}

window.open(generateIframeSrc(), '_blank', 'noopener,noreferrer');
}, [generateEmbedCode, generateIframeSrc, values.embedType]);

const handleSaveWidgetSettings = useCallback(async () => {
if (!onSaveWidgetSettings) {
Expand Down
47 changes: 38 additions & 9 deletions web/src/components/floating-chat-widget-markdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import {
currentReg,
parseCitationIndex,
preprocessLaTeX,
replaceRetrievingToSection,
replaceTextByOldReg,
replaceThinkToSection,
showImage,
Expand All @@ -36,7 +35,8 @@ import {
} from 'react-syntax-highlighter/dist/esm/styles/prism';
import rehypeKatex from 'rehype-katex';
import rehypeRaw from 'rehype-raw';
import { MarkdownRemarkPlugins } from '@/constants/markdown-remark-plugins';
import remarkGfm from 'remark-gfm';
import remarkMath from 'remark-math';
import { visitParents } from 'unist-util-visit-parents';
import styles from './floating-chat-widget-markdown.module.less';
import { useIsDarkTheme } from './theme-provider';
Expand All @@ -47,6 +47,18 @@ import { Tooltip, TooltipContent, TooltipTrigger } from './ui/tooltip';
const getChunkIndex = (match: string) =>
parseCitationIndex(match.replace(/\[|\]/g, ''));

const emitChatLinkClick = (url: string) => {
const target = window.self !== window.top ? window.parent : window;

target.postMessage(
{
type: 'CHAT_LINK_CLICK',
url,
},
'*',
);
};

const FloatingChatWidgetMarkdown = ({
reference,
clickDocumentButton,
Expand All @@ -66,7 +78,7 @@ const FloatingChatWidgetMarkdown = ({
const contentWithCursor = useMemo(() => {
const text = content === '' ? t('chat.searching') : content;
const nextText = replaceTextByOldReg(text);
return pipe(replaceThinkToSection, replaceRetrievingToSection, preprocessLaTeX)(nextText);
return pipe(replaceThinkToSection, preprocessLaTeX)(nextText);
}, [content, t]);

useEffect(() => {
Expand Down Expand Up @@ -291,15 +303,32 @@ const FloatingChatWidgetMarkdown = ({
<div className="floating-chat-widget" dir={dir}>
<Markdown
rehypePlugins={[rehypeWrapReference, rehypeKatex, rehypeRaw]}
remarkPlugins={MarkdownRemarkPlugins}
remarkPlugins={[remarkGfm, remarkMath]}
className="text-sm leading-relaxed space-y-2 prose-sm max-w-full"
components={
{
p: (props: any) => {
const { children, node, ...rest } = props;
void node;
return <p {...rest}>{children}</p>;
},
p: ({ children, ...props }: any) => <p {...props}>{children}</p>,
a: ({ href, onClick, children, ...props }: any) => (
<a
{...props}
href={href}
onClick={(event) => {
onClick?.(event);

const clickedUrl =
event.currentTarget.href ||
href ||
event.currentTarget.getAttribute('href') ||
'';

if (clickedUrl) {
emitChatLinkClick(clickedUrl);
}
}}
Comment on lines +315 to +327
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="web/src/components/floating-chat-widget-markdown.tsx"

# Show the relevant section with line numbers
nl -ba "$FILE" | sed -n '280,360p'

# Find where emitChatLinkClick is defined/used
rg -n "emitChatLinkClick" "$FILE"

# Check for any existing navigation-prevention patterns in the file
rg -n "preventDefault|stopPropagation" "$FILE"

# Check how anchors are rendered/processed (look for custom link component / preprocessing)
rg -n "anchor|link|remark|rehype|remarkGfm|remarkMath|onClick" "$FILE"

Repository: infiniflow/ragflow

Length of output: 105


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="web/src/components/floating-chat-widget-markdown.tsx"

# Basic sanity checks
test -f "$FILE"
echo "== file =="
wc -l "$FILE"
echo

echo "== excerpt around 295-345 (best-effort) =="
cat -n "$FILE" | sed -n '295,345p'
echo

echo "== emitChatLinkClick occurrences =="
rg -n "emitChatLinkClick" "$FILE" || true
echo

echo "== preventDefault/stopPropagation occurrences in file =="
rg -n "preventDefault|stopPropagation" "$FILE" || true
echo

echo "== onClick handler locations =="
rg -n "onClick=\{\(" "$FILE" || true
rg -n "onClick=\{\s*\(" "$FILE" || true
echo

Repository: infiniflow/ragflow

Length of output: 2657


Prevent default anchor navigation when emitting CHAT_LINK_CLICK (web/src/components/floating-chat-widget-markdown.tsx:315-327)

The custom a renderer calls emitChatLinkClick(clickedUrl) but never cancels the anchor’s default behavior, so clicks will still follow the link in addition to emitting the event.

Suggested fix
 onClick={(event) => {
   onClick?.(event);
+  if (event.defaultPrevented) return;
+  event.preventDefault();

   const clickedUrl =
     event.currentTarget.href ||
     href ||
     event.currentTarget.getAttribute('href') ||
     '';

   if (clickedUrl) {
     emitChatLinkClick(clickedUrl);
   }
 }}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/src/components/floating-chat-widget-markdown.tsx` around lines 315 - 327,
The onClick handler for the custom anchor lets the browser navigate before/while
emitting CHAT_LINK_CLICK; update the handler in
floating-chat-widget-markdown.tsx (the onClick function that calls
onClick?.(event) and emitChatLinkClick(clickedUrl)) to call
event.preventDefault() when a clickedUrl is present (before emitChatLinkClick)
so the navigation is cancelled, then emit the event; keep calling the original
onClick?.(event) (either before or after preventDefault) to preserve existing
behavior.

>
{children}
</a>
),
'custom-typography': ({ children }: { children: string }) =>
renderReference(children),
code(props: any) {
Expand Down