Free for 90 days: Sign up now and get 90 days managed free after your first deposit. See offer details

<title>Dismiss</title>
Engineering at Betterment

A Journey to Truly Safe HTML Rendering

We leverage Rubocop’s OutputSafety check to ensure we’re being diligent about safe HTML rendering, so when we found vulnerabilities, we fixed them.

Articles by Katelyn Lesse

By Katelyn Lesse
  |  Published: March 19, 2018

As developers of financial software on the web, one of our biggest responsibilities is to keep our applications secure. One area we need to be conscious of is how we render HTML. If we don’t escape content properly, we could open ourselves and our customers up to security risks. We take this seriously at Betterment, so we use tools like Rubocop, the Ruby static analysis tool, to keep us on the right track. When we found that Rubocop’s OutputSafety check had some holes, we plugged them.

What does it mean to escape content?

Escaping content simply means replacing special characters with entities so that HTML understands to print those characters rather than act upon their special meanings. For example, the < character is escaped using &lt;, the >character is escaped using &gt;, and the & character is escaped using &amp;.

What could happen if we don’t escape content?

We escape content primarily to avoid opening ourselves up to XSS (cross-site scripting) attacks. If we were to inject user-provided content onto a page without escaping it, we’d be vulnerable to executing malicious code in the user’s browser, allowing an attacker full control over a customer’s session.This resource is helpful to learn more about XSS.

Rails makes escaping content easier

Rails escapes content by default in some scenarios, including when tag helpers are used. In addition, Rails has a few methods that provide help in escaping content. safe_join escapes the content and returns a SafeBuffer (a String flagged as safe) containing it. On the other hand, some methods are just a means for us to mark content as already safe. For example, the <%==interpolation token renders content as is and rawhtml_safe, and safe_concat simply return a SafeBuffer containing the original content as is, which poses a security risk. If content is inside a SafeBuffer, Rails won’t try to escape it upon rendering.

Some examples:

html_safe:

[1] pry(main)> include ActionView::Helpers::OutputSafetyHelper
=> Object
[2] pry(main)> result = “<p>hi</p>”.html_safe
=> “<p>hi</p>”
[3] pry(main)> result.class
=> ActiveSupport::SafeBuffer

raw:

[1] pry(main)> result = raw(“<p>hi</p>”)
=> “<p>hi</p>”
[2] pry(main)> result.class
=> ActiveSupport::SafeBuffer

safe_concat:

[1] pry(main)> include ActionView::Helpers::TextHelper
=> Object
[2] pry(main)> buffer1 = “<p>hi</p>”.html_safe
=> “<p>hi</p>”
[3] pry(main)> result = buffer1.safe_concat(“<p>bye</p>”)
=> “<p>hi</p><p>bye</p>”
[4] pry(main)> result.class
=> ActiveSupport::SafeBuffer

safe_join:

[1] pry(main)> include ActionView::Helpers::OutputSafetyHelper
=> Object
[2] pry(main)> result = safe_join([“<p>hi</p>”, “<p>bye</p>”])
=> “&lt;p&gt;hi&lt;/p&gt;&lt;p&gt;bye&lt;/p&gt;”
[3] pry(main)> result.class
=> ActiveSupport::SafeBuffer
=> ActiveSupport::SafeBuffer

Rubocop: we’re safe!

As demonstrated, Rails provides some methods that mark content as safe without escaping it for us. Rubocop, a popular Ruby static analysis tool, provides a cop (which is what Rubocop calls a “check”) to alert us when we’re using these methods: Rails/OutputSafety. At Betterment, we explicitly enable this cop in our Rubocop configurations so if a developer wants to mark content as safe, they will need to explicitly disable the cop. This forces extra thought and extra conversation in code review to ensure that the usage is in fact safe.

… Almost

We were thrilled about the introduction of this cop — we had actually written custom cops prior to its introduction to protect us against using the methods that don’t escape content. However, we realized there were some issues with the opinions the cop held about some of these methods.

The first of these issues was that the cop allowed usage of raw and html_safewhen the usages were wrapped in safe_join. The problem with this is that when raw or html_safe are used to mark content as already safe by putting it in a SafeBuffer as is, safe_join will not actually do anything additional to escape the content. This means that these usages of raw and html_safeshould still be violations.

The second of these issues was that the cop prevented usages of raw and html_safe, but did not prevent usages of safe_concatsafe_concat has the same functionality as raw and html_safe — it simply marks the content safe as is by returning it in a SafeBuffer. Therefore, the cop should hold the same opinions about safe_concat as it does about the other two methods.

So, we fixed it

Rather than continue to use our custom cops, we decided to give back to the community and fix the issues we had found with the Rails/OutputSafety cop. We began with this pull request to patch the first issue — change the behavior of the cop to recognize raw and html_safe as violations regardless of being wrapped in safe_join. We found the Rubocop community to be welcoming, making only minor suggestions before merging our contribution. We followed up shortly after with a pull request to patch the second issue — change the behavior of the cop to disallow usages of safe_concat. This contribution was merged as well.

Contributing to Rubocop was such a nice experience that when we later found that we’d like to add a configuration option to an unrelated cop, we felt great about opening a pull request to do so, which was merged as well.

And here we are!

Our engineering team here at Betterment takes security seriously. We leverage tools like Rubocop and Brakeman, a static analysis tool specifically focused on security, to make our software safe by default against many of the most common security errors, even for code we haven’t written yet. We now rely on Rubocop’s Rails/OutputSafety cop (instead of our custom cop) to help ensure that our team is making good decisions about escaping HTML content. Along the way, we were able to contribute back to a great community.

This article is part of Engineering at Betterment.

These articles are maintained by Betterment Holdings Inc. and they are not associated with Betterment, LLC or MTG, LLC. The content on this article is for informational and educational purposes only. © 2017–2019 Betterment Holdings Inc.

Recommended Content

View All Resources
How to Roll Over Your 401(k) to Betterment

How to Roll Over Your 401(k) to Betterment

Betterment makes rolling over your 401(k) simple and efficient. Get started online, or with a rollover concierge.

CI/CD: Shortening the Feedback Loop

CI/CD: Shortening the Feedback Loop

As we improve and scale our CD platform, shortening the feedback loop with notifications was a small, effective, and important piece.

Cash Analysis Methodology

Cash Analysis Methodology

Betterment's cash analysis aims to provide smart feedback when we think you have extra cash that could be earning you more value if it were in a higher yield account.

Explore your first goal

Safety Net

This is a great place to start—an emergency fund for life's unplanned hiccups. A safety net is a conservative portfolio.

Retirement

Whether it's a long way off or just around the corner, we'll help you save for the retirement you deserve.

General Investing

If you want to invest and build wealth over time, then this is the goal for you. This is an excellent goal type for unknown future needs or money you plan to pass to future generations.

Smart Saver

You could earn 20X more than a typical savings account with our low-risk investing account for your extra cash.

How would you like to get started?

Your first step toward a smarter investing future starts here.

Create a Betterment account

Go ahead and join the smart, modern way to invest.

See what we can do for you

Tell us a bit about yourself, and we'll show you the benefits of investing with us.

Get a free investing checkup

Help us get a sense of your investing approach and see how you could improve.

Transfer a 401(k) or an IRA

Move an existing retirement account into a Betterment IRA.

Download the mobile app

Enjoy the Betterment experience anywhere on the go.

<title>Close</title>

Search our site

For more information and disclosures about the Betterment Resource Center, click here. | See our contributors.