├── .gitignore ├── README.md ├── config.js ├── index.js ├── package.json └── src ├── attrs.js ├── fuzzer.js ├── generator.js ├── mutator.js ├── parser.js ├── tags.js ├── traverser.js └── worker.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | package-lock.json 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # jsfuxx 2 | 3 | js based dumb fuzzer targeted nodejs modules. 4 | -------------------------------------------------------------------------------- /config.js: -------------------------------------------------------------------------------- 1 | const Tags = {}; 2 | const Attributes = {}; 3 | const Chars = {}; 4 | 5 | Tags.FULL = [].concat(...Object.values(require('./src/tags'))); 6 | Tags.SAFE = [ 7 | "ALTGLYPHDEF", 8 | "ALTGLYPHITEM", 9 | "ANIMATEMOTION", 10 | "ANIMATETRANSFORM", 11 | "CIRCLE", 12 | "CLIPPATH", 13 | "DEFS", 14 | "DESC", 15 | "FECOLORMATRIX", 16 | "FECOMPONENTTRANSFER", 17 | "FEDIFFUSELIGHTING", 18 | "FEDISTANTLIGHT", 19 | "FEFUNCA", 20 | "FEFUNCB", 21 | "FEFUNCR", 22 | "FEGAUSSIANBLUR", 23 | "FEIMAGE", 24 | "FEMERGENODE", 25 | "FEMORPHOLOGY", 26 | "FEOFFSET", 27 | "FEPOINTLIGHT", 28 | "FESPECULARLIGHTING", 29 | "FETILE", 30 | "FETURBULENCE", 31 | "GLYPH", 32 | "GLYPHREF", 33 | "IMAGE", 34 | "LINEARGRADIENT", 35 | "MENCLOSE", 36 | "MERROR", 37 | "MFRAC", 38 | "MLABELEDTR", 39 | "MMULTISCRIPTS", 40 | "MN", 41 | "MO", 42 | "MPHANTOM", 43 | "MSPACE", 44 | "MSQRT", 45 | "MSTYLE", 46 | "MTD", 47 | "MUNDER", 48 | "MUNDEROVER", 49 | "PATH", 50 | "PATTERN", 51 | "SWITCH", 52 | "SYMBOL", 53 | "TEXT", 54 | "TSPAN", 55 | "VKERN", 56 | 'A', 57 | 'ABBR', 58 | 'ACRONYM', 59 | 'ADDRESS', 60 | 'ALTGLYPH', 61 | 'ANIMATECOLOR', 62 | 'ANIMATETRANFORM', 63 | 'AREA', 64 | 'ARTICLE', 65 | 'ASIDE', 66 | 'AUDIO', 67 | 'B', 68 | 'BDI', 69 | 'BDO', 70 | 'BIG', 71 | 'BLINK', 72 | 'BLOCKQUOTE', 73 | 'BODY', 74 | 'BR', 75 | 'BUTTON', 76 | 'CANVAS', 77 | 'CAPTION', 78 | 'CENTER', 79 | 'CITE', 80 | 'CODE', 81 | 'COL', 82 | 'COLGROUP', 83 | 'CONTENT', 84 | 'DATA', 85 | 'DATALIST', 86 | 'DD', 87 | 'DECORATOR', 88 | 'DEL', 89 | 'DEPS', 90 | 'DETAILS', 91 | 'DFN', 92 | 'DIALOG', 93 | 'DIR', 94 | 'DIV', 95 | 'DL', 96 | 'DT', 97 | 'ELEMENT', 98 | 'ELLIPSE', 99 | 'EM', 100 | 'FEBLEND', 101 | 'FECOMPOSITE', 102 | 'FECONVOLVEMATRIX', 103 | 'FEDISPLACEMENTMAP', 104 | 'FEFLOOD', 105 | 'FEFUNCG', 106 | 'FEMERGE', 107 | 'FESPOTLIGHT', 108 | 'FIELDSET', 109 | 'FIGCAPTION', 110 | 'FIGURE', 111 | 'FILTER', 112 | 'FONT', 113 | 'FOOTER', 114 | 'FORM', 115 | 'FSFUNCB', 116 | 'G', 117 | 'H', 118 | 'HEAD', 119 | 'HEADER', 120 | 'HGROUP', 121 | 'HKERN', 122 | 'HR', 123 | 'HTML', 124 | 'I', 125 | 'IMG', 126 | 'INPUT', 127 | 'INS', 128 | 'KBD', 129 | 'LABEL', 130 | 'LEGEND', 131 | 'LI', 132 | 'LINE', 133 | 'MAIN', 134 | 'MAP', 135 | 'MARK', 136 | 'MARKER', 137 | 'MARQUEE', 138 | 'MASK', 139 | 'MATH', 140 | 'MENU', 141 | 'MENUITEM', 142 | 'MENULIST', 143 | 'METADATA', 144 | 'METER', 145 | 'MFENCED', 146 | 'MGLYPH', 147 | 'MI', 148 | 'MOVER', 149 | 'MPADDED', 150 | 'MPATH', 151 | 'MROOT', 152 | 'MROW', 153 | 'MS', 154 | 'MSUB', 155 | 'MSUBSUP', 156 | 'MSUP', 157 | 'MTABLE', 158 | 'MTEXT', 159 | 'MTR', 160 | 'NAV', 161 | 'NOBR', 162 | 'NUMBER', 163 | 'OL', 164 | 'OPTGROUP', 165 | 'OPTION', 166 | 'OUTPUT', 167 | 'P', 168 | 'PICTURE', 169 | 'POLYGON', 170 | 'POLYLINE', 171 | 'PRE', 172 | 'PROGRESS', 173 | 'Q', 174 | 'RADIALGRADIENT', 175 | 'RB', 176 | 'RECT', 177 | 'RP', 178 | 'RT', 179 | 'RTC', 180 | 'RUBY', 181 | 'S', 182 | 'SAMP', 183 | 'SECTION', 184 | 'SELECT', 185 | 'SHADOW', 186 | 'SMALL', 187 | 'SOURCE', 188 | 'SPACER', 189 | 'SPAN', 190 | 'STOP', 191 | 'STRIKE', 192 | 'STRONG', 193 | 'STYLE', 194 | 'SUB', 195 | 'SUMMARY', 196 | 'SUP', 197 | 'SVG', 198 | 'TABLE', 199 | 'TBODY', 200 | 'TD', 201 | 'TEMPLATE', 202 | 'TEXTAREA', 203 | 'TEXTPATH', 204 | 'TFOOT', 205 | 'TH', 206 | 'THEAD', 207 | 'TIME', 208 | 'TITLE', 209 | 'TR', 210 | 'TRACK', 211 | 'TREF', 212 | 'TT', 213 | 'U', 214 | 'UL', 215 | 'VAR', 216 | 'VIDEO', 217 | 'VIEW', 218 | 'WBR', 219 | 'H1', 220 | 'H2', 221 | 'H3', 222 | 'H4', 223 | 'H5', 224 | 'H6', 225 | 'H7' 226 | ]; 227 | 228 | Attributes.FULL = [].concat(...Object.values(require('./src/attrs'))); 229 | Attributes.SAFE = [ 230 | '0', 231 | '1', 232 | '2', 233 | '3', 234 | '4', 235 | '5', 236 | '6', 237 | '7', 238 | '8', 239 | '9', 240 | 'abbr', 241 | 'accent', 242 | 'accent-height', 243 | 'accentunder', 244 | 'accept', 245 | 'accumulate', 246 | 'action', 247 | 'additive', 248 | 'align', 249 | 'alignment-baseline', 250 | 'alt', 251 | 'ascent', 252 | 'attributename', 253 | 'attributetype', 254 | 'autocapitalize', 255 | 'autocomplete', 256 | 'autopictureinpicture', 257 | 'autoplay', 258 | 'azimuth', 259 | 'background', 260 | 'basefrequency', 261 | 'baseline-shift', 262 | 'begin', 263 | 'bevelled', 264 | 'bgcolor', 265 | 'bias', 266 | 'border', 267 | 'by', 268 | 'capture', 269 | 'cellpadding', 270 | 'cellspacing', 271 | 'checked', 272 | 'cite', 273 | 'class', 274 | 'clear', 275 | 'clip', 276 | 'clip-path', 277 | 'clip-rule', 278 | 'clippathunits', 279 | 'close', 280 | 'color', 281 | 'color-interpolation', 282 | 'color-interpolation-filters', 283 | 'color-profile', 284 | 'color-rendering', 285 | 'cols', 286 | 'colspan', 287 | 'columnlines', 288 | 'columnsalign', 289 | 'columnspan', 290 | 'controls', 291 | 'controlslist', 292 | 'coords', 293 | 'crossorigin', 294 | 'cx', 295 | 'cy', 296 | 'd', 297 | 'datetime', 298 | 'decoding', 299 | 'default', 300 | 'denomalign', 301 | 'depth', 302 | 'diffuseconstant', 303 | 'dir', 304 | 'direction', 305 | 'disabled', 306 | 'disablepictureinpicture', 307 | 'disableremoteplayback', 308 | 'display', 309 | 'displaystyle', 310 | 'divisor', 311 | 'download', 312 | 'draggable', 313 | 'dur', 314 | 'dx', 315 | 'dy', 316 | 'edgemode', 317 | 'elevation', 318 | 'encoding', 319 | 'enctype', 320 | 'end', 321 | 'enterkeyhint', 322 | 'face', 323 | 'fence', 324 | 'fill', 325 | 'fill-opacity', 326 | 'fill-rule', 327 | 'filter', 328 | 'filterunits', 329 | 'flood-color', 330 | 'flood-opacity', 331 | 'font-family', 332 | 'font-size', 333 | 'font-size-adjust', 334 | 'font-stretch', 335 | 'font-style', 336 | 'font-variant', 337 | 'font-weight', 338 | 'for', 339 | 'frame', 340 | 'fx', 341 | 'fy', 342 | 'g1', 343 | 'g2', 344 | 'glyph-name', 345 | 'glyphRef', 346 | 'glyphref', 347 | 'gradienttransform', 348 | 'gradientunits', 349 | 'headers', 350 | 'height', 351 | 'hidden', 352 | 'high', 353 | 'href', 354 | 'hreflang', 355 | 'id', 356 | 'image-rendering', 357 | 'in', 358 | 'in2', 359 | 'inputmode', 360 | 'integrity', 361 | 'is', 362 | 'ismap', 363 | 'k', 364 | 'k1', 365 | 'k2', 366 | 'k3', 367 | 'k4', 368 | 'kernelmatrix', 369 | 'kernelunitlength', 370 | 'kerning', 371 | 'keyPoints', 372 | 'keypoints', 373 | 'keysplines', 374 | 'keytimes', 375 | 'kind', 376 | 'label', 377 | 'lang', 378 | 'largeop', 379 | 'length', 380 | 'lengthadjust', 381 | 'letter-spacing', 382 | 'lighting-color', 383 | 'linethickness', 384 | 'list', 385 | 'loading', 386 | 'local', 387 | 'loop', 388 | 'low', 389 | 'lquote', 390 | 'lspace', 391 | 'marker-end', 392 | 'marker-mid', 393 | 'marker-start', 394 | 'markerheight', 395 | 'markerunits', 396 | 'markerwidth', 397 | 'mask', 398 | 'maskcontentunits', 399 | 'maskunits', 400 | 'mathbackground', 401 | 'mathcolor', 402 | 'mathsize', 403 | 'mathvariant', 404 | 'max', 405 | 'maxlength', 406 | 'maxsize', 407 | 'media', 408 | 'method', 409 | 'min', 410 | 'minlength', 411 | 'minsize', 412 | 'mode', 413 | 'movablelimits', 414 | 'multiple', 415 | 'muted', 416 | 'name', 417 | 'nonce', 418 | 'noshade', 419 | 'notation', 420 | 'novalidate', 421 | 'nowrap', 422 | 'numalign', 423 | 'numoctaves', 424 | 'offset', 425 | 'opacity', 426 | 'open', 427 | 'operator', 428 | 'optimum', 429 | 'order', 430 | 'orient', 431 | 'orientation', 432 | 'origin', 433 | 'overflow', 434 | 'paint-order', 435 | 'path', 436 | 'pathlength', 437 | 'pattern', 438 | 'patterncontentunits', 439 | 'patterntransform', 440 | 'patternunits', 441 | 'placeholder', 442 | 'playsinline', 443 | 'points', 444 | 'poster', 445 | 'preload', 446 | 'preservealpha', 447 | 'preserveaspectratio', 448 | 'primitiveunits', 449 | 'pubdate', 450 | 'r', 451 | 'radiogroup', 452 | 'radius', 453 | 'readonly', 454 | 'refx', 455 | 'refy', 456 | 'rel', 457 | 'repeatCount', 458 | 'repeatcount', 459 | 'repeatdur', 460 | 'required', 461 | 'restart', 462 | 'result', 463 | 'rev', 464 | 'reversed', 465 | 'role', 466 | 'rotate', 467 | 'rowalign', 468 | 'rowlines', 469 | 'rows', 470 | 'rowspacing', 471 | 'rowspan', 472 | 'rquote', 473 | 'rspace', 474 | 'rx', 475 | 'ry', 476 | 'scale', 477 | 'scope', 478 | 'scriptlevel', 479 | 'scriptminsize', 480 | 'scriptsizemultiplier', 481 | 'seed', 482 | 'selected', 483 | 'selection', 484 | 'separator', 485 | 'separators', 486 | 'shape', 487 | 'shape-rendering', 488 | 'size', 489 | 'sizes', 490 | 'slot', 491 | 'span', 492 | 'specularconstant', 493 | 'specularexponent', 494 | 'spellcheck', 495 | 'spreadmethod', 496 | 'src', 497 | 'srclang', 498 | 'srcset', 499 | 'start', 500 | 'startoffset', 501 | 'stddeviation', 502 | 'step', 503 | 'stitchtiles', 504 | 'stop-color', 505 | 'stop-opacity', 506 | 'stretchy', 507 | 'stroke', 508 | 'stroke-dasharray', 509 | 'stroke-dashoffset', 510 | 'stroke-linecap', 511 | 'stroke-linejoin', 512 | 'stroke-miterlimit', 513 | 'stroke-opacity', 514 | 'stroke-width', 515 | 'style', 516 | 'subscriptshift', 517 | 'summary', 518 | 'supscriptshift', 519 | 'surfacescale', 520 | 'symmetric', 521 | 'systemlanguage', 522 | 'tabindex', 523 | 'targetx', 524 | 'targety', 525 | 'text-anchor', 526 | 'text-decoration', 527 | 'text-rendering', 528 | 'textlength', 529 | 'title', 530 | 'transform', 531 | 'translate', 532 | 'type', 533 | 'u1', 534 | 'u2', 535 | 'unicode', 536 | 'usemap', 537 | 'valign', 538 | 'value', 539 | 'values', 540 | 'version', 541 | 'vert-adv-y', 542 | 'vert-origin-x', 543 | 'vert-origin-y', 544 | 'viewbox', 545 | 'visibility', 546 | 'voffset', 547 | 'width', 548 | 'word-spacing', 549 | 'wrap', 550 | 'writing-mode', 551 | 'x', 552 | 'x1', 553 | 'x2', 554 | 'xchannelselector', 555 | 'xlink:href', 556 | 'xlink:title', 557 | 'xml:id', 558 | 'xml:space', 559 | 'xmlns', 560 | 'xmlns:xlink', 561 | 'y', 562 | 'y1', 563 | 'y2', 564 | 'ychannelselector', 565 | 'z', 566 | 'zoomandpan', 567 | 'attributeName', 568 | 'attributeType', 569 | 'baseFrequency', 570 | 'diffuseConstant', 571 | 'kernelMatrix', 572 | 'markerHeight', 573 | 'numOctaves', 574 | 'patternContentUnits', 575 | 'refY', 576 | 'surfaceScale', 577 | 'systemLanguage', 578 | 'targetY', 579 | 'textLength' 580 | ]; 581 | 582 | Chars.SIMPLIFIED = '7abcdefghijklmnopqrstuvwxyz!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~ \t\n\r\x0b\x0c'; 583 | Chars.PRINTABLES = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~ \t\n\r\x0b\x0c'; 584 | Chars.MINIMUM = '?#hbia]t)7ug)k \t\n\r\x0b\x0c'; 585 | Chars.PICO = '?#hbia]t)7ug)k \t\x0b'; 586 | 587 | module.exports = { 588 | Tags, 589 | Attributes, 590 | Chars 591 | }; 592 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | const { cpus } = require('os'); 3 | const { Fuzzer } = require('./src/fuzzer.js'); 4 | const { Tags, Attributes, Chars } = require('./config.js'); 5 | 6 | const fuzzer = new Fuzzer({ 7 | parser: 'node-html-parser', 8 | traverser: 'list', 9 | mutators: [ 10 | 'marked', 11 | 'sanitize-html' 12 | ], 13 | generator: { 14 | type: 'dom', 15 | tags: Tags.FULL, 16 | attributes: Attributes.FULL, 17 | chars: Chars.PICO 18 | }, 19 | size: 32, 20 | num_of_workers: cpus().length 21 | }); 22 | 23 | fuzzer.run(); 24 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jsfuxx", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "1.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "ansi_up": "^5.1.0", 14 | "cheerio": "^1.0.0-rc.10", 15 | "colors": "^1.4.0", 16 | "dompurify": "^2.3.4", 17 | "ejs": "^3.1.6", 18 | "fast-xml-parser": "^4.0.2", 19 | "handlebars": "^4.7.7", 20 | "highlight.js": "^11.4.0", 21 | "html-dom-parser": "^1.0.4", 22 | "html-minifier": "^4.0.0", 23 | "html5parser": "^2.0.2", 24 | "htmlparser": "^1.7.7", 25 | "insane": "^2.6.2", 26 | "js-yaml": "^4.1.0", 27 | "jsdom": "^19.0.0", 28 | "markdown": "^0.5.0", 29 | "markdown-it": "^12.3.2", 30 | "marked": "^4.0.10", 31 | "md-to-pdf": "^5.0.1", 32 | "node-html-parser": "^5.2.0", 33 | "rehype-highlight": "^5.0.2", 34 | "rehype-parse": "^8.0.3", 35 | "rehype-sanitize": "^5.0.1", 36 | "rehype-stringify": "^9.0.2", 37 | "sanitize-html": "^2.6.1", 38 | "sanitizer": "^0.1.3", 39 | "serialize-javascript": "^6.0.0", 40 | "striptags": "^3.2.0", 41 | "uglify-js": "^3.14.5", 42 | "xml2js": "^0.4.23", 43 | "xss": "^1.0.10", 44 | "xss-clean": "^0.1.1" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/attrs.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | html: [ 3 | 'accept', 4 | 'action', 5 | 'align', 6 | 'alt', 7 | 'autocapitalize', 8 | 'autocomplete', 9 | 'autopictureinpicture', 10 | 'autoplay', 11 | 'background', 12 | 'bgcolor', 13 | 'border', 14 | 'capture', 15 | 'cellpadding', 16 | 'cellspacing', 17 | 'checked', 18 | 'cite', 19 | 'class', 20 | 'clear', 21 | 'color', 22 | 'cols', 23 | 'colspan', 24 | 'controls', 25 | 'controlslist', 26 | 'coords', 27 | 'crossorigin', 28 | 'datetime', 29 | 'decoding', 30 | 'default', 31 | 'dir', 32 | 'disabled', 33 | 'disablepictureinpicture', 34 | 'disableremoteplayback', 35 | 'download', 36 | 'draggable', 37 | 'enctype', 38 | 'enterkeyhint', 39 | 'face', 40 | 'for', 41 | 'headers', 42 | 'height', 43 | 'hidden', 44 | 'high', 45 | 'href', 46 | 'hreflang', 47 | 'id', 48 | 'inputmode', 49 | 'integrity', 50 | 'ismap', 51 | 'kind', 52 | 'label', 53 | 'lang', 54 | 'list', 55 | 'loading', 56 | 'loop', 57 | 'low', 58 | 'max', 59 | 'maxlength', 60 | 'media', 61 | 'method', 62 | 'min', 63 | 'minlength', 64 | 'multiple', 65 | 'muted', 66 | 'name', 67 | 'nonce', 68 | 'noshade', 69 | 'novalidate', 70 | 'nowrap', 71 | 'open', 72 | 'optimum', 73 | 'pattern', 74 | 'placeholder', 75 | 'playsinline', 76 | 'poster', 77 | 'preload', 78 | 'pubdate', 79 | 'radiogroup', 80 | 'readonly', 81 | 'rel', 82 | 'required', 83 | 'rev', 84 | 'reversed', 85 | 'role', 86 | 'rows', 87 | 'rowspan', 88 | 'spellcheck', 89 | 'scope', 90 | 'selected', 91 | 'shape', 92 | 'size', 93 | 'sizes', 94 | 'span', 95 | 'srclang', 96 | 'start', 97 | 'src', 98 | 'srcset', 99 | 'step', 100 | 'style', 101 | 'summary', 102 | 'tabindex', 103 | 'title', 104 | 'translate', 105 | 'type', 106 | 'usemap', 107 | 'valign', 108 | 'value', 109 | 'width', 110 | 'xmlns', 111 | 'slot', 112 | ], 113 | svg: [ 114 | 'accent-height', 115 | 'accumulate', 116 | 'additive', 117 | 'alignment-baseline', 118 | 'ascent', 119 | 'attributename', 120 | 'attributetype', 121 | 'azimuth', 122 | 'basefrequency', 123 | 'baseline-shift', 124 | 'begin', 125 | 'bias', 126 | 'by', 127 | 'class', 128 | 'clip', 129 | 'clippathunits', 130 | 'clip-path', 131 | 'clip-rule', 132 | 'color', 133 | 'color-interpolation', 134 | 'color-interpolation-filters', 135 | 'color-profile', 136 | 'color-rendering', 137 | 'cx', 138 | 'cy', 139 | 'd', 140 | 'dx', 141 | 'dy', 142 | 'diffuseconstant', 143 | 'direction', 144 | 'display', 145 | 'divisor', 146 | 'dur', 147 | 'edgemode', 148 | 'elevation', 149 | 'end', 150 | 'fill', 151 | 'fill-opacity', 152 | 'fill-rule', 153 | 'filter', 154 | 'filterunits', 155 | 'flood-color', 156 | 'flood-opacity', 157 | 'font-family', 158 | 'font-size', 159 | 'font-size-adjust', 160 | 'font-stretch', 161 | 'font-style', 162 | 'font-variant', 163 | 'font-weight', 164 | 'fx', 165 | 'fy', 166 | 'g1', 167 | 'g2', 168 | 'glyph-name', 169 | 'glyphref', 170 | 'gradientunits', 171 | 'gradienttransform', 172 | 'height', 173 | 'href', 174 | 'id', 175 | 'image-rendering', 176 | 'in', 177 | 'in2', 178 | 'k', 179 | 'k1', 180 | 'k2', 181 | 'k3', 182 | 'k4', 183 | 'kerning', 184 | 'keypoints', 185 | 'keysplines', 186 | 'keytimes', 187 | 'lang', 188 | 'lengthadjust', 189 | 'letter-spacing', 190 | 'kernelmatrix', 191 | 'kernelunitlength', 192 | 'lighting-color', 193 | 'local', 194 | 'marker-end', 195 | 'marker-mid', 196 | 'marker-start', 197 | 'markerheight', 198 | 'markerunits', 199 | 'markerwidth', 200 | 'maskcontentunits', 201 | 'maskunits', 202 | 'max', 203 | 'mask', 204 | 'media', 205 | 'method', 206 | 'mode', 207 | 'min', 208 | 'name', 209 | 'numoctaves', 210 | 'offset', 211 | 'operator', 212 | 'opacity', 213 | 'order', 214 | 'orient', 215 | 'orientation', 216 | 'origin', 217 | 'overflow', 218 | 'paint-order', 219 | 'path', 220 | 'pathlength', 221 | 'patterncontentunits', 222 | 'patterntransform', 223 | 'patternunits', 224 | 'points', 225 | 'preservealpha', 226 | 'preserveaspectratio', 227 | 'primitiveunits', 228 | 'r', 229 | 'rx', 230 | 'ry', 231 | 'radius', 232 | 'refx', 233 | 'refy', 234 | 'repeatcount', 235 | 'repeatdur', 236 | 'restart', 237 | 'result', 238 | 'rotate', 239 | 'scale', 240 | 'seed', 241 | 'shape-rendering', 242 | 'specularconstant', 243 | 'specularexponent', 244 | 'spreadmethod', 245 | 'startoffset', 246 | 'stddeviation', 247 | 'stitchtiles', 248 | 'stop-color', 249 | 'stop-opacity', 250 | 'stroke-dasharray', 251 | 'stroke-dashoffset', 252 | 'stroke-linecap', 253 | 'stroke-linejoin', 254 | 'stroke-miterlimit', 255 | 'stroke-opacity', 256 | 'stroke', 257 | 'stroke-width', 258 | 'style', 259 | 'surfacescale', 260 | 'systemlanguage', 261 | 'tabindex', 262 | 'targetx', 263 | 'targety', 264 | 'transform', 265 | 'text-anchor', 266 | 'text-decoration', 267 | 'text-rendering', 268 | 'textlength', 269 | 'type', 270 | 'u1', 271 | 'u2', 272 | 'unicode', 273 | 'values', 274 | 'viewbox', 275 | 'visibility', 276 | 'version', 277 | 'vert-adv-y', 278 | 'vert-origin-x', 279 | 'vert-origin-y', 280 | 'width', 281 | 'word-spacing', 282 | 'wrap', 283 | 'writing-mode', 284 | 'xchannelselector', 285 | 'ychannelselector', 286 | 'x', 287 | 'x1', 288 | 'x2', 289 | 'xmlns', 290 | 'y', 291 | 'y1', 292 | 'y2', 293 | 'z', 294 | 'zoomandpan', 295 | ], 296 | mathMl: [ 297 | 'accent', 298 | 'accentunder', 299 | 'align', 300 | 'bevelled', 301 | 'close', 302 | 'columnsalign', 303 | 'columnlines', 304 | 'columnspan', 305 | 'denomalign', 306 | 'depth', 307 | 'dir', 308 | 'display', 309 | 'displaystyle', 310 | 'encoding', 311 | 'fence', 312 | 'frame', 313 | 'height', 314 | 'href', 315 | 'id', 316 | 'largeop', 317 | 'length', 318 | 'linethickness', 319 | 'lspace', 320 | 'lquote', 321 | 'mathbackground', 322 | 'mathcolor', 323 | 'mathsize', 324 | 'mathvariant', 325 | 'maxsize', 326 | 'minsize', 327 | 'movablelimits', 328 | 'notation', 329 | 'numalign', 330 | 'open', 331 | 'rowalign', 332 | 'rowlines', 333 | 'rowspacing', 334 | 'rowspan', 335 | 'rspace', 336 | 'rquote', 337 | 'scriptlevel', 338 | 'scriptminsize', 339 | 'scriptsizemultiplier', 340 | 'selection', 341 | 'separator', 342 | 'separators', 343 | 'stretchy', 344 | 'subscriptshift', 345 | 'supscriptshift', 346 | 'symmetric', 347 | 'voffset', 348 | 'width', 349 | 'xmlns', 350 | ], 351 | xml: [ 352 | 'xlink:href', 353 | 'xml:id', 354 | 'xlink:title', 355 | 'xml:space', 356 | 'xmlns:xlink', 357 | ] 358 | }; 359 | -------------------------------------------------------------------------------- /src/fuzzer.js: -------------------------------------------------------------------------------- 1 | const { Worker } = require('worker_threads'); 2 | const colors = require('colors/safe'); 3 | 4 | const { 5 | Tags, 6 | Attributes 7 | } = require('../config'); 8 | 9 | class Fuzzer { 10 | workers = new Array(); 11 | initialized = false; 12 | 13 | constructor(config) { 14 | if (!config.parser || !config.traverser || !config.sanitizers && !config.mutators || !config.generator) { 15 | throw new Error('insuffcient argument'); 16 | } 17 | if (!config.size) { 18 | console.warn('The payload size are not specified!'); 19 | } 20 | if (!config.num_of_workers) { 21 | console.warn('The number of workers are not specified!'); 22 | } 23 | if (!['dom', 'text'].includes(config.generator.type)) { 24 | console.warn('Invalid generator type'); 25 | process.exit(-1); 26 | } 27 | this.parser = config.parser; 28 | this.traverser = config.traverser; 29 | this.mutators = config.mutators; 30 | this.sanitizers = config.sanitizers; 31 | this.generator = config.generator; 32 | this.size = config.size ?? 32; 33 | this.num_of_workers = config.num_of_workers ?? 8; 34 | } 35 | 36 | run() { 37 | if (this.initialized) { 38 | throw new Error('already running'); 39 | } 40 | this.initialized = true; 41 | 42 | for (let i = 0; i < this.num_of_workers; ++i) { 43 | const worker = new Worker('./src/worker'); 44 | worker.index = i; 45 | worker.iterations = 0; 46 | 47 | worker.postMessage({ 48 | type: 'intialize', 49 | config: { 50 | parser: this.parser, 51 | traverser: this.traverser, 52 | mutators: this.mutators, 53 | generator: this.generator, 54 | size: this.size, 55 | num_of_workers: this.num_of_workers 56 | } 57 | }); 58 | 59 | worker.on('message', (e) => { 60 | if (e.type === 'pong') { 61 | if (e.iterations <= 0 || e.iterations < worker.iterations) { 62 | throw new Error('unreachable'); 63 | } 64 | worker.iterations = e.iterations; 65 | worker.lastPong = Date.now(); 66 | } else if (e.type === 'error') { 67 | console.error(e); 68 | } else if (e.type === 'finding') { 69 | if (e.data.raw_input.indexOf(' { 85 | let sum = 0; 86 | ++count; 87 | 88 | for (let worker of this.workers) { 89 | sum += worker.iterations; 90 | } 91 | 92 | const elapsed = Date.now() - begin; 93 | 94 | process.stdout.write(`\r[${loadingIcon[count % 4]}] ${sum / 1000}k (${parseInt(sum / elapsed * 1000)}/s)`); 95 | }, 150); 96 | } 97 | } 98 | 99 | module.exports = { 100 | Fuzzer 101 | }; 102 | 103 | -------------------------------------------------------------------------------- /src/generator.js: -------------------------------------------------------------------------------- 1 | const crypto = require('crypto'); 2 | 3 | const randomInteger = (i) => { 4 | return Math.floor(Math.random() * i); 5 | } 6 | 7 | const shuffle_arr = (arr) => { 8 | for (let i = arr.length - 1; i > 0; i--) { 9 | const j = Math.floor(Math.random() * (i + 1)); 10 | [arr[i], arr[j]] = [arr[j], arr[i]]; 11 | } 12 | return arr; 13 | } 14 | 15 | class InputGenerator { 16 | _tags; 17 | _chars; 18 | _attributes; 19 | 20 | constructor(config) { 21 | if (!config.tags || !config.attributes || !config.chars) { 22 | throw new Error('insufficient arguments'); 23 | } 24 | this._tags = config.tags; 25 | this._attributes = config.attributes; 26 | this._chars = config.chars; 27 | } 28 | 29 | generate(size) { 30 | const bytes = crypto.randomBytes(size); 31 | const array = new Array(size); 32 | 33 | for (let i = 0; i < size; ++i) { 34 | array[i] = this._chars[bytes[i] % this._chars.length]; 35 | } 36 | return array.join(''); 37 | } 38 | } 39 | 40 | class DOMGenerator extends InputGenerator { 41 | randomTag() { 42 | return this._tags[randomInteger(this._tags.length)]; 43 | } 44 | randomAttribute() { 45 | return this._attributes[randomInteger(this._attributes.length)]; 46 | } 47 | generate(size) { 48 | let arr = []; 49 | let unclosed = []; 50 | 51 | for (let i = 0; i < size; ++i) { 52 | let unit_type = randomInteger(5); 53 | 54 | /* 0: `` 66 | } else if (unit_type == 3) { 67 | arr.push(this.randomAttribute() + ' >'); 68 | //4: 50%: `' ` 69 | //[> 5: `RANDOM_CHARS*24` <] 70 | } else if (unit_type == 4) { 71 | arr.push(super.generate(36)); 72 | } else { 73 | console.error('Unreachable'.red); 74 | process.exit(1); 75 | } 76 | } 77 | 78 | if (unclosed.length != 0) { 79 | unclosed = shuffle_arr(unclosed); 80 | 81 | for (let tag of unclosed) { 82 | arr.push(''); 83 | } 84 | } 85 | 86 | return arr.join(''); 87 | } 88 | }; 89 | 90 | module.exports = { 91 | InputGenerator, 92 | DOMGenerator 93 | }; 94 | -------------------------------------------------------------------------------- /src/mutator.js: -------------------------------------------------------------------------------- 1 | const sanitizer = require('sanitizer'); 2 | const sanitizeHtml = require('sanitize-html'); 3 | const xss = require('xss'); 4 | const insane = require('insane'); 5 | const marked = require('marked'); 6 | const MarkdownIt = require('markdown-it'); 7 | const { JSDOM } = require('jsdom'); 8 | const createDOMPurify = require('dompurify'); 9 | const AU = require('ansi_up'); 10 | const striptags = require('striptags'); 11 | const serializeJavascript = require('serialize-javascript'); 12 | const jsYaml = require('js-yaml'); 13 | const xml2js = require('xml2js') 14 | const highlightJs = require('highlight.js'); 15 | const cheerio = require('cheerio'); 16 | const uglifyJs = require('uglify-js'); 17 | const htmlMinifier = require('html-minifier'); 18 | const handlebars = require('handlebars'); 19 | const { XMLParser, XMLBuilder, XMLValidator} = require('fast-xml-parser'); 20 | const nodeHtmlParser = require('node-html-parser'); 21 | 22 | const MD = new MarkdownIt(); 23 | const window = new JSDOM('').window; 24 | const DOMPurify = createDOMPurify(window); 25 | const ansi_up = new AU.default; 26 | 27 | const Mutator = { 28 | /* markdown to html */ 29 | 'marked': (s) => { 30 | return marked.parse(s); 31 | }, 32 | 'markdown-it': (s) => { 33 | return MD.render(s); 34 | }, 35 | /* sanitizers */ 36 | 'sanitizer': (s) => { 37 | return sanitizer.sanitize(s); 38 | }, 39 | 'insane': (s, options = {}) => { 40 | return insane(s, options); 41 | }, 42 | 'DOMPurify': (s) => { 43 | return DOMPurify.sanitize(s); 44 | }, 45 | 'sanitize-html': (s) => { 46 | return sanitizeHtml(s); 47 | }, 48 | 'xss': (s, options = {}) => { 49 | return xss(s, options); 50 | }, 51 | 'ansi_up': (s) => { 52 | return ansi_up.ansi_to_html(s); 53 | }, 54 | 'striptags': (s) => { 55 | return striptags(s, ['a']); 56 | }, 57 | 'serialize-javascript': (s) => { 58 | return serializeJavascript(s); 59 | }, 60 | 'js-yaml': (s) => { 61 | return jsYaml.load(s); 62 | }, 63 | 'xml2js': (s) => { 64 | return xml2js.parseString(s); 65 | }, 66 | 'highlight.js': (s) => { 67 | return highlightJs.highlightAuto(s).value; 68 | }, 69 | 'cheerio': (s) => { 70 | return cheerio.load(s).html(); 71 | }, 72 | 'uglify-js': (s) => { 73 | const result = uglifyJs.minify(s); 74 | 75 | return result.code ?? ''; 76 | }, 77 | 'html-minifier': (s) => { 78 | return htmlMinifier.minify(s, { 79 | caseSensitive: false, 80 | collapseBooleanAttributes: true, 81 | collapseInlineTagWhitespace: true, 82 | collapseWhitespace: true, 83 | conservativeCollapse: true, 84 | continueOnParseError: true, 85 | keepClosingSlash: true, 86 | preserveLineBreaks: true, 87 | preventAttributesEscaping: true, 88 | processConditionalComments: true, 89 | removeAttributeQuotes: true, 90 | removeComments: false, 91 | removeEmptyAttributes: true 92 | }); 93 | }, 94 | 'handlebars': (s) => { 95 | return handlebars.compile(s)(); 96 | }, 97 | 'rehype-sanitize': async (s) => { 98 | const unified = (await import('unified')); 99 | const rehypeParse = (await import('rehype-parse')).default; 100 | const rehypeSanitize = (await import('rehype-sanitize')).default; 101 | const rehypeHighlight = (await import('rehype-highlight')).default; 102 | const rehypeStringify = (await import('rehype-stringify')).default; 103 | 104 | const file = await (unified.unified()) 105 | .use(rehypeParse, {fragment: true}) 106 | .use(rehypeSanitize) 107 | //.use(rehypeHighlight, {subset: false}) 108 | .use(rehypeStringify) 109 | .process(s) 110 | 111 | return String(file); 112 | }, 113 | 'fast-xml-parser': (s) => { 114 | const parser = new XMLParser(); 115 | let jObj = parser.parse(s); 116 | 117 | const builder = new XMLBuilder(); 118 | const xmlContent = builder.build(jObj); 119 | 120 | return xmlContent; 121 | }, 122 | 'node-html-parser': (s) => { 123 | return nodeHtmlParser.parse(s).outerHTML; 124 | } 125 | }; 126 | 127 | module.exports = { 128 | Mutator 129 | }; 130 | -------------------------------------------------------------------------------- /src/parser.js: -------------------------------------------------------------------------------- 1 | const nodeHtmlParser = require('node-html-parser'); 2 | const { JSDOM } = require('jsdom'); 3 | const parse5 = require('parse5'); 4 | const htmlparser2 = require('htmlparser2'); 5 | 6 | const Parser = { 7 | 'node-html-parser': (s) => { 8 | return nodeHtmlParser.parse(s); 9 | }, 10 | 'jsdom': (s) => { 11 | return new JSDOM(s).window.document; // this leaks memory :( 12 | }, 13 | 'parse5': (s) => { 14 | return parse5.parse(s); 15 | }, 16 | 'htmlparser2': (s) => { 17 | return htmlparser2.parseDocument(s); 18 | } 19 | }; 20 | 21 | module.exports = { 22 | Parser 23 | }; 24 | -------------------------------------------------------------------------------- /src/tags.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | html: [ 3 | 'a', 4 | 'abbr', 5 | 'acronym', 6 | 'address', 7 | 'area', 8 | 'article', 9 | 'aside', 10 | 'audio', 11 | 'b', 12 | 'bdi', 13 | 'bdo', 14 | 'big', 15 | 'blink', 16 | 'blockquote', 17 | 'body', 18 | 'br', 19 | 'button', 20 | 'canvas', 21 | 'caption', 22 | 'center', 23 | 'cite', 24 | 'code', 25 | 'col', 26 | 'colgroup', 27 | 'content', 28 | 'data', 29 | 'datalist', 30 | 'dd', 31 | 'decorator', 32 | 'del', 33 | 'details', 34 | 'dfn', 35 | 'dialog', 36 | 'dir', 37 | 'div', 38 | 'dl', 39 | 'dt', 40 | 'element', 41 | 'em', 42 | 'fieldset', 43 | 'figcaption', 44 | 'figure', 45 | 'font', 46 | 'footer', 47 | 'form', 48 | 'h1', 49 | 'h2', 50 | 'h3', 51 | 'h4', 52 | 'h5', 53 | 'h6', 54 | 'head', 55 | 'header', 56 | 'hgroup', 57 | 'hr', 58 | 'html', 59 | 'i', 60 | 'img', 61 | 'input', 62 | 'ins', 63 | 'kbd', 64 | 'label', 65 | 'legend', 66 | 'li', 67 | 'main', 68 | 'map', 69 | 'mark', 70 | 'marquee', 71 | 'menu', 72 | 'menuitem', 73 | 'meter', 74 | 'nav', 75 | 'nobr', 76 | 'ol', 77 | 'optgroup', 78 | 'option', 79 | 'output', 80 | 'p', 81 | 'picture', 82 | 'pre', 83 | 'progress', 84 | 'q', 85 | 'rp', 86 | 'rt', 87 | 'ruby', 88 | 's', 89 | 'samp', 90 | 'section', 91 | 'select', 92 | 'shadow', 93 | 'small', 94 | 'source', 95 | 'spacer', 96 | 'span', 97 | 'strike', 98 | 'strong', 99 | 'style', 100 | 'sub', 101 | 'summary', 102 | 'sup', 103 | 'table', 104 | 'tbody', 105 | 'td', 106 | 'template', 107 | 'textarea', 108 | 'tfoot', 109 | 'th', 110 | 'thead', 111 | 'time', 112 | 'tr', 113 | 'track', 114 | 'tt', 115 | 'u', 116 | 'ul', 117 | 'var', 118 | 'video', 119 | 'wbr', 120 | ], 121 | svg: [ 122 | 'svg', 123 | 'a', 124 | 'altglyph', 125 | 'altglyphdef', 126 | 'altglyphitem', 127 | 'animatecolor', 128 | 'animatemotion', 129 | 'animatetransform', 130 | 'circle', 131 | 'clippath', 132 | 'defs', 133 | 'desc', 134 | 'ellipse', 135 | 'filter', 136 | 'font', 137 | 'g', 138 | 'glyph', 139 | 'glyphref', 140 | 'hkern', 141 | 'image', 142 | 'line', 143 | 'lineargradient', 144 | 'marker', 145 | 'mask', 146 | 'metadata', 147 | 'mpath', 148 | 'path', 149 | 'pattern', 150 | 'polygon', 151 | 'polyline', 152 | 'radialgradient', 153 | 'rect', 154 | 'stop', 155 | 'style', 156 | 'switch', 157 | 'symbol', 158 | 'text', 159 | 'textpath', 160 | 'title', 161 | 'tref', 162 | 'tspan', 163 | 'view', 164 | 'vkern', 165 | ], 166 | svgFilters: [ 167 | 'feBlend', 168 | 'feColorMatrix', 169 | 'feComponentTransfer', 170 | 'feComposite', 171 | 'feConvolveMatrix', 172 | 'feDiffuseLighting', 173 | 'feDisplacementMap', 174 | 'feDistantLight', 175 | 'feFlood', 176 | 'feFuncA', 177 | 'feFuncB', 178 | 'feFuncG', 179 | 'feFuncR', 180 | 'feGaussianBlur', 181 | 'feImage', 182 | 'feMerge', 183 | 'feMergeNode', 184 | 'feMorphology', 185 | 'feOffset', 186 | 'fePointLight', 187 | 'feSpecularLighting', 188 | 'feSpotLight', 189 | 'feTile', 190 | 'feTurbulence', 191 | ], 192 | svgDisallowed: [ 193 | 'animate', 194 | 'color-profile', 195 | 'cursor', 196 | 'discard', 197 | 'fedropshadow', 198 | 'font-face', 199 | 'font-face-format', 200 | 'font-face-name', 201 | 'font-face-src', 202 | 'font-face-uri', 203 | 'foreignobject', 204 | 'hatch', 205 | 'hatchpath', 206 | 'mesh', 207 | 'meshgradient', 208 | 'meshpatch', 209 | 'meshrow', 210 | 'missing-glyph', 211 | 'script', 212 | 'set', 213 | 'solidcolor', 214 | 'unknown', 215 | 'use', 216 | ], 217 | mathMl: [ 218 | 'math', 219 | 'menclose', 220 | 'merror', 221 | 'mfenced', 222 | 'mfrac', 223 | 'mglyph', 224 | 'mi', 225 | 'mlabeledtr', 226 | 'mmultiscripts', 227 | 'mn', 228 | 'mo', 229 | 'mover', 230 | 'mpadded', 231 | 'mphantom', 232 | 'mroot', 233 | 'mrow', 234 | 'ms', 235 | 'mspace', 236 | 'msqrt', 237 | 'mstyle', 238 | 'msub', 239 | 'msup', 240 | 'msubsup', 241 | 'mtable', 242 | 'mtd', 243 | 'mtext', 244 | 'mtr', 245 | 'munder', 246 | 'munderover', 247 | ], 248 | mathMlDisallowed: [ 249 | 'maction', 250 | 'maligngroup', 251 | 'malignmark', 252 | 'mlongdiv', 253 | 'mscarries', 254 | 'mscarry', 255 | 'msgroup', 256 | 'mstack', 257 | 'msline', 258 | 'msrow', 259 | 'semantics', 260 | 'annotation', 261 | 'annotation-xml', 262 | 'mprescripts', 263 | 'none', 264 | ] 265 | }; 266 | -------------------------------------------------------------------------------- /src/traverser.js: -------------------------------------------------------------------------------- 1 | const sanitizer = require('sanitizer'); 2 | const marked = require('marked'); 3 | const sanitizeHtml = require('sanitize-html'); 4 | const xss = require('xss'); 5 | const markdownIt = require('markdown-it'); 6 | 7 | const { JSDOM } = require('jsdom'); 8 | const createDOMPurify = require('dompurify'); 9 | 10 | const window = new JSDOM('').window; 11 | const DOMPurify = createDOMPurify(window); 12 | 13 | const Traverser = { 14 | 'list': (dom, callback) => { 15 | const elements = dom.getElementsByTagName('*'); 16 | 17 | for (let element of elements) { 18 | const tagname = element.tagName; 19 | const attrs = element.attributes || element.attrs; 20 | 21 | callback(tagname, Object.keys(attrs)); 22 | } 23 | }, 24 | 'tree': (dom, callback, port /* debug purpose */) => { 25 | let tagName = 'P'; 26 | let attrs = []; 27 | let children = []; 28 | 29 | if (dom.tagName) { 30 | tagName = dom.tagName.toUpperCase(); 31 | } else if (dom.name) { 32 | tagName = dom.name; 33 | } 34 | 35 | if (dom.attrs) { 36 | attrs = dom.attrs.map(attr => attr.name); 37 | } else if (dom.attribs) { 38 | attrs = Object.keys(dom.attribs) 39 | } 40 | 41 | if (dom.childNodes) { 42 | children = dom.childNodes; 43 | } else if (dom.children) { 44 | children = dom.children 45 | }; 46 | 47 | for (let child of children) { 48 | Traverser['tree'](child, callback); 49 | } 50 | 51 | callback(tagName, attrs); 52 | } 53 | }; 54 | 55 | module.exports = { 56 | Traverser 57 | }; 58 | -------------------------------------------------------------------------------- /src/worker.js: -------------------------------------------------------------------------------- 1 | const { Worker, isMainThread, parentPort, workerData } = require('worker_threads'); 2 | 3 | const { Parser } = require('./parser'); 4 | const { Mutator } = require('./mutator'); 5 | const { Traverser } = require('./traverser'); 6 | const { InputGenerator, DOMGenerator } = require('./generator'); 7 | 8 | const { Tags, Attributes, Chars } = require('../config'); 9 | 10 | if (isMainThread || !parentPort) { 11 | throw new Error('unreachable'); 12 | } 13 | 14 | const report = (info) => { 15 | parentPort.postMessage({ type: 'finding', data: info}); 16 | }; 17 | 18 | let iterations = 0; 19 | 20 | parentPort.on('message', async ({type, config}) => { 21 | if (type === 'intialize') { 22 | const parser = Parser[config.parser], 23 | traverser = Traverser[config.traverser]; 24 | mutators = [].concat(config.mutators.map(name => Mutator[name])), 25 | generator = config.generator.type === 'dom' ? 26 | new DOMGenerator(config.generator) : 27 | new InputGenerator(config.generator), 28 | size = config.size; 29 | 30 | if (!Array.isArray(mutators) || mutators.length == 0) { 31 | console.warn('[!] mutators not specified!'.red); 32 | } 33 | 34 | for (;;) { 35 | const raw_input = generator.generate(size); 36 | 37 | let html = raw_input; 38 | let check = true; 39 | 40 | for (let mutator of mutators) { 41 | try { 42 | html = await mutator(html); 43 | } catch(e) { 44 | check = false; 45 | parentPort.postMessage({ type: 'error', message: e, data: html }); 46 | } 47 | } 48 | 49 | if (!html || !check) { 50 | continue; 51 | } 52 | 53 | ++iterations; 54 | 55 | if ((iterations & 0x3f) == 0) { // in every 64 56 | parentPort.postMessage({ type: 'pong', iterations }); 57 | } 58 | 59 | const dom = parser(html); 60 | 61 | traverser(dom, (tagName, attrNames) => { 62 | if (!Tags.SAFE.includes(tagName)) { 63 | report({ 64 | raw_input, 65 | html, 66 | message: `unexpected tag name: ${tagName}` 67 | }); 68 | } 69 | 70 | for (let attrName of attrNames) { 71 | if (!Attributes.SAFE.includes(attrName)) { 72 | report({ 73 | raw_input, 74 | html, 75 | message: `unexpected attrib name: ${attrName} in the ${tagName} tag` 76 | }); 77 | } 78 | } 79 | }, parentPort); 80 | } 81 | } else { 82 | parentPort.postMessage({ type: 'error', message: 'unreachable' }); 83 | } 84 | }); 85 | --------------------------------------------------------------------------------