Skip to content

Commit 43af64d

Browse files
david-cho-lerat-sonarsourcesonartech
authored andcommitted
SONAR-23741 Backport fixes for SSF-656 & SSF-657
1 parent 2ac9033 commit 43af64d

37 files changed

+930
-715
lines changed

server/sonar-web/.eslintrc

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,29 @@
33
"rules": {
44
"camelcase": "off",
55
"promise/no-return-wrap": "warn",
6+
"react/forbid-component-props": [
7+
"error",
8+
{
9+
"forbid": [
10+
{
11+
"propName": "dangerouslySetInnerHTML",
12+
"message": "Use the SafeHTMLInjection component instead of 'dangerouslySetInnerHTML', to prevent CSS injection along other XSS attacks"
13+
}
14+
]
15+
}
16+
],
17+
"react/forbid-dom-props": [
18+
"error",
19+
{
20+
"forbid": [
21+
{
22+
"propName": "dangerouslySetInnerHTML",
23+
"message": "Use the SafeHTMLInjection component instead of 'dangerouslySetInnerHTML', to prevent CSS injection along other XSS attacks"
24+
}
25+
]
26+
}
27+
],
628
"react/jsx-curly-brace-presence": "warn",
729
"testing-library/render-result-naming-convention": "off"
830
}
9-
}
31+
}

server/sonar-web/src/main/js/app/components/extensions/exposeLibraries.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
* along with this program; if not, write to the Free Software Foundation,
1818
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
1919
*/
20+
2021
import { FormattedMessage } from 'react-intl';
2122
import NotFound from '../../../app/components/NotFound';
2223
import A11ySkipTarget from '../../../components/a11y/A11ySkipTarget';
@@ -103,7 +104,7 @@ import {
103104
postJSONBody,
104105
request,
105106
} from '../../../helpers/request';
106-
import { sanitizeStringRestricted } from '../../../helpers/sanitize';
107+
import { sanitizeHTMLRestricted } from '../../../helpers/sanitize';
107108
import {
108109
getStandards,
109110
renderCWECategory,
@@ -166,7 +167,7 @@ const exposeLibraries = () => {
166167
getComponentSecurityHotspotsUrl,
167168
getMeasureHistoryUrl,
168169
getRulesUrl,
169-
sanitizeStringRestricted,
170+
sanitizeStringRestricted: sanitizeHTMLRestricted,
170171
};
171172
},
172173
});

server/sonar-web/src/main/js/app/components/search/SearchResult.tsx

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,13 @@
1717
* along with this program; if not, write to the Free Software Foundation,
1818
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
1919
*/
20+
2021
import * as React from 'react';
2122
import Link from '../../../components/common/Link';
2223
import ClockIcon from '../../../components/icons/ClockIcon';
2324
import FavoriteIcon from '../../../components/icons/FavoriteIcon';
2425
import QualifierIcon from '../../../components/icons/QualifierIcon';
26+
import { SafeHTMLInjection } from '../../../helpers/sanitize';
2527
import { getComponentOverviewUrl } from '../../../helpers/urls';
2628
import { ComponentResult } from './utils';
2729

@@ -60,12 +62,9 @@ export default class SearchResult extends React.PureComponent<Props> {
6062
</span>
6163

6264
{component.match ? (
63-
<span
64-
className="navbar-search-item-match"
65-
// Safe: comes from the search engine, that injects bold tags into component names
66-
// eslint-disable-next-line react/no-danger
67-
dangerouslySetInnerHTML={{ __html: component.match }}
68-
/>
65+
<SafeHTMLInjection htmlAsString={component.match}>
66+
<span className="navbar-search-item-match" />
67+
</SafeHTMLInjection>
6968
) : (
7069
<span className="navbar-search-item-match">{component.name}</span>
7170
)}

server/sonar-web/src/main/js/app/components/search/__tests__/__snapshots__/SearchResult-test.tsx.snap

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -80,14 +80,13 @@ exports[`renders match 1`] = `
8080
qualifier="TRK"
8181
/>
8282
</span>
83-
<span
84-
className="navbar-search-item-match"
85-
dangerouslySetInnerHTML={
86-
{
87-
"__html": "f<mark>o</mark>o",
88-
}
89-
}
90-
/>
83+
<SafeHTMLInjection
84+
htmlAsString="f<mark>o</mark>o"
85+
>
86+
<span
87+
className="navbar-search-item-match"
88+
/>
89+
</SafeHTMLInjection>
9190
</div>
9291
<div
9392
className="navbar-search-item-right text-muted-2"

server/sonar-web/src/main/js/apps/coding-rules/components/ActivationFormModal.tsx

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
* along with this program; if not, write to the Free Software Foundation,
1818
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
1919
*/
20+
2021
import classNames from 'classnames';
2122
import * as React from 'react';
2223
import { OptionTypeBase } from 'react-select';
@@ -26,7 +27,7 @@ import Modal from '../../../components/controls/Modal';
2627
import Select from '../../../components/controls/Select';
2728
import { Alert } from '../../../components/ui/Alert';
2829
import { translate } from '../../../helpers/l10n';
29-
import { sanitizeString } from '../../../helpers/sanitize';
30+
import { SafeHTMLInjection, SanitizeLevel } from '../../../helpers/sanitize';
3031
import { Dict, Rule, RuleActivation, RuleDetails } from '../../../types/types';
3132
import { sortProfiles } from '../../quality-profiles/utils';
3233
import { SeveritySelect } from './SeveritySelect';
@@ -218,11 +219,12 @@ export default class ActivationFormModal extends React.PureComponent<Props, Stat
218219
/>
219220
)}
220221
{param.htmlDesc !== undefined && (
221-
<div
222-
className="note"
223-
// eslint-disable-next-line react/no-danger
224-
dangerouslySetInnerHTML={{ __html: sanitizeString(param.htmlDesc) }}
225-
/>
222+
<SafeHTMLInjection
223+
htmlAsString={param.htmlDesc}
224+
sanitizeLevel={SanitizeLevel.FORBID_SVG_MATHML}
225+
>
226+
<div className="note" />
227+
</SafeHTMLInjection>
226228
)}
227229
</div>
228230
))

server/sonar-web/src/main/js/apps/coding-rules/components/CustomRuleFormModal.tsx

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
* along with this program; if not, write to the Free Software Foundation,
1818
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
1919
*/
20+
2021
import * as React from 'react';
2122
import { components, OptionProps, OptionTypeBase, SingleValueProps } from 'react-select';
2223
import { createRule, updateRule } from '../../../api/rules';
@@ -31,7 +32,7 @@ import MandatoryFieldsExplanation from '../../../components/ui/MandatoryFieldsEx
3132
import { RULE_STATUSES, RULE_TYPES } from '../../../helpers/constants';
3233
import { csvEscape } from '../../../helpers/csv';
3334
import { translate } from '../../../helpers/l10n';
34-
import { sanitizeString } from '../../../helpers/sanitize';
35+
import { SafeHTMLInjection, SanitizeLevel } from '../../../helpers/sanitize';
3536
import { latinize } from '../../../helpers/strings';
3637
import { Dict, RuleDetails, RuleParameter } from '../../../types/types';
3738
import { SeveritySelect } from './SeveritySelect';
@@ -317,11 +318,12 @@ export default class CustomRuleFormModal extends React.PureComponent<Props, Stat
317318
/>
318319
)}
319320
{param.htmlDesc !== undefined && (
320-
<div
321-
className="modal-field-description"
322-
// eslint-disable-next-line react/no-danger
323-
dangerouslySetInnerHTML={{ __html: sanitizeString(param.htmlDesc) }}
324-
/>
321+
<SafeHTMLInjection
322+
htmlAsString={param.htmlDesc}
323+
sanitizeLevel={SanitizeLevel.FORBID_SVG_MATHML}
324+
>
325+
<div className="modal-field-description" />
326+
</SafeHTMLInjection>
325327
)}
326328
</div>
327329
);

server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsDescription.tsx

Lines changed: 25 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,14 @@
1717
* along with this program; if not, write to the Free Software Foundation,
1818
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
1919
*/
20+
2021
import * as React from 'react';
2122
import { updateRule } from '../../../api/rules';
2223
import FormattingTips from '../../../components/common/FormattingTips';
2324
import { Button, ResetButtonLink } from '../../../components/controls/buttons';
2425
import RuleTabViewer from '../../../components/rules/RuleTabViewer';
2526
import { translate, translateWithParameters } from '../../../helpers/l10n';
26-
import { sanitizeString, sanitizeUserInput } from '../../../helpers/sanitize';
27+
import { SafeHTMLInjection, SanitizeLevel } from '../../../helpers/sanitize';
2728
import { RuleDetails } from '../../../types/types';
2829
import { RuleDescriptionSections } from '../rule';
2930
import RemoveExtendedDescriptionModal from './RemoveExtendedDescriptionModal';
@@ -112,14 +113,14 @@ export default class RuleDetailsDescription extends React.PureComponent<Props, S
112113
renderExtendedDescription = () => (
113114
<div id="coding-rules-detail-description-extra">
114115
{this.props.ruleDetails.htmlNote !== undefined && (
115-
<div
116-
className="rule-desc spacer-bottom markdown"
117-
// eslint-disable-next-line react/no-danger
118-
dangerouslySetInnerHTML={{
119-
__html: sanitizeUserInput(this.props.ruleDetails.htmlNote),
120-
}}
121-
/>
116+
<SafeHTMLInjection
117+
htmlAsString={this.props.ruleDetails.htmlNote}
118+
sanitizeLevel={SanitizeLevel.USER_INPUT}
119+
>
120+
<div className="rule-desc spacer-bottom markdown" />
121+
</SafeHTMLInjection>
122122
)}
123+
123124
{this.props.canWrite && (
124125
<Button
125126
id="coding-rules-detail-extend-description"
@@ -216,23 +217,28 @@ export default class RuleDetailsDescription extends React.PureComponent<Props, S
216217
return (
217218
<div className="js-rule-description">
218219
{defaultSection && (
219-
<section
220-
className="coding-rules-detail-description markdown"
221-
key={defaultSection.key}
222-
/* eslint-disable-next-line react/no-danger */
223-
dangerouslySetInnerHTML={{ __html: sanitizeString(defaultSection.content) }}
224-
/>
220+
<SafeHTMLInjection
221+
htmlAsString={defaultSection.content}
222+
sanitizeLevel={SanitizeLevel.FORBID_SVG_MATHML}
223+
>
224+
<section
225+
className="coding-rules-detail-description markdown"
226+
key={defaultSection.key}
227+
/>
228+
</SafeHTMLInjection>
225229
)}
226230

227231
{hasDescriptionSection && !defaultSection && (
228232
<>
229233
{introductionSection && (
230-
<div
231-
className="rule-desc"
232-
// eslint-disable-next-line react/no-danger
233-
dangerouslySetInnerHTML={{ __html: sanitizeString(introductionSection) }}
234-
/>
234+
<SafeHTMLInjection
235+
htmlAsString={introductionSection}
236+
sanitizeLevel={SanitizeLevel.FORBID_SVG_MATHML}
237+
>
238+
<div className="rule-desc" />
239+
</SafeHTMLInjection>
235240
)}
241+
236242
<RuleTabViewer ruleDetails={ruleDetails} />
237243
</>
238244
)}

server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsParameters.tsx

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,10 @@
1717
* along with this program; if not, write to the Free Software Foundation,
1818
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
1919
*/
20+
2021
import * as React from 'react';
2122
import { translate } from '../../../helpers/l10n';
22-
import { sanitizeString } from '../../../helpers/sanitize';
23+
import { SafeHTMLInjection, SanitizeLevel } from '../../../helpers/sanitize';
2324
import { RuleParameter } from '../../../types/types';
2425

2526
interface Props {
@@ -30,13 +31,17 @@ export default class RuleDetailsParameters extends React.PureComponent<Props> {
3031
renderParameter = (param: RuleParameter) => (
3132
<tr className="coding-rules-detail-parameter" key={param.key}>
3233
<td className="coding-rules-detail-parameter-name">{param.key}</td>
34+
3335
<td className="coding-rules-detail-parameter-description">
3436
{param.htmlDesc !== undefined && (
35-
<p
36-
// eslint-disable-next-line react/no-danger
37-
dangerouslySetInnerHTML={{ __html: sanitizeString(param.htmlDesc) }}
38-
/>
37+
<SafeHTMLInjection
38+
htmlAsString={param.htmlDesc}
39+
sanitizeLevel={SanitizeLevel.FORBID_SVG_MATHML}
40+
>
41+
<p />
42+
</SafeHTMLInjection>
3943
)}
44+
4045
{param.defaultValue !== undefined && (
4146
<div className="note spacer-top">
4247
{translate('coding_rules.parameters.default_value')}

server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/__snapshots__/ActivationFormModal-test.tsx.snap

Lines changed: 32 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -160,14 +160,14 @@ exports[`should render correctly: default 1`] = `
160160
type="text"
161161
value="1"
162162
/>
163-
<div
164-
className="note"
165-
dangerouslySetInnerHTML={
166-
{
167-
"__html": "description",
168-
}
169-
}
170-
/>
163+
<SafeHTMLInjection
164+
htmlAsString="description"
165+
sanitizeLevel={1}
166+
>
167+
<div
168+
className="note"
169+
/>
170+
</SafeHTMLInjection>
171171
</div>
172172
<div
173173
className="modal-field"
@@ -281,14 +281,14 @@ exports[`should render correctly: submitting 1`] = `
281281
type="text"
282282
value="1"
283283
/>
284-
<div
285-
className="note"
286-
dangerouslySetInnerHTML={
287-
{
288-
"__html": "description",
289-
}
290-
}
291-
/>
284+
<SafeHTMLInjection
285+
htmlAsString="description"
286+
sanitizeLevel={1}
287+
>
288+
<div
289+
className="note"
290+
/>
291+
</SafeHTMLInjection>
292292
</div>
293293
<div
294294
className="modal-field"
@@ -400,14 +400,14 @@ exports[`should render correctly: update mode 1`] = `
400400
type="text"
401401
value="1"
402402
/>
403-
<div
404-
className="note"
405-
dangerouslySetInnerHTML={
406-
{
407-
"__html": "description",
408-
}
409-
}
410-
/>
403+
<SafeHTMLInjection
404+
htmlAsString="description"
405+
sanitizeLevel={1}
406+
>
407+
<div
408+
className="note"
409+
/>
410+
</SafeHTMLInjection>
411411
</div>
412412
<div
413413
className="modal-field"
@@ -555,14 +555,14 @@ exports[`should render correctly: with deep profiles 1`] = `
555555
type="text"
556556
value="1"
557557
/>
558-
<div
559-
className="note"
560-
dangerouslySetInnerHTML={
561-
{
562-
"__html": "description",
563-
}
564-
}
565-
/>
558+
<SafeHTMLInjection
559+
htmlAsString="description"
560+
sanitizeLevel={1}
561+
>
562+
<div
563+
className="note"
564+
/>
565+
</SafeHTMLInjection>
566566
</div>
567567
<div
568568
className="modal-field"

0 commit comments

Comments
 (0)