Saturday, December 1, 2007

Why The h Can't Rails Escape HTML Automatically?

We all know about the dangers of cross-site scripting. The solution is simple: if you're not sure the data you're displaying is absolutely safe, run it through a filter that escapes HTML tag characters. Do this with data entered by users, data you get from other applications, etc. Be paranoid.

Rails has an html_escape method that does exactly this. Its alias is h, and you'll see it sprinkled throughout templates:
<%= h foo.bar %>
So where's the wart? The wart is that you, the developer, have to do this. Rails should escape HTML automatically, by default. That's what you want, 99% of the time. Instead, you have to remember the h. You have to put it in manually, again and again. Nothing DRY about that. And, if you forget even once, you've left a security hole in your application.

Yes, you can use plugins or alternative template engines to fix this. But many developers will use Rails out of the box, and some of them will forget an h or two. Django escapes HTML automatically now. Rails is nearing 2.0. Time to catch up.

Thursday, November 29, 2007

validates_uniqueness_of is broken, and dangerous

Update: Progess. The Rails API docs now mention this problem. Thanks, Koz.


validates_uniqueness_of guarantees unique column values, just like a unique key constraint in a database, right? Wrong. It doesn't guarantee anything.

Suppose your model validates_uniqueness_of :user_name. Two requests come along, each in a separate process. Both try to save a new row with a user_name of 'foobar'.
  • Request 1 checks 'foobar' - no problem.

  • Request 2 checks 'foobar' - no problem.

  • Request 1 inserts.

  • Request 2 inserts.
Oops - duplicate user_name. If you want more technical detail, here's a good link.

This is a nasty, subtle bug. You won't see it in your unit tests. You probably won't see it under low loads. Depending on your application's design, you may never see it.

But, if you do see it, you'll see it under high request volumes. Once in a blue moon, maybe. You'll see it and tell yourself it's impossible, because you're validating uniqueness. And you may spend a lot of time looking for the answer, because the problem is not well known. The Rails API documentation doesn't mention it. The Agile book mentions it, but way down on page 371, well after validates_uniqueness_of has been introduced.

Why don't "they" fix it? Maybe because making it work correctly, and efficiently, across multiple database platforms, is far from trivial. But "they" should document the problem.

Should you stop using validates_uniqueness_of? Not necessarily. It'll do the right thing almost all the time, the error messages are handy, and it's a good way to show your intentions in your model.

But always back it up with constraints in the database. When that blue moon rises, better to have a save fail with a clumsy error message than store duplicate values in a column that should be unique.