├── .editorconfig ├── .github └── workflows │ ├── bb.yml │ └── main.yml ├── .gitignore ├── .npmrc ├── .prettierignore ├── index.js ├── lib └── index.js ├── license ├── package.json ├── readme.md ├── test.js └── tsconfig.json /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | -------------------------------------------------------------------------------- /.github/workflows/bb.yml: -------------------------------------------------------------------------------- 1 | name: bb 2 | on: 3 | issues: 4 | types: [opened, reopened, edited, closed, labeled, unlabeled] 5 | pull_request_target: 6 | types: [opened, reopened, edited, closed, labeled, unlabeled] 7 | jobs: 8 | main: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: unifiedjs/beep-boop-beta@main 12 | with: 13 | repo-token: ${{secrets.GITHUB_TOKEN}} 14 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: main 2 | on: 3 | - pull_request 4 | - push 5 | jobs: 6 | main: 7 | name: ${{matrix.node}} 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v3 11 | - uses: actions/setup-node@v3 12 | with: 13 | node-version: ${{matrix.node}} 14 | - run: npm install 15 | - run: npm test 16 | - uses: codecov/codecov-action@v3 17 | strategy: 18 | matrix: 19 | node: 20 | - lts/gallium 21 | - node 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | coverage/ 2 | node_modules/ 3 | .DS_Store 4 | *.d.ts 5 | *.log 6 | yarn.lock 7 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | ignore-scripts=true 2 | package-lock=false 3 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | coverage/ 2 | *.md 3 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @typedef {import('hast-util-sanitize').Schema} Options 3 | */ 4 | 5 | export {defaultSchema} from 'hast-util-sanitize' 6 | export {default} from './lib/index.js' 7 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @typedef {import('hast').Root} Root 3 | * @typedef {import('hast-util-sanitize').Schema} Schema 4 | */ 5 | 6 | import {sanitize} from 'hast-util-sanitize' 7 | 8 | /** 9 | * Sanitize HTML. 10 | * 11 | * @param {Schema | null | undefined} [options] 12 | * Configuration (optional). 13 | * @returns 14 | * Transform. 15 | */ 16 | export default function rehypeSanitize(options) { 17 | /** 18 | * @param {Root} tree 19 | * Tree. 20 | * @returns {Root} 21 | * New tree. 22 | */ 23 | return function (tree) { 24 | // Assume root in -> root out. 25 | const result = /** @type {Root} */ (sanitize(tree, options)) 26 | return result 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /license: -------------------------------------------------------------------------------- 1 | (The MIT License) 2 | 3 | Copyright (c) 2016 Titus Wormer 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | 'Software'), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rehype-sanitize", 3 | "version": "6.0.0", 4 | "description": "rehype plugin to sanitize HTML", 5 | "license": "MIT", 6 | "keywords": [ 7 | "clean", 8 | "html", 9 | "plugin", 10 | "rehype", 11 | "rehype-plugin", 12 | "sanitize", 13 | "unified", 14 | "xss" 15 | ], 16 | "repository": "rehypejs/rehype-sanitize", 17 | "bugs": "https://github.com/rehypejs/rehype-sanitize/issues", 18 | "funding": { 19 | "type": "opencollective", 20 | "url": "https://opencollective.com/unified" 21 | }, 22 | "author": "Titus Wormer (https://wooorm.com)", 23 | "contributors": [ 24 | "Titus Wormer (https://wooorm.com)" 25 | ], 26 | "sideEffects": false, 27 | "type": "module", 28 | "exports": "./index.js", 29 | "files": [ 30 | "lib/", 31 | "index.d.ts", 32 | "index.js" 33 | ], 34 | "dependencies": { 35 | "@types/hast": "^3.0.0", 36 | "hast-util-sanitize": "^5.0.0" 37 | }, 38 | "devDependencies": { 39 | "@types/node": "^20.0.0", 40 | "c8": "^8.0.0", 41 | "deepmerge": "^4.0.0", 42 | "prettier": "^3.0.0", 43 | "rehype": "^13.0.0", 44 | "remark-cli": "^11.0.0", 45 | "remark-preset-wooorm": "^9.0.0", 46 | "type-coverage": "^2.0.0", 47 | "typescript": "^5.0.0", 48 | "xo": "^0.56.0" 49 | }, 50 | "scripts": { 51 | "build": "tsc --build --clean && tsc --build && type-coverage", 52 | "format": "remark . --frail --output --quiet && prettier . --log-level warn --write && xo --fix", 53 | "prepack": "npm run build && npm run format", 54 | "test": "npm run build && npm run format && npm run test-coverage", 55 | "test-api": "node --conditions development test.js", 56 | "test-coverage": "c8 --100 --check-coverage --reporter lcov npm run test-api" 57 | }, 58 | "prettier": { 59 | "bracketSpacing": false, 60 | "singleQuote": true, 61 | "semi": false, 62 | "tabWidth": 2, 63 | "trailingComma": "none", 64 | "useTabs": false 65 | }, 66 | "remarkConfig": { 67 | "plugins": [ 68 | "remark-preset-wooorm" 69 | ] 70 | }, 71 | "typeCoverage": { 72 | "atLeast": 100, 73 | "detail": true, 74 | "ignoreCatch": true, 75 | "strict": true 76 | }, 77 | "xo": { 78 | "prettier": true 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # rehype-sanitize 2 | 3 | [![Build][build-badge]][build] 4 | [![Coverage][coverage-badge]][coverage] 5 | [![Downloads][downloads-badge]][downloads] 6 | [![Size][size-badge]][size] 7 | [![Sponsors][sponsors-badge]][collective] 8 | [![Backers][backers-badge]][collective] 9 | [![Chat][chat-badge]][chat] 10 | 11 | **[rehype][]** plugin to sanitize HTML. 12 | 13 | ## Contents 14 | 15 | * [What is this?](#what-is-this) 16 | * [When should I use this?](#when-should-i-use-this) 17 | * [Install](#install) 18 | * [Use](#use) 19 | * [API](#api) 20 | * [`defaultSchema`](#defaultschema) 21 | * [`unified().use(rehypeSanitize[, schema])`](#unifieduserehypesanitize-schema) 22 | * [`Options`](#options) 23 | * [Example](#example) 24 | * [Example: headings (DOM clobbering)](#example-headings-dom-clobbering) 25 | * [Example: math](#example-math) 26 | * [Example: syntax highlighting](#example-syntax-highlighting) 27 | * [Types](#types) 28 | * [Compatibility](#compatibility) 29 | * [Security](#security) 30 | * [Related](#related) 31 | * [Contribute](#contribute) 32 | * [License](#license) 33 | 34 | ## What is this? 35 | 36 | This package is a [unified][] ([rehype][]) plugin to make sure HTML is safe. 37 | It drops anything that isn’t explicitly allowed by a schema (defaulting to how 38 | `github.com` works). 39 | 40 | **unified** is a project that transforms content with abstract syntax trees 41 | (ASTs). 42 | **rehype** adds support for HTML to unified. 43 | **hast** is the HTML AST that rehype uses. 44 | This is a rehype plugin that transforms hast. 45 | 46 | ## When should I use this? 47 | 48 | It’s recommended to sanitize your HTML any time you do not completely trust 49 | authors or the plugins being used. 50 | 51 | This plugin is built on [`hast-util-sanitize`][hast-util-sanitize], which cleans 52 | [hast][] syntax trees. 53 | rehype focusses on making it easier to transform content by abstracting such 54 | internals away. 55 | 56 | ## Install 57 | 58 | This package is [ESM only][esm]. 59 | In Node.js (version 16+), install with [npm][]: 60 | 61 | ```sh 62 | npm install rehype-sanitize 63 | ``` 64 | 65 | In Deno with [`esm.sh`][esmsh]: 66 | 67 | ```js 68 | import rehypeSanitize from 'https://esm.sh/rehype-sanitize@6' 69 | ``` 70 | 71 | In browsers with [`esm.sh`][esmsh]: 72 | 73 | ```html 74 | 77 | ``` 78 | 79 | ## Use 80 | 81 | Say we have the following file `index.html`: 82 | 83 | ```html 84 |
85 | delta 86 | 87 | 88 | 89 | 90 | 91 |
92 | 95 | ``` 96 | 97 | …and our module `example.js` looks as follows: 98 | 99 | ```js 100 | import rehypeParse from 'rehype-parse' 101 | import rehypeSanitize from 'rehype-sanitize' 102 | import rehypeStringify from 'rehype-stringify' 103 | import {read} from 'to-vfile' 104 | import {unified} from 'unified' 105 | 106 | const file = await unified() 107 | .use(rehypeParse, {fragment: true}) 108 | .use(rehypeSanitize) 109 | .use(rehypeStringify) 110 | .process(await read('index.html')) 111 | 112 | console.log(String(file)) 113 | ``` 114 | 115 | Now running `node example.js` yields: 116 | 117 | ```html 118 |
119 | delta 120 | 121 | 122 | 123 | 124 | 125 |
126 | ``` 127 | 128 | ## API 129 | 130 | This package exports the identifier [`defaultSchema`][api-default-schema]. 131 | The default export is [`rehypeSanitize`][api-rehype-sanitize]. 132 | 133 | ### `defaultSchema` 134 | 135 | Default schema ([`Options`][api-options]). 136 | 137 | Follows GitHub style sanitation. 138 | 139 | ### `unified().use(rehypeSanitize[, schema])` 140 | 141 | Sanitize HTML. 142 | 143 | ###### Parameters 144 | 145 | * `options` ([`Options`][api-options], optional) 146 | — configuration 147 | 148 | ###### Returns 149 | 150 | Transform ([`Transformer`][unified-transformer]). 151 | 152 | ### `Options` 153 | 154 | Schema that defines what nodes and properties are allowed (TypeScript type). 155 | 156 | This option is a bit advanced as it requires knowledge of syntax trees, so see 157 | the docs for [`Schema` in `hast-util-sanitize`][hast-util-sanitize-schema]. 158 | 159 | ## Example 160 | 161 | ### Example: headings (DOM clobbering) 162 | 163 | DOM clobbering is an attack in which malicious HTML confuses an application by 164 | naming elements, through `id` or `name` attributes, such that they overshadow 165 | presumed properties in `window` (the global scope in browsers). 166 | DOM clobbering often occurs when user content is used to generate heading IDs. 167 | To illustrate, say we have this `browser.js` file: 168 | 169 | ```js 170 | console.log(current) 171 | ``` 172 | 173 | And our module `example.js` contains: 174 | 175 | ```js 176 | /** 177 | * @typedef {import('hast').Root} Root 178 | */ 179 | 180 | import fs from 'node:fs/promises' 181 | import rehypeParse from 'rehype-parse' 182 | import rehypeStringify from 'rehype-stringify' 183 | import {unified} from 'unified' 184 | 185 | const browser = String(await fs.readFile('browser.js')) 186 | const document = ` 187 |

Current

188 | ${`

${'Lorem ipsum dolor sit amet. '.repeat(20)}

\n`.repeat(20)} 189 |

Link to current, link to old.` 190 | 191 | const file = await unified() 192 | .use(rehypeParse, {fragment: true}) 193 | .use(function () { 194 | /** 195 | * @param {Root} tree 196 | */ 197 | return function (tree) { 198 | tree.children.push({ 199 | type: 'element', 200 | tagName: 'script', 201 | properties: {type: 'module'}, 202 | children: [{type: 'text', value: browser}] 203 | }) 204 | } 205 | }) 206 | .use(rehypeStringify) 207 | .process(document) 208 | 209 | await fs.writeFile('output.html', String(file)) 210 | ``` 211 | 212 | This code processes HTML, inlines our browser script into it, and writes it out. 213 | The input HTML models how markdown often looks on platforms like GitHub, which 214 | allow heading IDs to be generated from their text and embedded HTML (including 215 | ``, which can be used to create anchors for renamed headings 216 | to prevent links from breaking). 217 | The generated HTML looks like: 218 | 219 | ```html 220 | 221 |

Current

222 |

Lorem ipsum dolor sit amet.

223 |

Link to current, link to old.

224 | 225 | ``` 226 | 227 | When you run this code locally and open the generated `output.html`, you can 228 | observe that the links at the bottom work, but also that the `

` element 229 | is printed to the console (the clobbering). 230 | 231 | `rehype-sanitize` solves the clobbering by prefixing every `id` and `name` 232 | attribute with `'user-content-'`. 233 | Changing `example.js`: 234 | 235 | ```diff 236 | @@ -15,6 +15,7 @@ ${`

${'Lorem ipsum dolor sit amet. '.repeat(20)}

\n`.repeat(20)} 237 | 238 | const file = await unified() 239 | .use(rehypeParse, {fragment: true}) 240 | + .use(rehypeSanitize) 241 | .use(function () { 242 | /** 243 | * @param {Root} tree 244 | ``` 245 | 246 | Now yields: 247 | 248 | ```diff 249 | - 250 | -

Current

251 | + 252 | +

Current

253 | ``` 254 | 255 | This introduces another problem as the links are now broken. 256 | It could perhaps be solved by changing all links, but that would make the links 257 | rather ugly, and we’d need to track what IDs we have outside of the user content 258 | on our pages too. 259 | Alternatively, and what arguably looks better, we could rewrite pretty links to 260 | their safe but ugly prefixed elements. 261 | This is what GitHub does. 262 | Replace `browser.js` with the following: 263 | 264 | ```js 265 | /// 266 | /* eslint-env browser */ 267 | 268 | // Page load (you could wrap this in a DOM ready if the script is loaded early). 269 | hashchange() 270 | 271 | // When URL changes. 272 | window.addEventListener('hashchange', hashchange) 273 | 274 | // When on the URL already, perhaps after scrolling, and clicking again, which 275 | // doesn’t emit `hashchange`. 276 | document.addEventListener( 277 | 'click', 278 | function (event) { 279 | if ( 280 | event.target && 281 | event.target instanceof HTMLAnchorElement && 282 | event.target.href === location.href && 283 | location.hash.length > 1 284 | ) { 285 | setImmediate(function () { 286 | if (!event.defaultPrevented) { 287 | hashchange() 288 | } 289 | }) 290 | } 291 | }, 292 | false 293 | ) 294 | 295 | function hashchange() { 296 | /** @type {string | undefined} */ 297 | let hash 298 | 299 | try { 300 | hash = decodeURIComponent(location.hash.slice(1)).toLowerCase() 301 | } catch { 302 | return 303 | } 304 | 305 | const name = 'user-content-' + hash 306 | const target = 307 | document.getElementById(name) || document.getElementsByName(name)[0] 308 | 309 | if (target) { 310 | setImmediate(function () { 311 | target.scrollIntoView() 312 | }) 313 | } 314 | } 315 | ``` 316 | 317 | ### Example: math 318 | 319 | Math can be enabled in rehype by using the plugins 320 | [`rehype-katex`][rehype-katex] or [`rehype-mathjax`][rehype-mathjax]. 321 | The operate on elements with certain classes and inject complex markup and of 322 | inline styles, most of which this plugin will remove. 323 | Say our module `example.js` contains: 324 | 325 | ```js 326 | import rehypeKatex from 'rehype-katex' 327 | import rehypeParse from 'rehype-parse' 328 | import rehypeSanitize from 'rehype-sanitize' 329 | import rehypeStringify from 'rehype-stringify' 330 | import {unified} from 'unified' 331 | 332 | const file = await unified() 333 | .use(rehypeParse, {fragment: true}) 334 | .use(rehypeKatex) 335 | .use(rehypeSanitize) 336 | .use(rehypeStringify) 337 | .process('L') 338 | 339 | console.log(String(file)) 340 | ``` 341 | 342 | Running that yields: 343 | 344 | ```html 345 | LLL 346 | ``` 347 | 348 | It is possible to pass a schema which allows MathML and inline styles, but it 349 | would be complex and allows *all* inline styles, which is unsafe. 350 | Alternatively, and arguably better, would be to *first* sanitize the HTML, 351 | allowing only the specific classes that `rehype-katex` and `rehype-mathjax` use, 352 | and *then* using those plugins: 353 | 354 | ```diff 355 | @@ -1,13 +1,20 @@ 356 | import rehypeKatex from 'rehype-katex' 357 | import rehypeParse from 'rehype-parse' 358 | -import rehypeSanitize from 'rehype-sanitize' 359 | +import rehypeSanitize, {defaultSchema} from 'rehype-sanitize' 360 | import rehypeStringify from 'rehype-stringify' 361 | import {unified} from 'unified' 362 | 363 | const file = await unified() 364 | .use(rehypeParse, {fragment: true}) 365 | + .use(rehypeSanitize, { 366 | + ...defaultSchema, 367 | + attributes: { 368 | + ...defaultSchema.attributes, 369 | + // The `language-*` regex is allowed by default. 370 | + code: [['className', /^language-./, 'math-inline', 'math-display']] 371 | + } 372 | + }) 373 | .use(rehypeKatex) 374 | - .use(rehypeSanitize) 375 | .use(rehypeStringify) 376 | .process('L') 377 | ``` 378 | 379 | Running that yields: 380 | 381 | ```html 382 | 383 | ``` 384 | 385 | ### Example: syntax highlighting 386 | 387 | Highlighting, for example with [`rehype-highlight`][rehype-highlight], can be 388 | solved similar to how math is solved (see previous example). 389 | That is, use `rehype-sanitize` and allow the classes needed for highlighting, 390 | and highlight afterwards: 391 | 392 | ```js 393 | import rehypeHighlight from 'rehype-highlight' 394 | import rehypeParse from 'rehype-parse' 395 | import rehypeSanitize, {defaultSchema} from 'rehype-sanitize' 396 | import rehypeStringify from 'rehype-stringify' 397 | import {unified} from 'unified' 398 | 399 | const file = await unified() 400 | .use(rehypeParse, {fragment: true}) 401 | .use(rehypeSanitize, { 402 | ...defaultSchema, 403 | attributes: { 404 | ...defaultSchema.attributes, 405 | code: [ 406 | ...(defaultSchema.attributes.code || []), 407 | // List of all allowed languages: 408 | ['className', 'language-js', 'language-css', 'language-md'] 409 | ] 410 | } 411 | }) 412 | .use(rehypeHighlight, {subset: false}) 413 | .use(rehypeStringify) 414 | .process('
console.log(1)
') 415 | 416 | console.log(String(file)) 417 | ``` 418 | 419 | Alternatively, it’s possible to make highlighting safe by allowing all the 420 | classes used on tokens. 421 | Modifying the above code like so: 422 | 423 | ```diff 424 | const file = await unified() 425 | .use(rehypeParse, {fragment: true}) 426 | + .use(rehypeHighlight, {subset: false}) 427 | .use(rehypeSanitize, { 428 | ...defaultSchema, 429 | attributes: { 430 | ...defaultSchema.attributes, 431 | - code: [ 432 | - ...(defaultSchema.attributes.code || []), 433 | - // List of all allowed languages: 434 | - ['className', 'hljs', 'language-js', 'language-css', 'language-md'] 435 | + span: [ 436 | + ...(defaultSchema.attributes.span || []), 437 | + // List of all allowed tokens: 438 | + ['className', 'hljs-addition', 'hljs-attr', 'hljs-attribute', 'hljs-built_in', 'hljs-bullet', 'hljs-char', 'hljs-code', 'hljs-comment', 'hljs-deletion', 'hljs-doctag', 'hljs-emphasis', 'hljs-formula', 'hljs-keyword', 'hljs-link', 'hljs-literal', 'hljs-meta', 'hljs-name', 'hljs-number', 'hljs-operator', 'hljs-params', 'hljs-property', 'hljs-punctuation', 'hljs-quote', 'hljs-regexp', 'hljs-section', 'hljs-selector-attr', 'hljs-selector-class', 'hljs-selector-id', 'hljs-selector-pseudo', 'hljs-selector-tag', 'hljs-string', 'hljs-strong', 'hljs-subst', 'hljs-symbol', 'hljs-tag', 'hljs-template-tag', 'hljs-template-variable', 'hljs-title', 'hljs-type', 'hljs-variable' 439 | + ] 440 | ] 441 | } 442 | }) 443 | - .use(rehypeHighlight, {subset: false}) 444 | .use(rehypeStringify) 445 | .process('
console.log(1)
') 446 | ``` 447 | 448 | ## Types 449 | 450 | This package is fully typed with [TypeScript][]. 451 | It exports the additional type [`Options`][api-options]. 452 | 453 | ## Compatibility 454 | 455 | Projects maintained by the unified collective are compatible with maintained 456 | versions of Node.js. 457 | 458 | When we cut a new major release, we drop support for unmaintained versions of 459 | Node. 460 | This means we try to keep the current release line, `rehype-sanitize@^6`, 461 | compatible with Node.js 16. 462 | 463 | This plugin works with `rehype-parse` version 3+, `rehype-stringify` version 3+, 464 | `rehype` version 5+, and `unified` version 6+. 465 | 466 | ## Security 467 | 468 | The defaults are safe but improper use of `rehype-sanitize` can open you up to a 469 | [cross-site scripting (XSS)][xss] attack. 470 | 471 | Use `rehype-sanitize` after the last unsafe thing: everything after 472 | `rehype-sanitize` could be unsafe (but is fine if you do trust it). 473 | 474 | ## Related 475 | 476 | * [`hast-util-sanitize`](https://github.com/syntax-tree/hast-util-sanitize) 477 | — utility to sanitize [hast][] 478 | * [`rehype-format`](https://github.com/rehypejs/rehype-format) 479 | — format HTML 480 | * [`rehype-minify`](https://github.com/rehypejs/rehype-minify) 481 | — minify HTML 482 | 483 | ## Contribute 484 | 485 | See [`contributing.md`][contributing] in [`rehypejs/.github`][health] for ways 486 | to get started. 487 | See [`support.md`][support] for ways to get help. 488 | 489 | This project has a [code of conduct][coc]. 490 | By interacting with this repository, organization, or community you agree to 491 | abide by its terms. 492 | 493 | ## License 494 | 495 | [MIT][license] © [Titus Wormer][author] 496 | 497 | 498 | 499 | [build-badge]: https://github.com/rehypejs/rehype-sanitize/workflows/main/badge.svg 500 | 501 | [build]: https://github.com/rehypejs/rehype-sanitize/actions 502 | 503 | [coverage-badge]: https://img.shields.io/codecov/c/github/rehypejs/rehype-sanitize.svg 504 | 505 | [coverage]: https://codecov.io/github/rehypejs/rehype-sanitize 506 | 507 | [downloads-badge]: https://img.shields.io/npm/dm/rehype-sanitize.svg 508 | 509 | [downloads]: https://www.npmjs.com/package/rehype-sanitize 510 | 511 | [size-badge]: https://img.shields.io/bundlejs/size/rehype-sanitize 512 | 513 | [size]: https://bundlejs.com/?q=rehype-sanitize 514 | 515 | [sponsors-badge]: https://opencollective.com/unified/sponsors/badge.svg 516 | 517 | [backers-badge]: https://opencollective.com/unified/backers/badge.svg 518 | 519 | [collective]: https://opencollective.com/unified 520 | 521 | [chat-badge]: https://img.shields.io/badge/chat-discussions-success.svg 522 | 523 | [chat]: https://github.com/rehypejs/rehype/discussions 524 | 525 | [esm]: https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c 526 | 527 | [esmsh]: https://esm.sh 528 | 529 | [npm]: https://docs.npmjs.com/cli/install 530 | 531 | [health]: https://github.com/rehypejs/.github 532 | 533 | [contributing]: https://github.com/rehypejs/.github/blob/HEAD/contributing.md 534 | 535 | [support]: https://github.com/rehypejs/.github/blob/HEAD/support.md 536 | 537 | [coc]: https://github.com/rehypejs/.github/blob/HEAD/code-of-conduct.md 538 | 539 | [license]: license 540 | 541 | [author]: https://wooorm.com 542 | 543 | [xss]: https://en.wikipedia.org/wiki/Cross-site_scripting 544 | 545 | [typescript]: https://www.typescriptlang.org 546 | 547 | [hast]: https://github.com/syntax-tree/hast 548 | 549 | [hast-util-sanitize]: https://github.com/syntax-tree/hast-util-sanitize 550 | 551 | [hast-util-sanitize-schema]: https://github.com/syntax-tree/hast-util-sanitize#schema 552 | 553 | [rehype]: https://github.com/rehypejs/rehype 554 | 555 | [rehype-katex]: https://github.com/remarkjs/remark-math/tree/main/packages/rehype-katex 556 | 557 | [rehype-mathjax]: https://github.com/remarkjs/remark-math/tree/main/packages/rehype-mathjax 558 | 559 | [rehype-highlight]: https://github.com/rehypejs/rehype-highlight 560 | 561 | [unified]: https://github.com/unifiedjs/unified 562 | 563 | [unified-transformer]: https://github.com/unifiedjs/unified#transformer 564 | 565 | [api-default-schema]: #defaultschema 566 | 567 | [api-options]: #options 568 | 569 | [api-rehype-sanitize]: #unifieduserehypesanitize-schema 570 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | import assert from 'node:assert/strict' 2 | import test from 'node:test' 3 | import merge from 'deepmerge' 4 | import {rehype} from 'rehype' 5 | import rehypeSanitize, {defaultSchema} from 'rehype-sanitize' 6 | 7 | test('rehypeSanitize', async function (t) { 8 | await t.test('should expose the public api', async function () { 9 | assert.deepEqual(Object.keys(await import('rehype-sanitize')).sort(), [ 10 | 'default', 11 | 'defaultSchema' 12 | ]) 13 | }) 14 | 15 | await t.test('should work', async function () { 16 | const file = await rehype() 17 | .use(rehypeSanitize) 18 | .process('') 19 | 20 | assert.equal(file.messages.length, 0) 21 | assert.equal(String(file), '') 22 | }) 23 | 24 | await t.test('should support options', async function () { 25 | const file = await rehype() 26 | .use(rehypeSanitize, merge(defaultSchema, {tagNames: ['math', 'mi']})) 27 | .process( 28 | '' 29 | ) 30 | 31 | assert.equal(String(file), '') 32 | }) 33 | }) 34 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "checkJs": true, 4 | "customConditions": ["development"], 5 | "declaration": true, 6 | "emitDeclarationOnly": true, 7 | "exactOptionalPropertyTypes": true, 8 | "lib": ["es2022"], 9 | "module": "node16", 10 | "strict": true, 11 | "target": "es2022" 12 | }, 13 | "exclude": ["coverage/", "node_modules/"], 14 | "include": ["**/*.js"] 15 | } 16 | --------------------------------------------------------------------------------