I just published a new extension on Chrome and Firefox that allows anyone to run Code Tours from the Github UI. More information about Code Tours and the extension in this blog post.
Article No Longer Available
I thought it would be nice to write a series about how you could do exactly the same, step by step.
This fifth blog post will focus on integrating your features securely into a website.
Short notice
For this part of the extension, we need to be able to import some external modules.
I won't explain how to setup webpack in this post, but if it's something you'd be interested in, drop a comment and I may write another post in the series about this.
What we are building
Here is a screenshot of what we'll have at the end of this post. We'll display safely the description of a Code Tour Step:
The challenge
In order to display a Code Tour to the user, here are the different steps that we implemented:
- Find the list of tours
- Get the content of each code tour
- Redirect to the right page
Now that we are at the right place, with the content of the Code Tour, we need to load it into the page.
The content of a Code Tour is written using the Markdown language. There are ways to generate html out of Markdown, but we need to make sure that it's safe.
But let's first build a basic version!
The innocent version
First, let's add the text directly to the UI. Here is the code we had so far:
function forwardRequest(message) {
return new Promise((resolve, reject) => {
chrome.runtime.sendMessage(message, (response) => {
if (!response) return reject(chrome.runtime.lastError);
return resolve(response);
});
});
}
document.addEventListener("DOMContentLoaded", async () => {
const urlParams = new URLSearchParams(window.location.search);
const title = urlParams.get("code-tour-title");
if (!title) return;
const tour = await forwardRequest({ title });
const step = urlParams.get("step");
console.log(tour.steps[step]);
});
Now, instead of logging the state, let's add the description of the tour on the right line:
document.addEventListener("DOMContentLoaded", async () => {
const urlParams = new URLSearchParams(window.location.search);
const title = urlParams.get("code-tour-title");
if (!title) return;
const tour = await forwardRequest({ title });
const step = urlParams.get("step");
// We'll add the description on the right line
const parent = document.querySelector(
`#LC${tour.steps[step].line}.blob-code`
);
const section = document.createElement("div");
const span = document.createElement("span");
span.innerHTML = tour.steps[step].description;
section.append(span);
// A bit of style
section.setAttribute(
"style",
`
padding: 14px;
margin: 14px;
border: 1px lightgrey solid;
background-color: white;
border-radius: 1em;
font-family: sans-serif;
`
);
parent.append(section);
});
Transform Markdown to HTML
In order to transform the Markdown to HTML, we can use a generator such as showdown. It's really easy to use:
const showdown = require('showdown')
const converter = new showdown.Converter()
const htmlString = converter.makeHtml(yourMarkdownString)
Now we can use this as inner HTML for the section:
span.innerHTML = converter.makeHtml(tour.steps[step].description);
XSS injections with Markdown
Since our Markdown generation can write html, we can probably generate dangerous code as well. Consider the following Markdown code:
[XSS injection](javascript:alert('xss'))
Once you use a html generator (for instance showdown) with this code, you will get this html:
<p><a href="javascript:alert('xss')">XSS injection</a></p>
Try this in your browser, if you click it, it executes the JavaScript. Of course this is a very basic example, but there are a lot of more complex ways to exploit this. And since the Code Tours we load are untrusted code, we better protect our users!
Protecting from the XSS
There are a few libraries you can use to protect from xss. For instance the xss library on npm.
Using this, we are able to escape the dangerous bits of our HTML. Just use it this way:
filterXSS(converter.makeHtml(rawText))
Let's use it in our code:
span.innerHTML = filterXSS(converter.makeHtml(tour.steps[step].description));
Now our users are protected.
Conclusion
If there is one thing to remember from this post, it's this:
don't trust external inputs, even if it's text
As soon as you apply any kind of transformation to something you have no control over, there is a risk for it to be exploited. And trust me, it’s way worse when the code is in an extension that can be loaded on any website on your browser.
In the next post, we'll see how to deploy this extension on the different stores. Feel free to follow me here if you want to check the post when it's out:
Photo by Ricardo Gomez Angel on Unsplash
Top comments (0)