Skip to content

Commit c7298d3

Browse files
author
Bob Strahan
committed
Security: Cross-Site Scripting (XSS) Vulnerability in FileViewer Component
1 parent 06e45d5 commit c7298d3

File tree

3 files changed

+84
-30
lines changed

3 files changed

+84
-30
lines changed

CHANGELOG.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,9 @@ SPDX-License-Identifier: MIT-0
4141
- **Action Required**: Review your current CloudFormation parameter settings before updating and update your configuration accordingly to preserve existing behavior
4242

4343
### Fixed
44-
**Fixed B615 "Unsafe Hugging Face Hub download without revision pinning" security finding in Pattern-3 fine-tuning module** - Added revision pinning with to prevent supply chain attacks and ensure reproducible deployments
45-
**Fixed CloudWatch Log Group Missing Retention regression**
44+
- **Fixed B615 "Unsafe Hugging Face Hub download without revision pinning" security finding in Pattern-3 fine-tuning module** - Added revision pinning with to prevent supply chain attacks and ensure reproducible deployments
45+
- **Fixed CloudWatch Log Group Missing Retention regression**
46+
- **Security: Cross-Site Scripting (XSS) Vulnerability in FileViewer Component** - Fixed high-risk XSS vulnerability in `src/ui/src/components/document-viewer/FileViewer.jsx` where `innerHTML` was used with user-controlled data
4647

4748
## [0.3.11]
4849

src/ui/.eslintrc.security.js

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// Security-focused ESLint configuration
2+
// Add this configuration to help detect potential XSS vulnerabilities
3+
4+
module.exports = {
5+
"extends": [
6+
"react-app",
7+
"react-app/jest"
8+
],
9+
"plugins": [
10+
"no-unsanitized"
11+
],
12+
"rules": {
13+
// Prevent innerHTML usage with dynamic content
14+
"no-unsanitized/property": "error",
15+
16+
// Prevent unsafe DOM methods
17+
"no-unsanitized/method": "error",
18+
19+
// React specific security rules
20+
"react/no-danger": "warn",
21+
"react/no-danger-with-children": "error",
22+
23+
// General security rules
24+
"no-eval": "error",
25+
"no-implied-eval": "error",
26+
"no-new-func": "error",
27+
"no-script-url": "error"
28+
}
29+
};

src/ui/src/components/document-viewer/FileViewer.jsx

Lines changed: 52 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,8 @@ const FileViewer = ({ objectKey }) => {
7070
const [isLoading, setIsLoading] = useState(false);
7171
const [error, setError] = useState(null);
7272
const [viewMethod, setViewMethod] = useState('presigned'); // 'presigned' or 'content'
73+
const [imageError, setImageError] = useState(false);
74+
const [imageErrorUrl, setImageErrorUrl] = useState('');
7375

7476
// Fetch file contents via GraphQL API and process special file types
7577
const fetchFileContents = async (s3Url) => {
@@ -169,6 +171,14 @@ const FileViewer = ({ objectKey }) => {
169171
}
170172
};
171173

174+
// Reset image error state when presigned URL changes
175+
React.useEffect(() => {
176+
if (presignedUrl) {
177+
setImageError(false);
178+
setImageErrorUrl('');
179+
}
180+
}, [presignedUrl]);
181+
172182
React.useEffect(() => {
173183
generateUrl();
174184
}, [objectKey]);
@@ -241,37 +251,51 @@ const FileViewer = ({ objectKey }) => {
241251
}
242252
// For image files, display them inline using img tag
243253
if (fileType === 'image') {
254+
const handleImageError = () => {
255+
setImageError(true);
256+
setImageErrorUrl(presignedUrl);
257+
};
258+
244259
return (
245260
<Box className="document-container" padding={{ top: 's' }}>
246261
<Box textAlign="center">
247-
<img
248-
src={presignedUrl}
249-
alt="Document preview"
250-
style={{
251-
maxWidth: '100%',
252-
maxHeight: '800px',
253-
height: 'auto',
254-
objectFit: 'contain',
255-
}}
256-
onError={(e) => {
257-
// On error, replace the image with a download link
258-
const container = e.target.parentElement;
259-
container.innerHTML = `
260-
<div style="padding: 48px;">
261-
<h3>🖼️ Image Document</h3>
262-
<p>Unable to display this image.</p>
263-
<p>
264-
<a href="${presignedUrl}" target="_blank" rel="noopener noreferrer"
265-
style="display: inline-block; padding: 12px 24px; background-color: #0073bb;
266-
color: white; text-decoration: none; border-radius: 4px;
267-
font-weight: bold; margin: 10px;">
268-
📥 Download Image
269-
</a>
270-
</p>
271-
</div>
272-
`;
273-
}}
274-
/>
262+
{!imageError ? (
263+
<img
264+
src={presignedUrl}
265+
alt="Document preview"
266+
style={{
267+
maxWidth: '100%',
268+
maxHeight: '800px',
269+
height: 'auto',
270+
objectFit: 'contain',
271+
}}
272+
onError={handleImageError}
273+
/>
274+
) : (
275+
<Box padding="xl">
276+
<h3>🖼️ Image Document</h3>
277+
<p>Unable to display this image.</p>
278+
<p>
279+
<a
280+
href={imageErrorUrl}
281+
target="_blank"
282+
rel="noopener noreferrer"
283+
style={{
284+
display: 'inline-block',
285+
padding: '12px 24px',
286+
backgroundColor: '#0073bb',
287+
color: 'white',
288+
textDecoration: 'none',
289+
borderRadius: '4px',
290+
fontWeight: 'bold',
291+
margin: '10px',
292+
}}
293+
>
294+
📥 Download Image
295+
</a>
296+
</p>
297+
</Box>
298+
)}
275299
</Box>
276300
</Box>
277301
);

0 commit comments

Comments
 (0)