Header Image How to Scan File Uploads for Viruses on Heroku

How to Scan File Uploads for Viruses on Heroku

If your Heroku app accepts file uploads, those files aren't scanned for malware. Heroku handles routing, scaling, and deployment — but a user can upload a malicious file and it'll be stored and served like any other attachment.

The AttachmentScanner addon adds malware scanning with a single command. Files are checked against multiple antivirus engines and you get back a clear result: clean, malicious, or suspicious.

This guide covers provisioning the addon, scanning files from your application code, and setting up async scanning for production workloads. For the full picture on scanning strategies, see the complete guide to scanning user uploads.

Provision the Addon

heroku addons:create attachment-scanner

Heroku sets two config vars automatically:

  • ATTACHMENT_SCANNER_URL — your scanner's base URL
  • ATTACHMENT_SCANNER_API_TOKEN — your API token

Pull them into your local .env for development:

heroku config:get ATTACHMENT_SCANNER_URL -s >> .env
heroku config:get ATTACHMENT_SCANNER_API_TOKEN -s >> .env

The addon works on all Heroku plans. You can open the dashboard to view scan history and manage settings:

heroku addons:open attachment-scanner

Your First Scan

Test the connection with the EICAR test file — a standardised test file that every antivirus engine detects:

curl -H "Authorization: Bearer $ATTACHMENT_SCANNER_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://www.attachmentscanner.com/eicar.com"}' \
  -XPOST $ATTACHMENT_SCANNER_URL/v1.0/scans
{
  "status": "found",
  "filename": "eicar.com",
  "matches": ["Eicar-Test-Signature"]
}

That's it — the addon is working. Now let's integrate it into your app.

Scanning from Your App

The examples below use Node.js and Ruby, but AttachmentScanner works with any language. See the documentation for examples in Python, Go, and more.

Node.js

const response = await fetch(
  `${process.env.ATTACHMENT_SCANNER_URL}/v1.0/scans`,
  {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.ATTACHMENT_SCANNER_API_TOKEN}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ url: fileUrl }),
  }
);

const result = await response.json();

if (result.status === "found") {
  // Reject the upload
}

To scan a file upload directly, send it as multipart/form-data:

const formData = new FormData();
formData.append("file", fileBuffer, filename);

const response = await fetch(
  `${process.env.ATTACHMENT_SCANNER_URL}/v1.0/scans`,
  {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.ATTACHMENT_SCANNER_API_TOKEN}`,
    },
    body: formData,
  }
);

Ruby

conn = Faraday.new(ENV['ATTACHMENT_SCANNER_URL']) do |f|
  f.request :json
  f.response :json
  f.authorization :Bearer, ENV['ATTACHMENT_SCANNER_API_TOKEN']
end

response = conn.post('/v1.0/scans', {
  url: 'https://www.attachmentscanner.com/eicar.com'
})

puts response.body['status'] # => "found"

If you're using CarrierWave, there's a gem that scans uploads automatically:

gem 'carrierwave-attachmentscanner'
class DocumentUploader < CarrierWave::Uploader::Base
  include CarrierWave::AttachmentScanner
end

Every file uploaded through this uploader is scanned before it's stored.

The examples above are synchronous — your server blocks while the scan runs. This works for simple cases, but for production we recommend async scanning with callbacks. Your upload handler returns immediately, and AttachmentScanner POSTs the result to your callback URL when the scan finishes.

Your App stage upload async + callback AttachmentScanner multi-engine scan callback ok → move to uploads found → quarantine

// Upload handler — returns immediately
await fetch(`${process.env.ATTACHMENT_SCANNER_URL}/v1.0/scans`, {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.ATTACHMENT_SCANNER_API_TOKEN}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    url: presignedUrlFor(stagedPath),
    async: true,
    callback: "https://your-app.herokuapp.com/webhooks/scan-complete",
  }),
});

return res.status(202).json({ status: "processing" });

The uploads guide covers the full async pattern including callback handling, staging areas, and the warning status.

Getting Started

  1. Provision the addon: heroku addons:create attachment-scanner
  2. Test with EICAR to confirm scanning works
  3. Set up async scanning with callbacks for production
  4. Open the dashboard with heroku addons:open attachment-scanner to view scan history

If you need help with your integration, get in touch — we're always happy to help.

2026-03-24
Profile Image: AttachmentScanner Team AttachmentScanner Team

Other Articles