The project is in a healthy, maintained state
active_storage_av_scan adds background antivirus scanning to Rails Active Storage attachments, with a ClamAV scanner, scan status model, view helpers, and configurable infection handling.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
 Dependencies

Development

~> 3.12

Runtime

>= 6.1, < 9.0
 Project Readme

ActiveStorage AV Scan

A lightweight antivirus scanning extension for Rails Active Storage. Automatically scans uploaded files using ClamAV, records results, and exposes clean helpers for models and views.

⚠️ Currently an early release (v0.1.0) — battle-tested in real Rails apps, but specs and HTTP/Lambda integrations are coming soon.


Features

  • Automatic background scanning of Active Storage attachments
  • Adapter-agnostic — works with Sidekiq, SolidQueue, Resque, or any ActiveJob backend
  • Built-in ClamAV scanner
  • Dedicated AttachmentScan model to store results
  • Easy view helpers for showing scan status
  • Configurable infection policy (infected_handler)

🚀 Installation

Add to your Gemfile:

gem "active_storage_av_scan"

Install and migrate:

bundle install
bundle exec rails g active_storage_av_scan:install
bundle exec rails db:migrate

This creates:

  • The active_storage_attachment_scans table
  • An initializer at config/initializers/active_storage_av_scan.rb

⚙️ Configuration

Default: Local ClamAV

# config/initializers/active_storage_av_scan.rb
ActiveStorageAvScan.queue_name = :av_scan
ActiveStorageAvScan.scanner = ActiveStorageAvScan::Scanners::Clamav.new

💡 Check ClamAV is installed: macOS → brew install clamav Debian/Ubuntu → sudo apt install clamav

💡 Custom Scanners

You can plug in any custom scanner backend by assigning your own object that implements the same simple scan interface:

class MyCustomScanner
  include ActiveStorageAvScan::Scanner

  # Must return an ActiveStorageAvScan::ScanResult
  def scan(io:, filename:, blob: nil, **_opts)
    # Example: call your external AV API or service
    response = ExternalAV.scan(io)
    if response.clean?
      ActiveStorageAvScan::ScanResult.new(status: :clean, details: "Clean via ExternalAV")
    else
      ActiveStorageAvScan::ScanResult.new(
        status: :infected,
        virus_name: response.virus_name,
        details: response.details
      )
    end
  end
end

# Use it in your initializer
ActiveStorageAvScan.scanner = MyCustomScanner.new

Any scanner that responds to:

scan(io:, filename:, blob: nil, **opts)

and returns an instance of:

ActiveStorageAvScan::ScanResult.new(status:, virus_name:, details:)

will work out of the box.

Upcoming: the gem will include first-class support for HTTP-based external scanners (e.g. AWS Lambda AV, REST APIs) in a future release.


Usage

1. Enable scanning

Add av_scanned_attachments to your model:

class Expense < ApplicationRecord
  has_many_attached :receipts

  av_scanned_attachments :receipts
end

Every upload triggers a background scan automatically.

2. Check results

@expense.receipts_all_clean?     # => true / false
@expense.receipts_any_infected?  # => true / false
@expense.receipts_scan_statuses  # => [[attachment, "clean"], ...]

All results are stored in ActiveStorageAvScan::AttachmentScan.


🧠 Infection Handling

You can define what happens when a file is infected:

ActiveStorageAvScan.infected_handler = lambda do |blob, scan|
  Rails.logger.warn "[AV] Infected file: #{blob.filename} (#{scan.virus_name})"
  blob.attachments.each(&:purge_later)
end

Handler signature:

->(blob, scan_record) { ... }

💻 View Helpers

Add scan badges next to your file links:

<% @expense.receipts.each do |attachment| %>
  <%= link_to attachment.filename, url_for(attachment) %>
  <%= av_scan_badge(attachment) %>
<% end %>

Default output:

<span class="av-scan-badge av-scan-badge-clean">Clean</span>

You can also override classes inline for a specific badge:

  <%= av_scan_badge(attachment, css_class: "ml-2 text-xs") %>

Customize badge classes for Tailwind/Bootstrap/etc.:

ActiveStorageAvScan.badge_class_map = {
  "pending"  => "text-gray-700 bg-gray-200 rounded px-2 py-0.5 text-xs",
  "clean"    => "text-green-800 bg-green-100 rounded px-2 py-0.5 text-xs",
  "infected" => "text-red-800 bg-red-100 rounded px-2 py-0.5 text-xs",
  "error"    => "text-yellow-800 bg-yellow-100 rounded px-2 py-0.5 text-xs"
}

When you run rails g active_storage_av_scan:install, a default app/assets/stylesheets/active_storage_av_scan.css file is created to style scan-badges. You can import it into your main stylesheet, tweak it, or replace it entirely with your own styles or Tailwind/Bootstrap classes.


🔍 Model Reference

ActiveStorageAvScan::AttachmentScan

Column Type Description
blob_id bigint References ActiveStorage::Blob
status string "pending", "clean", "infected", "error"
scanner string Scanner class used
virus_name string Virus signature name (if any)
details text Raw output or error message
scanned_at datetime Time scanned

🧩 Storage Compatibility

Works transparently with all Active Storage services:

amazon:
  service: S3
  bucket: your-bucket-<%= Rails.env %>

google:
  service: GCS
  bucket: your-bucket-<%= Rails.env %>

mirror:
  service: Mirror
  primary: amazon
  mirrors: [google]

The scanner only ever sees an IO object (blob.open), regardless of backend.


🔒 Requirements

  • Ruby 3.0+
  • Rails 6.1+ (tested on 6.1, 7.0, 7.1)
  • Active Storage configured
  • ClamAV installed (clamscan available in PATH)

🧰 Development

Clone and run locally:

git clone git@github.com:ajazfarhad/active_storage_av_scan.git
cd active_storage_av_scan
bundle install

You can test inside a sample Rails app:

rails g active_storage_av_scan:install
rails db:migrate

Attach a file and observe the scan results in the console or DB.


🧭 Roadmap

  • HTTP / Lambda scanner backend
  • Async result polling & notifications
  • Slack / Email alert hooks
  • CLI task (rails active_storage_av_scan:scan_all)
  • Lightweight audit log
  • Rails Admin / ActiveAdmin integration
  • RSpec test coverage (planned for v0.2.0)

🧑‍💻 Contributing

Contributions welcome! Bug reports and PRs on GitHub: https://github.com/ajazfarhad/active_storage_av_scan


📄 License

MIT License © 2025 [Ajaz Farhad]