Skip to content

ControllerTransaction#sentry_around_action can replace the action's response: request.params raises inside ensure #3099

Description

@ursm

Summary

Sentry::Rails::ControllerTransaction#sentry_around_action calls request.params from an ensure block. When the request body cannot be parsed, that call raises ActionDispatch::Http::Parameters::ParseError after the action has already produced its response, and because the exception is raised from ensure it replaces that response with a 400 Bad Request.

https://github.com/getsentry/sentry-ruby/blob/master/sentry-rails/lib/sentry/rails/controller_transaction.rb#L45-L50

              ensure
                child_span.set_http_status(response.status)
                ...
                # all params request + body
                params = request.params

The callback is installed with prepend_around_action, so it wraps every action in the application. An action that deliberately does not touch params — reading request.raw_post instead, which is the usual shape for a webhook endpoint that verifies an HMAC over the raw body — is therefore no longer able to answer a request whose body is malformed. Merely having the SDK initialized changes the response.

ActionDispatch::Request#POST does not memoize the failure, so every call raises again; nothing later in the stack can recover.

Why it is easy to miss

ActionDispatch::Http::Parameters::ParseError is in IGNORE_DEFAULT, so the SDK does not report the exception it just caused. In production the only visible symptom is "this endpoint returns 400 instead of 200", with nothing in Sentry.

It is also invisible in most test suites: without a DSN the SDK is effectively inert, so the instrumentation never runs and the action's real response is observed.

Impact for us

A webhook endpoint that must answer 200 (the provider cancels the installation otherwise) verifies the signature over request.raw_post and tolerates a non-JSON body by design:

def payload
  @payload ||= JSON.parse(request.raw_post, symbolize_names: true)
rescue JSON::ParserError
  {}
end

With the SDK initialized this endpoint answers 400.

Reproduction

Rails 8.1.3.1, sentry-rails 7.0.0, Ruby 4.0.7. Any controller action that does not read params:

class BoomController < ActionController::Base
  def quiet = head(:ok)
end
POST /quiet
Content-Type: application/json

not json
  • SDK not initialized: 200 OK
  • SDK initialized: 400 Bad Request (no event reported)

Suggestion

Nothing in that ensure block should be able to raise. Reading the params defensively would be enough, e.g.

params = begin
  request.params
rescue ActionDispatch::Http::Parameters::ParseError, ActionController::BadRequest
  nil
end

request.query_parameters, read a few lines above, can raise ActionController::BadRequest for a malformed query string for the same reason.

Workaround

For anyone hitting this before it is fixed: an around_action registered inside Sentry's (a plain around_action is appended, so it always nests inside the prepend_around_action one) can settle the parameters before Sentry's ensure runs:

around_action do |_controller, action|
  action.call
ensure
  begin
    request.params
  rescue ActionDispatch::Http::Parameters::ParseError
    request.request_parameters = {}
  end
end

Actions that read params themselves still get the usual 400.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions