Angelos Orfanakos

Strip Exif metadata from images in Rails

I recently wanted to strip the Exif metadata from user-uploaded, ActiveStorage-backed images in Rails. The reason is that images taken with mobile phones are usually geotagged and contain the user’s location, which is sensitive personal data.

Initially, I thought of implementing it in the model by calling the relevant code with a before_save callback, having it process the attached image.

Sadly, I wasn’t able to find a publicly-documented, usable API in ActiveStorage that allows this kind of manipulation. After some digging, I did manage to make it work, but the implementation was based on an internal, undocumented API, so I quickly abandoned it.

Given this limitation, I quickly realized that the most viable solution would be to take care of things before the image file ever reaches ActiveStorage. This way, I wouldn’t have to mess with its quirky API.

The solution

Install ImageMagick:

sudo apt install imagemagick # Debian/Ubuntu

Install the mini_magick Gem:

bundle add mini_magick

Add the following code in your controller (or a separate library file):

private def strip_exif_from_attachment_params(record, record_params)
  record.attachment_reflections.each_key do |attachment_attribute|
    Array(record_params[attachment_attribute]).each do |file_param|
      next if file_param.content_type != 'image/jpeg'

      uploaded_file = file_param.tempfile
      image = MiniMagick::Image.read(uploaded_file)
      image.auto_orient # Before stripping
      image.strip # Strip Exif
      uploaded_file.rewind
      image.write(uploaded_file)
    end
  end
end

Things to note:

  • Line 1: Accept the record as a parameter
  • Line 2: Use reflection on the record to figure out its ActiveStorage attachments (so it operates on all of them)
  • Line 4: Skip attachments that are not JPEG images
  • Line 6: Operate on the uploaded file’s Tempfile
  • Line 8: Use Exif metadata, before stripping, to orient the image (e.g. fix it if it’s rotated)
  • Line 9: Strip Exif metadata
  • Line 11: Persist changes to the file

Assuming you have a user model with an attached avatar:

class User < ApplicationRecord
  has_one_attached :avatar
end

Here’s an example on how you’d use it:

class UsersController < ApplicationController
  before_action :set_user, only: %i[update]

  def update
    strip_exif_from_attachment_params(update_params)
    if !@user.update(update_params)
      flash.now[:alert] = 'Failed to update user'
      render :edit
      return
    end

    redirect_to user_path(@user), notice: 'User updated'
  end

  private def set_user
    @user = User.find(params.require(:id))
  end

  private def update_params
    params.require(:user).permit(:avatar)
  end
end

Have fun!