Preventing accidental serialization of ActiveRecord models

We’ve introduced a patch to prevent the accidental serialization of ActiveRecord models without specifying the fields to be serialized. This change ensures that we control which fields are included, avoiding potential issues with incomplete or excessive data being exposed.

By default, rendering an ActiveRecord model as JSON includes all attributes, which may not be desirable in many cases. To enforce better practices, we need to specify which fields should be serialized.

Usage Examples

Incorrect Usage:

def show
  @user = User.first
  render json: @user
end

In development and tests, this will result in:

ActiveRecordSerializationSafety::BlockedSerializationError:
Serializing ActiveRecord models (User) without specifying fields is not allowed.
Use a Serializer, or pass the :only option to #serializable_hash. More info: https://meta.discourse.org/t/-/314495    
./lib/freedom_patches/active_record_disable_serialization.rb:15:in `serializable_hash'

Correct Usage:

  1. Using a Serializer
class UserSerializer < ApplicationSerializer
  attributes :id, :email
end

def show
  @user = User.first
  render json: @user, serializer: UserSerializer
end
  1. Using the :only option
def show
  @user = User.first
  render json: @user.as_json(only: [:id, :email])
end
5 Likes

Just to clarify for those that may encounter this in the wild, this means that all uses of the serialization methods in ActiveModel::Serialization, e.g. as_json, regardless of context (including in specs), will result in an error unless you pass the only option. See further

https://apidock.com/rails/ActiveModel/Serializers/JSON/as_json

For an example see:

3 Likes

Should something like this work?

      render_json_dump(ServerSerializer.new(@server, scope: guardian), is_index: false)

Here are all of my renders:

      # render json: servers, each_serializer: ServerIndexSerializer
      # render json: MultiJson.dump(server_serializer, group_serializer)
      # render json: servers, each_serializer: ServerIndexSerializer
      #render_json_dump(both) # FIX!!!! this is the thing we really want. . . 
      render json: servers, each_serializer: ServerIndexSerializer
      # render json: @server, serializer: ServerSerializer, scope: { is_admin => current_user.admin? }
      render_json_dump(ServerSerializer.new(@server, scope: guardian), is_index: false)
        render plain: "ok"
        render plain: "updating API key failed"
      render plain: @server.ssh_key_public
          render json: success_json
          render json: failed_json, status: 500
        render json: failed_json, status: 500
        return render json: failed_json, status: 404 unless guardian.can_manage_server?(@server)
          render json: failed_json, status: 403
            render json: @server, serializer: ServerSerializer
            render json: failed_json, status: 500
        render json: failed_json, status: 500
      render plain: text
          render json: failed_json, status: 403
          render json: failed_json, status: 501
          return render json: failed_json, status: 403 unless guardian.can_install_server?(@server)
          render json: @server, serializer: ServerSerializer
        render json: failed_json, status: 501
      render_json_dump(ServerSerializer.new(@server, scope: guardian), is_index: false)
      render_json_dump(ServerSerializer.new(@server, scope: guardian), is_index: false)
      render_json_dump(ServerSerializer.new(@server, scope: guardian), is_index: false)
            # render_json_error(@server.errors.full_messages)
          render json: @server, serializer: ServerSerializer
          # render_json_error(@server.errors.full_messages)
          render_json_error(ServerSerializer.new(@server, scope: guardian).errors.full_messages)

This is, I think, what’s giving me the BlockedSerialization error:

    def index
      if params["group_name"]
        group = Group.find_by_name(params["group_name"])
        if (params["group_name"] == "everyone") && current_user.admin?
          # puts "Getting all servers!!!!!!!!!!!!!!!!!!!"
          servers = ::Pfaffmanager::Server.all.sort_by(&:updated_at).reverse
        else
          servers = ::Pfaffmanager::Server.where(group_id: group.id).sort_by(&:updated_at).reverse
        end
      elsif params["s"]
        s = params["s"]
        servers =
          ::Pfaffmanager::Server.where("hostname like '%#{s}%'").sort_by(&:updated_at).reverse
      else
        servers =
          ::Pfaffmanager::Server.where(user_id: current_user.id).sort_by(&:updated_at).reverse
      end
      # render json: servers, each_serializer: ServerIndexSerializer

      server_groups = Pfaffmanager::Server.where("group_id is not null").pluck(:group_id).uniq
      groups = Group.where(id: server_groups)

      my_groups = groups.select { |g| current_user.groups.pluck(:id).include? g.id }

      product_serializer = ActiveModel::ArraySerializer.new(Pfaffmanager::Server.product_hash)

      # add only: to serializer to limit fields

      server_serializer =
        ActiveModel::ArraySerializer.new(
          servers,
          each_serializer: ServerIndexSerializer,
          scope: guardian,
        )

      group_serializer =
        ActiveModel::ArraySerializer.new(
          my_groups,
          each_serializer: BasicGroupSerializer,
          scope: guardian,
        )
      both = {
        install_types: PfaffmanagerInstallType.install_types,
        product_hash: Pfaffmanager::Server.product_hash,
        servers: server_serializer,
        groups: group_serializer,
      }
      # render json: MultiJson.dump(server_serializer, group_serializer)
      # render json: servers, each_serializer: ServerIndexSerializer
      #render_json_dump(both) # FIX!!!! this is the thing we really want. . . 
      render json: servers, each_serializer: ServerIndexSerializer
      
    end