DOMPurify
marked
Our great sponsors
- SurveyJS - A Non-Cloud Alternative to Google Forms that has it all.
- Appwrite - The open-source backend cloud platform
- Amplication - open-source Node.js backend code generator
- InfluxDB - Collect and Analyze Billions of Data Points in Real Time
- Sonar - Write Clean JavaScript Code. Always.
- Mergify - Tired of breaking your main and manually rebasing outdated pull requests?
DOMPurify | marked | |
---|---|---|
38 | 58 | |
11,661 | 30,484 | |
- | 1.2% | |
7.6 | 9.3 | |
8 days ago | 2 days ago | |
JavaScript | TypeScript | |
GNU General Public License v3.0 or later | GNU General Public License v3.0 or later |
Stars - the number of stars that a project has on GitHub. Growth - month over month growth in stars.
Activity is a relative number indicating how actively a project is being developed. Recent commits have higher weight than older ones.
For example, an activity of 9.0 indicates that a project is amongst the top 10% of the most actively developed projects that we are tracking.
DOMPurify
-
Crafting a Dynamic Blog with Next.js 13 App Directory
It is highly recommended to use an XSS Sanitizer like DOMPurify to sanitize HTML and prevent XSS attacks. For Next.js projects, which prominently feature server-side rendering, Isomorphic DOMPurify is especially valuable. It offers a seamless sanitization process across both server and client, ensuring consistent HTML sanitization in environments like Next.js where a native server-side DOM isn't present.
-
5 injection vulnerabilities hackers don't want developers to know about (and how to prevent them)
body, input.value property, or body are all different). If you need to insert untrusted input into raw HTML, use a well-tested sanitizer such as DOMPurify.
Setting a strong Content Security Policy without
unsafe-inline
orunsafe-eval
in thescript-src
ordefault-src
directives is an effective defense-in-depth) measure to prevent modern browsers from executing attacker code even if the attacker is able to insertelements into the page.
3. HTTP API injection
RESTful APIs, GraphQL, and other HTTP-based APIs are ubiquitous. When a web application makes an API call to another service, injection vulnerabilities are possible when that request includes untrusted input.
Consider a contrived example in which a web app integrates with a payments service that has a REST API endpoint for creating a subscription:
POST /subscriptions/{product_id}?price_usd=
whereprice_usd
is optional, and a pre-configured price is used if omitted. If an attacker controls the value ofproduct_id
and passes a value ofdesired_product_id?price_id=0
, the web app would end up making a request toPOST /subscriptions/desired_product_id?price_id=0
, which would allow the attacker to sign up for a free subscription.In JavaScript, the standard way to sanitize untrusted inputs in URL paths is
encodeURIComponent
, which replaces problematic characters such as?
and/
with safe percent-encoded sequences. When inserting untrusted input into URL query parameters,new URLSearchParams(queryParams)
provides a convenient, safe interface for building a query string from a JavaScript object of key-value pairs.4. Shell injection
Backend APIs sometimes need to execute external commands on the machine where they run. Consider an API that performs WHOIS lookups for a requested domain by executing the
whois
command locally.Consider the following vulnerable Node.js code:
const whois = child_process.execSync(`whois ${whoisRequest.domain}`);
If an attacker can pass the domain
reddit.com && rm -rf /
, the backend will execute the commandwhois reddit.com && rm -rf /
. Thechild_process.execSync
function passes the command string to the shell (/bin/sh
by default on Linux), which parses&& rm -rf /
as a subsequent command to wipe the filesystem.To avoid this issue, never pass untrusted input to a shell. Instead, use an interface such as
child_process.execFileSync
that executes a specific binary (which shouldn't be a shell!) and passes arguments as an array:const whois = child_process.execFileSync("whois", [whoisRequest.domain]);
Now, even if the user passes a domain
reddit.com && rm -rf /
, that entire string will be passed as the command-line argument towhois
, which will exit with an error but will not cause any harmful side-effects. Perhaps an even better solution would be to use a library to perform WHOIS queries without needing to execute a separate command.Astute readers may point out that validating the domain against a regex would also likely prevent shell injection in this case. However, avoiding the possibility of shell injection by using a safe interface that keeps untrusted input away from a shell's command parser is a more robust solution that avoids shell injection in all cases.
5. Path traversal
Finally, a path traversal vulnerability arises when an untrusted input is inserted into a filesystem path, which can cause the wrong file to be read or even written. Consider a backend API that reads a file at the path
/teams/${team_id}/${report_name}.csv
. If an attacker controls the value ofreport_name
but notteam_id
, they could pass areport_name
of../other_team_id/private.
This would cause the file/teams/team_id/../other_team_id/private.csv
(resolved to/teams/other_team_id/private.csv
) to be read, leaking data from a different team.To avoid path traversal vulnerabilities, never use untrusted input in file or directory names. It's safest always to control the names of files and directories, including IDs that you generate and control (e.g., UUIDs, KSUIDs, etc.). If the name of a file or directory absolutely must be derived from untrusted input, consider hashing it (e.g., using SHA-256) or at least encoding it into a format that doesn't include dots or slashes (e.g., URL-safe base64).
Know of good Node.js libraries for avoiding injection vulnerabilities? Let folks know in the comments!
-
Six security risk of user input in ruby code
If you're using an external view engine, or a javascript framework like react in addition to your ruby backend, you can rely on similar sanitization methods like the DOMPurify library.
-
Wat
You shouldn't roll your own for this. From what I've had to do web-wise, here's a few tools.
First, for the APIs, you need documentation: https://swagger.io/
From which you can generate JSON schemas and use those to validate in the browser and on the backend. https://www.npmjs.com/package/jsonschema
As well you should be writing a few more schemas for your application state and leverage the regex validation of your input components...
Speaking of which, you also need to sanitize out some potentially nasty input. https://www.npmjs.com/package/dompurify
Obviously this isn't everything and not perfect, but a lot of this tedium can be automated away if you have a few good examples of the happy path and some basic tests in place to prevent quick and dirty changes from poking holes in these layers.
-
Alternatives to dangerouslySetInnerHTML
Use DOMPUrify. That plus dangerouslySetInnerHTML and you're good to go.
-
Improving Render Performance in React
element. Note that using dangerouslySetInnerHTML may leave your site vulnerable to XSS attacks. In order to mitigate this risk, we need to sanitize the HTML before we inject it. We'll use DOMPurify for that task. import React, { Component } from 'react'; import marked from 'marked'; import * as DOMPurify from 'dompurify'; class UserProfile extends Component { constructor (props) { super(props); } render () { const { userId, name, skills = [] } = this.props; return ( {name} ({userId}) Skills {skills.map((skill) => ( {skill.name} ))} ); } } Enter fullscreen mode Exit fullscreen mode To recap, our process is: Parse the skill description markdown to HTML Sanitize the HTML Inject the HTML into a element If we think through this process in the context of the component render cycle, we can see how it could have a negative effect on performance: Perform two potentially expensive tasks in series (markdown parsing and HTML sanitization) Perform those tasks in a loop, which is also executed in series Repeat every time the component renders We can't do much about the first and second issues — we have to parse the markdown, sanitize the resulting HTML and render it — but we can avoid the third issue. Why render if nothing has changed? Here is an updated version of the previous example that uses shouldComponentUpdate to block the component from rendering unless the userId prop has changed: import React, { Component } from 'react'; import marked from 'marked'; import * as DOMPurify from 'dompurify'; class UserProfile extends Component { constructor (props) { super(props); } shouldComponentUpdate (nextProps) { return nextProps.userId !== this.props.userId; } render () { const { userId, name, skills = [] } = this.props; return ( {name} ({userId}) Skills {skills.map((skill) => ( {skill.name} ))} ); } } Enter fullscreen mode Exit fullscreen mode We're comparing the userId prop because it's a simple equality comparison that will tell us when the user has changed. You might be tempted to try to perform a deep equality comparison of the skills array instead, but that will likely create a worse performance issue than the one we're trying to solve. shouldComponentUpdate is intended to be used for performance optimization but should be used carefully and sparingly. Blocking a component from rendering can cause bugs in descendant components that can be difficult to fix, and there are usually better ways to improve performance. Reducing the rendering workload In the previous example, we tried to mitigate a potential performance problem by blocking a component from rendering. That was the wrong solution to take because it didn't address the real problem. The real problem isn't that we're allowing the component to render when it doesn't have to, it's that we're doing too much work in the render cycle. Therefore, the correct solution is not to block rendering entirely, but to reduce the amount of work we do while rendering. Option 1: Store preprocessed data on state One way of reducing the workload is to run the expensive process only when inputs change and store the results on state. This is simple and doesn't interfere with the render cycle. It works equally well in both class and functional components. Class component The following example updates our previous examples to preprocess the user's skills and store them on state, instead of reading directly from props in the render cycle. The preprocessing logic has been moved into the processSkills function, which is called in two places: componentDidMount, where it will run before the initial render; and componentDidUpdate, where it will run when the value of the userId prop changes. If the component renders for any other reason, it will continue to use the preprocessed data from state at no additional cost. import React, { Component } from 'react'; import marked from 'marked'; import * as DOMPurify from 'dompurify'; const processSkills = (skills = []) => { return skills.map((skill) => ({ ...skill, htmlDescription: DOMPurify.sanitize(marked.parse(skill.description)), })); }; class UserProfile extends Component { constructor (props) { super(props); this.state = { processedSkills: [], }; } componentDidMount () { this.setState({ processedSkills: processSkills(this.props.skills), }); } componentDidUpdate (prevProps) { if (this.props.userId !== prevProps.userId) { this.setState({ processedSkills: processSkills(this.props.skills), }); } } render () { const { userId, name } = this.props; const { processedSkills = [] } = this.state; return ( {name} ({userId}) Skills {processedSkills.map((skill) => ( {skill.name} ))} ); } } Enter fullscreen mode Exit fullscreen mode Functional component (hooks) The functional component produces the same result as the class component, but is more concise. The useEffect hook calls processSkills with the skills array from props when the value of the userId prop changes. The resulting array is stored on state and used to render the skills list. import React, { useState, useEffect } from 'react'; import marked from 'marked'; import * as DOMPurify from 'dompurify'; const processSkills = (skills = []) => { return skills.map((skill) => ({ ...skill, htmlDescription: DOMPurify.sanitize(marked.parse(skill.description)), })); }; const UserProfile = (props) => { const { userId, name, skills = [] } = this.props; const [ processedSkills, setProcessedSkills ] = useState([]); useEffect( () => setProcessedSkills(processSkills(skills)), [userId] ); return ( {name} ({userId}) Skills {processedSkills.map((skill) => ( {skill.name} ))} ); } Enter fullscreen mode Exit fullscreen mode Option 2: Memoize preprocessed data Another option is to memoize (not memorize) the results of the process. Memoization is a form of in-memory caching. We're only going to discuss this in the context of functional components where we can use the useMemo hook provided by React, but you may be able to achieve similar results in a class component using a third-party helper. In this example, the useMemo hook is called on every render. On the initial render and any time the userId prop is updated, useMemo returns the result of calling processSkills with the skills array. If the userId prop hasn't changed since the last render, useMemo returns the previously cached result. import React, { useMemo } from 'react'; import marked from 'marked'; import * as DOMPurify from 'dompurify'; const processSkills = (skills = []) => { return skills.map((skill) => ({ ...skill, htmlDescription: DOMPurify.sanitize(marked.parse(skill.description)), })); }; const UserProfile = (props) => { const { userId, name, skills = [] } = this.props; const processedSkills = useMemo( () => processSkills(skills), [userId] ); return ( {name} ({userId}) Skills {processedSkills.map((skill) => ( {skill.name} ))} ); } Enter fullscreen mode Exit fullscreen mode Which option should I choose? If you're working with class components, I suggest preprocessing and storing data on state unless you think you have a strong use case for integrating a memoization helper (e.g. you need memoization throughout your app, not just in one or two places). In functional components, memoization offers the same benefit as preprocessing and storing the result on state without having to maintain state. However, it isn't guaranteed to be predictable. From the React docs: You may rely on useMemo as a performance optimization, not as a semantic guarantee. In the future, React may choose to “forget” some previously memoized values and recalculate them on next render, e.g. to free memory for offscreen components. Write your code so that it still works without useMemo — and then add it to optimize performance. Storing data on state may be a better choice if the predictability of your performance optimizations is critical to your application. In many, if not most cases it won't be critical, and memoization will likely be the cleanest and simplest way to improve render performance.
-
Ask HN: Is it time for a new Storybook?
Coupled with DOMPurify [0], it helps much to simplify the messy JavaScript, HTML. Yin [1] has book on that.
-
How I Made My Portfolio with Next.js
You need to install dompurify. DOMPurify is a DOM-only, super-fast, uber-tolerant XSS sanitizer for HTML and SVG. This is the optional step.
-
Storing Rich Text from ReactJS Editor
I'm assuming JavaScript (or TypeScript) is the desired language here as you're using React. DOMPurify would work client-side and also with Nodejs if you have a JS backend that you use to to interact with Postgres (which would naturally be safer place to handle the sanitation compared to client-side). To be extra cautious, sanitising the user input both when writing and printing would be done
-
Displaying WYSIWYG editor's output with React
https://www.npmjs.com/package/dompurify or similar libraries can actually do the escaping themselves, but you will be able to set what tags you allow or what not to allow. The list of allowed tags needs to be similar to what you allow in the CKEditor. By using this library to sanitize the input, you will be able to actually use dangerouslySetHtml without issues
marked
-
🤖 AI Search and Q&A for Your Dev.to Content with Vrite
Vrite SDK provides a few built-in input and output transformers. These are functions, with standardized signatures to process the content from and into Vrite. In this case, gfmInputTransformer is essentially a GitHub Flavored Markdown parser, using Marked.js under the hood.
-
Better code highlighting on the web: rehype-tree-sitter
Another contestant in this realm is Bright[1]. It runs entirely on the server and doesn't increase bundle size as seen here[2]. Regarding parsing speed tree-sitter is without a doubt performant since it is written in Rust, but I don't have any problems "parsing on every keystroke" with a setup containing Marked[3], highlight.js[4] and a sanitizer. I did however experience performance issues with other Markdown parser libraries than Marked.
[1]: https://bright.codehike.org/
[2]: https://aihelperbot.com/test-suite
-
Vrite Editor: Open-Source WYSIWYG Markdown Editor
To handle pasting block Markdown content like this, I had to tap into ProseMirror and implement a custom mechanism (though somewhat based on TipTap’s paste rules), detecting starting and ending points of the blocks and parsing them with Marked.js.
-
How I put ChatGPT into a WYSIWYG editor
Again, with streaming enabled, you’ll now receive new tokens as soon as they’re available. Given that OpenAI’s API uses Markdown in its response format, a full message will need to be put together from the incoming tokens and parsed to HTML, as accepted by the replaceContent function. For this purpose, I’ve used the Marked.js parser.
-
Can you use Eleventy with just md files? Or do you need a templating language?
Eleventy can take an .md file and output a .html but if you want to go as vanilla as possible you can just use a module like marked to do that.
-
Next-Level Technical Blogging with Dev.to API
Once you have the article data, you’ll likely have to process its body_markdown to a format required by your website, like HTML. There are many tools you can do this with — here’s an example using Marked.js:
-
[AskJS] Advice on how to manage breaking changes in the first versions of a UI Library
While changeset still lacks some features like a unified changelog for all the package, this can be handled with some scripts. Using for example marked one could set up some parsing to create a unified changelog for the whole system.
-
Releasing Longdown: Convert longform markdown files to outline format used by Logseq
did you look at the marked parser? (https://github.com/markedjs/marked) I'm using it for an upcoming plugin I'm working on
-
Good Markdown Editor for SvelteKit?
I used marked.js to build my blog. I use it as both an in browser editing tool and to compile markdown from a database on the server. It's certainly not as advanced as ByteMD or Milkdown, but it does have some extensions to bridge a few of the gaps.
Markdown isn't really standardised, but that's understandable wanting to use it if not all clients are web-based. If you only want to display the result and not have a rich editor, you could look into something simpler like Marked.
What are some alternatives?
sanitize-html - Clean up user-submitted HTML, preserving whitelisted elements and whitelisted attributes on a per-element basis. Built on htmlparser2 for speed and tolerance
remark - markdown processor powered by plugins part of the @unifiedjs collective
js-xss - Sanitize untrusted HTML (to prevent XSS) with a configuration specified by a Whitelist
markdown-it - Markdown parser, done right. 100% CommonMark support, extensions, syntax plugins & high speed
HtmlSanitizer - Cleans HTML to avoid XSS attacks
xss-filters
Next.js - The React Framework
snarkdown - :smirk_cat: A snarky 1kb Markdown parser written in JavaScript
isomorphic-dompurify - Use DOMPurify on server and client in the same way
Retire.js - scanner detecting the use of JavaScript libraries with known vulnerabilities. Can also generate an SBOM of the libraries it finds.
Themis - Easy to use cryptographic framework for data protection: secure messaging with forward secrecy and secure data storage. Has unified APIs across 14 platforms.
SuperTokens Community - Open source alternative to Auth0 / Firebase Auth / AWS Cognito