It is common practice to run a Rails app using a custom domain locally.
app.lvh.me is very common. We also use
ngrok.io sometimes to interact with third party services. If you are using a custom domain on a Rails 6 app, you will see an error with the very first request itself when you hit
app.lvh.me in the browser.
But if we just try
localhost:3000 then it works. We see this error only when using a custom domain.
http://lvh.me is a free service that resolves itself along with all subdomains to
DNS rebinding attack
This error is raised because Rails 6 has added protection against DNS rebinding attacks. But what is DNS rebinding attack in the context of a Rails app running locally?
Rails ships with web-console in development environment to help debug errors while developing the app. web-console gem was vulnerable to the DNS rebinding attacks till Rails 6 as the attacker could steal the session id used by web-console and then make Ajax requests using this session id to execute arbitrary code.
An example of this attack is a script to open calculator app on your system while the Rails app is running locally.
Guard against DNS rebinding attack in Rails 6
In Rails 6, a new middleware
HostAuthorization is added which provides a guard against the DNS rebinding errors. The middleware is included in all environments but it gets kicked in development environment by default.
It works as follows:
- For a given incoming request, it checks the value of
config.hosts. This configuration option is supposed to hold values of the hosts which are allowed by the Rails app.
config.hostsis not empty, then the host of the incoming request is checked against all the allowed values by
config.hosts. This is done by comparing the
Hostheader as well
X-Forwarded-Hostheader of the incoming request with the allowed hosts from
- If there is at least one match then the request is authorized. But if there is no match then the the
Blocked hosterror is thrown that we saw above.
Let's check what is the default value of this option.
irb(main):002:0> Rails.application.config.hosts => [#<IPAddr: IPv4:0.0.0.0/0.0.0.0>#<IPAddr:IPv6:0000:0000:0000:0000:0000:0000:0000:0000/0000:0000:0000:0000:0000:0000:0000:0000>, ".localhost"] irb(main):003:0>
These values make sense as they are only allowing the requests from
0.0.0.0 which are the most likely places from where a request will be made to the Rails app running locally.
So if we are using a custom domain like
app.lvh.me, we see the above error about host being blocked. The fix is simple, we need to allow
lvh.me in the
We can fix it by adding following code in
config.hosts << '.lvh.me'
By prefixing the host with
.it allows all the subdomains of lvh.me as well. So
api.lvh.mewill also be allowed by above code. It even allows Procs, regular expressions and IPAddr objects as values for
What about production?
In production, the value for
config.hosts is not set by Rails. So no check is performed by the
HostAuthorization middleware. If you want to perform the header check then you will have manually add the allowed domain to the
config.hosts << '.myawesomeapp.io`
But I want to disable this check, tell me how?
nil in the
config/application.rb file and this check will be disabled for all environments. You can also disable it conditionally for specific environment by adding this line to
Road to Rails 6
Want to know more about such small but important changes which are part of Rails 6 and which affect your day to day development? Subscribe to my newsletter and be on the course for making your app Rails 6 ready.