Skip to content

Commit 325b3b4

Browse files
committed
Ensure all dynamic content rendered within HTML attributes is properly HTML escaped to prevent XSS vulnerabilities
1 parent 537dce4 commit 325b3b4

2 files changed

Lines changed: 41 additions & 23 deletions

File tree

packages/ui/src/components/editor/panel/PromptField/hooks/use-handlers.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ const reconstruct_raw_value_from_node = (node: Node): string => {
2020

2121
switch (el.dataset.type) {
2222
case 'file-keyword': {
23-
const path = el.getAttribute('title')
23+
const path = el.dataset.path
2424
return path ? `\`${path}\`` : ''
2525
}
2626
case 'changes-keyword': {

packages/ui/src/components/editor/panel/PromptField/utils/get-highlighted-text.tsx

Lines changed: 40 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,16 @@
11
import cn from 'classnames'
22
import styles from '../PromptField.module.scss'
33

4+
const escape_html = (str: string): string => {
5+
if (!str) return ''
6+
return str
7+
.replace(/&/g, '&')
8+
.replace(/</g, '&lt;')
9+
.replace(/>/g, '&gt;')
10+
.replace(/"/g, '&quot;')
11+
.replace(/'/g, '&#039;')
12+
}
13+
414
const process_text_part_for_files = (
515
text: string,
616
context_file_paths: string[]
@@ -18,17 +28,19 @@ const process_text_part_for_files = (
1828
return `<span class="${cn(
1929
styles['keyword'],
2030
styles['keyword--file']
21-
)}" contentEditable="false" data-type="file-keyword" title="${file_path}"><span class="${
31+
)}" contentEditable="false" data-type="file-keyword" data-path="${escape_html(
32+
file_path
33+
)}" title="${escape_html(file_path)}"><span class="${
2234
styles['keyword__icon']
23-
}"></span><span class="${
24-
styles['keyword__text']
25-
}">${filename}</span></span>`
35+
}"></span><span class="${styles['keyword__text']}">${escape_html(
36+
filename
37+
)}</span></span>`
2638
}
2739
// If not a context file, return with backticks
28-
return `\`${file_path}\``
40+
return `\`${escape_html(file_path)}\``
2941
}
3042
// part at even index is regular text
31-
return part
43+
return escape_html(part)
3244
})
3345
.join('')
3446
}
@@ -55,11 +67,11 @@ export const get_highlighted_text = (params: {
5567
return `<span class="${cn(
5668
styles['keyword'],
5769
styles['keyword--saved-context']
58-
)}" contentEditable="false" data-type="saved-context-keyword" data-context-type="${context_type}" data-context-name="${context_name}"><span class="${
59-
styles['keyword__icon']
60-
}"></span><span class="${
70+
)}" contentEditable="false" data-type="saved-context-keyword" data-context-type="${context_type}" data-context-name="${escape_html(
71+
context_name
72+
)}"><span class="${styles['keyword__icon']}"></span><span class="${
6173
styles['keyword__text']
62-
}">Context "${context_name}"</span></span>`
74+
}">Context "${escape_html(context_name)}"</span></span>`
6375
}
6476
return process_text_part_for_files(part, params.context_file_paths)
6577
})
@@ -89,11 +101,11 @@ export const get_highlighted_text = (params: {
89101
return `<span class="${cn(
90102
styles['keyword'],
91103
styles['keyword--changes']
92-
)}" contentEditable="false" data-type="changes-keyword" data-branch-name="${branch_name}"><span class="${
93-
styles['keyword__icon']
94-
}"></span><span class="${
104+
)}" contentEditable="false" data-type="changes-keyword" data-branch-name="${escape_html(
105+
branch_name
106+
)}"><span class="${styles['keyword__icon']}"></span><span class="${
95107
styles['keyword__text']
96-
}">Diff with ${branch_name}</span></span>`
108+
}">Diff with ${escape_html(branch_name)}</span></span>`
97109
}
98110
const saved_context_match = part.match(
99111
/^#SavedContext:(WorkspaceState|JSON)\s+"([^"]+)"$/
@@ -104,11 +116,11 @@ export const get_highlighted_text = (params: {
104116
return `<span class="${cn(
105117
styles['keyword'],
106118
styles['keyword--saved-context']
107-
)}" contentEditable="false" data-type="saved-context-keyword" data-context-type="${context_type}" data-context-name="${context_name}"><span class="${
108-
styles['keyword__icon']
109-
}"></span><span class="${
119+
)}" contentEditable="false" data-type="saved-context-keyword" data-context-type="${context_type}" data-context-name="${escape_html(
120+
context_name
121+
)}"><span class="${styles['keyword__icon']}"></span><span class="${
110122
styles['keyword__text']
111-
}">Context "${context_name}"</span></span>`
123+
}">Context "${escape_html(context_name)}"</span></span>`
112124
}
113125
const commit_match = part.match(/^#Commit:([^:]+):([^\s"]+)\s+"([^"]*)"$/)
114126
if (part && commit_match) {
@@ -119,11 +131,17 @@ export const get_highlighted_text = (params: {
119131
return `<span class="${cn(
120132
styles['keyword'],
121133
styles['keyword--commit']
122-
)}" contentEditable="false" data-type="commit-keyword" data-repo-name="${repo_name}" data-commit-hash="${commit_hash}" data-commit-message="${commit_message}" title="${commit_message}"><span class="${
134+
)}" contentEditable="false" data-type="commit-keyword" data-repo-name="${escape_html(
135+
repo_name
136+
)}" data-commit-hash="${escape_html(
137+
commit_hash
138+
)}" data-commit-message="${escape_html(
139+
commit_message
140+
)}" title="${escape_html(commit_message)}"><span class="${
123141
styles['keyword__icon']
124-
}"></span><span class="${
125-
styles['keyword__text']
126-
}">${short_hash}</span></span>`
142+
}"></span><span class="${styles['keyword__text']}">${escape_html(
143+
short_hash
144+
)}</span></span>`
127145
}
128146

129147
return process_text_part_for_files(part, params.context_file_paths)

0 commit comments

Comments
 (0)