Plugin for using DOMPurify as Aurelia's HTML sanitizer

This week, a two year old vulnerability report resurfaced in the news: https://portswigger.net/daily-swig/aurelia-frameworks-default-html-sanitizer-opens-the-door-to-xss-attacks

I therefore decided to create a very simple Aurelia plugin for using DOMPurify as Aurelia’s sanitizer implementation: @appex/aurelia-dompurify - npm

Feedback is welcome.

An issue I had, is that when using it as a plugin, having config.singleton(HTMLSanitizer, DOMPurifySanitizer) in the plugin’s index.ts and using it as aurelia.use.plugin(PLATFORM.moduleName('@appex/aurelia-dompurify')), the result is two DI resolvers for HTMLSanitizer, one returning DOMPurifySanitizer and the other the default version. Inspecting the container shows them seemingly having the same key, but the objects are evidently not identical. Does anybody have an idea why this is?

Edit: This issue was due to referencing the local source folder of the package in package.json for testing. Another issue remains, however:

The aurelia.use.plugin(PLATFORM.moduleName('@appex/aurelia-dompurify')) needs to be put before the call to .standardConfiguration(), otherwise the default implementation will be used. That is also the case if I add a config.container.unregister(HTMLSanitizer) in the plugin’s index.ts. Is there any way of avoiding that?

Edit: Fixed in v0.4.0 with help from @MaximBalaganskiy

Edit: v0.5.0 allows specifying a custom DOMPurify configuration

2 Likes

Could it be that duplicate aurelia-templating-resources are present in your node_modules?

No, there’s only one. That was my initial suspicion, and I had differing versions in my yarn.lock. I switched to the same version that aurelia-bootstrapper uses (1.7.0), but the problem remains. Could it be a webpack issue?

@MaximBalaganskiy Actually, there are two folders, but they are the same version.

@ Team Aurelia, important problem I would think!

Perhaps fixable by just replacing the Sanitizer with the other suggested one? Or somekind of hotfix in the default sanitizer based on the findings of GoSecure?

I agree that this needs to be addressed, and just created a separate post for that important discussion: XSS vulnerability in HTMLSanitizer might be insufficiently handled

1 Like

Well, that’s the problem - the class gets imported from from both and those imports ARE different. Try running npm ddp to clean the directory.

Thanks, but even if that works, @MaximBalaganskiy , it’s not possible to do this in a plugin in a reliable way: I’m testing this in an empty project created with au new, and I’ve tried deleting both yarn.lock and node_modules to no avail.

I can’t require the users of the plugin to do npm ddpor its Yarn equivalent to be sure that it works. :confused: Seems like this is an inherent problem with using the class as the key, as Aurelia v1 does?

This is not an aurelia issue but rather node_modules one. Not sure why fresh cli project pulls two folders. Is the cli version latest?

Which are the packages with that double?

@MaximBalaganskiy Thanks a lot for pointing me in the right direction. I feel stupid now, the reason was that I had made package.json reference the local copy of the package for testing, and yarn then copies the whole folder into node_modules, including the packages own node_modules. :blush:

But one issue remains: The aurelia.use.plugin(PLATFORM.moduleName('@appex/aurelia-dompurify')) needs to be put before the call to .standardConfiguration(), otherwise the default implementation will be used. That is also the case if I add a config.container.unregister(HTMLSanitizer) in the plugin’s index.ts. Is there any way of avoiding that?

try augmenting the default class to add static get method and a special di flag. The get must return an instance of your implementation. This must happen in your module index, not in a configuration function where it is already too late. See

That doesn’t seem to work, and I think I might know why: The default HTMLSanitizer will already have been injected into SanitizeHTMLValueConverter because of @inject(HTMLSanitizer) during .standardConfiguration(). Am I correct?

If you hijack the default class on your module import, not config, then it should work. The decorator only locks the class, the actual instance will be created later. This definitely worked in the materialise bridge

If I understand this correctly, using PLATFORM.moduleName('@appex/aurelia-dompurify')) in the plugin registration will prevent that. Or am I misunderstanding something?

If you put the code straight to the root of index file it will be executed immediately on bundle loading

Even with this index.ts, the order matters:

import { Container, FrameworkConfiguration } from 'aurelia-framework';
import { HTMLSanitizer, SanitizeHTMLValueConverter } from 'aurelia-templating-resources';
import { DOMPurifySanitizer } from './dom-purify-sanitizer';

export function configure(config: FrameworkConfiguration): void {
}

export { DOMPurifySanitizer };

HTMLSanitizer['get'] = (container: Container) => {
  console.log('Creating DOMPurifySanitizer');
  return new DOMPurifySanitizer();
}
  
HTMLSanitizer['protocol:aurelia:resolver'] = true;

SanitizeHTMLValueConverter['protocol:aurelia:resolver'] = true;
SanitizeHTMLValueConverter['get'] = (container: Container) => {
  console.log('Creating SanitizeHTMLValueConverter');
  return new SanitizeHTMLValueConverter(new DOMPurifySanitizer());
}

If I put this above aurelia.use.standardConfiguration, I can put the aurelia.use.plugin(PLATFORM.moduleName('@appex/aurelia-dompurify')) anywhere I like:

DOMPurifySanitizer['fsg'] = 'sdf';

So it seems that if it’s not referenced anywhere, it won’t be loaded until the aurelia.use.plugin() statement.

Do you split bundles? Unfortunately, your module does not get loaded until use call.

Try this in the config function

  let vc = frameworkConfiguration.aurelia.container.get(SanitizeHTMLValueConverter);
  vc['sanitizer'] = new MyHTMLSanitizer();

@dabide :point_up_2: works before and after standard config