Skip to content

Commit ba6f3f8

Browse files
authored
feat(security): Strip HTML script tags before inserting content into DOM. Fixes #1974,#1665 (#2134)
* fixed a typo (#1931) * Create utils/strip-sripts.js Utility for removing script tags from injected HTML (i.e. for use with v-html or domProps.innerHTML) Prevents user supplied input form injecting scripts into the DOM * mixins/form-options.js use new striptScripts util * Update button-group.js Remove validator of size prop... to allow for custom CSS defined sizes * Update card-body.js * Update dropdown.js * Update form-group.js * Update input-group.js * Update jumbotron.js * Update modal.js * Update nav-item-dropdown.js * Update progress-bar.js * Update table.js * pagination mixin: add stripScripts and remove temporary button styling * Minor update to table readme
1 parent afe1cd0 commit ba6f3f8

File tree

15 files changed

+67
-44
lines changed

15 files changed

+67
-44
lines changed

src/components/button-group/button-group.js

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { mergeData } from 'vue-functional-data-merge'
2-
import { arrayIncludes } from '../../utils/array'
32

43
export const props = {
54
vertical: {
@@ -8,8 +7,7 @@ export const props = {
87
},
98
size: {
109
type: String,
11-
default: null,
12-
validator: size => arrayIncludes(['sm', '', 'lg'], size)
10+
default: null
1311
},
1412
tag: {
1513
type: String,

src/components/card/card-body.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { mergeData } from 'vue-functional-data-merge'
22
import prefixPropName from '../../utils/prefix-prop-name'
33
import copyProps from '../../utils/copyProps'
44
import { assign } from '../../utils/object'
5+
import stripScripts from '../../utils/strip-scripts'
56
import cardMixin from '../../mixins/card-mixin'
67

78
export const props = assign(
@@ -47,14 +48,14 @@ export default {
4748
if (props.title) {
4849
cardTitle = h(props.titleTag, {
4950
staticClass: 'card-title',
50-
domProps: { innerHTML: props.title }
51+
domProps: { innerHTML: stripScripts(props.title) }
5152
})
5253
}
5354

5455
if (props.subTitle) {
5556
cardSubTitle = h(props.subTitleTag, {
5657
staticClass: 'card-subtitle mb-2 text-muted',
57-
domProps: { innerHTML: props.subTitle }
58+
domProps: { innerHTML: stripScripts(props.subTitle) }
5859
})
5960
}
6061

src/components/dropdown/dropdown.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import idMixin from '../../mixins/id'
22
import dropdownMixin from '../../mixins/dropdown'
3+
import stripScripts from '../../utils/strip-scripts'
34
import bButton from '../button/button'
45

56
// Needed when dropdowns are inside an input group
@@ -27,7 +28,7 @@ export default {
2728
click: this.click
2829
}
2930
},
30-
[this.$slots['button-content'] || this.$slots.text || this.text]
31+
[this.$slots['button-content'] || this.$slots.text || stripScripts(this.text)]
3132
)
3233
}
3334
const toggle = h(
@@ -54,7 +55,7 @@ export default {
5455
[
5556
this.split
5657
? h('span', { class: ['sr-only'] }, [this.toggleText])
57-
: this.$slots['button-content'] || this.$slots.text || this.text
58+
: this.$slots['button-content'] || this.$slots.text || stripScripts(this.text)
5859
]
5960
)
6061
const menu = h(

src/components/embed/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ Any additional attributes provided to `<b-embed>` (other than the above `type`,
4343
Any children elements between the opening and closing `<b-embed>` will be placed
4444
inside the inner embeded element. Note that type `iframe` does not support any children.
4545

46-
**Example: Responsive embeding of an HTML5 `<video>`**
46+
**Example: Responsive embedding of an HTML5 `<video>`**
4747
```html
4848
<b-embed type="video" aspect="4by3" controls poster="poster.png">
4949
<source src="devstories.webm"

src/components/form-group/form-group.js

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import warn from '../../utils/warn'
2+
import stripScripts from '../../utils/strip-scripts'
23
import { select, selectAll, isVisible, setAttr, removeAttr, getAttr } from '../../utils/dom'
34
import idMixin from '../../mixins/id'
45
import formStateMixin from '../../mixins/form-state'
@@ -21,7 +22,7 @@ export default {
2122
if (this.hasLabel) {
2223
let children = $slots['label']
2324
const legendTag = this.labelFor ? 'label' : 'legend'
24-
const legendDomProps = children ? {} : { innerHTML: this.label }
25+
const legendDomProps = children ? {} : { innerHTML: stripScripts(this.label) }
2526
const legendAttrs = { id: this.labelId, for: this.labelFor || null }
2627
const legendClick = (this.labelFor || this.labelSrOnly) ? {} : { click: this.legendClick }
2728
if (this.horizontal) {
@@ -69,7 +70,7 @@ export default {
6970
if (this.hasInvalidFeedback) {
7071
let domProps = {}
7172
if (!$slots['invalid-feedback'] && !$slots['feedback']) {
72-
domProps = { innerHTML: this.invalidFeedback || this.feedback || '' }
73+
domProps = { innerHTML: stripScripts(this.invalidFeedback || this.feedback || '') }
7374
}
7475
invalidFeedback = h(
7576
'b-form-invalid-feedback',
@@ -92,7 +93,7 @@ export default {
9293
// Valid feeback text (explicitly hidden if state is invalid)
9394
let validFeedback = h(false)
9495
if (this.hasValidFeedback) {
95-
const domProps = $slots['valid-feedback'] ? {} : { innerHTML: this.validFeedback || '' }
96+
const domProps = $slots['valid-feedback'] ? {} : { innerHTML: stripScripts(this.validFeedback || '') }
9697
validFeedback = h(
9798
'b-form-valid-feedback',
9899
{
@@ -114,7 +115,7 @@ export default {
114115
// Form help text (description)
115116
let description = h(false)
116117
if (this.hasDescription) {
117-
const domProps = $slots['description'] ? {} : { innerHTML: this.description || '' }
118+
const domProps = $slots['description'] ? {} : { innerHTML: stripScripts(this.description || '') }
118119
description = h(
119120
'b-form-text',
120121
{ attrs: { id: this.descriptionId }, domProps: domProps },

src/components/input-group/input-group.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { mergeData } from 'vue-functional-data-merge'
2+
import stripScripts from '../../utils/strip-scripts'
23
import InputGroupPrepend from './input-group-prepend'
34
import InputGroupAppend from './input-group-append'
45
import InputGroupText from './input-group-text'
@@ -40,7 +41,7 @@ export default {
4041
if (props.prepend) {
4142
childNodes.push(
4243
h(InputGroupPrepend, [
43-
h(InputGroupText, { domProps: { innerHTML: props.prepend } })
44+
h(InputGroupText, { domProps: { innerHTML: stripScripts(props.prepend) } })
4445
])
4546
)
4647
} else {
@@ -65,7 +66,7 @@ export default {
6566
if (props.append) {
6667
childNodes.push(
6768
h(InputGroupAppend, [
68-
h(InputGroupText, { domProps: { innerHTML: props.append } })
69+
h(InputGroupText, { domProps: { innerHTML: stripScripts(props.append) } })
6970
])
7071
)
7172
} else {

src/components/jumbotron/jumbotron.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { mergeData } from 'vue-functional-data-merge'
2+
import stripScripts from '../../utils/strip-scripts'
23
import Container from '../layout/container'
34

45
export const props = {
@@ -66,7 +67,7 @@ export default {
6667
[`display-${props.headerLevel}`]: Boolean(props.headerLevel)
6768
}
6869
},
69-
$slots.header || props.header
70+
$slots.header || stripScripts(props.header)
7071
))
7172
}
7273

@@ -75,7 +76,7 @@ export default {
7576
childNodes.push(h(
7677
props.leadTag,
7778
{ staticClass: 'lead' },
78-
$slots.lead || props.lead
79+
$slots.lead || stripScripts(props.lead)
7980
))
8081
}
8182

src/components/modal/modal.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import observeDom from '../../utils/observe-dom'
66
import warn from '../../utils/warn'
77
import KeyCodes from '../../utils/key-codes'
88
import BvEvent from '../../utils/bv-event.class'
9+
import stripScripts from '../../utils/strip-scripts'
910

1011
import {
1112
isVisible,
@@ -70,7 +71,7 @@ export default {
7071
}
7172
modalHeader = [
7273
h(this.titleTag, { class: ['modal-title'] }, [
73-
$slots['modal-title'] || this.title
74+
$slots['modal-title'] || stripScripts(this.title)
7475
]),
7576
closeButton
7677
]
@@ -116,7 +117,7 @@ export default {
116117
}
117118
}
118119
},
119-
[$slots['modal-cancel'] || this.cancelTitle]
120+
[$slots['modal-cancel'] || stripScripts(this.cancelTitle)]
120121
)
121122
}
122123
const okButton = h(
@@ -133,7 +134,7 @@ export default {
133134
}
134135
}
135136
},
136-
[$slots['modal-ok'] || this.okTitle]
137+
[$slots['modal-ok'] || stripScripts(this.okTitle)]
137138
)
138139
modalFooter = [cancelButton, okButton]
139140
}

src/components/nav/nav-item-dropdown.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import idMixin from '../../mixins/id'
22
import dropdownMixin from '../../mixins/dropdown'
3+
import stripScripts from '../../utils/strip-scripts'
34

45
export default {
56
mixins: [idMixin, dropdownMixin],
@@ -24,7 +25,7 @@ export default {
2425
[
2526
this.$slots['button-content'] ||
2627
this.$slots.text ||
27-
h('span', { domProps: { innerHTML: this.text } })
28+
h('span', { domProps: { innerHTML: stripScripts(this.text) } })
2829
]
2930
)
3031
const menu = h(

src/components/progress/progress-bar.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
1+
import stripScripts from '../../utils/strip-scripts'
2+
13
export default {
24
render (h) {
35
let childNodes = h(false)
46
if (this.$slots.default) {
57
childNodes = this.$slots.default
68
} else if (this.label) {
7-
childNodes = h('span', { domProps: { innerHTML: this.label } })
9+
childNodes = h('span', { domProps: { innerHTML: stripScripts(this.label) } })
810
} else if (this.computedShowProgress) {
911
childNodes = this.progress.toFixed(this.computedPrecision)
1012
} else if (this.computedShowValue) {

0 commit comments

Comments
 (0)