Unexpected Unpermitted Parameters In Rails

Based on a true story; I’ve reframed this to be about a personal project to protect identities involved.

Some surprises are good; this one is just annoying. I recently had some unexpected “unauthorized parameters” show up in my Rails app logs during development. Nothing was breaking, it was just noise. Annoying noise. Turns out, there were two issues at play:

First: Unexpected Parameter

Let’s say I had a POST endpoint at /some_things/:session_id that expected a JSON payload like this:

{
	"some_param": "one",
	"some_other_param": "two"
}

Then in my controller, I permitted the parameters like so:

permitted_params = params.permit(:session_id, :some_param, :some_other_param)

And yet, without fail, my logs would show “Unpermitted parameter: :some_thing”. What gives?

Turns out, by default Rails will wrap a JSON or XML payload in its own singular parameter which turned my payload into this:

{
	"some_param" => "one",
	"some_other_param" => "two",
	"some_thing" => {
		"some_param" => "one",
		"some_other_param" => "two"
	}
}

There are reasons for this that mostly have to do with building create/update routes for resources. Reasons that have nothing to do with my controller!

The solution

Fixing this meant adding this single line to my controller to opt-out of this default Rails behavior:

wrap_parameters false

But I was still getting “Unpermitted parameter” in my logs, this time from a different source.

Second: Premature Permitting

The log entry was pretty sparse, and I wanted to know what line was causing this error! The quickest way was to change the behavior when Rails encountered an unpermitted parameter to throw an exception instead of just logging the parameter. This should not be done in production!

ActionController::Parameters.action_on_unpermitted_parameters = :raise

Unless you need to have your application come to a full stop if the input is even the slightest bit off, you don’t want to do this in production. But doing this locally, it will pinpoint exactly where the issue is happening. And sure enough, I found the culprit:

class SomeConcern
	# ...
	def parameter_is_set?
		params.permit(:override)[:override] == "yes"
	end
	# ...
end

Somewhere deep in the controller stack, I was doing what I was “supposed” to do and only checking “permitted” parameters. Since this was a relatively safe check, I removed the “permit” check:

params[:override] == "yes"

This left the only params.permit call as the one in my main controller.

And the logs were quiet again. For now…

Evan Hildreth @oddevan