Scan for Malware with Ruby

Use AttachmentScanner to scan files and URLs for viruses and malware using Faraday with multipart support.

Scan a URL

The simplest way to scan — pass a URL and get back a result:

require "faraday"

conn = Faraday.new(SCANNER_URL) do |f|
  f.request :authorization, "Bearer", API_TOKEN
  f.request :json
  f.response :json
end

response = conn.post("/v1.0/scans") do |req|
  req.body = { url: "https://www.attachmentscanner.com/eicar.com" }
end

puts response.body["status"]  # => "found"
puts response.body["matches"] # => ["Eicar-Test-File-Signature"]

Scan a File

Upload a file directly from disk:

require "faraday"
require "faraday/multipart"

conn = Faraday.new(SCANNER_URL) do |f|
  f.request :authorization, "Bearer", API_TOKEN
  f.request :multipart
  f.response :json
end

response = conn.post("/v1.0/scans") do |req|
  req.body = {
    file: Faraday::Multipart::FilePart.new(
      "/path/to/file.pdf",
      "application/pdf",
      "file.pdf"
    )
  }
end

puts response.body["status"] # => "ok" if clean, "found" if malware detected

Async Scan with Callback

For large files or non-blocking workflows, use async: true with a callback URL. The API returns immediately with a pending status and POSTs the result to your callback when the scan completes:

# 1. Submit the scan
conn = Faraday.new(SCANNER_URL) do |f|
  f.request :authorization, "Bearer", API_TOKEN
  f.request :json
  f.response :json
end

response = conn.post("/v1.0/scans") do |req|
  req.body = {
    url: "https://example.com/large-file.zip",
    async: true,
    callback: "https://your-app.com/webhooks/scan-complete"
  }
end

scan_id = response.body["id"]     # => "a1b2c3d4-..."
status  = response.body["status"] # => "pending"
# 2. Handle the callback (Sinatra example)
post "/webhooks/scan-complete" do
  result = JSON.parse(request.body.read)

  if result["status"] == "found"
    # quarantine or delete the file
  end

  content_type :json
  { received: true }.to_json
end

Or poll for results instead:

# Poll for results (alternative to callback)
result = conn.get("/v1.0/scans/#{scan_id}")

puts result.body["status"] # => "ok", "found", "pending", etc.

Sinatra

A Sinatra app that accepts file uploads and scans them:

require "sinatra"
require "faraday"
require "faraday/multipart"
require "json"

SCANNER_URL = ENV.fetch("ATTACHMENT_SCANNER_URL")
API_TOKEN = ENV.fetch("ATTACHMENT_SCANNER_API_TOKEN")

def scanner_connection(type)
  Faraday.new(SCANNER_URL) do |f|
    f.request :authorization, "Bearer", API_TOKEN
    f.request type
    f.response :json
  end
end

# Scan by URL
post "/scan/url" do
  body = JSON.parse(request.body.read)

  response = scanner_connection(:json).post("/v1.0/scans") do |req|
    req.body = { url: body["url"] }
  end

  content_type :json
  response.body.to_json
end

# Scan by file upload
post "/scan/file" do
  file = params[:file]

  response = scanner_connection(:multipart).post("/v1.0/scans") do |req|
    req.body = {
      file: Faraday::Multipart::FilePart.new(
        file[:tempfile], file[:type], file[:filename]
      )
    }
  end

  content_type :json
  response.body.to_json
end

# Webhook callback handler
post "/webhooks/scan-complete" do
  result = JSON.parse(request.body.read)

  if result["status"] == "found"
    # quarantine or delete the file
  elsif result["status"] == "ok"
    # file is clean — move to final storage
  end

  content_type :json
  { received: true }.to_json
end

Rails

require "faraday"
require "faraday/multipart"

class ScansController < ActionController::API
  def scan_url
    conn = scanner_connection(:json)

    response = conn.post("/v1.0/scans") do |req|
      req.body = { url: params[:url] }
    end

    render json: response.body
  end

  def scan_file
    uploaded = params[:file]
    conn = scanner_connection(:multipart)

    response = conn.post("/v1.0/scans") do |req|
      req.body = {
        file: Faraday::Multipart::FilePart.new(
          uploaded.tempfile,
          uploaded.content_type,
          uploaded.original_filename
        )
      }
    end

    render json: response.body
  end

  # Webhook callback handler
  def scan_callback
    if params[:status] == "found"
      # quarantine or delete the file
    elsif params[:status] == "ok"
      # file is clean — move to final storage
    end

    render json: { received: true }
  end

  private

  def scanner_connection(type)
    Faraday.new(ENV.fetch("ATTACHMENT_SCANNER_URL")) do |f|
      f.request :authorization, "Bearer", ENV.fetch("ATTACHMENT_SCANNER_API_TOKEN")
      f.request type
      f.response :json
    end
  end
end

curl Equivalent

# Scan by URL
curl -X POST https://YOUR_SCANNER_URL/v1.0/scans \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://www.attachmentscanner.com/eicar.com"}'

# Scan by file upload
curl -X POST https://YOUR_SCANNER_URL/v1.0/scans \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -F "file=@/path/to/file.pdf"

# Async scan with callback
curl -X POST https://YOUR_SCANNER_URL/v1.0/scans \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://example.com/file.zip", "async": true, "callback": "https://your-app.com/webhooks/scan-complete"}'