A common complaint about content editing in Plone is that it is too strict about filtering out certain HTML. There is a good reason for its strictness: since some HTML tags can be used to inject unsafe content (such as cross-site scripting (XSS)) and since many Plone sites allow content editing by untrusted users, these HTML tags are disallowed by default as a security precaution.
But sometimes security is less important that being able to insert any markup. A client recently came to me with this situation:
- It’s a news site that commonly wants to embed iframes, video, and scripts.
- The only users with access to edit HTML are trusted editorial staff.
- There are no other, untrusted sites on the same domain or Zope instance.
Step 1: Turn off Plone’s safe_html transform. Go to /portal_transforms/safe_html in the ZMI, and enter a 1 in the ‘disable_transform’ box. This prevents Plone from removing tags and attributes while rendering rich text.
That used to be all that was necessary to turn off filtering. But these days TinyMCE also filters HTML on the client side, using configuration based on the safe_html transform’s settings, and unfortunately it doesn’t pay attention to the ‘disable_transform’ flag. Luckily we can get around that…
Step 2: Monkey-patch the TinyMCE utility to return a wildcard when determining the allowed tags and attributes:
fromProducts.TinyMCE.utilityimportTinyMCETinyMCE.getValidElements=lambdaself:{'*':['*']}
Now you can edit a page, open TinyMCE’s HTML dialog, enter whatever you want, save, and it will get saved to the database and be shown on subsequent views of the page. However, if you’re using Safari, Chrome, and IE, some unsafe markup will still not show up on the initial view of the page right after saving. Why not?
These browsers provide automatic reflexive XSS protection. That means that if they detect the same potentially unsafe markup in both a request and its response, they’ll block or ignore it. It’s normally a pretty nice security precaution, but in this case it’s cramping our style. Fortunately these browsers also provide a way for a site to opt out of the protection using an HTTP response header.
Step 3: Set the "X-XSS-Protection: 0" response header. This can be done in your frontend webserver such as apache or nginx. In my case, though, I figured I only need to disable the protection for users who have permission to edit, so I added this to the site’s main_template:
tal:define="dummypython:checkPermission('Modifyportalcontent',context)andrequest.RESPONSE.setHeader('X-XSS-Protection','0');"
Et voilà. No more filtering, and the editorial staff can enter whatever markup they dream up.
P.S. If someone wants to fix TinyMCE to pay attention to safe_html's disable_transform flag, that'd be nice!