Skip to content

Commit 9920073

Browse files
authored
Merge pull request #2732 from plotly/fix/xss-props
Sanitize html props with xss vulnerability.
2 parents 000ec18 + f1fc2e0 commit 9920073

File tree

16 files changed

+9448
-7913
lines changed

16 files changed

+9448
-7913
lines changed

Diff for: CHANGELOG.md

+6-1
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,13 @@ This project adheres to [Semantic Versioning](https://semver.org/).
44

55
## [UNRELEASED]
66

7-
### Added
7+
## Added
88
- [#2695](https://github.com/plotly/dash/pull/2695) Adds `triggered_id` to `dash_clientside.callback_context`. Fixes [#2692](https://github.com/plotly/dash/issues/2692)
9+
- [#2732](https://github.com/plotly/dash/pull/2732) Add special key `_dash_error` to `setProps`, allowing component developers to send error without throwing in render. Usage `props.setProps({_dash_error: new Error("custom error")})`
10+
11+
## Fixed
12+
13+
- [#2732](https://github.com/plotly/dash/pull/2732) Sanitize html props that are vulnerable to xss vulnerability if user data is inserted. Fix Validate url to prevent XSS attacks [#2729](https://github.com/plotly/dash/issues/2729)
914

1015
## Changed
1116
- [#2652](https://github.com/plotly/dash/pull/2652) dcc.Clipboard supports htm_content and triggers a copy to clipboard when n_clicks are changed

Diff for: components/dash-core-components/package-lock.json

+2,949-2,144
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: components/dash-core-components/package.json

+8-6
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
"maintainer": "Alex Johnson <[email protected]>",
3636
"license": "MIT",
3737
"dependencies": {
38+
"@braintree/sanitize-url": "^7.0.0",
3839
"@fortawesome/fontawesome-svg-core": "1.2.36",
3940
"@fortawesome/free-regular-svg-icons": "^5.15.4",
4041
"@fortawesome/free-solid-svg-icons": "^5.15.4",
@@ -49,7 +50,7 @@
4950
"moment": "^2.29.4",
5051
"node-polyfill-webpack-plugin": "^2.0.1",
5152
"prop-types": "^15.8.1",
52-
"ramda": "^0.29.0",
53+
"ramda": "^0.29.1",
5354
"rc-slider": "^9.7.5",
5455
"react-addons-shallow-compare": "^15.6.3",
5556
"react-dates": "^21.8.0",
@@ -64,11 +65,11 @@
6465
"uniqid": "^5.4.0"
6566
},
6667
"devDependencies": {
67-
"@babel/cli": "^7.23.0",
68-
"@babel/core": "^7.23.0",
68+
"@babel/cli": "^7.23.4",
69+
"@babel/core": "^7.23.7",
6970
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
70-
"@babel/preset-env": "^7.22.20",
71-
"@babel/preset-react": "^7.22.15",
71+
"@babel/preset-env": "^7.23.8",
72+
"@babel/preset-react": "^7.23.3",
7273
"@plotly/dash-component-plugins": "^1.2.3",
7374
"@plotly/webpack-dash-dynamic-import": "^1.3.0",
7475
"babel-loader": "^9.1.3",
@@ -88,9 +89,10 @@
8889
"react-jsx-parser": "1.21.0",
8990
"style-loader": "^3.3.3",
9091
"styled-jsx": "^3.4.4",
91-
"webpack": "^5.88.2",
92+
"webpack": "^5.90.0",
9293
"webpack-cli": "^5.1.4"
9394
},
95+
"optionalDependencies": { "fsevents": "*" },
9496
"files": [
9597
"/dash_core_components/*{.js,.map}",
9698
"/lib/"

Diff for: components/dash-core-components/src/components/Link.react.js

+47-46
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import PropTypes from 'prop-types';
22

3-
import React, {Component} from 'react';
4-
3+
import React, {useEffect, useMemo} from 'react';
4+
import {sanitizeUrl} from '@braintree/sanitize-url';
55
import {isNil} from 'ramda';
66

77
/*
@@ -33,15 +33,23 @@ CustomEvent.prototype = window.Event.prototype;
3333
* For links with destinations outside the current app, `html.A` is a better
3434
* component to use.
3535
*/
36-
export default class Link extends Component {
37-
constructor(props) {
38-
super(props);
39-
this.updateLocation = this.updateLocation.bind(this);
40-
}
36+
const Link = props => {
37+
const {
38+
className,
39+
style,
40+
id,
41+
href,
42+
loading_state,
43+
children,
44+
title,
45+
target,
46+
refresh,
47+
setProps,
48+
} = props;
49+
const sanitizedUrl = useMemo(() => sanitizeUrl(href), [href]);
4150

42-
updateLocation(e) {
51+
const updateLocation = e => {
4352
const hasModifiers = e.metaKey || e.shiftKey || e.altKey || e.ctrlKey;
44-
const {href, refresh, target} = this.props;
4553

4654
if (hasModifiers) {
4755
return;
@@ -52,49 +60,40 @@ export default class Link extends Component {
5260
// prevent anchor from updating location
5361
e.preventDefault();
5462
if (refresh) {
55-
window.location = href;
63+
window.location = sanitizedUrl;
5664
} else {
57-
window.history.pushState({}, '', href);
65+
window.history.pushState({}, '', sanitizedUrl);
5866
window.dispatchEvent(new CustomEvent('_dashprivate_pushstate'));
5967
}
6068
// scroll back to top
6169
window.scrollTo(0, 0);
62-
}
70+
};
6371

64-
render() {
65-
const {
66-
className,
67-
style,
68-
id,
69-
href,
70-
loading_state,
71-
children,
72-
title,
73-
target,
74-
} = this.props;
75-
/*
76-
* ideally, we would use cloneElement however
77-
* that doesn't work with dash's recursive
78-
* renderTree implementation for some reason
79-
*/
80-
return (
81-
<a
82-
data-dash-is-loading={
83-
(loading_state && loading_state.is_loading) || undefined
84-
}
85-
id={id}
86-
className={className}
87-
style={style}
88-
href={href}
89-
onClick={e => this.updateLocation(e)}
90-
title={title}
91-
target={target}
92-
>
93-
{isNil(children) ? href : children}
94-
</a>
95-
);
96-
}
97-
}
72+
useEffect(() => {
73+
if (sanitizedUrl !== href) {
74+
setProps({
75+
_dash_error: new Error(`Dangerous link detected:: ${href}`),
76+
});
77+
}
78+
}, [href, sanitizedUrl]);
79+
80+
return (
81+
<a
82+
data-dash-is-loading={
83+
(loading_state && loading_state.is_loading) || undefined
84+
}
85+
id={id}
86+
className={className}
87+
style={style}
88+
href={sanitizedUrl}
89+
onClick={updateLocation}
90+
title={title}
91+
target={target}
92+
>
93+
{isNil(children) ? sanitizedUrl : children}
94+
</a>
95+
);
96+
};
9897

9998
Link.propTypes = {
10099
/**
@@ -151,8 +150,10 @@ Link.propTypes = {
151150
*/
152151
component_name: PropTypes.string,
153152
}),
153+
setProps: PropTypes.func,
154154
};
155155

156156
Link.defaultProps = {
157157
refresh: false,
158158
};
159+
export default Link;

0 commit comments

Comments
 (0)