<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Ron Perris on Medium]]></title>
        <description><![CDATA[Stories by Ron Perris on Medium]]></description>
        <link>https://medium.com/@ronperris?source=rss-87b0dc43e7e1------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*d2b2taiPBWltKOiPxOCb2A.jpeg</url>
            <title>Stories by Ron Perris on Medium</title>
            <link>https://medium.com/@ronperris?source=rss-87b0dc43e7e1------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Tue, 09 Jun 2026 02:57:46 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@ronperris/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com" rel="hub"/>
        <item>
            <title><![CDATA[Avoiding XSS via Markdown in React]]></title>
            <link>https://medium.com/javascript-security/avoiding-xss-via-markdown-in-react-91665479900?source=rss-87b0dc43e7e1------2</link>
            <guid isPermaLink="false">https://medium.com/p/91665479900</guid>
            <category><![CDATA[xss-attack]]></category>
            <category><![CDATA[markdown]]></category>
            <category><![CDATA[react]]></category>
            <category><![CDATA[ron-perris]]></category>
            <category><![CDATA[javascript]]></category>
            <dc:creator><![CDATA[Ron Perris]]></dc:creator>
            <pubDate>Thu, 19 Apr 2018 23:44:30 GMT</pubDate>
            <atom:updated>2018-04-19T23:50:49.417Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/650/1*QzcF1kXTC33bKJ-ucS2acA.jpeg" /></figure><p><strong>Using Markdown with Sanitization</strong></p><p>Websites often use <a href="https://en.wikipedia.org/wiki/Markdown">Markdown</a> to allow their users to create content. It provides a lightweight markup language that can be converted to HTML and other formats by many common libraries.</p><p>There has been some recent discussion in the news about Markdown based XSS <a href="https://github.com/Nhoya/PastebinMarkdownXSS">exploits in major websites</a> like Pastebin.</p><p>We also heard about this Markdown XSS <a href="https://twitter.com/manicode/status/982068499798409221">issue during a recent presentation</a> at the <a href="https://twitter.com/locomocosec">LocoMoco Security Conference</a>.</p><p>The root of the issue it that Markdown specification <a href="https://daringfireball.net/projects/markdown/syntax#html">actively encourages HTML in Markdown</a>, but that isn’t a good default for sites who are worried about code injection attacks.</p><p><strong>Markdown Converts to HTML</strong></p><pre>// Markdown Input</pre><pre>Heading<br>=======</pre><pre>// HTML Output</pre><pre>&lt;<strong>h1</strong>&gt;Heading&lt;/<strong>h1</strong>&gt;</pre><p>Some developers choose Markdown over HTML because they feel that it will help them <a href="https://twitter.com/manicode/status/986714962365001729">avoid code injection attacks like XSS</a>. Markdown doesn’t provide any security benefits by default.</p><p>In fact the most popular Markdown parsing library on npm, called <a href="https://www.npmjs.com/package/marked">marked</a>, by default doesn’t sanitize or escape HTML content found within Markdown text when making a conversion. <a href="https://daringfireball.net/projects/markdown/syntax#html">This is also what the standard encourages</a>.</p><pre>// Markdown Input</pre><pre>Heading<br>=======<br>&lt;script&gt;alert(1)&lt;/script&gt;</pre><pre>// HTML Output</pre><pre>&lt;h1&gt;Heading&lt;/h1&gt;<br>&lt;script&gt;alert(1)&lt;/script&gt;</pre><p>The <a href="https://www.npmjs.com/package/marked">JavaScript Markdown conversion library name marked</a> has 1,054,581 downloads in the last 7 days and the npm hosted webpage for the project doesn’t mention security or sanitization. If you click some links and visit the <a href="https://marked.js.org/#/README.md#README.md">projects home page</a> you will find this message.</p><blockquote>Security</blockquote><blockquote>The only completely secure system is the one that doesn’t exist in the first place. Having said that, we take the security of Marked very seriously.</blockquote><p><strong>Code Injection via Unsanitized Markdown (The Default)</strong></p><p>Using the code example from the <a href="https://marked.js.org/#/README.md#README.md">marked webpage</a> we can add a <a href="https://developer.mozilla.org/en-US/docs/Glossary/Cross-site_scripting">XSS</a> payload to the <a href="https://developer.mozilla.org/en-US/docs/Web/API/Element/innerHTML#Security_considerations">innerHTML</a> of an element in the <a href="https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model">DOM</a> and perform a XSS attack. This is the default behavior of the library. <a href="https://daringfireball.net/projects/markdown/syntax#html">It just forwards on any HTML</a> that is found in the source text and puts it directly into the results after converting the Markdown tags to HTML.</p><pre>&lt;!doctype html&gt;<br>&lt;html&gt;<br>&lt;head&gt;<br>  &lt;meta charset=&quot;utf-8&quot;/&gt;<br>  &lt;title&gt;Marked in the browser&lt;/title&gt;<br>&lt;/head&gt;<br>&lt;body&gt;<br>  &lt;div id=&quot;content&quot;&gt;&lt;/div&gt;<br>  &lt;script src=&quot;https://cdn.jsdelivr.net/npm/marked/marked.min.js&quot;&gt;&lt;/script&gt;<br>  &lt;script&gt;<br>    document<br>      .getElementById(&#39;content&#39;)<br>      .innerHTML = marked(&#39;&lt;img src=x onerror=alert(1)&gt;&lt;/img&gt;&#39;);<br>  &lt;/script&gt;<br>&lt;/body&gt;<br>&lt;/html&gt;</pre><p><strong>Opting in to Markdown Sanitization</strong></p><p>Marked knows how to safely handle XSS payloads in the Markdown text, you just need to opt-in to that behavior. It is three clicks from the <a href="https://www.npmjs.com/package/marked">npm page</a> on the <a href="https://marked.js.org/#/USING_ADVANCED.md">advanced configuration page</a>.</p><blockquote>myMarked.setOptions({<br> renderer: new myMarked.Renderer(),<br> highlight: function(code) {<br> return require(&#39;highlight.js&#39;).highlightAuto(code).value;<br> },<br> pedantic: false,<br> gfm: true,<br> tables: true,<br> breaks: false,<br> sanitize: false,<br> smartLists: true,<br> smartypants: false,<br> xhtml: false<br>});</blockquote><blockquote>// Compile<br>console.log(myMarked(&#39;I am using __markdown__.&#39;));</blockquote><p>If you read through the list of options passed into setOptions you can see that the sanitize property defaults to false. If you would like marked to prevent XSS attacks in your Markdown you just have to set that value to true.</p><pre>marked.setOptions({ sanitize: true })<br>marked(&#39;&lt;img src=x onerror=alert(1)&gt;&lt;/img&gt;&#39;)<br>// Returns &quot;&lt;p&gt;&amp;lt;img src=x onerror=alert(1)&amp;gt;&amp;lt;/img&amp;gt;&lt;/p&gt;&quot;</pre><p>The fine author of the marked package got the <a href="https://github.com/markedjs/marked/blob/master/lib/marked.js#L1003">XSS sanitization right</a>, the sanitization works, if you opt-in for it.</p><p><strong>Injection Attacks in React Components with Sanitization Turned On</strong></p><p>I found a popular React component module on npm called react-marked-markdown. It allows you to make React components that render Markdown. It uses the marked library we talked about earlier under the hood.</p><p>The react-marked-markdown module was downloaded 972 times last week. It mentions on the npm page that it used the sanitize: true option from marked by default.</p><p>Unfortunately, if you use the react-marked-markdown module you will be vulnerable to XSS attacks even with the sanitize: true option.</p><pre>import { MarkdownPreview } from &#39;react-marked-markdown&#39;</pre><pre>const spike = &#39;[XSS](javascript: alert`1`)&#39;</pre><pre>const Post = ({ post }) =&gt; (<br>  &lt;div&gt;<br>    &lt;h1&gt;{post.title}&lt;/h1&gt;<br>    &lt;MarkdownPreview<br>      markedOptions={{<br>        gfm: true,<br>        tables: true,<br>        breaks: false,<br>        pedantic: false,<br>        sanitize: true,<br>        smartLists: true,<br>        smartypants: false<br>      }}<br>      value={post.content}<br>    /&gt;<br>  &lt;/div&gt;<br>)</pre><p>This is where things get interesting. It is possible that the marked npm package contains the correct sanitization logic but when the marked package is used by the react-marked-markdown module it is still vulnerable. Let me show you how this can happen. This is the code from the marked library that handles sanitization of links.</p><pre>// <a href="https://github.com/markedjs/marked/blob/master/lib/marked.js#L973">https://github.com/markedjs/marked/blob/master/lib/marked.js#L973</a></pre><pre>Renderer.prototype.link = function(href, title, text) {<br>  if (this.options.sanitize) {<br>    try {<br>      var prot = decodeURIComponent(unescape(href))<br>        .replace(/[^\w:]/g, &#39;&#39;)<br>        .toLowerCase()<br>    } catch (e) {<br>      return text<br>    }<br>    if (<br>      prot.indexOf(&#39;javascript:&#39;) === 0 ||<br>      prot.indexOf(&#39;vbscript:&#39;) === 0 ||<br>      prot.indexOf(&#39;data:&#39;) === 0<br>    ) {<br>      return text<br>    }<br>  }<br>  if (this.options.baseUrl &amp;&amp; !originIndependentUrl.test(href)) {<br>    href = resolveUrl(this.options.baseUrl, href)<br>  }<br>  try {<br>    href = encodeURI(href).replace(/%25/g, &#39;%&#39;)<br>  } catch (e) {<br>    return text<br>  }<br>  var out = &#39;&lt;a href=&quot;&#39; + escape(href) + &#39;&quot;&#39;<br>  if (title) {<br>    out += &#39; title=&quot;&#39; + title + &#39;&quot;&#39;<br>  }<br>  out += &#39;&gt;&#39; + text + &#39;&lt;/a&gt;&#39;<br>  return out<br>}</pre><p>Here is the code from the react-marked-markdown library overriding the render.link method with a custom function that doesn’t do the sanitization anymore.</p><pre>const renderer = new marked.Renderer()<br>renderer.link = (href, title, text) =&gt;<br>  `&lt;a target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot; href=&quot;${href}&quot; title=&quot;${title}&quot;&gt;${text}&lt;/a&gt;`</pre><p><strong>Securely Adding Markdown Output to a React Component</strong></p><p>The <a href="https://www.npmjs.com/package/react-markdown">react-markdown module </a>on npm takes a different approach to adding the HTML generated from Markdown into React components. They mention that they don’t use <a href="https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml">dangerouslySetInnerHTML</a> on their npm page. That is a great start.</p><blockquote>If you don’t need to render HTML, this component does not use dangerouslySetInnerHTML at all - this is a Good Thing™.</blockquote><p>Rather than using strings to string conversions, they convert the Markdown to an Abstract Syntax Tree (AST). Then they use the React library functions, like React.createElement, to make the element tree. Then they let ReactDOM worry about rendering the tree and applying the correct contextual escaping.</p><p>They also take extra care in the react-markdown module to handle any dangerous props that could contain XSS exploits. For example they transform all anchor tag’s href attributes by default.</p><blockquote>transformLinkUri - <em>function|null</em> Function that gets called for each encountered link with a single argument - uri. The returned value is used in place of the original. The default link URI transformer acts as an XSS-filter, neutralizing things like javascript:, vbscript: and file: protocols. If you specify a custom function, this default filter won&#39;t be called, but you can access it as require(&#39;react-markdown&#39;).uriTransformer. If you want to disable the default transformer, pass null to this option.</blockquote><pre><a href="https://github.com/rexxars/react-markdown/blob/master/src/uriTransformer.js">https://github.com/rexxars/react-markdown/blob/master/src/uriTransformer.js</a></pre><pre>&#39;use strict&#39;</pre><pre>var protocols = [&#39;http&#39;, &#39;https&#39;, &#39;mailto&#39;, &#39;tel&#39;]</pre><pre>module.exports = function uriTransformer(uri) {<br><br>  var url = (uri || &#39;&#39;).trim()<br>  var first = url.charAt(0)</pre><pre>if (first === &#39;#&#39; || first === &#39;/&#39;) {<br>    return url<br>  }</pre><pre>var colon = url.indexOf(&#39;:&#39;)<br>  if (colon === -1) {<br>    return url<br>  }</pre><pre>var length = protocols.length<br>  var index = -1</pre><pre>while (++index &lt; length) {<br>    var protocol = protocols[index]</pre><pre>if (<br>      colon === protocol.length &amp;&amp;<br>      url.slice(0, protocol.length) === protocol<br>    ) {<br>      return url<br>    }<br>  }</pre><pre>index = url.indexOf(&#39;?&#39;)<br>  if (index !== -1 &amp;&amp; colon &gt; index) {<br>    return url<br>  }</pre><pre>index = url.indexOf(&#39;#&#39;)<br>  if (index !== -1 &amp;&amp; colon &gt; index) {<br>    return url<br>  }</pre><pre>// eslint-disable-next-line no-script-url<br>  return &#39;javascript:void(0)&#39;<br>}</pre><p>If you use the react-markdown module and pass in attacker controlled anchor href attribute values they will be replaced with the string javascript:void(0).</p><pre>const spike = &#39;[xss](javascript:onerror=alert;throw%20&quot;hi&quot;)&#39;<br>ReactDOM.render(<br>  &lt;ReactMarkdown<br>    source={ spike }<br>  /&gt;,<br>  document.getElementById(&#39;root&#39;)<br>)</pre><p><strong>Conclusion</strong></p><p>Using Markdown output in the innerHTML of an element will lead to trouble most of the time. If you use Markdown in the dangerouslySetInnerHTML prop of a React component you are also asking for trouble. If you want protection then make sure you enable the sanitization options in your Markdown library. If you are using a third-party module take a peak under the hood and make sure they are correctly setting the Markdown library options and sanitizing values before inserting them into the DOM.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=91665479900" width="1" height="1" alt=""><hr><p><a href="https://medium.com/javascript-security/avoiding-xss-via-markdown-in-react-91665479900">Avoiding XSS via Markdown in React</a> was originally published in <a href="https://medium.com/javascript-security">javascript-security</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Avoiding XSS in React is Still Hard]]></title>
            <link>https://medium.com/javascript-security/avoiding-xss-in-react-is-still-hard-d2b5c7ad9412?source=rss-87b0dc43e7e1------2</link>
            <guid isPermaLink="false">https://medium.com/p/d2b5c7ad9412</guid>
            <category><![CDATA[ron-perris]]></category>
            <category><![CDATA[secure-coding]]></category>
            <category><![CDATA[security]]></category>
            <category><![CDATA[javascript]]></category>
            <category><![CDATA[react]]></category>
            <dc:creator><![CDATA[Ron Perris]]></dc:creator>
            <pubDate>Sun, 15 Apr 2018 04:43:30 GMT</pubDate>
            <atom:updated>2018-04-15T05:34:23.019Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/650/1*QzcF1kXTC33bKJ-ucS2acA.jpeg" /></figure><p><strong>Introduction</strong></p><p>I’ve spent the last few weeks thinking about <a href="https://reactjs.org/">React </a>from a <a href="https://www.synopsys.com/software-integrity/training/elearning.html">secure coding </a>perspective. Since React is a library for creating component based user interfaces, most of the attack surface is related to issues with rendering elements in the <a href="https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model">DOM</a>. The smart folks over at Facebook have handled this by building automatic escaping into the <a href="https://reactjs.org/docs/react-dom.html">React DOM</a> library code.</p><p><strong>Built-in Escaping is Limited</strong></p><p>The escaping code in React DOM works great when you are passing a string value into [...children] . Notice the other two arguments to React.createElement type and [props], values passed into them are unescaped.</p><pre>// From <a href="https://reactjs.org/docs/react-api.html#createelement">https://reactjs.org/docs/react-api.html#createelement</a><br>React.createElement(<br>  type,<br>  [props],<br>  [...children]<br>)</pre><p><strong>Data Passed as Props is Unescaped</strong></p><p>When you pass data into a <a href="https://reactjs.org/docs/react-api.html#createelement">React element via props</a>, the data is not escaped before being rendered into the DOM. This means that an attacker can control the raw values inside of HTML attributes. A classic <a href="https://developer.mozilla.org/en-US/docs/Glossary/Cross-site_scripting">XSS</a> attack is to put a URL with a javascript: protocol into the href value of an anchor tag. When a user clicks on the anchor tag the browser will execute the JavaScript found in the href attribute value.</p><pre>// Classic XSS via anchor tag href attribute.<br>&lt;a href=&quot;javascript: alert(1)&quot;&gt;Click me!&lt;/a&gt;</pre><p>This classic XSS attack still works in React when rendering a component with React DOM.</p><pre>// Classic XSS via anchor tag href attribute in a React component.<br>ReactDOM.render(<br>  &lt;a href=&quot;javascript: alert(1)&quot;&gt;Click me!&lt;/a&gt;,<br>  document.getElementById(&#39;root&#39;)<br>)</pre><p><strong>Mitigating XSS Attacks on React Props</strong></p><p>There are a few options for mitigating attacks on React components. You could do contextual escaping for the prop value.</p><p>You would need a list of known bad values for each attribute and you would need to know which characters to escape to make the value benign. <a href="https://www.owasp.org/index.php/XSS_Filter_Evasion_Cheat_Sheet">Historically this hasn’t gone very well</a>.</p><p>You could also try filtering, <a href="https://www.owasp.org/index.php/XSS_Filter_Evasion_Cheat_Sheet">which also hasn’t gone very well in the past.</a></p><p>For prop values you probably want to use validation. Here is a common attempt at avoiding XSS with blacklist style validation.</p><pre>const URL = require(&#39;url-parse&#39;)<br>const url = new URL(attackerControlled)</pre><pre>function isSafe(url) {<br>  if (url.protocol === &#39;javascript:&#39;) return false</pre><pre>  return true<br>}</pre><pre>isSafe(URL(&#39;javascript: alert(1)&#39;)) // Returns false<br>isSafe(URL(&#39;<a href="http://www.reactjs.org&#39;">http://www.reactjs.org&#39;</a>)) // Returns true</pre><p>This approach seems to be working, but as we will see shortly it will only prevent simple attacks that don’t attempt to evade the blacklist.</p><p><strong>Validating Against a Blacklist is Hard</strong></p><p>In the example above we are doing a lot of things right. We are using the <a href="https://www.npmjs.com/">npm</a> module called <a href="https://www.npmjs.com/package/url-parse">url-parse</a> to parse the URL instead of hand-rolling a solution. We are attempting to validate the url with an isolated reusable function, so that our security audits and remediation tasks will be easier. We are handling the failure case first in the function and using an early return strategy to handle a failure.</p><p>It is usually a bad idea to use blacklists to enforce validation. Here we can defeat the isSafe function using our spacebar.</p><pre>const URL = require(&#39;url-parse&#39;)</pre><pre>function isSafe(url) {<br>  if (url.protocol === &#39;javascript:&#39;) return false</pre><pre>  return true<br>}</pre><pre>isSafe(URL(&#39; javascript: alert(1)&#39;)) // <strong>Returns true</strong><br>isSafe(URL(&#39;<a href="http://www.reactjs.org&#39;">http://www.reactjs.org&#39;</a>)) // Returns true</pre><p><strong>Reading npm Module Documentation is Hard (Not Joking)</strong></p><p>The reason that isSafe(URL(&#39; javascript: alert(1)&#39;)) doesn’t work as intended in our isSafe function is described in the documentation page for url-parse over on npm.</p><blockquote>baseURL (Object | String): An object or string representing the base URL to use in case urlis a relative URL. This argument is optional and defaults to <a href="https://developer.mozilla.org/en-US/docs/Web/API/Location">location</a> in the browser.</blockquote><p>So when we pass the string javascript: alert(1) with a leading space I think url-parse assumes we are providing a relative URL and it is happy to assume the protocol from the browser’s location. In this case it believes the protocol for javascript: alert(1) is http:.</p><pre>const URL = require(&#39;url-parse&#39;)</pre><pre>URL(&#39; javascript: alert(1)&#39;).protocol // Returns <strong>http</strong>:</pre><p>If we look further down in the documentation for url-parse on npm we will find this part.</p><blockquote>Note that when url-parse is used in a browser environment, it will default to using the browser&#39;s current window location as the base URL when parsing all inputs. To parse an input independently of the browser&#39;s current URL (e.g. for functionality parity with the library in a Node environment), pass an empty location object as the second parameter:</blockquote><p>It tells us that if we pass an empty location object as the second parameter to instances of url-parse we can disable the behavior that is causing all strings to be treated as having the browser’s location protocol as their protocol.</p><pre>const URL = require(&#39;url-parse&#39;)</pre><pre>URL(&#39; javascript: alert(1)&#39;, {}).protocol // Returns &quot;&quot;</pre><p>With an empty object as the second argument we can see that we get an empty string back as the protocol for javascript: alert(1) .</p><p><strong>Fixing that Blacklist Function</strong></p><p>Looking back at the isSafe(url) blacklist function we can improve it by looking for empty strings in addition to the javascript: protocol.</p><pre>const URL = require(&#39;url-parse&#39;)<br>const url = new URL(attackerControlled)</pre><pre>function isSafe(url) {<br>  if (url.protocol === &#39;javascript:&#39;) return false<br>  if (url.protocol === &#39;&#39;) return false</pre><pre>  return true<br>}</pre><pre>isSafe(URL(&#39;javascript: alert(1)&#39;, {})) // Returns false<br>isSafe(URL(&#39;<a href="http://www.reactjs.org&#39;">http://www.reactjs.org&#39;</a>)) // Returns true</pre><p>Oh yeah, this is a post about React XSS security. Let’s get back to that now. We can try to use our improved isSafe function to do some validation in a React component.</p><pre>import React, { Component } from &#39;react&#39;<br>import ReactDOM from &#39;react-dom&#39;<br>import URL from &#39;url-parse&#39;</pre><pre>class SafeURL extends Component {<br>  isSafe(dangerousURL, text) {<br>    const url = URL(dangerousURL, {})<br>    if (url.protocol === &#39;javascript:&#39;) return false<br>    if (url.protocol === &#39;&#39;) return false</pre><pre>    return true<br>  }</pre><pre>  render() {<br>    const dangerousURL = this.props.dangerousURL<br>    const safeURL = this.isSafe(dangerousURL) ? dangerousURL : null</pre><pre>    return &lt;a href={safeURL}&gt;{this.props.text}&lt;/a&gt;<br>  }<br>}</pre><pre>ReactDOM.render(<br>  &lt;SafeURL dangerousURL=&quot; javascript: alert(1)&quot; text=&quot;Click me!&quot; /&gt;,<br>  document.getElementById(&#39;root&#39;)<br>)</pre><p>This example above is not injectable, maybe.</p><p><strong>Whitelist Validation</strong></p><p>I’ve never feel very comfortable with blacklist based solutions for security. It would be like if you heard a noise in your house at night and went downstairs to find an unfamiliar person standing in your living room and in order to figure out if they belonged in your house you looked them up in a criminal offenders database.</p><p>I prefer whitelist based solutions. I know who is supposed to be in my house.</p><pre>import React, { Component } from &#39;react&#39;<br>import ReactDOM from &#39;react-dom&#39;</pre><pre>const URL = require(&#39;url-parse&#39;)</pre><pre>class SafeURL extends Component {<br>  isSafe(dangerousURL, text) {<br>    const url = URL(dangerousURL, {})<br>    if (url.protocol === &#39;http:&#39;) return true<br>    if (url.protocol === &#39;https:&#39;) return true</pre><pre>    return false<br>  }</pre><pre>  render() {<br>    const dangerousURL = this.props.dangerousURL<br>    const safeURL = this.isSafe(dangerousURL) ? dangerousURL : null</pre><pre>    return &lt;a href={safeURL}&gt;{this.props.text}&lt;/a&gt;<br>  }<br>}</pre><pre>ReactDOM.render(<br>  &lt;SafeURL dangerousURL=&quot; javascript: alert(1)&quot; text=&quot;Click me!&quot; /&gt;,<br>  document.getElementById(&#39;root&#39;)<br>)</pre><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=d2b5c7ad9412" width="1" height="1" alt=""><hr><p><a href="https://medium.com/javascript-security/avoiding-xss-in-react-is-still-hard-d2b5c7ad9412">Avoiding XSS in React is Still Hard</a> was originally published in <a href="https://medium.com/javascript-security">javascript-security</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
    </channel>
</rss>