{"_id":"rehype-harden","_rev":"8-ece759f61b8e5913f75fcd36587e8e25","name":"rehype-harden","dist-tags":{"latest":"1.1.8"},"versions":{"1.1.0":{"name":"rehype-harden","version":"1.1.0","keywords":["rehype","markdown","security","url-filtering","xss-protection"],"author":{"name":"S. Elliott Johnson"},"license":"MIT","_id":"rehype-harden@1.1.0","maintainers":[{"name":"cramforce","email":"malte.ubl@gmail.com"}],"homepage":"https://github.com/vercel-labs/markdown-sanitizers#readme","bugs":{"url":"https://github.com/vercel-labs/markdown-sanitizers/issues"},"dist":{"shasum":"3c541f236d317ca3de26511bd61c23cd9d1ea3a1","tarball":"https://registry.npmjs.org/rehype-harden/-/rehype-harden-1.1.0.tgz","fileCount":11,"integrity":"sha512-/VRw3AOXUvylt4UCPxItNiZyMntHxZh3vjv4riFCA8uBUgya8Fe/iqxD2vA6k10AG7LAnv/tOi1FA8bYLz2ndA==","signatures":[{"sig":"MEYCIQCoHVAFMCPJRsLRErvJlSalAv9EXmSngz4ia2SUbPDqhAIhAJfYKTh75vT9H4JrgLUkcGx56aeC0UHR8HkM3KGxGIiT","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":86197},"main":"dist/index.js","_from":"file:rehype-harden-1.1.0.tgz","types":"dist/index.d.ts","exports":{".":"./dist/index.js"},"scripts":{"test":"vitest --run","build":"tsc","check":"tsc --noEmit","test:ui":"vitest --ui","test:watch":"vitest --watch"},"_npmUser":{"name":"cramforce","email":"malte.ubl@gmail.com"},"_resolved":"/private/var/folders/1h/2pv55r5s2m7ctn562xnr3b880000gn/T/2142643907a0d8e4ee061a329d501999/rehype-harden-1.1.0.tgz","_integrity":"sha512-/VRw3AOXUvylt4UCPxItNiZyMntHxZh3vjv4riFCA8uBUgya8Fe/iqxD2vA6k10AG7LAnv/tOi1FA8bYLz2ndA==","repository":{"url":"git+https://github.com/vercel-labs/markdown-sanitizers.git","type":"git","directory":"rehype-harden"},"_npmVersion":"10.9.3","description":"A security-focused rehype plugin that filters URLs based on allowed prefixes","directories":{},"_nodeVersion":"22.18.0","_hasShrinkwrap":false,"devDependencies":{"vite":"^7.0.6","vitest":"^3.2.4","unified":"^11.0.5","typescript":"^5","@types/hast":"^3.0.4","remark-parse":"^11.0.0","remark-rehype":"^11.1.1","rehype-stringify":"^10.0.1","unist-util-visit":"^5.0.0"},"_npmOperationalInternal":{"tmp":"tmp/rehype-harden_1.1.0_1758673906595_0.6775931136240592","host":"s3://npm-registry-packages-npm-production"}},"1.1.2":{"name":"rehype-harden","version":"1.1.2","keywords":["rehype","markdown","security","url-filtering","xss-protection"],"author":{"name":"S. Elliott Johnson"},"license":"MIT","_id":"rehype-harden@1.1.2","maintainers":[{"name":"cramforce","email":"malte.ubl@gmail.com"}],"homepage":"https://github.com/vercel-labs/markdown-sanitizers#readme","bugs":{"url":"https://github.com/vercel-labs/markdown-sanitizers/issues"},"dist":{"shasum":"bd0972d06a63191b065c926bfec2f277c616b009","tarball":"https://registry.npmjs.org/rehype-harden/-/rehype-harden-1.1.2.tgz","fileCount":11,"integrity":"sha512-58RSgd3BAYW/hULy6qvrLBIRe8qe5PElwEpRjrLilvhJ3N+Y6ptKAmy1CLIIyoMz7CMI30GqENhNXksJd5hGDg==","signatures":[{"sig":"MEYCIQC92acd3e1Ux1ljIquyaG69rvYGTSLGtjl3f63qJveEZwIhAJX8ae9u9zZwsIlQN+Iu8ZPGtYyZaYSLdPpbJ3/I0cUd","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":86238},"main":"dist/index.js","_from":"file:rehype-harden-1.1.2.tgz","types":"dist/index.d.ts","exports":{".":"./dist/index.js"},"scripts":{"test":"vitest --run","build":"tsc","check":"tsc --noEmit","test:ui":"vitest --ui","test:watch":"vitest --watch"},"_npmUser":{"name":"cramforce","email":"malte.ubl@gmail.com"},"_resolved":"/tmp/826ccffaa61949cc32b4bfd871a44787/rehype-harden-1.1.2.tgz","_integrity":"sha512-58RSgd3BAYW/hULy6qvrLBIRe8qe5PElwEpRjrLilvhJ3N+Y6ptKAmy1CLIIyoMz7CMI30GqENhNXksJd5hGDg==","repository":{"url":"git+https://github.com/vercel-labs/markdown-sanitizers.git","type":"git","directory":"rehype-harden"},"_npmVersion":"11.6.0","description":"A security-focused rehype plugin that filters URLs based on allowed prefixes","directories":{},"_nodeVersion":"24.8.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vite":"^7.0.6","vitest":"^3.2.4","unified":"^11.0.5","typescript":"^5","@types/hast":"^3.0.4","remark-parse":"^11.0.0","remark-rehype":"^11.1.1","rehype-stringify":"^10.0.1","unist-util-visit":"^5.0.0"},"_npmOperationalInternal":{"tmp":"tmp/rehype-harden_1.1.2_1758740037433_0.2694749485541845","host":"s3://npm-registry-packages-npm-production"}},"1.1.3":{"name":"rehype-harden","version":"1.1.3","keywords":["rehype","markdown","security","url-filtering","xss-protection"],"author":{"name":"S. Elliott Johnson"},"license":"MIT","_id":"rehype-harden@1.1.3","maintainers":[{"name":"cramforce","email":"malte.ubl@gmail.com"}],"homepage":"https://github.com/vercel-labs/markdown-sanitizers#readme","bugs":{"url":"https://github.com/vercel-labs/markdown-sanitizers/issues"},"dist":{"shasum":"9a35f1b3e4c9082ba2679d34d3c807e1927e94ca","tarball":"https://registry.npmjs.org/rehype-harden/-/rehype-harden-1.1.3.tgz","fileCount":11,"integrity":"sha512-qM9o+A0OLYh4j06Cvew3ZOCKsiDPs0gJ9n9V6ALzZIIPI5B2+Cxfr4jdxefmoHYjyZiLXa3EIqiIVl/JgBqFSg==","signatures":[{"sig":"MEUCIFE5AJRMULI4lvlD3O3UvwxfPxU10D1OpfSJ8nkofizWAiEAl0UUI8z+00oCrMZjOOfLHADo1N23KKVIg0iNa8fgKwk=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":86216},"main":"dist/index.js","_from":"file:rehype-harden-1.1.3.tgz","types":"dist/index.d.ts","exports":{".":"./dist/index.js"},"scripts":{"test":"vitest --run","build":"tsc","check":"tsc --noEmit","test:ui":"vitest --ui","test:watch":"vitest --watch"},"_npmUser":{"name":"cramforce","email":"malte.ubl@gmail.com"},"_resolved":"/tmp/313516c0cb6657d342859d4f58227e10/rehype-harden-1.1.3.tgz","_integrity":"sha512-qM9o+A0OLYh4j06Cvew3ZOCKsiDPs0gJ9n9V6ALzZIIPI5B2+Cxfr4jdxefmoHYjyZiLXa3EIqiIVl/JgBqFSg==","repository":{"url":"git+https://github.com/vercel-labs/markdown-sanitizers.git","type":"git","directory":"rehype-harden"},"_npmVersion":"11.6.0","description":"A security-focused rehype plugin that filters URLs based on allowed prefixes","directories":{},"_nodeVersion":"24.9.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vite":"^7.0.6","vitest":"^3.2.4","unified":"^11.0.5","typescript":"^5","@types/hast":"^3.0.4","remark-parse":"^11.0.0","remark-rehype":"^11.1.1","rehype-stringify":"^10.0.1","unist-util-visit":"^5.0.0"},"_npmOperationalInternal":{"tmp":"tmp/rehype-harden_1.1.3_1760050571441_0.5285805126956871","host":"s3://npm-registry-packages-npm-production"}},"1.1.4":{"name":"rehype-harden","version":"1.1.4","keywords":["rehype","markdown","security","url-filtering","xss-protection"],"author":{"name":"S. Elliott Johnson"},"license":"MIT","_id":"rehype-harden@1.1.4","maintainers":[{"name":"cramforce","email":"malte.ubl@gmail.com"}],"homepage":"https://github.com/vercel-labs/markdown-sanitizers#readme","bugs":{"url":"https://github.com/vercel-labs/markdown-sanitizers/issues"},"dist":{"shasum":"be4989c67e7268d554cf932041abb70750642a6b","tarball":"https://registry.npmjs.org/rehype-harden/-/rehype-harden-1.1.4.tgz","fileCount":11,"integrity":"sha512-B2TB1Jj+fwFlro7mnhYKFpvKIELG++xPHDitv37oyhF2VT9zMODuEhexvEm0AXUvFvxUQGUkLylm8uWPDQp2Zw==","signatures":[{"sig":"MEUCIQCc9LxcySesxeWc1Sem6NfbMKKJhbXXw5m2Rv8liEc+OgIgTsBXSAKac9bGK5bvfiOKP8wSsDMKIWZpg6/vZGugxZU=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":98633},"main":"dist/index.js","_from":"file:rehype-harden-1.1.4.tgz","types":"dist/index.d.ts","exports":{".":"./dist/index.js"},"scripts":{"test":"vitest --run","build":"tsc","check":"tsc --noEmit","test:ui":"vitest --ui","test:watch":"vitest --watch"},"_npmUser":{"name":"cramforce","email":"malte.ubl@gmail.com"},"_resolved":"/tmp/dbb893857a2995f9ad30c88079abe5cd/rehype-harden-1.1.4.tgz","_integrity":"sha512-B2TB1Jj+fwFlro7mnhYKFpvKIELG++xPHDitv37oyhF2VT9zMODuEhexvEm0AXUvFvxUQGUkLylm8uWPDQp2Zw==","repository":{"url":"git+https://github.com/vercel-labs/markdown-sanitizers.git","type":"git","directory":"rehype-harden"},"_npmVersion":"11.6.0","description":"A security-focused rehype plugin that filters URLs based on allowed prefixes","directories":{},"_nodeVersion":"24.9.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vite":"^7.0.6","vitest":"^3.2.4","unified":"^11.0.5","typescript":"^5","@types/hast":"^3.0.4","remark-parse":"^11.0.0","remark-rehype":"^11.1.1","rehype-stringify":"^10.0.1","unist-util-visit":"^5.0.0"},"_npmOperationalInternal":{"tmp":"tmp/rehype-harden_1.1.4_1760115138831_0.09332927632158805","host":"s3://npm-registry-packages-npm-production"}},"1.1.5":{"name":"rehype-harden","version":"1.1.5","keywords":["rehype","markdown","security","url-filtering","xss-protection"],"author":{"name":"S. Elliott Johnson"},"license":"MIT","_id":"rehype-harden@1.1.5","maintainers":[{"name":"cramforce","email":"malte.ubl@gmail.com"}],"homepage":"https://github.com/vercel-labs/markdown-sanitizers#readme","bugs":{"url":"https://github.com/vercel-labs/markdown-sanitizers/issues"},"dist":{"shasum":"cf88d06efbcfe1653f6d20e5e642c6bb6445eeff","tarball":"https://registry.npmjs.org/rehype-harden/-/rehype-harden-1.1.5.tgz","fileCount":11,"integrity":"sha512-JrtBj5BVd/5vf3H3/blyJatXJbzQfRT9pJBmjafbTaPouQCAKxHwRyCc7dle9BXQKxv4z1OzZylz/tNamoiG3A==","signatures":[{"sig":"MEQCIC8y+Aq5Fp2IffYAd7aVtN4u2jK5A85k1Td2cm/VX+WfAiBuWAL7Juu/DFRREzd11TcCmDHHlkUSTINjiVx/USrKhg==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":100731},"main":"dist/index.js","_from":"file:rehype-harden-1.1.5.tgz","types":"dist/index.d.ts","exports":{".":"./dist/index.js"},"scripts":{"test":"vitest --run","build":"tsc","check":"tsc --noEmit","test:ui":"vitest --ui","test:watch":"vitest --watch"},"_npmUser":{"name":"cramforce","email":"malte.ubl@gmail.com"},"_resolved":"/tmp/ec9e837a24366db0f44a9914ebc0be3d/rehype-harden-1.1.5.tgz","_integrity":"sha512-JrtBj5BVd/5vf3H3/blyJatXJbzQfRT9pJBmjafbTaPouQCAKxHwRyCc7dle9BXQKxv4z1OzZylz/tNamoiG3A==","repository":{"url":"git+https://github.com/vercel-labs/markdown-sanitizers.git","type":"git","directory":"rehype-harden"},"_npmVersion":"11.6.0","description":"A security-focused rehype plugin that filters URLs based on allowed prefixes","directories":{},"_nodeVersion":"24.9.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vite":"^7.0.6","vitest":"^3.2.4","unified":"^11.0.5","typescript":"^5","@types/hast":"^3.0.4","remark-parse":"^11.0.0","remark-rehype":"^11.1.1","rehype-stringify":"^10.0.1","unist-util-visit":"^5.0.0"},"_npmOperationalInternal":{"tmp":"tmp/rehype-harden_1.1.5_1760117756822_0.4005633649228677","host":"s3://npm-registry-packages-npm-production"}},"1.1.6":{"name":"rehype-harden","version":"1.1.6","keywords":["rehype","markdown","security","url-filtering","xss-protection"],"author":{"name":"S. Elliott Johnson"},"license":"MIT","_id":"rehype-harden@1.1.6","maintainers":[{"name":"cramforce","email":"malte.ubl@gmail.com"}],"homepage":"https://github.com/vercel-labs/markdown-sanitizers#readme","bugs":{"url":"https://github.com/vercel-labs/markdown-sanitizers/issues"},"dist":{"shasum":"d86912ea498ebcaa1625999ce712790a02fe910d","tarball":"https://registry.npmjs.org/rehype-harden/-/rehype-harden-1.1.6.tgz","fileCount":11,"integrity":"sha512-5WyX6BFEWYmmbCF/S2gNRklfgPGTiGjviAjbseO4XlpqEilWBkvWwve6uU/JB3C0JvG/qxCZa3rBn8+ajy4i/A==","signatures":[{"sig":"MEYCIQDTiSnXrUMNYHMIAJtMsfI0nR8LTC52PDKdVFXHY4XeAAIhAPK81yRp7P6WUhNLXFWCS/Sz4GfiVAZpLGTdx4e9NjiN","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":129530},"main":"dist/index.js","type":"module","_from":"file:rehype-harden-1.1.6.tgz","types":"dist/index.d.ts","exports":{".":"./dist/index.js"},"scripts":{"test":"vitest --run","build":"tsc","check":"tsc --noEmit","test:ui":"vitest --ui","test:watch":"vitest --watch"},"_npmUser":{"name":"cramforce","email":"malte.ubl@gmail.com"},"_resolved":"/tmp/0e663921c4f868f4efda6a3e0b7c7f7e/rehype-harden-1.1.6.tgz","_integrity":"sha512-5WyX6BFEWYmmbCF/S2gNRklfgPGTiGjviAjbseO4XlpqEilWBkvWwve6uU/JB3C0JvG/qxCZa3rBn8+ajy4i/A==","repository":{"url":"git+https://github.com/vercel-labs/markdown-sanitizers.git","type":"git","directory":"rehype-harden"},"_npmVersion":"11.6.2","description":"A security-focused rehype plugin that filters URLs based on allowed prefixes","directories":{},"_nodeVersion":"24.11.1","dependencies":{"unist-util-visit":"^5.0.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vite":"^7.0.6","vitest":"^3.2.4","unified":"^11.0.5","typescript":"^5","@types/hast":"^3.0.4","remark-parse":"^11.0.0","remark-rehype":"^11.1.1","rehype-stringify":"^10.0.1"},"_npmOperationalInternal":{"tmp":"tmp/rehype-harden_1.1.6_1764372206370_0.310128824366082","host":"s3://npm-registry-packages-npm-production"}},"1.1.7":{"name":"rehype-harden","version":"1.1.7","keywords":["rehype","markdown","security","url-filtering","xss-protection"],"author":{"name":"S. Elliott Johnson"},"license":"MIT","_id":"rehype-harden@1.1.7","maintainers":[{"name":"cramforce","email":"malte.ubl@gmail.com"}],"homepage":"https://github.com/vercel-labs/markdown-sanitizers#readme","bugs":{"url":"https://github.com/vercel-labs/markdown-sanitizers/issues"},"dist":{"shasum":"4de9ec2f42c5e7f316eec06f66976ca6ac197e14","tarball":"https://registry.npmjs.org/rehype-harden/-/rehype-harden-1.1.7.tgz","fileCount":11,"integrity":"sha512-j5DY0YSK2YavvNGV+qBHma15J9m0WZmRe8posT5AtKDS6TNWtMVTo6RiqF8SidfcASYz8f3k2J/1RWmq5zTXUw==","signatures":[{"sig":"MEYCIQCoQOQ5w6Nw74vfK3ElJ3n2hfepaiR0NTTjIQy6HmgiWAIhAI/KXoM88nU5foKhTzAQvEXwoSvDzr1Cxf5j20h9riD9","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":141432},"main":"dist/index.js","type":"module","_from":"file:rehype-harden-1.1.7.tgz","types":"dist/index.d.ts","exports":{".":"./dist/index.js"},"scripts":{"test":"vitest --run","build":"tsc","check":"tsc --noEmit","test:ui":"vitest --ui","test:watch":"vitest --watch"},"_npmUser":{"name":"cramforce","email":"malte.ubl@gmail.com"},"_resolved":"/tmp/6b5e8aebfbe2f69d5ac4f9552518e86d/rehype-harden-1.1.7.tgz","_integrity":"sha512-j5DY0YSK2YavvNGV+qBHma15J9m0WZmRe8posT5AtKDS6TNWtMVTo6RiqF8SidfcASYz8f3k2J/1RWmq5zTXUw==","repository":{"url":"git+https://github.com/vercel-labs/markdown-sanitizers.git","type":"git","directory":"rehype-harden"},"_npmVersion":"11.6.2","description":"A security-focused rehype plugin that filters URLs based on allowed prefixes","directories":{},"_nodeVersion":"24.11.1","dependencies":{"unist-util-visit":"^5.0.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vite":"^7.0.6","vitest":"^3.2.4","unified":"^11.0.5","typescript":"^5","@types/hast":"^3.0.4","remark-parse":"^11.0.0","remark-rehype":"^11.1.1","rehype-stringify":"^10.0.1"},"_npmOperationalInternal":{"tmp":"tmp/rehype-harden_1.1.7_1765230784435_0.19856100184905223","host":"s3://npm-registry-packages-npm-production"}},"1.1.8":{"name":"rehype-harden","version":"1.1.8","type":"module","description":"A security-focused rehype plugin that filters URLs based on allowed prefixes","main":"dist/index.js","types":"dist/index.d.ts","exports":{".":"./dist/index.js"},"keywords":["rehype","markdown","security","url-filtering","xss-protection"],"author":{"name":"S. Elliott Johnson"},"license":"MIT","repository":{"type":"git","url":"git+https://github.com/vercel-labs/markdown-sanitizers.git","directory":"rehype-harden"},"bugs":{"url":"https://github.com/vercel-labs/markdown-sanitizers/issues"},"homepage":"https://github.com/vercel-labs/markdown-sanitizers#readme","dependencies":{"unist-util-visit":"^5.0.0"},"devDependencies":{"@types/hast":"^3.0.4","remark-parse":"^11.0.0","remark-rehype":"^11.1.1","rehype-stringify":"^10.0.1","typescript":"^5","unified":"^11.0.5","vite":"^7.0.6","vitest":"^3.2.4"},"publishConfig":{"access":"public"},"scripts":{"check":"tsc --noEmit","build":"tsc","test":"vitest --run","test:watch":"vitest --watch","test:ui":"vitest --ui"},"_id":"rehype-harden@1.1.8","_integrity":"sha512-Qn7vR1xrf6fZCrkm9TDWi/AB4ylrHy+jqsNm1EHOAmbARYA6gsnVJBq/sdBh6kmT4NEZxH5vgIjrscefJAOXcw==","_resolved":"/tmp/a69450ef6335b81c1ec4519774f48197/rehype-harden-1.1.8.tgz","_from":"file:rehype-harden-1.1.8.tgz","_nodeVersion":"24.13.0","_npmVersion":"11.6.2","dist":{"integrity":"sha512-Qn7vR1xrf6fZCrkm9TDWi/AB4ylrHy+jqsNm1EHOAmbARYA6gsnVJBq/sdBh6kmT4NEZxH5vgIjrscefJAOXcw==","shasum":"c5326fb691f64daa40711e44f0b7ce1a75e620d3","tarball":"https://registry.npmjs.org/rehype-harden/-/rehype-harden-1.1.8.tgz","fileCount":7,"unpackedSize":40403,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIAwEQBkZcYdFL6NxaqL1EME/gsvycV72lZbXoy33Ms8NAiBf1PBfs9AGF/AEMVh5EvGTFINbIBlYolD1kdqpoTEQJg=="}]},"_npmUser":{"name":"cramforce","email":"malte.ubl@gmail.com"},"directories":{},"maintainers":[{"name":"cramforce","email":"malte.ubl@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/rehype-harden_1.1.8_1771470339518_0.8808416708062541"},"_hasShrinkwrap":false}},"time":{"created":"2025-09-24T00:31:46.594Z","modified":"2026-02-19T03:05:40.193Z","1.1.0":"2025-09-24T00:31:46.778Z","1.1.2":"2025-09-24T18:53:57.631Z","1.1.3":"2025-10-09T22:56:11.632Z","1.1.4":"2025-10-10T16:52:19.019Z","1.1.5":"2025-10-10T17:35:57.004Z","1.1.6":"2025-11-28T23:23:26.569Z","1.1.7":"2025-12-08T21:53:04.591Z","1.1.8":"2026-02-19T03:05:39.698Z"},"bugs":{"url":"https://github.com/vercel-labs/markdown-sanitizers/issues"},"author":{"name":"S. Elliott Johnson"},"license":"MIT","homepage":"https://github.com/vercel-labs/markdown-sanitizers#readme","keywords":["rehype","markdown","security","url-filtering","xss-protection"],"repository":{"type":"git","url":"git+https://github.com/vercel-labs/markdown-sanitizers.git","directory":"rehype-harden"},"description":"A security-focused rehype plugin that filters URLs based on allowed prefixes","maintainers":[{"name":"cramforce","email":"malte.ubl@gmail.com"}],"readme":"# rehype-harden\n\nA rehype plugin that ensures that untrusted markdown does not contain images from and links to unexpected origins.\n\nThis is particularly important for markdown returned from [LLMs in AI agents which might have been subject to prompt\ninjection](https://vercel.com/blog/building-secure-ai-agents).\n\n## Secure prefixes\n\nThis package validates URL prefixes and URL origins. Prefix allow-lists can be circumvented\nwith open redirects, so make sure to make the prefixes are specific enough to avoid such attacks.\n\nE.g. it is more secure to allow `https://example.com/images/` than it is to allow all of\n`https://example.com/` which may contain open redirects.\n\nAdditionally, URLs may contain path traversal like `/../`. This package does not resolve these.\nIt is your responsibility that your web server does not allow such traversal.\n\n## Features\n\n- 🔒 **URL Filtering**: Blocks links and images that don't match allowed URL prefixes\n- 🔧 **Drop-in**: Works with any rehype-compatible pipeline\n\n## Installation\n\n```bash\nnpm install rehype-harden\n# or\nyarn add rehype-harden\n# or\npnpm add rehype-harden\n```\n\n## Quick Start\n\n```ts\nimport { harden } from \"rehype-harden\";\nimport remarkParse from \"remark-parse\";\nimport remarkRehype from \"remarkRehype\";\nimport { unified } from \"unified\";\n\nconst processor = unified()\n  .use(remarkParse)\n  .use(remarkRehype)\n  .use(harden, {\n    defaultOrigin: \"https://mysite.com\",\n    allowedLinkPrefixes: [\"https://github.com/\", \"https://docs.\"],\n    allowedImagePrefixes: [\"https://via.placeholder.com\", \"/\"],\n  })\n  .use(/* whatever compiler you want, eg hast-to-jsx-runtime or hast-to-svelte */);\n```\n\n## API\n\n### Args\n\n#### `defaultOrigin?: string`\n\n- The origin to resolve relative URLs against\n- Required when `allowedLinkPrefixes` or `allowedImagePrefixes` are provided (except when using wildcard `[\"*\"]`)\n- When using wildcard `[\"*\"]` without `defaultOrigin`, relative URLs (e.g., `/path`, `./page`) are allowed and preserved as-is\n- Example: `\"https://mysite.com\"`\n\n#### `allowedLinkPrefixes?: string[]`\n\n- Array of URL prefixes that are allowed for links\n- Links not matching these prefixes will be blocked and shown as `[blocked]`\n- Use `\"*\"` to allow all URLs (disables filtering. However, `javascript:` and `data:` URLs are always disallowed)\n- Default: `[]` (blocks all links)\n- Example: `['https://github.com/', 'https://docs.example.com/']` or `['*']`\n\n#### `allowedImagePrefixes?: string[]`\n\n- Array of URL prefixes that are allowed for images\n- Images not matching these prefixes will be blocked and shown as placeholders\n- Use `\"*\"` to allow all URLs (disables filtering. However, `javascript:` and `data:` URLs are always disallowed unless `allowDataImages` is enabled)\n- Default: `[]` (blocks all images)\n- Example: `['https://via.placeholder.com/', '/']` or `['*']`\n\n#### `allowDataImages?: boolean`\n\n- When set to `true`, allows `data:image/*` URLs (base64-encoded images) in image sources\n- This is useful for scenarios where images are embedded directly in markdown (e.g., documents converted from PDF or .docx)\n- Only `data:image/*` URLs are allowed; other `data:` URLs (like `data:text/html`) remain blocked for security\n- `data:` URLs are never allowed in links, regardless of this setting\n- Default: `false` (blocks all data: URLs)\n- Example: `true`\n\n#### `allowedProtocols?: string[]`\n\n- Array of custom URL protocols that are allowed in links\n- Useful for deep links to applications (e.g., `tel:`, `mailto:`, `postman:`, `vscode:`, `slack:`)\n- Use `\"*\"` to allow all protocols that can be parsed as valid URLs\n- Dangerous protocols (`javascript:`, `data:`, `file:`, `vbscript:`) are **always blocked** regardless of this setting\n- Default: `[]` (only allows built-in safe protocols: `https:`, `http:`, `mailto:`, `irc:`, `ircs:`, `xmpp:`, `blob:`)\n- Example: `['tel:', 'postman:', 'vscode:']` or `['*']`\n\n#### `linkBlockPolicy?: BlockPolicyType`\n\n- Controls how blocked links are handled\n- `\"indicator\"` (default): Renders as plain text with `[blocked]` suffix and the blocked URL in a title attribute\n- `\"text-only\"`: Renders just the link text without any indicator or URL\n- `\"remove\"`: Removes the blocked link entirely from the output\n\n#### `imageBlockPolicy?: BlockPolicyType`\n\n- Controls how blocked images are handled\n- `\"indicator\"` (default): Renders as a placeholder span with `[Image blocked: {alt text}]`\n- `\"text-only\"`: Renders just the alt text (images with no alt text are removed)\n- `\"remove\"`: Removes the blocked image entirely from the output\n\n#### `blockedImageClass?: string`\n\n- When an image is blocked with the `\"indicator\"` policy, the replacement span includes this class for styling.\n\n#### `blockedLinkClass?: string`\n\n- Same as above, but for blocked links using the `\"indicator\"` policy.\n\n## Examples\n\n### Basic Usage with Default Blocking\n\n```ts\nimport { harden } from \"rehype-harden\";\nimport remarkParse from \"remark-parse\";\nimport remarkRehype from \"remark-rehype\";\nimport { unified } from \"unified\";\n\n// Blocks all external links and images by default\nconst processor = unified()\n  .use(remarkParse)\n  .use(remarkRehype)\n  .use(harden) // No options = blocks everything\n  .use(/* your compiler */);\n\nconst result = processor.processSync(markdownContent);\n```\n\n### Allow Specific Domains\n\n```ts\nimport { harden } from \"rehype-harden\";\nimport remarkParse from \"remark-parse\";\nimport remarkRehype from \"remark-rehype\";\nimport { unified } from \"unified\";\n\nconst processor = unified()\n  .use(remarkParse)\n  .use(remarkRehype)\n  .use(harden, {\n    defaultOrigin: \"https://mysite.com\",\n    allowedLinkPrefixes: [\n      \"https://github.com/\",\n      \"https://docs.github.com/\",\n      \"https://www.npmjs.com/\",\n    ],\n    allowedImagePrefixes: [\n      \"https://via.placeholder.com/\",\n      \"https://images.unsplash.com/\",\n      \"/\", // Allow relative images\n    ],\n  })\n  .use(/* your compiler */);\n\nconst result = processor.processSync(markdownContent);\n```\n\n### Relative URL Handling\n\n```ts\nimport { harden } from \"rehype-harden\";\nimport remarkParse from \"remark-parse\";\nimport remarkRehype from \"remark-rehype\";\nimport { unified } from \"unified\";\n\nconst processor = unified()\n  .use(remarkParse)\n  .use(remarkRehype)\n  .use(harden, {\n    defaultOrigin: \"https://mysite.com\",\n    allowedLinkPrefixes: [\"https://mysite.com/\"],\n    allowedImagePrefixes: [\"https://mysite.com/\"],\n  })\n  .use(/* your compiler */);\n\nconst markdownWithRelativeUrls = `\n[Relative Link](/internal-page)\n![Relative Image](/images/logo.png)\n`;\n\nconst result = processor.processSync(markdownWithRelativeUrls);\n```\n\n### Allow All URLs (Wildcard)\n\n```ts\nimport { harden } from \"rehype-harden\";\nimport remarkParse from \"remark-parse\";\nimport remarkRehype from \"remark-rehype\";\nimport { unified } from \"unified\";\n\nconst processor = unified()\n  .use(remarkParse)\n  .use(remarkRehype)\n  .use(harden, {\n    allowedLinkPrefixes: [\"*\"],\n    allowedImagePrefixes: [\"*\"],\n  })\n  .use(/* your compiler */);\n\nconst markdownWithExternalUrls = `\n[Any Link](https://anywhere.com/link)\n![Any Image](https://untrusted-site.com/image.jpg)\n[Relative Link](/internal-page)\n`;\n\nconst result = processor.processSync(markdownWithExternalUrls);\n// All URLs are allowed, including relative URLs like /internal-page\n```\n\n**Note**: Using `\"*\"` disables URL filtering entirely. Only use this when you trust the markdown source. When using wildcard without `defaultOrigin`, relative URLs are preserved as-is in the output.\n\n### Allow Base64 Images\n\n```ts\nimport { harden } from \"rehype-harden\";\nimport remarkParse from \"remark-parse\";\nimport remarkRehype from \"remark-rehype\";\nimport { unified } from \"unified\";\n\nconst processor = unified()\n  .use(remarkParse)\n  .use(remarkRehype)\n  .use(harden, {\n    defaultOrigin: \"https://mysite.com\",\n    allowedImagePrefixes: [\"https://mysite.com/\"],\n    allowDataImages: true, // Enable base64 images\n  })\n  .use(/* your compiler */);\n\nconst markdownWithBase64Images = `\n![Base64 Image](data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==)\n![Regular Image](https://mysite.com/image.png)\n`;\n\nconst result = processor.processSync(markdownWithBase64Images);\n```\n\n**Note**: This is particularly useful when converting documents from formats like PDF or .docx where images are embedded as base64. Only `data:image/*` URLs are allowed; other data: URLs remain blocked for security.\n\n### Blob URLs\n\nBlob URLs (`blob:`) are automatically allowed by default for both links and images. These are browser-generated URLs that reference in-memory objects and are commonly used for:\n- Previewing user-uploaded files before upload\n- Client-side image manipulation\n- Displaying generated content\n\n```ts\nimport { harden } from \"rehype-harden\";\nimport remarkParse from \"remark-parse\";\nimport remarkRehype from \"remark-rehype\";\nimport { unified } from \"unified\";\n\nconst processor = unified()\n  .use(remarkParse)\n  .use(remarkRehype)\n  .use(harden, {\n    defaultOrigin: \"https://mysite.com\",\n    allowedImagePrefixes: [\"https://mysite.com/\"],\n  })\n  .use(/* your compiler */);\n\nconst markdownWithBlobUrl = `\n![Preview](blob:https://example.com/40a5fb5a-d56d-4a33-b4e2-0acf6a8e5f64)\n`;\n\nconst result = processor.processSync(markdownWithBlobUrl);\n// The blob: URL will be allowed even without being in allowedImagePrefixes\n```\n\n**Note**: Blob URLs are safe because they can only reference content already loaded in the browser's memory. They cannot be used to exfiltrate data or load external resources.\n\n### Custom Protocol Support\n\nEnable custom protocols for deep linking to applications and services:\n\n```ts\nimport { harden } from \"rehype-harden\";\nimport remarkParse from \"remark-parse\";\nimport remarkRehype from \"remark-rehype\";\nimport { unified } from \"unified\";\n\nconst processor = unified()\n  .use(remarkParse)\n  .use(remarkRehype)\n  .use(harden, {\n    allowedProtocols: ['tel:', 'mailto:', 'postman:', 'vscode:', 'slack:'],\n  })\n  .use(/* your compiler */);\n\nconst markdownWithCustomProtocols = `\n[Call us](tel:+1234567890)\n[Email support](mailto:support@example.com)\n[Open in Postman](postman://open/collection)\n[View in VS Code](vscode://file/path/to/file.ts)\n[Join Slack](slack://channel?id=C123456)\n`;\n\nconst result = processor.processSync(markdownWithCustomProtocols);\n// All these custom protocol links will be allowed\n```\n\n**Common use cases:**\n- **`tel:`** - Phone number links that open the dialer on mobile devices\n- **`mailto:`** - Email links (allowed by default, but shown here for completeness)\n- **`sms:`** - SMS/text message links\n- **`postman:`**, **`vscode:`**, **`slack:`** - Deep links to desktop applications\n- **Custom app protocols** - Links to your own Electron or native applications\n\nYou can also use the wildcard to allow any custom protocol:\n\n```ts\nconst processor = unified()\n  .use(remarkParse)\n  .use(remarkRehype)\n  .use(harden, {\n    allowedProtocols: ['*'], // Allow all protocols\n  })\n  .use(/* your compiler */);\n```\n\n**Security Note**: Even with `allowedProtocols: ['*']`, dangerous protocols like `javascript:`, `data:`, `file:`, and `vbscript:` are **always blocked** for security. Custom protocols are safe because they trigger OS-level protocol handlers and don't execute in the browser context.\n\n### Block Policies\n\nControl how blocked content is handled instead of the default `[blocked]` indicator:\n\n```ts\nimport { harden, BlockPolicy } from \"rehype-harden\";\nimport remarkParse from \"remark-parse\";\nimport remarkRehype from \"remarkRehype\";\nimport { unified } from \"unified\";\n\nconst processor = unified()\n  .use(remarkParse)\n  .use(remarkRehype)\n  .use(harden, {\n    defaultOrigin: \"https://mysite.com\",\n    allowedLinkPrefixes: [\"https://trusted.com/\"],\n    allowedImagePrefixes: [\"https://trusted.com/\"],\n    linkBlockPolicy: \"text-only\", // Show link text only, no [blocked] indicator\n    imageBlockPolicy: \"remove\", // Remove blocked images entirely\n  })\n  .use(/* your compiler */);\n```\n\nAvailable policies: `\"indicator\"` (default), `\"text-only\"`, `\"remove\"`.\n\n### Custom Styling for Blocked Content\n\n```ts\nimport { harden } from \"rehype-harden\";\nimport remarkParse from \"remark-parse\";\nimport remarkRehype from \"remark-rehype\";\nimport { unified } from \"unified\";\n\nconst processor = unified()\n  .use(remarkParse)\n  .use(remarkRehype)\n  .use(harden, {\n    defaultOrigin: \"https://mysite.com\",\n    allowedLinkPrefixes: [\"https://trusted.com/\"],\n    allowedImagePrefixes: [\"https://trusted.com/\"],\n    blockedLinkClass: \"blocked-link\",\n    blockedImageClass: \"blocked-image\",\n  })\n  .use(/* your compiler */);\n\nconst result = processor.processSync(markdownContent);\n```\n\n## Security Features\n\n### URL Filtering\n\n- **Links**: Filters `href` attributes in `<a>` elements\n- **Images**: Filters `src` attributes in `<img>` elements\n- **Relative URLs**: Properly resolves and validates relative URLs against `defaultOrigin`\n- **Path Traversal Protection**: Normalizes URLs to prevent `../` attacks\n- **Wildcard Support**: Use `\"*\"` prefix to disable filtering (only when markdown is trusted)\n- **Prefix Matching**: Validates that URLs start with allowed prefixes and have matching origins\n\n### Blocked Content Handling\n\nBehavior is configurable per element type via `linkBlockPolicy` and `imageBlockPolicy`:\n\n- **`\"indicator\"`** (default): Blocked links show a `[blocked]` suffix; blocked images show `[Image blocked: {alt}]`\n- **`\"text-only\"`**: Outputs just the link text or image alt text with no indicator\n- **`\"remove\"`**: Removes blocked elements entirely from the output\n\n### Attack Prevention\n\n- **XSS Prevention**: Blocks `javascript:`, `data:`, `vbscript:`, `file:` and other dangerous protocols (always, regardless of configuration)\n- **Redirect Protection**: Prevents unauthorized redirects to malicious sites\n- **Tracking Prevention**: Blocks unauthorized image tracking pixels\n- **Domain Spoofing**: Validates full URLs, not just domains\n- **Safe Protocols**: Allows safe protocols including `https:`, `http:`, `mailto:`, `blob:`, and others while blocking dangerous ones\n- **Custom Protocols**: Optional support for custom protocols (e.g., `tel:`, `postman:`, `vscode:`) with explicit opt-in via `allowedProtocols`\n\n## Testing\n\nThe package includes comprehensive tests covering:\n\n- Basic markdown rendering\n- URL filtering for links and images\n- Relative URL handling\n- Security bypass prevention\n- Edge cases and malformed URLs\n- TypeScript type safety\n\nRun tests:\n\n```bash\npnpm test\n```\n\n## Contributing\n\n1. Fork the repository\n2. Create your feature branch (`git checkout -b feature/amazing-feature`)\n3. Commit your changes (`git commit -m 'Add some amazing feature'`)\n4. Push to the branch (`git push origin feature/amazing-feature`)\n5. Open a Pull Request\n\n## License\n\nMIT License - see the [LICENSE](LICENSE) file for details.\n\n## Security\n\nIf you discover a security vulnerability, please send an e-mail to <security@vercel.com>.\n","readmeFilename":"README.md"}