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
12 changes: 12 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Force LF for shell scripts and binstubs (avoids CRLF issues on Windows)
*.sh text eol=lf
bin/* text eol=lf

# Common configs that must always be LF
Dockerfile text eol=lf
*.yml text eol=lf
*.yaml text eol=lf

# Allow Markdown and text files to use system defaults
*.md text
*.txt text
14 changes: 7 additions & 7 deletions client/app/utils/__tests__/index.spec.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -91,18 +91,18 @@ describe('Utils', () => {
it('should verify that for html strings without any issues, proper object representation is created without any value missing', () => {
const h1HeadingString = '<h1 id="main-heading">THIS IS A HEADING</h1>';
const h1Object = Utils.renderContent(h1HeadingString);
expect(h1Object.type).toEqual('h1');
expect(h1Object.props.id).toEqual('main-heading');

expect(h1Object.props.children).toEqual('THIS IS A HEADING');
expect(h1Object.type).toEqual('span');
expect(h1Object.props.dangerouslySetInnerHTML.__html).toContain('THIS IS A HEADING');
expect(h1Object.props.dangerouslySetInnerHTML.__html).toContain('id="main-heading"');
});

it('should verify that for html strings with vulnerable code, it gets sanitized in object representation', () => {
const imageHTMLString = '<img src=img.jpg onerror=alert(1)>';
const imageObject = Utils.renderContent(imageHTMLString);
expect(imageObject.type).toEqual('img');
expect(imageObject.props.src).toEqual('img.jpg');
expect(imageObject.props.onError).toBe(undefined);
expect(imageObject.type).toEqual('span');
expect(imageObject.props.dangerouslySetInnerHTML.__html).toContain('img.jpg');
expect(imageObject.props.dangerouslySetInnerHTML.__html).not.toContain('onerror');
expect(imageObject.props.dangerouslySetInnerHTML.__html).not.toContain('alert');
});

it('should apply attributes to React elements', () => {
Expand Down
17 changes: 15 additions & 2 deletions client/app/utils/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
import axios from 'axios';
import { sanitize } from 'dompurify';
import React from 'react';
import parse from 'html-react-parser';

const randomString = (): string => Math.random()
.toString(36)
Expand Down Expand Up @@ -34,9 +33,23 @@ const getPusher = (): Object | null => {
return null;
};

/**
* Lightweight HTML renderer without html-react-parser dependency.
* Saves ~200KB from the bundle.
*
* For HTML strings: Uses React's dangerouslySetInnerHTML with DOMPurify sanitization
* For React elements: Clones with additional attributes
* For other types: Returns as-is
*/
const renderContent = (content: string | any, attributes: Object = {}): any => {
if (typeof content === 'string') {
return parse(sanitize(content));
const sanitized = sanitize(content);
// Create a simple wrapper that renders sanitized HTML
// eslint-disable-next-line react/no-danger
return React.createElement('span', {
dangerouslySetInnerHTML: { __html: sanitized },
...attributes,
});
}
if (React.isValidElement(content)) {
return React.cloneElement(content, attributes);
Expand Down
3 changes: 0 additions & 3 deletions client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
"client/node_modules"
],
"dependencies": {
"@babel/polyfill": "^7.4.4",
"@babel/runtime": "^7.26.10",
"@fortawesome/fontawesome-svg-core": "^1.2.22",
"@fortawesome/free-solid-svg-icons": "^5.10.2",
Expand All @@ -37,10 +36,8 @@
"core-js": "3",
"dompurify": "^3.2.4",
"dot-prop": "^5.1.1",
"es5-shim": "^4.5.13",
"font-awesome": "^4.7.0",
"history": "^4.9.0",
"html-react-parser": "^5.1.18",
"js-cookie": "^2.2.1",
"jstimezonedetect": "^1.0.6",
"location-autocomplete": "^1.2.4",
Expand Down
9 changes: 3 additions & 6 deletions client/webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,12 +49,9 @@ const config = {
},

entry: {
// Shims should be singletons, and webpack bundle is always loaded
webpack_bundle: [
'es5-shim/es5-shim',
'es5-shim/es5-sham',
'@babel/polyfill',
].concat(glob.sync('./app/startup/*')),
// Modern browsers don't need ES5 shims - removing saves ~100KB
// Babel runtime handles necessary polyfills via @babel/preset-env + browserslist
webpack_bundle: glob.sync('./app/startup/*'),
},

output: {
Expand Down