Scan for Malware with C# / .NET
Use AttachmentScanner to scan files and URLs for viruses and malware using .NET's built-in HttpClient.
Scan a URL
The simplest way to scan — pass a URL and get back a result:
using System.Text.Json;
var scannerUrl = Environment.GetEnvironmentVariable("ATTACHMENT_SCANNER_URL")!;
var apiToken = Environment.GetEnvironmentVariable("ATTACHMENT_SCANNER_API_TOKEN")!;
var httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {apiToken}");
var content = new StringContent(
JsonSerializer.Serialize(new { url = "https://www.attachmentscanner.com/eicar.com" }),
System.Text.Encoding.UTF8,
"application/json"
);
var response = await httpClient.PostAsync($"{scannerUrl}/v1.0/scans", content);
var json = await response.Content.ReadAsStringAsync();
Console.WriteLine(json);
// {"status":"found","matches":["Eicar-Test-File-Signature"],...}
Scan a File
Upload a file directly from disk:
var multipart = new MultipartFormDataContent();
var fileStream = File.OpenRead("/path/to/file.pdf");
multipart.Add(new StreamContent(fileStream), "file", "file.pdf");
var response = await httpClient.PostAsync($"{scannerUrl}/v1.0/scans", multipart);
var json = await response.Content.ReadAsStringAsync();
Console.WriteLine(json);
// {"status":"ok",...} if clean, {"status":"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
var content = new StringContent(
JsonSerializer.Serialize(new {
url = "https://example.com/large-file.zip",
@async = true,
callback = "https://your-app.com/webhooks/scan-complete"
}),
System.Text.Encoding.UTF8,
"application/json"
);
var response = await httpClient.PostAsync($"{scannerUrl}/v1.0/scans", content);
var scan = JsonSerializer.Deserialize<JsonElement>(
await response.Content.ReadAsStringAsync()
);
Console.WriteLine(scan.GetProperty("id")); // "a1b2c3d4-..."
Console.WriteLine(scan.GetProperty("status")); // "pending"
// 2. Handle the callback (Minimal API example)
app.MapPost("/webhooks/scan-complete", async (HttpRequest req) =>
{
var result = await req.ReadFromJsonAsync<JsonElement>();
var status = result.GetProperty("status").GetString();
if (status == "found")
{
// quarantine or delete the file
}
return Results.Json(new { received = true });
});
Or poll for results instead:
// Poll for results (alternative to callback)
var result = await httpClient.GetStringAsync($"{scannerUrl}/v1.0/scans/{scanId}");
Console.WriteLine(result); // includes "status": "ok", "found", "pending", etc.
Typed
Type-safe wrapper with full response types:
using System.Net.Http.Json;
using System.Text.Json;
using System.Text.Json.Serialization;
record ScanResult(
string Id,
string Status,
string? Callback,
string? Url,
string? Filename,
[property: JsonPropertyName("content_length")] long? ContentLength,
string? Md5,
string? Sha256,
string[] Matches,
[property: JsonPropertyName("created_at")] string CreatedAt,
[property: JsonPropertyName("updated_at")] string UpdatedAt
);
class AttachmentScanner(string baseUrl, string apiToken)
{
private readonly HttpClient _http = new()
{
DefaultRequestHeaders = { { "Authorization", $"Bearer {apiToken}" } }
};
public async Task<ScanResult> ScanUrlAsync(string url)
{
var response = await _http.PostAsJsonAsync(
$"{baseUrl}/v1.0/scans", new { url }
);
return (await response.Content.ReadFromJsonAsync<ScanResult>())!;
}
public async Task<ScanResult> ScanFileAsync(string filePath)
{
var multipart = new MultipartFormDataContent();
multipart.Add(new StreamContent(File.OpenRead(filePath)), "file", Path.GetFileName(filePath));
var response = await _http.PostAsync($"{baseUrl}/v1.0/scans", multipart);
return (await response.Content.ReadFromJsonAsync<ScanResult>())!;
}
public async Task<ScanResult> ScanUrlAsync(string url, string callback)
{
var response = await _http.PostAsJsonAsync(
$"{baseUrl}/v1.0/scans",
new { url, @async = true, callback }
);
return (await response.Content.ReadFromJsonAsync<ScanResult>())!;
}
public async Task<ScanResult> GetScanAsync(string id)
{
return (await _http.GetFromJsonAsync<ScanResult>($"{baseUrl}/v1.0/scans/{id}"))!;
}
}
// Usage
var scanner = new AttachmentScanner(
Environment.GetEnvironmentVariable("ATTACHMENT_SCANNER_URL")!,
Environment.GetEnvironmentVariable("ATTACHMENT_SCANNER_API_TOKEN")!
);
var result = await scanner.ScanUrlAsync("https://www.attachmentscanner.com/eicar.com");
if (result.Status == "found")
{
Console.WriteLine($"Malware detected: {string.Join(", ", result.Matches)}");
}
Minimal API Integration
A full .NET minimal API that accepts file uploads and scans them:
using System.Text.Json;
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
var scannerUrl = Environment.GetEnvironmentVariable("ATTACHMENT_SCANNER_URL")!;
var apiToken = Environment.GetEnvironmentVariable("ATTACHMENT_SCANNER_API_TOKEN")!;
var httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {apiToken}");
// Scan by URL
app.MapPost("/scan/url", async (HttpRequest req) =>
{
var body = await req.ReadFromJsonAsync<UrlRequest>();
var content = new StringContent(
JsonSerializer.Serialize(new { url = body!.Url }),
System.Text.Encoding.UTF8,
"application/json"
);
var response = await httpClient.PostAsync($"{scannerUrl}/v1.0/scans", content);
var result = await response.Content.ReadAsStringAsync();
return Results.Content(result, "application/json");
});
// Scan by file upload
app.MapPost("/scan/file", async (HttpRequest req) =>
{
var form = await req.ReadFormAsync();
var file = form.Files["file"]!;
var multipart = new MultipartFormDataContent();
var stream = file.OpenReadStream();
var streamContent = new StreamContent(stream);
multipart.Add(streamContent, "file", file.FileName);
var response = await httpClient.PostAsync($"{scannerUrl}/v1.0/scans", multipart);
var result = await response.Content.ReadAsStringAsync();
return Results.Content(result, "application/json");
});
// Webhook callback handler
app.MapPost("/webhooks/scan-complete", async (HttpRequest req) =>
{
var result = await req.ReadFromJsonAsync<JsonElement>();
var status = result.GetProperty("status").GetString();
if (status == "found")
{
// quarantine or delete the file
}
else if (status == "ok")
{
// file is clean — move to final storage
}
return Results.Json(new { received = true });
});
app.Run("http://0.0.0.0:3000");
record UrlRequest(string Url);
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"}'