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"}'