{"_id":"neosanitize","_rev":"5-4ca1cd1b548f508860054ed8325bab9d","name":"neosanitize","dist-tags":{"latest":"0.5.0"},"versions":{"0.1.0":{"name":"neosanitize","version":"0.1.0","keywords":["html","parser","sanitizer","sanitize","xss","typescript","zero-dependency"],"author":{"name":"Puru Vijay","email":"devpuruvj@gmail.com"},"license":"MIT","_id":"neosanitize@0.1.0","maintainers":[{"name":"puruvj","email":"awesomepuruvj@gmail.com"}],"homepage":"https://github.com/PuruVJ/neosanitize#readme","bugs":{"url":"https://github.com/PuruVJ/neosanitize/issues"},"dist":{"shasum":"d5226e86c4ab9f406f80597143e796b1c47de258","tarball":"https://registry.npmjs.org/neosanitize/-/neosanitize-0.1.0.tgz","fileCount":33,"integrity":"sha512-fFd+yPikGQRbd+IBLdmNEw1rESBG1nM7v0D0Wii4Lliv3zTeQjNSZOzvzlOXMJ4ahNxcR6BSmTdGzd/T3LHAAg==","signatures":[{"sig":"MEQCICpiPBWSXzTVEoUEID51RMlNcskCjPiuAtwIr9fLRGgeAiBKUrTGp6lisSy4jO2VVxsEUJkAkd9JZSe3yDnDJ9GbDw==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":1014640},"main":"./dist/main/index.mjs","type":"module","_from":"file:neosanitize-0.1.0.tgz","types":"./dist/main/index.d.mts","module":"./dist/main/index.mjs","exports":{".":{"types":"./dist/main/index.d.mts","import":"./dist/main/index.mjs","browser":"./dist/main/browser.mjs","default":"./dist/main/index.mjs"},"./legacy":{"import":{"types":"./dist/legacy/index.d.mts","default":"./dist/legacy/index.mjs"},"default":"./dist/legacy/index.cjs","require":{"types":"./dist/legacy/index.d.cts","default":"./dist/legacy/index.cjs"}},"./browser":{"types":"./dist/main/browser.d.mts","default":"./dist/main/browser.mjs"},"./presets":{"types":"./dist/main/presets/index.d.mts","import":"./dist/main/presets/index.mjs"}},"scripts":{"dev":"tsdown --watch","fuzz":"pnpm build && node audit/fuzz.mjs","test":"pnpm build && vitest run","bench":"pnpm build && node bench/index.mjs","build":"rm -rf dist && tsdown && node scripts/legacy-stubs.mjs","coverage":"vitest run --coverage","typecheck":"tsc --noEmit","bench:3way":"pnpm build && node bench/three-way.mjs","test:watch":"vitest"},"_npmUser":{"name":"puruvj","email":"awesomepuruvj@gmail.com"},"_resolved":"/private/var/folders/g3/rxrpm2hd0j1cp1zw5sqckdyc0000gn/T/659d8f972383f9fc4e9013bba0f0aee8/neosanitize-0.1.0.tgz","_integrity":"sha512-fFd+yPikGQRbd+IBLdmNEw1rESBG1nM7v0D0Wii4Lliv3zTeQjNSZOzvzlOXMJ4ahNxcR6BSmTdGzd/T3LHAAg==","repository":{"url":"git+https://github.com/PuruVJ/neosanitize.git","type":"git","directory":"packages/neosanitize"},"_npmVersion":"11.3.0","description":"Zero-dependency, isomorphic HTML sanitizer: a fast browser-faithful WHATWG engine (deny-by-default) plus a drop-in sanitize-html-compatible legacy engine.","directories":{},"sideEffects":false,"_nodeVersion":"24.1.0","_hasShrinkwrap":false,"devDependencies":{"vite":"^6","sinon":"^22.0.0","tsdown":"^0.22.0","vitest":"^4.1.7","happy-dom":"^20.10.6","tinybench":"^6.0.2","typescript":"^6.0.3","@types/node":"^22.10.0","sanitize-html":"^2.17.5","@vitest/coverage-v8":"^4","@types/sanitize-html":"^2.16.1"},"_npmOperationalInternal":{"tmp":"tmp/neosanitize_0.1.0_1782058359656_0.03652021330304933","host":"s3://npm-registry-packages-npm-production"}},"0.2.0":{"name":"neosanitize","version":"0.2.0","keywords":["html","parser","sanitizer","sanitize","xss","typescript","zero-dependency"],"author":{"name":"Puru Vijay","email":"devpuruvj@gmail.com"},"license":"MIT","_id":"neosanitize@0.2.0","maintainers":[{"name":"puruvj","email":"awesomepuruvj@gmail.com"}],"homepage":"https://github.com/PuruVJ/neosanitize#readme","bugs":{"url":"https://github.com/PuruVJ/neosanitize/issues"},"dist":{"shasum":"d5ee34b81d6466a9a3b3642e82c695d655b1d4d5","tarball":"https://registry.npmjs.org/neosanitize/-/neosanitize-0.2.0.tgz","fileCount":40,"integrity":"sha512-K+HO6G1DOU/5HOTNHjJdh7Dbm7474yMm9vlTb2mL6yEuxtJN1WH/aa8mm1u/ztU9QYguI4ACSqq/HTvwfuRsdQ==","signatures":[{"sig":"MEUCIHViL/dUOzMOGzxzEuL9uSAoUlEATSQUr7s1Tn9kqnlIAiEA3v7gmQgJpWep5M8JJ/E48nuDhP35ozP2hfAAO5C1Ehk=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/neosanitize@0.2.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":1058526},"main":"./dist/main/index.mjs","type":"module","_from":"file:neosanitize-0.2.0.tgz","types":"./dist/main/index.d.mts","module":"./dist/main/index.mjs","exports":{".":{"types":"./dist/main/index.d.mts","import":"./dist/main/index.mjs","browser":"./dist/main/browser.mjs","default":"./dist/main/index.mjs"},"./parse":{"types":"./dist/main/parse/index.d.mts","import":"./dist/main/parse/index.mjs"},"./legacy":{"import":{"types":"./dist/legacy/index.d.mts","default":"./dist/legacy/index.mjs"},"default":"./dist/legacy/index.cjs","require":{"types":"./dist/legacy/index.d.cts","default":"./dist/legacy/index.cjs"}},"./browser":{"types":"./dist/main/browser.d.mts","default":"./dist/main/browser.mjs"},"./presets":{"types":"./dist/main/presets/index.d.mts","import":"./dist/main/presets/index.mjs"}},"scripts":{"dev":"tsdown --watch","fuzz":"pnpm build && node audit/fuzz.mjs","test":"pnpm build && vitest run","bench":"pnpm build && node bench/index.mjs","build":"rm -rf dist && tsdown && node scripts/legacy-stubs.mjs","coverage":"vitest run --coverage","fuzz:loop":"pnpm build && node audit/fuzz-loop.mjs","typecheck":"tsc --noEmit","bench:3way":"pnpm build && node bench/three-way.mjs","test:watch":"vitest"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:934fa0c5-0d15-4798-87f3-aa91c8e1e00b"}},"_resolved":"/tmp/af8652f43e0a462a44d1e4e152f50017/neosanitize-0.2.0.tgz","_integrity":"sha512-K+HO6G1DOU/5HOTNHjJdh7Dbm7474yMm9vlTb2mL6yEuxtJN1WH/aa8mm1u/ztU9QYguI4ACSqq/HTvwfuRsdQ==","repository":{"url":"git+https://github.com/PuruVJ/neosanitize.git","type":"git","directory":"packages/neosanitize"},"_npmVersion":"11.17.0","description":"Zero-dependency, isomorphic HTML sanitizer: a fast browser-faithful WHATWG engine (deny-by-default) plus a drop-in sanitize-html-compatible legacy engine.","directories":{},"sideEffects":false,"_nodeVersion":"22.22.3","_hasShrinkwrap":false,"devDependencies":{"vite":"^6","sinon":"^22.0.0","tsdown":"^0.22.0","vitest":"^4.1.7","happy-dom":"^20.10.6","tinybench":"^6.0.2","typescript":"^6.0.3","@types/node":"^22.10.0","sanitize-html":"^2.17.5","@vitest/coverage-v8":"^4","@types/sanitize-html":"^2.16.1"},"_npmOperationalInternal":{"tmp":"tmp/neosanitize_0.2.0_1782105815957_0.8135014798382905","host":"s3://npm-registry-packages-npm-production"}},"0.3.0":{"name":"neosanitize","version":"0.3.0","keywords":["html","parser","sanitizer","sanitize","xss","typescript","zero-dependency"],"author":{"name":"Puru Vijay","email":"devpuruvj@gmail.com"},"license":"MIT","_id":"neosanitize@0.3.0","maintainers":[{"name":"puruvj","email":"awesomepuruvj@gmail.com"}],"homepage":"https://github.com/PuruVJ/neosanitize#readme","bugs":{"url":"https://github.com/PuruVJ/neosanitize/issues"},"dist":{"shasum":"9d56e2a0392c086403d0cd5468aa4a2d5ca8aa63","tarball":"https://registry.npmjs.org/neosanitize/-/neosanitize-0.3.0.tgz","fileCount":48,"integrity":"sha512-3jXsJfTnhprg8ktSNaz23WI0cjIJ2A/FQHFz/VaHzaKFCEEN9Li+YoTrDKuX4j2ic8Bn3y/TZXQKpfK5JtJEcQ==","signatures":[{"sig":"MEUCIQDOztx+MK51TOLFO9GKTBw2IEyZdQzvmcYeFx/08vcvrgIgMI82mJYSdi3oY4YSSeDEnKYK2VBOSuP2sL7V6/fNoSE=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/neosanitize@0.3.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":1079311},"main":"./dist/main/index.mjs","type":"module","_from":"file:neosanitize-0.3.0.tgz","types":"./dist/main/index.d.mts","module":"./dist/main/index.mjs","exports":{".":{"types":"./dist/main/index.d.mts","import":"./dist/main/index.mjs","browser":"./dist/main/browser.mjs","default":"./dist/main/index.mjs"},"./legacy":{"import":{"types":"./dist/legacy/index.d.mts","default":"./dist/legacy/index.mjs"},"default":"./dist/legacy/index.cjs","require":{"types":"./dist/legacy/index.d.cts","default":"./dist/legacy/index.cjs"}},"./parse5":{"types":"./dist/main/parse5.d.mts","import":"./dist/main/parse5.mjs"},"./browser":{"types":"./dist/main/browser.d.mts","default":"./dist/main/browser.mjs"},"./presets":{"types":"./dist/main/presets/index.d.mts","import":"./dist/main/presets/index.mjs"},"./htmlparser2":{"types":"./dist/main/htmlparser2.d.mts","import":"./dist/main/htmlparser2.mjs"},"./whatwg-parser":{"types":"./dist/main/whatwg-parser/index.d.mts","import":"./dist/main/whatwg-parser/index.mjs"}},"scripts":{"dev":"tsdown --watch","fuzz":"pnpm build && node audit/fuzz.mjs","test":"pnpm build && vitest run","bench":"pnpm build && node bench/index.mjs","build":"rm -rf dist && tsdown && node scripts/legacy-stubs.mjs","coverage":"vitest run --coverage","fuzz:loop":"pnpm build && node audit/fuzz-loop.mjs","typecheck":"tsc --noEmit","bench:3way":"pnpm build && node bench/three-way.mjs","test:watch":"vitest","bench:adapters":"pnpm build && node bench/adapters.mjs --json"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:934fa0c5-0d15-4798-87f3-aa91c8e1e00b"}},"_resolved":"/tmp/bcca62e4258d7e9a24a4059071a2b4c4/neosanitize-0.3.0.tgz","_integrity":"sha512-3jXsJfTnhprg8ktSNaz23WI0cjIJ2A/FQHFz/VaHzaKFCEEN9Li+YoTrDKuX4j2ic8Bn3y/TZXQKpfK5JtJEcQ==","repository":{"url":"git+https://github.com/PuruVJ/neosanitize.git","type":"git","directory":"packages/neosanitize"},"_npmVersion":"11.17.0","description":"Zero-dependency, isomorphic HTML sanitizer: a fast browser-faithful WHATWG engine (deny-by-default) plus a drop-in sanitize-html-compatible legacy engine.","directories":{},"sideEffects":false,"_nodeVersion":"22.22.3","_hasShrinkwrap":false,"devDependencies":{"vite":"^6","sinon":"^22.0.0","parse5":"^8.0.1","tsdown":"^0.22.0","vitest":"^4.1.7","happy-dom":"^20.10.6","tinybench":"^6.0.2","typescript":"^6.0.3","@types/node":"^22.10.0","htmlparser2":"^12.0.0","sanitize-html":"^2.17.5","@vitest/coverage-v8":"^4","@types/sanitize-html":"^2.16.1"},"peerDependencies":{"parse5":">=7","htmlparser2":">=9"},"peerDependenciesMeta":{"parse5":{"optional":true},"htmlparser2":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/neosanitize_0.3.0_1782144262733_0.9607281060269706","host":"s3://npm-registry-packages-npm-production"}},"0.4.0":{"name":"neosanitize","version":"0.4.0","keywords":["html","parser","sanitizer","sanitize","xss","typescript","zero-dependency"],"author":{"name":"Puru Vijay","email":"devpuruvj@gmail.com"},"license":"MIT","_id":"neosanitize@0.4.0","maintainers":[{"name":"puruvj","email":"awesomepuruvj@gmail.com"}],"homepage":"https://github.com/PuruVJ/neosanitize#readme","bugs":{"url":"https://github.com/PuruVJ/neosanitize/issues"},"dist":{"shasum":"64f352ce6d71bb0bc44e12a0df5b5effec9d37b1","tarball":"https://registry.npmjs.org/neosanitize/-/neosanitize-0.4.0.tgz","fileCount":48,"integrity":"sha512-+OOQXv2MbEy8c5eruo2T56meJZFVcemC7C4LGJcK+f5I1Fga98FjJGoZlTRIpB/cX/3gvGhHQT06S3hJ6TUwgQ==","signatures":[{"sig":"MEQCICUR959eLpMng6HugNCfKkrRYwnxTj7Wi2pK/dlGGi+FAiBWz7b1yKODfpT2GcgOhZ29NDf9pYxFRjkQs+6rHJHIBg==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"sig":"MEYCIQDHexayIcJeI9Sv+9l3MJjzkCv9hZ6nw1nfe3XPr/s8ngIhALk0vQ6hz6k/o7824MNipoTLcFZ+aIo6TBZ8QBS+cp2V","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/neosanitize@0.4.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":1111372},"main":"./dist/main/index.mjs","type":"module","_from":"file:neosanitize-0.4.0.tgz","types":"./dist/main/index.d.mts","module":"./dist/main/index.mjs","exports":{".":{"types":"./dist/main/index.d.mts","import":"./dist/main/index.mjs","browser":"./dist/main/browser.mjs","default":"./dist/main/index.mjs"},"./legacy":{"import":{"types":"./dist/legacy/index.d.mts","default":"./dist/legacy/index.mjs"},"default":"./dist/legacy/index.cjs","require":{"types":"./dist/legacy/index.d.cts","default":"./dist/legacy/index.cjs"}},"./parse5":{"types":"./dist/main/parse5.d.mts","import":"./dist/main/parse5.mjs"},"./browser":{"types":"./dist/main/browser.d.mts","default":"./dist/main/browser.mjs"},"./presets":{"types":"./dist/main/presets/index.d.mts","import":"./dist/main/presets/index.mjs"},"./htmlparser2":{"types":"./dist/main/htmlparser2.d.mts","import":"./dist/main/htmlparser2.mjs"},"./whatwg-parser":{"types":"./dist/main/whatwg-parser/index.d.mts","import":"./dist/main/whatwg-parser/index.mjs"}},"scripts":{"dev":"tsdown --watch","fuzz":"pnpm build && node audit/fuzz.mjs","test":"pnpm build && vitest run","bench":"pnpm build && node bench/index.mjs","build":"rm -rf dist && tsdown && node scripts/legacy-stubs.mjs","coverage":"vitest run --coverage","fuzz:loop":"pnpm build && node audit/fuzz-loop.mjs","typecheck":"tsc --noEmit","bench:3way":"pnpm build && node bench/three-way.mjs","test:watch":"vitest","bench:adapters":"pnpm build && node bench/adapters.mjs --json"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:934fa0c5-0d15-4798-87f3-aa91c8e1e00b"}},"_resolved":"/tmp/2660be31ca6d4a5b2628c5db2f7fb4a8/neosanitize-0.4.0.tgz","_integrity":"sha512-+OOQXv2MbEy8c5eruo2T56meJZFVcemC7C4LGJcK+f5I1Fga98FjJGoZlTRIpB/cX/3gvGhHQT06S3hJ6TUwgQ==","repository":{"url":"git+https://github.com/PuruVJ/neosanitize.git","type":"git","directory":"packages/neosanitize"},"_npmVersion":"11.20.0","description":"Zero-dependency, isomorphic HTML sanitizer: a fast browser-faithful WHATWG engine (deny-by-default) plus a drop-in sanitize-html-compatible legacy engine.","directories":{},"sideEffects":false,"_nodeVersion":"22.23.2","_hasShrinkwrap":false,"devDependencies":{"vite":"^6","sinon":"^22.0.0","parse5":"^8.0.1","tsdown":"^0.22.0","vitest":"^4.1.7","happy-dom":"^20.10.6","tinybench":"^6.0.2","typescript":"^6.0.3","@types/node":"^22.10.0","htmlparser2":"^12.0.0","sanitize-html":"^2.17.5","@vitest/coverage-v8":"^4","@types/sanitize-html":"^2.16.1"},"peerDependencies":{"parse5":">=7","htmlparser2":">=9"},"peerDependenciesMeta":{"parse5":{"optional":true},"htmlparser2":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/neosanitize_0.4.0_1790357604519_0.7467731908127835","host":"s3://npm-registry-packages-npm-production"}},"0.5.0":{"_id":"neosanitize@0.5.0","bugs":{"url":"https://github.com/PuruVJ/neosanitize/issues"},"dist":{"shasum":"965975597251d322cc9007a46934ec0507cdbea7","tarball":"https://registry.npmjs.org/neosanitize/-/neosanitize-0.5.0.tgz","fileCount":50,"integrity":"sha512-4kMuNOVUo4Gge1hDzaacOVr4hTBuZdPJxJbJdK3XDt6GlgaZDXH87XkngHntMn4Rt6fJo4u1IiGZP9/wDiMYZQ==","signatures":[{"sig":"MEQCIEaWzSjHzRn5sb2DE16KkOZqpYPYg+mzT/Q4QFvMTBV3AiBT0FpSxHB4/RD8wJFmp7qBxe2YSiEpeGdO1LQMyj8PiQ==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIDEJ0AKBCejRO21xqb6THcBcgDLeMWN4dLBa3oX/vAHKAiEAqt+msdhEeJFqE9v8FA7nVqLO8KEBPsfqIvjb8RyaewE="}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/neosanitize@0.5.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":1231692},"main":"./dist/main/index.mjs","name":"neosanitize","type":"module","_from":"file:neosanitize-0.5.0.tgz","types":"./dist/main/index.d.mts","author":{"name":"Puru Vijay","email":"devpuruvj@gmail.com"},"module":"./dist/main/index.mjs","exports":{".":{"types":"./dist/main/index.d.mts","import":"./dist/main/index.mjs","browser":"./dist/main/browser.mjs","default":"./dist/main/index.mjs"},"./legacy":{"import":{"types":"./dist/legacy/index.d.mts","default":"./dist/legacy/index.mjs"},"default":"./dist/legacy/index.cjs","require":{"types":"./dist/legacy/index.d.cts","default":"./dist/legacy/index.cjs"}},"./parse5":{"types":"./dist/main/parse5.d.mts","import":"./dist/main/parse5.mjs"},"./browser":{"types":"./dist/main/browser.d.mts","default":"./dist/main/browser.mjs"},"./presets":{"types":"./dist/main/presets/index.d.mts","import":"./dist/main/presets/index.mjs"},"./htmlparser2":{"types":"./dist/main/htmlparser2.d.mts","import":"./dist/main/htmlparser2.mjs"},"./whatwg-parser":{"types":"./dist/main/whatwg-parser/index.d.mts","import":"./dist/main/whatwg-parser/index.mjs"}},"license":"MIT","scripts":{"dev":"tsdown --watch","fuzz":"pnpm build && node audit/fuzz.mjs","test":"pnpm build && vitest run","bench":"pnpm build && node bench/index.mjs","build":"rm -rf dist && tsdown && node scripts/legacy-stubs.mjs","coverage":"vitest run --coverage","fuzz:loop":"pnpm build && node audit/fuzz-loop.mjs","typecheck":"tsc --noEmit","bench:3way":"pnpm build && node bench/three-way.mjs","test:watch":"vitest","bench:scale":"pnpm build && node bench/scale.mjs","bench:adapters":"pnpm build && node bench/adapters.mjs --json"},"version":"0.5.0","_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:934fa0c5-0d15-4798-87f3-aa91c8e1e00b"}},"homepage":"https://github.com/PuruVJ/neosanitize#readme","keywords":["html","parser","sanitizer","sanitize","xss","typescript","zero-dependency"],"_resolved":"/tmp/73eba84acc8453d0405cb20636af60b0/neosanitize-0.5.0.tgz","_integrity":"sha512-4kMuNOVUo4Gge1hDzaacOVr4hTBuZdPJxJbJdK3XDt6GlgaZDXH87XkngHntMn4Rt6fJo4u1IiGZP9/wDiMYZQ==","repository":{"url":"git+https://github.com/PuruVJ/neosanitize.git","type":"git","directory":"packages/neosanitize"},"_npmVersion":"11.20.0","description":"Zero-dependency, isomorphic HTML sanitizer: a fast browser-faithful WHATWG engine (deny-by-default) plus a drop-in sanitize-html-compatible legacy engine.","directories":{},"maintainers":[{"name":"puruvj","email":"awesomepuruvj@gmail.com"}],"sideEffects":false,"_nodeVersion":"22.23.2","_hasShrinkwrap":false,"devDependencies":{"vite":"^6","sinon":"^22.0.0","parse5":"^8.0.1","tsdown":"^0.22.0","vitest":"^4.1.7","happy-dom":"^20.10.6","tinybench":"^6.0.2","typescript":"^6.0.3","@types/node":"^22.10.0","htmlparser2":"^12.0.0","sanitize-html":"^2.17.5","@vitest/coverage-v8":"^4","@types/sanitize-html":"^2.16.1"},"peerDependencies":{"parse5":">=7","htmlparser2":">=9"},"peerDependenciesMeta":{"parse5":{"optional":true},"htmlparser2":{"optional":true}},"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/neosanitize_0.5.0_1790366404826_0.4941983736932909"}}},"time":{"created":"2026-06-21T16:12:39.590Z","modified":"2026-09-25T20:00:05.278Z","0.1.0":"2026-06-21T16:12:39.832Z","0.2.0":"2026-06-22T05:23:36.132Z","0.3.0":"2026-06-22T16:04:22.976Z","0.4.0":"2026-09-25T17:33:24.838Z","0.5.0":"2026-09-25T20:00:04.914Z"},"bugs":{"url":"https://github.com/PuruVJ/neosanitize/issues"},"author":{"name":"Puru Vijay","email":"devpuruvj@gmail.com"},"license":"MIT","homepage":"https://github.com/PuruVJ/neosanitize#readme","keywords":["html","parser","sanitizer","sanitize","xss","typescript","zero-dependency"],"repository":{"url":"git+https://github.com/PuruVJ/neosanitize.git","type":"git","directory":"packages/neosanitize"},"description":"Zero-dependency, isomorphic HTML sanitizer: a fast browser-faithful WHATWG engine (deny-by-default) plus a drop-in sanitize-html-compatible legacy engine.","maintainers":[{"name":"puruvj","email":"awesomepuruvj@gmail.com"}],"readme":"<p align=\"center\">\n  <img src=\"assets/logo.svg\" width=\"84\" height=\"84\" alt=\"neosanitize\" />\n</p>\n\n<h1 align=\"center\">neosanitize</h1>\n\nZero-dependency, isomorphic HTML sanitizer in TypeScript. **Two engines in one package:**\n\n- **`.` (main)**, a new, forward-looking engine built on a **browser-faithful WHATWG parser** (100% [html5lib](https://github.com/html5lib/html5lib-tests) tokenizer conformance), **deny-by-default** policy, and an **inviolable safe baseline**. Roughly **2.3× faster** than `sanitize-html` across a 13-scenario benchmark.\n- **`./legacy`**, a byte-identical, drop-in port of [`sanitize-html`](https://github.com/apostrophecms/sanitize-html) 2.x: same API, same output, verified against the original with millions of differential-fuzz cases. Use it to migrate off `sanitize-html` (and its `htmlparser2` + `postcss` dependency tree) with no behaviour change.\n\nNo runtime dependencies. ESM. `sideEffects: false` and subpath exports, you ship only what you import.\n\n```bash\nnpm install neosanitize\n```\n\n---\n\n## Which engine should I use?\n\n| | `.` (main) | `./legacy` |\n|---|---|---|\n| **Use when** | new code; you want speed, a browser-faithful parse, and deny-by-default safety | dropping in for an existing `sanitize-html` install with zero behaviour change |\n| **Parser** | own WHATWG/HTML5 parser (matches the browser) | `htmlparser2` semantics (matches `sanitize-html`) |\n| **API** | class-only: build a `Sanitizer`, call `.sanitize()` | functional: `sanitize(html, options)` |\n| **Default posture** | deny-by-default + inviolable baseline | identical to `sanitize-html` |\n| **Stability** | evolves (semver) | frozen to `sanitize-html`'s behaviour |\n\nThe two share **no runtime code**, importing one never pulls in the other.\n\n---\n\n## Quick start, the main engine\n\nThe main engine is **class-only by design**. You build a `Sanitizer` with an explicit policy and call `.sanitize()`. There is deliberately **no** one-shot `sanitize(html)` helper: forcing an explicit policy means there's no implicit global default to misconfigure, and the policy is compiled **once** so repeated `.sanitize()` calls are cheap.\n\n```ts\nimport { Sanitizer } from 'neosanitize';\nimport * as presets from 'neosanitize/presets';\n\n// Build once (compiles the policy), reuse everywhere.\nconst sanitizer = Sanitizer.builder(presets.ugc).allow('img', ['src', 'alt']).build();\n\nsanitizer.sanitize('<p>hi <img src=x onerror=alert(1)> <script>bad()</script></p>');\n// → '<p>hi <img src=\"x\"> </p>'\n//   onerror handler stripped, <script> dropped with its content.\n```\n\nStart from scratch (deny-by-default, everything not allow-listed is removed):\n\n```ts\nconst s = Sanitizer.builder().allow(['a', 'b', 'p']).allow('a', ['href']).build();\ns.sanitize('<p>see <a href=\"/docs\" onclick=\"x()\">docs</a><iframe></iframe></p>');\n// → '<p>see <a href=\"/docs\">docs</a></p>'\n```\n\n### Presets\n\nCurated, ready-to-use policies, all under the single `neosanitize/presets` entry. The convention is a namespace import:\n\n```ts\nimport * as presets from 'neosanitize/presets';\n// presets.none · presets.basic · presets.ugc · presets.markdown\n```\n\n| Preset | For |\n|---|---|\n| `none` | strip all tags to safe text |\n| `basic` | minimal inline formatting |\n| `ugc` | user-generated content (comments, posts) |\n| `markdown` | the HTML a Markdown renderer emits |\n\n### Refining a policy with the builder\n\n```ts\nconst s = Sanitizer.builder(presets.basic) // start from a preset (or a partial policy)\n  .allow('a', ['href', 'title'])           // add tags + attributes\n  .allow('img', ['src', 'alt'])\n  .deny('span')                      // remove a tag (and its attributes)\n  .build();\n```\n\n`allow('*', [...])` sets attributes allowed on any tag. `allow` is polymorphic: an exact name, an array of names (`allow(['p', 'b', 'i'])`), or a **pattern** for custom elements whose set isn't known up front, with `'*'` for any attribute:\n\n```ts\nconst s = Sanitizer.builder(presets.ugc)\n  .allow(/^(ui|wc)-/, '*')                               // dynamic custom-element tags\n  .transformAttribute(({ name, value }) =>               // arbitrary per-attribute logic\n    name === 'class' ? value.replace(/\\binternal-\\S+/g, '').trim() : value)\n  .build();\n\n// derive a variant from a shared sanitizer without re-declaring the base (base is untouched):\nconst local = s.toExtended((b) => b.allow(/^acme-/, '*'));\n```\n\nThe inviolable baseline still applies to pattern-matched tags and to hook output (a hook can rewrite or drop, never reintroduce `on*` / `javascript:`).\n\n### Output targets\n\n```ts\ns.sanitize(html);             // → string (the default)\ns.sanitizeToText(html);       // → plain text, all markup removed\ns.sanitizeToFragment(html);   // → DocumentFragment (browser only; skips re-parsing)\ns.sanitizeToTrustedHTML(html);// → TrustedHTML when Trusted Types is available, else string\ns.sanitizeTo(html, sink);     // → streams the result to a sink (no return value)\n```\n\n### Streaming output\n\n`sanitizeTo(html, sink, opts?)` delivers the **same bytes** as `sanitize()` incrementally instead of returning one string, handy for large documents (no big result string) and for writing straight to a response or file stream. The sink is a callback or any object with a Node-style `write(chunk)`:\n\n```ts\ns.sanitizeTo(html, (chunk) => res.write(chunk));   // callback\ns.sanitizeTo(html, res);                           // an HTTP response / fs write stream\ns.sanitizeTo(html, sink, { chunkSize: 64 * 1024 }); // tune the flush size (default 16 KB)\n```\n\nFragments are batched into ~`chunkSize`-character writes (so the sink isn't hit once per tag). The same inviolable baseline applies. It's synchronous, the whole input is parsed first (a faithful tree is required), so it streams *output*, not input, and doesn't await backpressure.\n\n### Report mode\n\nSee exactly what was removed and why, for audits, debugging, or telemetry:\n\n```ts\nconst { html, removed } = s.sanitizeWithReport('<a href=javascript:alert(1) onclick=x>y</a>');\n// html    → '<a>y</a>'\n// removed → [\n//   { kind: 'url',  name: 'href',    reason: 'dangerous-url' },\n//   { kind: 'attr', name: 'onclick', reason: 'not-allowed'   },\n// ]\n```\n\n### The inviolable safe baseline\n\nEven if your allow-list permits them, the baseline **always** strips known-dangerous constructs, `<script>`, `on*` event handlers, and `javascript:` / `vbscript:` / non-image `data:` URLs, mirroring the browser's native `setHTML()`. An allow-list can never re-introduce them.\n\nThe only escape hatch is explicit, and named to make that obvious:\n\n```ts\ns.sanitizeUnsafe(html); // skips the baseline (mirrors setHTMLUnsafe); the allow-list still applies\n```\n\n---\n\n## Browser build (native parser, ~3 KB)\n\nIn the browser you don't need to ship an HTML parser, the platform already has one. The package's `browser` export condition automatically routes bundlers (Vite, esbuild, webpack, Rollup) to a build that parses with the native `DOMParser` and runs the **same** policy engine. Same `Sanitizer` API, **zero parser bytes**:\n\n```ts\nimport { Sanitizer } from 'neosanitize'; // resolves to the browser build in a bundler\n```\n\n| Build | Min+gzip | Min+brotli | Notes |\n|---|---:|---:|---|\n| `.` browser | ~3.2 KB | **~2.9 KB** | native `DOMParser`, no bundled parser |\n| `.` Node/default | ~27 KB | ~23 KB | bundled WHATWG parser + full entity table |\n| `./legacy` | ~21 KB | ~18 KB | single-file `sanitize-html` port |\n\nBecause parsing is the browser's own, the browser build is byte-for-byte what the user's browser would build, which closes parser-differential / mutation-XSS gaps by construction.\n\n---\n\n## Parser adapters\n\nThe main engine's parser is **pluggable**. Each entry has an environment default (the bundled WHATWG parser in Node, native `DOMParser` in the browser), and you can override it per-`Sanitizer` with `.parser(adapter)`, keeping the exact same deny-by-default policy + serializer, swapping only how HTML becomes a tree:\n\n```ts\nimport { Sanitizer } from 'neosanitize';\nimport { parse5Adapter } from 'neosanitize/parse5';        // npm i parse5      (optional peer)\nimport { htmlparser2Adapter } from 'neosanitize/htmlparser2'; // npm i htmlparser2 (optional peer)\n\nSanitizer.builder(ugc).parser(parse5Adapter).build();      // 100% spec-conformant tree\nSanitizer.builder(ugc).parser(htmlparser2Adapter).build(); // fast & lenient (sanitize-html's parser)\n```\n\n- **`ours` (default)**, fastest, browser-faithful, zero-dependency. Tokenizer 100% / tree ~95.6% html5lib.\n- **`parse5`**, the reference WHATWG parser; pick it for **full spec conformance** on degenerate/adversarial markup (≈0.5× the speed).\n- **`htmlparser2`**, very fast and forgiving, but not a full WHATWG tree builder (no foster-parenting / foreign-content namespacing).\n\nBoth optional adapters are peer dependencies, nothing is pulled in unless you import them. Write your own with `type ParseAdapter = (html: string) => ParentNode`. Full guide + benchmarks: [neosanitize.puruvj.dev/adapters](https://neosanitize.puruvj.dev/adapters).\n\n---\n\n## Legacy drop-in\n\nIdentical API and output to `sanitize-html` 2.x:\n\n```ts\nimport sanitize from 'neosanitize/legacy';\n\nsanitize('<img src=x onerror=alert(1) />', {\n  allowedTags: ['img'],\n  allowedAttributes: { img: ['src'] },\n});\n// → '<img src=\"x\" />'  (exactly what sanitize-html produces)\n```\n\nIt reimplements `sanitize-html`'s three parsers, `htmlparser2`, `parse-srcset`, and `postcss`, **inline, with zero runtime dependencies**. Notably, `postcss` is only there to filter the `style` attribute for `allowedStyles`; our hand-written declaration parser matches it on every realistic style **and** works in the browser (the original's postcss path is Node-only). Details: [the legacy guide](https://neosanitize.puruvj.dev/legacy#zero-dependencies-what-we-replaced).\n\n---\n\n## Parsing, `neosanitize/whatwg-parser`\n\nNeed the tree, not the sanitizer? `neosanitize/whatwg-parser` exposes the **same browser-faithful WHATWG parser**, policy-free, zero-dep, no DOM. The tree is what a browser builds (misnesting, foster parenting, the adoption agency, all handled), and `parse()` returns a full document just like `DOMParser.parseFromString(html, 'text/html')`.\n\n```ts\nimport { parse, findAll, textContent, serialize } from 'neosanitize/whatwg-parser';\n\nconst doc = parse('<main><a href=\"/x\">one</a><a href=\"/y\">two</a></main>');\nfindAll(doc, 'a').map((a) => a.attrs.find(([k]) => k === 'href')?.[1]); // ['/x','/y']\ntextContent(doc); // 'onetwo'\nserialize(doc);   // round-trips to the normalized HTML the browser would produce\n```\n\n`parse`, `serialize`, `find`/`findAll` (tag name or predicate), `walk`, `textContent`. It's a parse tree + helpers, not a full DOM, see the [parsing guide](https://neosanitize.puruvj.dev/whatwg-parser).\n\n---\n\n## Performance\n\nThroughput vs. the original `sanitize-html`, across a 13-scenario corpus (`node bench/three-way.mjs`):\n\n- **main: geomean ~2.3× faster** than `sanitize-html` (up to 3.7× on entity-heavy input), while doing a *full* WHATWG-conformant parse + tree construction. On heavy/adversarial inputs (XSS payloads, attribute-dense markup, big tables) it now **matches or beats** `./legacy`.\n- The `./legacy` port (~2.7×) edges ahead only on benign prose and tiny documents, where its `htmlparser2`-style streaming parse skips the tree's fixed setup cost.\n\nBoth engines compile their policy once and reuse it, so the hot path is just parse → walk → serialize.\n\n---\n\n## Security\n\n- **Deny-by-default** + an **inviolable baseline** that an allow-list can't override (see above).\n- **Browser-faithful parsing** (main): 100% html5lib tokenizer conformance, so the tree we sanitize is the tree a browser builds.\n- **Defense-in-depth:** sanitized output is verified safe and reparse-stable against a 20,000-case adversarial fuzz (zero XSS bypasses).\n\nFull threat model and responsible-disclosure process: [SECURITY.md](./SECURITY.md).\n\n---\n\n## Conformance & tests\n\n- **Tokenizer:** 100% of the vendored html5lib tokenizer suite (6946/6946).\n- **Tree construction:** ~95.6% of the html5lib tree-construction suite (ratcheted upward). The remaining divergences are degenerate adoption-agency/`<nobr>`/table nesting and bleeding-edge `<select>` cases, tree-shape differences that don't affect sanitization safety (output stays safe + reparse-stable).\n- **Legacy parity:** the `./legacy` port is differential-fuzzed against `sanitize-html` itself.\n\n```bash\npnpm test        # build + full suite\npnpm typecheck\npnpm bench       # legacy vs sanitize-html\npnpm bench:3way  # original vs legacy vs main\n```\n\n---\n\n## Acknowledgements\n\nThis project stands on two MIT-licensed projects:\n\n- **[sanitize-html](https://github.com/apostrophecms/sanitize-html)** (Apostrophe Technologies, Inc.), the `./legacy` entry point is a faithful re-implementation of its behaviour. MIT.\n- **[html5lib-tests](https://github.com/html5lib/html5lib-tests)** (html5lib contributors), vendored under `test/fixtures/` as the parser-conformance oracle for the main engine (test-only; not shipped in the published package). MIT.\n\n## License\n\n[MIT](./LICENSE) © Puru Vijay\n","readmeFilename":"README.md"}