MFM only use sanitised data-* attribute values
All checks were successful
ci/woodpecker/pr/woodpecker Pipeline was successful

We take the value from a data-* attribute and then add this to the style attribute.
This will probably be OK in most cases, but just to be sure, we check for "weird" characters first.
For now we only allow letters, numbers, dot, hash, and plus and minus sign, because those are the ones I currently know of who are used in MFM.
The data-* attribute remains because it was already considered proper HTML as-is.
This commit is contained in:
ilja 2024-08-11 17:48:36 +02:00
parent 3210873d7f
commit 6666a273a4
2 changed files with 35 additions and 4 deletions

View file

@ -194,14 +194,16 @@ export default {
// Turn data-mfm- attributes into a string for the `style` attribute
// If they have a value different than `true`, they need to be added to `style`
// e.g. `attrs={'data-mfm-some': '1deg', 'data-mfm-thing': '5s'}` => "--mfm-some: 1deg;--mfm-thing: 5s;"
// Note that we only add the value to `style` when they contain only letters, numbers, dot, hash, or plus or minus signs
// At the moment of writing, this should be enough for legitimite purposes and reduces the chance of injection by using special characters
let mfm_style = Object.keys(attrs).filter(
(key) => key.startsWith('data-mfm-') && attrs[key] !== true
(key) => key.startsWith('data-mfm-') && attrs[key] !== true && /^[a-zA-Z0-9.\-+#]*$/.test(attrs[key])
).map(
(key) => '--mfm-' + key.substr(9) + ': ' + attrs[key] + ';'
).reduce((a,v) => a+v, "")
if (mfm_style !== "") {
).reduce((a,v) => a+v, '')
if (mfm_style !== '') {
return [
opener.slice(0,-1) + " style=\"" + mfm_style + "\">",
opener.slice(0,-1) + ' style="' + mfm_style + '">',
children.map(processItem),
closer
]

View file

@ -40,6 +40,35 @@ describe('RichContent', () => {
expect(wrapper.html().replace(/\n/g, '')).to.eql(compwrap(html))
})
it('does not allow injection through MFM data- attributes', () => {
const html_ok = '<span class="mfm-spin" data-mfm-speed="-0.2s" data-mfm-color="#000" data-mfm-deg="+30">brrr</span>'
const expected_ok = '<span class="mfm-spin" data-mfm-speed="-0.2s" data-mfm-color="#000" data-mfm-deg="+30" style="--mfm-speed: -0.2s; --mfm-color: #000; --mfm-deg: +30;">brrr</span>'
const wrapper_ok = shallowMount(RichContent, {
global,
props: {
attentions,
handleLinks: true,
greentext: true,
emoji: [],
html: html_ok
}
})
const html_nok = '<span class="mfm-spin" data-mfm-speed="<" data-mfm-color="\\">brrr</span>'
const wrapper_nok = shallowMount(RichContent, {
global,
props: {
attentions,
handleLinks: true,
greentext: true,
emoji: [],
html: html_nok
}
})
expect(wrapper_ok.html()).to.eql(compwrap(expected_ok))
expect(wrapper_nok.html()).to.eql(compwrap(html_nok))
})
it('unescapes everything as needed', () => {
const html = [
p('Testing &#39;em all'),