source: node_modules/dompurify/README.md

main
Last change on this file was d24f17c, checked in by Aleksandar Panovski <apano77@…>, 15 months ago

Initial commit

  • Property mode set to 100644
File size: 25.2 KB
Line 
1# DOMPurify
2
3[![npm version](https://badge.fury.io/js/dompurify.svg)](http://badge.fury.io/js/dompurify) ![Build and Test](https://github.com/cure53/DOMPurify/workflows/Build%20and%20Test/badge.svg?branch=main) [![Downloads](https://img.shields.io/npm/dm/dompurify.svg)](https://www.npmjs.com/package/dompurify) ![npm package minimized gzipped size (select exports)](https://img.shields.io/bundlejs/size/dompurify?color=%233C1&label=minified) ![GitHub code size in bytes](https://img.shields.io/github/languages/code-size/cure53/dompurify?color=%233C1) [![dependents](https://badgen.net/github/dependents-repo/cure53/dompurify?color=green&label=dependents)](https://github.com/cure53/DOMPurify/network/dependents)
4
5[![NPM](https://nodei.co/npm/dompurify.png)](https://nodei.co/npm/dompurify/)
6
7DOMPurify is a DOM-only, super-fast, uber-tolerant XSS sanitizer for HTML, MathML and SVG.
8
9It's also very simple to use and get started with. DOMPurify was [started in February 2014](https://github.com/cure53/DOMPurify/commit/a630922616927373485e0e787ab19e73e3691b2b) and, meanwhile, has reached version **v3.0.8**.
10
11DOMPurify is written in JavaScript and works in all modern browsers (Safari (10+), Opera (15+), Edge, Firefox and Chrome - as well as almost anything else using Blink, Gecko or WebKit). It doesn't break on MSIE or other legacy browsers. It simply does nothing.
12
13**Note that [DOMPurify v2.4.7](https://github.com/cure53/DOMPurify/releases/tag/2.4.6) is the latest version supporting MSIE. For important security updates compatible with MSIE, please use the [2.x branch](https://github.com/cure53/DOMPurify/tree/2.x).**
14
15Our automated tests cover [19 different browsers](https://github.com/cure53/DOMPurify/blob/main/test/karma.custom-launchers.config.js#L5) right now, more to come. We also cover Node.js v16.x, v17.x, v18.x and v19.x, running DOMPurify on [jsdom](https://github.com/jsdom/jsdom). Older Node versions are known to work as well, but hey... no guarantees.
16
17DOMPurify is written by security people who have vast background in web attacks and XSS. Fear not. For more details please also read about our [Security Goals & Threat Model](https://github.com/cure53/DOMPurify/wiki/Security-Goals-&-Threat-Model). Please, read it. Like, really.
18
19## What does it do?
20
21DOMPurify sanitizes HTML and prevents XSS attacks. You can feed DOMPurify with string full of dirty HTML and it will return a string (unless configured otherwise) with clean HTML. DOMPurify will strip out everything that contains dangerous HTML and thereby prevent XSS attacks and other nastiness. It's also damn bloody fast. We use the technologies the browser provides and turn them into an XSS filter. The faster your browser, the faster DOMPurify will be.
22
23## How do I use it?
24
25It's easy. Just include DOMPurify on your website.
26
27### Using the unminified development version
28
29```html
30<script type="text/javascript" src="src/purify.js"></script>
31```
32
33### Using the minified and tested production version (source-map available)
34
35```html
36<script type="text/javascript" src="dist/purify.min.js"></script>
37```
38
39Afterwards you can sanitize strings by executing the following code:
40
41```js
42const clean = DOMPurify.sanitize(dirty);
43```
44
45Or maybe this, if you love working with Angular or alike:
46
47```js
48import * as DOMPurify from 'dompurify';
49
50const clean = DOMPurify.sanitize('<b>hello there</b>');
51```
52
53The resulting HTML can be written into a DOM element using `innerHTML` or the DOM using `document.write()`. That is fully up to you.
54Note that by default, we permit HTML, SVG **and** MathML. If you only need HTML, which might be a very common use-case, you can easily set that up as well:
55
56```js
57const clean = DOMPurify.sanitize(dirty, { USE_PROFILES: { html: true } });
58```
59
60### Where are the TypeScript type definitions?
61
62They can be found here: [@types/dompurify](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/dompurify)
63
64### Is there any foot-gun potential?
65
66Well, please note, if you _first_ sanitize HTML and then modify it _afterwards_, you might easily **void the effects of sanitization**. If you feed the sanitized markup to another library _after_ sanitization, please be certain that the library doesn't mess around with the HTML on its own.
67
68### Okay, makes sense, let's move on
69
70After sanitizing your markup, you can also have a look at the property `DOMPurify.removed` and find out, what elements and attributes were thrown out. Please **do not use** this property for making any security critical decisions. This is just a little helper for curious minds.
71
72### Running DOMPurify on the server
73
74DOMPurify technically also works server-side with Node.js. Our support strives to follow the [Node.js release cycle](https://nodejs.org/en/about/releases/).
75
76Running DOMPurify on the server requires a DOM to be present, which is probably no surprise. Usually, [jsdom](https://github.com/jsdom/jsdom) is the tool of choice and we **strongly recommend** to use the latest version of _jsdom_.
77
78Why? Because older versions of _jsdom_ are known to be buggy in ways that result in XSS _even if_ DOMPurify does everything 100% correctly. There are **known attack vectors** in, e.g. _jsdom v19.0.0_ that are fixed in _jsdom v20.0.0_ - and we really recommend to keep _jsdom_ up to date because of that.
79
80Other than that, you are fine to use DOMPurify on the server. Probably. This really depends on _jsdom_ or whatever DOM you utilize server-side. If you can live with that, this is how you get it to work:
81
82```bash
83npm install dompurify
84npm install jsdom
85```
86
87For _jsdom_ (please use an up-to-date version), this should do the trick:
88
89```js
90const createDOMPurify = require('dompurify');
91const { JSDOM } = require('jsdom');
92
93const window = new JSDOM('').window;
94const DOMPurify = createDOMPurify(window);
95const clean = DOMPurify.sanitize('<b>hello there</b>');
96```
97
98Or even this, if you prefer working with imports:
99
100```js
101import { JSDOM } from 'jsdom';
102import DOMPurify from 'dompurify';
103
104const window = new JSDOM('').window;
105const purify = DOMPurify(window);
106const clean = purify.sanitize('<b>hello there</b>');
107```
108
109If you have problems making it work in your specific setup, consider looking at the amazing [isomorphic-dompurify](https://github.com/kkomelin/isomorphic-dompurify) project which solves lots of problems people might run into.
110
111```bash
112npm install isomorphic-dompurify
113```
114
115```js
116import DOMPurify from 'isomorphic-dompurify';
117
118const clean = DOMPurify.sanitize('<s>hello</s>');
119```
120
121## Is there a demo?
122
123Of course there is a demo! [Play with DOMPurify](https://cure53.de/purify)
124
125## What if I find a _security_ bug?
126
127First of all, please immediately contact us via [email](mailto:mario@cure53.de) so we can work on a fix. [PGP key](https://keyserver.ubuntu.com/pks/lookup?op=vindex&search=0xC26C858090F70ADA)
128
129Also, you probably qualify for a bug bounty! The fine folks over at [Fastmail](https://www.fastmail.com/) use DOMPurify for their services and added our library to their bug bounty scope. So, if you find a way to bypass or weaken DOMPurify, please also have a look at their website and the [bug bounty info](https://www.fastmail.com/about/bugbounty/).
130
131## Some purification samples please?
132
133How does purified markup look like? Well, [the demo](https://cure53.de/purify) shows it for a big bunch of nasty elements. But let's also show some smaller examples!
134
135```js
136DOMPurify.sanitize('<img src=x onerror=alert(1)//>'); // becomes <img src="x">
137DOMPurify.sanitize('<svg><g/onload=alert(2)//<p>'); // becomes <svg><g></g></svg>
138DOMPurify.sanitize('<p>abc<iframe//src=jAva&Tab;script:alert(3)>def</p>'); // becomes <p>abc</p>
139DOMPurify.sanitize('<math><mi//xlink:href="data:x,<script>alert(4)</script>">'); // becomes <math><mi></mi></math>
140DOMPurify.sanitize('<TABLE><tr><td>HELLO</tr></TABL>'); // becomes <table><tbody><tr><td>HELLO</td></tr></tbody></table>
141DOMPurify.sanitize('<UL><li><A HREF=//google.com>click</UL>'); // becomes <ul><li><a href="//google.com">click</a></li></ul>
142```
143
144## What is supported?
145
146DOMPurify currently supports HTML5, SVG and MathML. DOMPurify per default allows CSS, HTML custom data attributes. DOMPurify also supports the Shadow DOM - and sanitizes DOM templates recursively. DOMPurify also allows you to sanitize HTML for being used with the jQuery `$()` and `elm.html()` API without any known problems.
147
148## What about legacy browsers like Internet Explorer?
149
150DOMPurify does nothing at all. It simply returns exactly the string that you fed it. DOMPurify exposes a property called `isSupported`, which tells you whether it will be able to do its job, so you can come up with your own backup plan.
151
152## What about DOMPurify and Trusted Types?
153
154In version 1.0.9, support for [Trusted Types API](https://github.com/w3c/webappsec-trusted-types) was added to DOMPurify.
155In version 2.0.0, a config flag was added to control DOMPurify's behavior regarding this.
156
157When `DOMPurify.sanitize` is used in an environment where the Trusted Types API is available and `RETURN_TRUSTED_TYPE` is set to `true`, it tries to return a `TrustedHTML` value instead of a string (the behavior for `RETURN_DOM` and `RETURN_DOM_FRAGMENT` config options does not change).
158
159## Can I configure DOMPurify?
160
161Yes. The included default configuration values are pretty good already - but you can of course override them. Check out the [`/demos`](https://github.com/cure53/DOMPurify/tree/main/demos) folder to see a bunch of examples on how you can [customize DOMPurify](https://github.com/cure53/DOMPurify/tree/main/demos#what-is-this).
162
163### General settings
164```js
165// strip {{ ... }}, ${ ... } and <% ... %> to make output safe for template systems
166// be careful please, this mode is not recommended for production usage.
167// allowing template parsing in user-controlled HTML is not advised at all.
168// only use this mode if there is really no alternative.
169const clean = DOMPurify.sanitize(dirty, {SAFE_FOR_TEMPLATES: true});
170```
171
172### Control our allow-lists and block-lists
173```js
174// allow only <b> elements, very strict
175const clean = DOMPurify.sanitize(dirty, {ALLOWED_TAGS: ['b']});
176
177// allow only <b> and <q> with style attributes
178const clean = DOMPurify.sanitize(dirty, {ALLOWED_TAGS: ['b', 'q'], ALLOWED_ATTR: ['style']});
179
180// allow all safe HTML elements but neither SVG nor MathML
181// note that the USE_PROFILES setting will override the ALLOWED_TAGS setting
182// so don't use them together
183const clean = DOMPurify.sanitize(dirty, {USE_PROFILES: {html: true}});
184
185// allow all safe SVG elements and SVG Filters, no HTML or MathML
186const clean = DOMPurify.sanitize(dirty, {USE_PROFILES: {svg: true, svgFilters: true}});
187
188// allow all safe MathML elements and SVG, but no SVG Filters
189const clean = DOMPurify.sanitize(dirty, {USE_PROFILES: {mathMl: true, svg: true}});
190
191// change the default namespace from HTML to something different
192const clean = DOMPurify.sanitize(dirty, {NAMESPACE: 'http://www.w3.org/2000/svg'});
193
194// leave all safe HTML as it is and add <style> elements to block-list
195const clean = DOMPurify.sanitize(dirty, {FORBID_TAGS: ['style']});
196
197// leave all safe HTML as it is and add style attributes to block-list
198const clean = DOMPurify.sanitize(dirty, {FORBID_ATTR: ['style']});
199
200// extend the existing array of allowed tags and add <my-tag> to allow-list
201const clean = DOMPurify.sanitize(dirty, {ADD_TAGS: ['my-tag']});
202
203// extend the existing array of allowed attributes and add my-attr to allow-list
204const clean = DOMPurify.sanitize(dirty, {ADD_ATTR: ['my-attr']});
205
206// prohibit ARIA attributes, leave other safe HTML as is (default is true)
207const clean = DOMPurify.sanitize(dirty, {ALLOW_ARIA_ATTR: false});
208
209// prohibit HTML5 data attributes, leave other safe HTML as is (default is true)
210const clean = DOMPurify.sanitize(dirty, {ALLOW_DATA_ATTR: false});
211```
212
213### Control behavior relating to Custom Elements
214```js
215// DOMPurify allows to define rules for Custom Elements. When using the CUSTOM_ELEMENT_HANDLING
216// literal, it is possible to define exactly what elements you wish to allow (by default, none are allowed).
217//
218// The same goes for their attributes. By default, the built-in or configured allow.list is used.
219//
220// You can use a RegExp literal to specify what is allowed or a predicate, examples for both can be seen below.
221// The default values are very restrictive to prevent accidental XSS bypasses. Handle with great care!
222
223const clean = DOMPurify.sanitize(
224 '<foo-bar baz="foobar" forbidden="true"></foo-bar><div is="foo-baz"></div>',
225 {
226 CUSTOM_ELEMENT_HANDLING: {
227 tagNameCheck: null, // no custom elements are allowed
228 attributeNameCheck: null, // default / standard attribute allow-list is used
229 allowCustomizedBuiltInElements: false, // no customized built-ins allowed
230 },
231 }
232); // <div is=""></div>
233
234const clean = DOMPurify.sanitize(
235 '<foo-bar baz="foobar" forbidden="true"></foo-bar><div is="foo-baz"></div>',
236 {
237 CUSTOM_ELEMENT_HANDLING: {
238 tagNameCheck: /^foo-/, // allow all tags starting with "foo-"
239 attributeNameCheck: /baz/, // allow all attributes containing "baz"
240 allowCustomizedBuiltInElements: true, // customized built-ins are allowed
241 },
242 }
243); // <foo-bar baz="foobar"></foo-bar><div is="foo-baz"></div>
244
245const clean = DOMPurify.sanitize(
246 '<foo-bar baz="foobar" forbidden="true"></foo-bar><div is="foo-baz"></div>',
247 {
248 CUSTOM_ELEMENT_HANDLING: {
249 tagNameCheck: (tagName) => tagName.match(/^foo-/), // allow all tags starting with "foo-"
250 attributeNameCheck: (attr) => attr.match(/baz/), // allow all containing "baz"
251 allowCustomizedBuiltInElements: true, // allow customized built-ins
252 },
253 }
254); // <foo-bar baz="foobar"></foo-bar><div is="foo-baz"></div>
255```
256### Control behavior relating to URI values
257```js
258// extend the existing array of elements that can use Data URIs
259const clean = DOMPurify.sanitize(dirty, {ADD_DATA_URI_TAGS: ['a', 'area']});
260
261// extend the existing array of elements that are safe for URI-like values (be careful, XSS risk)
262const clean = DOMPurify.sanitize(dirty, {ADD_URI_SAFE_ATTR: ['my-attr']});
263
264```
265### Control permitted attribute values
266```js
267// allow external protocol handlers in URL attributes (default is false, be careful, XSS risk)
268// by default only http, https, ftp, ftps, tel, mailto, callto, sms, cid and xmpp are allowed.
269const clean = DOMPurify.sanitize(dirty, {ALLOW_UNKNOWN_PROTOCOLS: true});
270
271// allow specific protocols handlers in URL attributes via regex (default is false, be careful, XSS risk)
272// by default only http, https, ftp, ftps, tel, mailto, callto, sms, cid and xmpp are allowed.
273// Default RegExp: /^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i;
274const clean = DOMPurify.sanitize(dirty, {ALLOWED_URI_REGEXP: /^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp|xxx):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i});
275
276```
277### Influence the return-type
278```js
279// return a DOM HTMLBodyElement instead of an HTML string (default is false)
280const clean = DOMPurify.sanitize(dirty, {RETURN_DOM: true});
281
282// return a DOM DocumentFragment instead of an HTML string (default is false)
283const clean = DOMPurify.sanitize(dirty, {RETURN_DOM_FRAGMENT: true});
284
285// use the RETURN_TRUSTED_TYPE flag to turn on Trusted Types support if available
286const clean = DOMPurify.sanitize(dirty, {RETURN_TRUSTED_TYPE: true}); // will return a TrustedHTML object instead of a string if possible
287
288// use a provided Trusted Types policy
289const clean = DOMPurify.sanitize(dirty, {
290 // supplied policy must define createHTML and createScriptURL
291 TRUSTED_TYPES_POLICY: trustedTypes.createPolicy({
292 createHTML(s) { return s},
293 createScriptURL(s) { return s},
294 }
295});
296```
297### Influence how we sanitize
298```js
299// return entire document including <html> tags (default is false)
300const clean = DOMPurify.sanitize(dirty, {WHOLE_DOCUMENT: true});
301
302// disable DOM Clobbering protection on output (default is true, handle with care, minor XSS risks here)
303const clean = DOMPurify.sanitize(dirty, {SANITIZE_DOM: false});
304
305// enforce strict DOM Clobbering protection via namespace isolation (default is false)
306// when enabled, isolates the namespace of named properties (i.e., `id` and `name` attributes)
307// from JS variables by prefixing them with the string `user-content-`
308const clean = DOMPurify.sanitize(dirty, {SANITIZE_NAMED_PROPS: true});
309
310// keep an element's content when the element is removed (default is true)
311const clean = DOMPurify.sanitize(dirty, {KEEP_CONTENT: false});
312
313// glue elements like style, script or others to document.body and prevent unintuitive browser behavior in several edge-cases (default is false)
314const clean = DOMPurify.sanitize(dirty, {FORCE_BODY: true});
315
316// remove all <a> elements under <p> elements that are removed
317const clean = DOMPurify.sanitize(dirty, {FORBID_CONTENTS: ['a'], FORBID_TAGS: ['p']});
318
319// change the parser type so sanitized data is treated as XML and not as HTML, which is the default
320const clean = DOMPurify.sanitize(dirty, {PARSER_MEDIA_TYPE: 'application/xhtml+xml'});
321```
322### Influence where we sanitize
323```js
324// use the IN_PLACE mode to sanitize a node "in place", which is much faster depending on how you use DOMPurify
325const dirty = document.createElement('a');
326dirty.setAttribute('href', 'javascript:alert(1)');
327
328const clean = DOMPurify.sanitize(dirty, {IN_PLACE: true}); // see https://github.com/cure53/DOMPurify/issues/288 for more info
329```
330
331There is even [more examples here](https://github.com/cure53/DOMPurify/tree/main/demos#what-is-this), showing how you can run, customize and configure DOMPurify to fit your needs.
332
333## Persistent Configuration
334
335Instead of repeatedly passing the same configuration to `DOMPurify.sanitize`, you can use the `DOMPurify.setConfig` method. Your configuration will persist until your next call to `DOMPurify.setConfig`, or until you invoke `DOMPurify.clearConfig` to reset it. Remember that there is only one active configuration, which means once it is set, all extra configuration parameters passed to `DOMPurify.sanitize` are ignored.
336
337## Hooks
338
339DOMPurify allows you to augment its functionality by attaching one or more functions with the `DOMPurify.addHook` method to one of the following hooks:
340
341- `beforeSanitizeElements`
342- `uponSanitizeElement` (No 's' - called for every element)
343- `afterSanitizeElements`
344- `beforeSanitizeAttributes`
345- `uponSanitizeAttribute`
346- `afterSanitizeAttributes`
347- `beforeSanitizeShadowDOM`
348- `uponSanitizeShadowNode`
349- `afterSanitizeShadowDOM`
350
351It passes the currently processed DOM node, when needed a literal with verified node and attribute data and the DOMPurify configuration to the callback. Check out the [MentalJS hook demo](https://github.com/cure53/DOMPurify/blob/main/demos/hooks-mentaljs-demo.html) to see how the API can be used nicely.
352
353_Example_:
354
355```js
356DOMPurify.addHook(
357 'beforeSanitizeElements',
358 function (currentNode, hookEvent, config) {
359 // Do something with the current node and return it
360 // You can also mutate hookEvent (i.e. set hookEvent.forceKeepAttr = true)
361 return currentNode;
362 }
363);
364```
365
366## Continuous Integration
367
368We are currently using Github Actions in combination with BrowserStack. This gives us the possibility to confirm for each and every commit that all is going according to plan in all supported browsers. Check out the build logs here: https://github.com/cure53/DOMPurify/actions
369
370You can further run local tests by executing `npm test`. The tests work fine with Node.js v0.6.2 and jsdom@8.5.0.
371
372All relevant commits will be signed with the key `0x24BB6BF4` for additional security (since 8th of April 2016).
373
374### Development and contributing
375
376#### Installation (`npm i`)
377
378We support `npm` officially. GitHub Actions workflow is configured to install dependencies using `npm`. When using deprecated version of `npm` we can not fully ensure the versions of installed dependencies which might lead to unanticipated problems.
379
380#### Scripts
381
382We rely on npm run-scripts for integrating with our tooling infrastructure. We use ESLint as a pre-commit hook to ensure code consistency. Moreover, to ease formatting we use [prettier](https://github.com/prettier/prettier) while building the `/dist` assets happens through `rollup`.
383
384These are our npm scripts:
385
386- `npm run dev` to start building while watching sources for changes
387- `npm run test` to run our test suite via jsdom and karma
388 - `test:jsdom` to only run tests through jsdom
389 - `test:karma` to only run tests through karma
390- `npm run lint` to lint the sources using ESLint (via xo)
391- `npm run format` to format our sources using prettier to ease to pass ESLint
392- `npm run build` to build our distribution assets minified and unminified as a UMD module
393 - `npm run build:umd` to only build an unminified UMD module
394 - `npm run build:umd:min` to only build a minified UMD module
395
396Note: all run scripts triggered via `npm run <script>`.
397
398There are more npm scripts but they are mainly to integrate with CI or are meant to be "private" for instance to amend build distribution files with every commit.
399
400## Security Mailing List
401
402We maintain a mailing list that notifies whenever a security-critical release of DOMPurify was published. This means, if someone found a bypass and we fixed it with a release (which always happens when a bypass was found) a mail will go out to that list. This usually happens within minutes or few hours after learning about a bypass. The list can be subscribed to here:
403
404[https://lists.ruhr-uni-bochum.de/mailman/listinfo/dompurify-security](https://lists.ruhr-uni-bochum.de/mailman/listinfo/dompurify-security)
405
406Feature releases will not be announced to this list.
407
408## Who contributed?
409
410Many people helped and help DOMPurify become what it is and need to be acknowledged here!
411
412[dcramer 💸](https://github.com/dcramer), [JGraph 💸](https://github.com/jgraph), [baekilda 💸](https://github.com/baekilda), [Healthchecks 💸](https://github.com/healthchecks), [Sentry 💸](https://github.com/getsentry), [jarrodldavis 💸](https://github.com/jarrodldavis), [CynegeticIO](https://github.com/CynegeticIO), [ssi02014 ❤️](https://github.com/ssi02014), [kevin_mizu](https://twitter.com/kevin_mizu), [GrantGryczan](https://github.com/GrantGryczan), [Lowdefy](https://twitter.com/lowdefy), [granlem](https://twitter.com/MaximeVeit), [oreoshake](https://github.com/oreoshake), [tdeekens ❤️](https://github.com/tdeekens), [peernohell ❤️](https://github.com/peernohell), [is2ei](https://github.com/is2ei), [SoheilKhodayari](https://github.com/SoheilKhodayari), [franktopel](https://github.com/franktopel), [NateScarlet](https://github.com/NateScarlet), [neilj](https://github.com/neilj), [fhemberger](https://github.com/fhemberger), [Joris-van-der-Wel](https://github.com/Joris-van-der-Wel), [ydaniv](https://github.com/ydaniv), [terjanq](https://twitter.com/terjanq), [filedescriptor](https://github.com/filedescriptor), [ConradIrwin](https://github.com/ConradIrwin), [gibson042](https://github.com/gibson042), [choumx](https://github.com/choumx), [0xSobky](https://github.com/0xSobky), [styfle](https://github.com/styfle), [koto](https://github.com/koto), [tlau88](https://github.com/tlau88), [strugee](https://github.com/strugee), [oparoz](https://github.com/oparoz), [mathiasbynens](https://github.com/mathiasbynens), [edg2s](https://github.com/edg2s), [dnkolegov](https://github.com/dnkolegov), [dhardtke](https://github.com/dhardtke), [wirehead](https://github.com/wirehead), [thorn0](https://github.com/thorn0), [styu](https://github.com/styu), [mozfreddyb](https://github.com/mozfreddyb), [mikesamuel](https://github.com/mikesamuel), [jorangreef](https://github.com/jorangreef), [jimmyhchan](https://github.com/jimmyhchan), [jameydeorio](https://github.com/jameydeorio), [jameskraus](https://github.com/jameskraus), [hyderali](https://github.com/hyderali), [hansottowirtz](https://github.com/hansottowirtz), [hackvertor](https://github.com/hackvertor), [freddyb](https://github.com/freddyb), [flavorjones](https://github.com/flavorjones), [djfarrelly](https://github.com/djfarrelly), [devd](https://github.com/devd), [camerondunford](https://github.com/camerondunford), [buu700](https://github.com/buu700), [buildog](https://github.com/buildog), [alabiaga](https://github.com/alabiaga), [Vector919](https://github.com/Vector919), [Robbert](https://github.com/Robbert), [GreLI](https://github.com/GreLI), [FuzzySockets](https://github.com/FuzzySockets), [ArtemBernatskyy](https://github.com/ArtemBernatskyy), [@garethheyes](https://twitter.com/garethheyes), [@shafigullin](https://twitter.com/shafigullin), [@mmrupp](https://twitter.com/mmrupp), [@irsdl](https://twitter.com/irsdl),[ShikariSenpai](https://github.com/ShikariSenpai), [ansjdnakjdnajkd](https://github.com/ansjdnakjdnajkd), [@asutherland](https://twitter.com/asutherland), [@mathias](https://twitter.com/mathias), [@cgvwzq](https://twitter.com/cgvwzq), [@robbertatwork](https://twitter.com/robbertatwork), [@giutro](https://twitter.com/giutro), [@CmdEngineer\_](https://twitter.com/CmdEngineer_), [@avr4mit](https://twitter.com/avr4mit) and especially [@securitymb ❤️](https://twitter.com/securitymb) & [@masatokinugawa ❤️](https://twitter.com/masatokinugawa)
413
414## Testing powered by
415
416<a target="_blank" href="https://www.browserstack.com/"><img width="200" src="https://www.browserstack.com/images/layout/browserstack-logo-600x315.png"></a><br>
417
418And last but not least, thanks to [BrowserStack Open-Source Program](https://www.browserstack.com/open-source) for supporting this project with their services for free and delivering excellent, dedicated and very professional support on top of that.
Note: See TracBrowser for help on using the repository browser.