Scan for Malware with Elixir

Use AttachmentScanner to scan files and URLs for viruses and malware using Req.

Scan a URL

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

scanner_url = System.get_env("ATTACHMENT_SCANNER_URL")
api_token = System.get_env("ATTACHMENT_SCANNER_API_TOKEN")

{:ok, response} =
  Req.post("#{scanner_url}/v1.0/scans",
    json: %{url: "https://www.attachmentscanner.com/eicar.com"},
    headers: [{"authorization", "Bearer #{api_token}"}]
  )

IO.inspect(response.body["status"])   # "found"
IO.inspect(response.body["matches"])  # ["Eicar-Test-File-Signature"]

Scan a File

Upload a file directly from disk:

{:ok, response} =
  Req.post("#{scanner_url}/v1.0/scans",
    headers: [{"authorization", "Bearer #{api_token}"}],
    form_multipart: [
      file: {File.read!("/path/to/file.pdf"),
        filename: "file.pdf", content_type: "application/pdf"}
    ]
  )

IO.inspect(response.body["status"])  # "ok" if clean, "found" if malware

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
{:ok, response} =
  Req.post("#{scanner_url}/v1.0/scans",
    json: %{
      url: "https://example.com/large-file.zip",
      async: true,
      callback: "https://your-app.com/webhooks/scan-complete"
    },
    headers: [{"authorization", "Bearer #{api_token}"}]
  )

scan_id = response.body["id"]       # "a1b2c3d4-..."
IO.inspect(response.body["status"]) # "pending"
# 2. Handle the callback (Plug example)
post "/webhooks/scan-complete" do
  result = conn.body_params

  case result["status"] do
    "found" ->
      # quarantine or delete the file
      :ok
    _ ->
      :ok
  end

  conn
  |> put_resp_content_type("application/json")
  |> send_resp(200, Jason.encode!(%{received: true}))
end

Or poll for results instead:

# Poll for results (alternative to callback)
{:ok, result} =
  Req.get("#{scanner_url}/v1.0/scans/#{scan_id}",
    headers: [{"authorization", "Bearer #{api_token}"}]
  )

IO.inspect(result.body["status"])  # "ok", "found", "pending", etc.

Plug Integration

A Plug router that accepts file uploads and scans them:

defmodule AttachmentScannerExample.Router do
  use Plug.Router

  plug Plug.Parsers,
    parsers: [:json, :multipart],
    json_decoder: Jason

  plug :match
  plug :dispatch

  post "/scan/url" do
    scanner_url = System.get_env("ATTACHMENT_SCANNER_URL")
    api_token = System.get_env("ATTACHMENT_SCANNER_API_TOKEN")

    %{"url" => url} = conn.body_params

    {:ok, response} =
      Req.post("#{scanner_url}/v1.0/scans",
        json: %{url: url},
        headers: [{"authorization", "Bearer #{api_token}"}]
      )

    conn
    |> put_resp_content_type("application/json")
    |> send_resp(200, Jason.encode!(response.body))
  end

  post "/scan/file" do
    scanner_url = System.get_env("ATTACHMENT_SCANNER_URL")
    api_token = System.get_env("ATTACHMENT_SCANNER_API_TOKEN")

    %Plug.Upload{path: path, filename: filename, content_type: content_type} =
      conn.body_params["file"]

    {:ok, response} =
      Req.post("#{scanner_url}/v1.0/scans",
        headers: [{"authorization", "Bearer #{api_token}"}],
        form_multipart: [
          file: {File.read!(path), filename: filename, content_type: content_type}
        ]
      )

    conn
    |> put_resp_content_type("application/json")
    |> send_resp(200, Jason.encode!(response.body))
  end

  # Webhook callback handler
  post "/webhooks/scan-complete" do
    result = conn.body_params

    case result["status"] do
      "found" ->
        IO.puts("Malware detected: #{inspect(result["matches"])}")
        # quarantine or delete the file
      "ok" ->
        # file is clean — move to final storage
        :ok
      _ ->
        :ok
    end

    conn
    |> put_resp_content_type("application/json")
    |> send_resp(200, Jason.encode!(%{received: true}))
  end
end

Phoenix

A Phoenix controller that accepts file uploads and scans them:

defmodule MyAppWeb.ScanController do
  use MyAppWeb, :controller

  def scan_url(conn, %{"url" => url}) do
    scanner_url = System.get_env("ATTACHMENT_SCANNER_URL")
    api_token = System.get_env("ATTACHMENT_SCANNER_API_TOKEN")

    {:ok, response} =
      Req.post("#{scanner_url}/v1.0/scans",
        json: %{url: url},
        headers: [{"authorization", "Bearer #{api_token}"}]
      )

    json(conn, response.body)
  end

  def scan_file(conn, %{"file" => upload}) do
    scanner_url = System.get_env("ATTACHMENT_SCANNER_URL")
    api_token = System.get_env("ATTACHMENT_SCANNER_API_TOKEN")

    {:ok, response} =
      Req.post("#{scanner_url}/v1.0/scans",
        headers: [{"authorization", "Bearer #{api_token}"}],
        form_multipart: [
          file: {File.read!(upload.path),
            filename: upload.filename, content_type: upload.content_type}
        ]
      )

    json(conn, response.body)
  end

  # Webhook callback handler
  def scan_callback(conn, params) do
    case params["status"] do
      "found" ->
        # quarantine or delete the file
        :ok
      "ok" ->
        # file is clean — move to final storage
        :ok
      _ ->
        :ok
    end

    json(conn, %{received: true})
  end
end
# router.ex
scope "/", MyAppWeb do
  pipe_through :api

  post "/scan/url", ScanController, :scan_url
  post "/scan/file", ScanController, :scan_file
  post "/webhooks/scan-complete", ScanController, :scan_callback
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"}'