A .NET library for virus scanning with a provider-agnostic design. VirusScanner.Core defines the interfaces and models; any backend can implement them. VirusScanner.ClamAV ships the first-party ClamAV provider. Licensed under the MIT License.
Two NuGet packages are published from this repository:
| Package | Role | Target Frameworks |
|---|---|---|
VirusScanner.Core | Abstractions only – IVirusScanner, IBatchProcessor, shared models. No external dependencies. | netstandard2.0, netstandard2.1, net10.0 |
VirusScanner.ClamAV | ClamAV provider – implements IVirusScanner against a clamd server. Depends on VirusScanner.Core. | netstandard2.0, netstandard2.1, net10.0 |
Which package do I need?
- Libraries / shared code that should stay backend-independent → install only
VirusScanner.Coreand code againstIVirusScanner. - Applications that use ClamAV → install only
VirusScanner.ClamAV.VirusScanner.Coreis pulled in automatically as a transitive dependency.
# Application – one package is enough, Core comes along automatically
Install-Package VirusScanner.ClamAV
# Library / shared project – abstractions only, no backend coupling
Install-Package VirusScanner.Core
A running ClamAV (clamd) server is required. ClamAV is a free, open-source virus scanner.
Current stable release lines (as of 2026-03):
1.5.x (latest: 1.5.2)
1.4.x (latest: 1.4.4)
1.0.x LTS (latest: 1.0.9)
Docs: https://docs.clamav.net/
Communication uses the standard clamd protocol commands PING, VERSION, and INSTREAM, so VirusScanner is compatible with any current ClamAV release that supports the clamd protocol.
This repository includes a ready-to-use docker-compose.yml to run ClamAV locally.
# Start ClamAV
docker compose up -d
# Follow logs until clamd is ready
docker compose logs -f clamav
# Stop
docker compose downConnection settings:
- .NET app running on the host machine →
localhost:3310 - .NET app running as a Compose service in the same network →
clamav:3310
usingVirusScanner.ClamAV;usingVirusScanner.Core;varscanner=newClamAvScanner("localhost",3310);// Check connectivityboolisReady=awaitscanner.TryPingAsync();// Scan a fileScanResultresult=awaitscanner.ScanAsync(@"C:\test.txt");switch(result.Status){caseScanStatus.Clean:Console.WriteLine("The file is clean!");break;caseScanStatus.VirusDetected:Console.WriteLine($"Virus found: {result.InfectedFiles![0].VirusName}");break;caseScanStatus.Error:Console.WriteLine("Scan error.");break;}You can also construct ClamAvScanner with an IPAddress:
varscanner=newClamAvScanner(IPAddress.Parse("127.0.0.1"),3310);ClamAvScanner implements IVirusScanner and supports scanning from multiple sources:
// From a file pathScanResultresult=awaitscanner.ScanAsync(@"C:\test.txt");// From a streamawaitusingvarstream=File.OpenRead(@"C:\test.txt");ScanResultresult=awaitscanner.ScanAsync(stream);// From a byte arraybyte[]data=awaitFile.ReadAllBytesAsync(@"C:\test.txt");ScanResultresult=awaitscanner.ScanAsync(data);// PING – throws if the server does not respond with PONGawaitscanner.PingAsync();// TryPing – returns false instead of throwingboolavailable=awaitscanner.TryPingAsync();// Server version stringstringversion=awaitscanner.GetVersionAsync();// Server statsstringstats=awaitscanner.GetStatsAsync();VirusScanner.ClamAV includes batch processing for scanning multiple files efficiently.
// Scan a list of filesIEnumerable<BatchScanResult>results=awaitscanner.BatchScanFilesAsync(filePaths);// Scan a directoryIEnumerable<BatchScanResult>results=awaitscanner.BatchScanDirectoryAsync(@"C:\MyFolder",recursive:true);// Scan by file extensionsIEnumerable<BatchScanResult>results=awaitscanner.BatchScanByExtensionsAsync(@"C:\MyFolder",new[]{".exe",".dll"});// Scan executable files onlyIEnumerable<BatchScanResult>results=awaitscanner.BatchScanExecutableFilesAsync(@"C:\MyFolder",recursive:true);varprocessor=newClamAvBatchProcessor(scanner,maxConcurrency:4,connectionTimeoutSeconds:10);IEnumerable<BatchScanResult>results=awaitprocessor.ScanDirectoryAsync(@"C:\MyFolder",recursive:true);varprogress=newProgress<BatchProgress>(p =>{Console.WriteLine($"Progress: {p.CompletedFiles}/{p.TotalFiles} ({p.PercentageComplete:F1}%)");Console.WriteLine($"Current: {p.CurrentFile}");});varresults=awaitscanner.BatchScanDirectoryAsync(@"C:\MyFolder",recursive:true,progressCallback:progress);// Generate a detailed text reportstringreport=ClamAvBatchUtilities.GenerateReport(results,DateTime.Now);// Filter resultsvarinfected=results.Where(r =>r.IsInfected);varerrors=results.Where(r =>r.HasError);varclean=results.Where(r =>r.IsClean);- Concurrent scanning – configurable degree of parallelism
- Progress tracking – real-time
IProgress<BatchProgress>callbacks - Directory scanning – recursive and non-recursive
- File filtering – by extension or predefined categories
- Connection resilience – graceful handling of daemon disconnections
- Configurable timeouts – prevent hanging operations
- Report generation – built-in text report via
ClamAvBatchUtilities
| Project | Description |
|---|---|
VirusScanner.Core | Abstractions: IVirusScanner, IBatchProcessor, ScanResult, ScanStatus, BatchScanResult, BatchProgress, InfectedFile, ScanException |
VirusScanner.ClamAV | ClamAV implementation: ClamAvScanner, IClamAvScanner, ClamAvBatchProcessor, ClamAvBatchExtensions, ClamAvBatchUtilities |
VirusScanner.ConsoleTest | Interactive console test application |
VirusScanner.Tests | Unit and integration tests |
Because all consuming code depends only on IVirusScanner from VirusScanner.Core, you can swap or add any backend without touching the rest of your application.
usingSystem.IO;usingSystem.Threading;usingSystem.Threading.Tasks;usingVirusScanner.Core;// Example: a provider that delegates to a proprietary REST APIpublicclassMyRestScanner:IVirusScanner{publicTask<bool>IsAvailableAsync(CancellationTokencancellationToken=default)=>/* call health endpoint */Task.FromResult(true);publicTask<ScanResult>ScanAsync(byte[]data,CancellationTokencancellationToken=default)=>ScanAsync(newMemoryStream(data),cancellationToken);publicasyncTask<ScanResult>ScanAsync(Streamdata,CancellationTokencancellationToken=default){// send stream to your API, parse responsereturnnewScanResult(ScanStatus.Clean);}publicasyncTask<ScanResult>ScanAsync(stringfilePath,CancellationTokencancellationToken=default){awaitusingvarstream=File.OpenRead(filePath);returnawaitScanAsync(stream,cancellationToken);}}Register it exactly like the built-in ClamAV provider:
// ASP.NET Core – swap the implementation without changing any other codebuilder.Services.AddScoped<IVirusScanner,MyRestScanner>();// orbuilder.Services.AddScoped<IVirusScanner,ClamAvScanner>(_ =>newClamAvScanner("localhost",3310));The same principle applies to IBatchProcessor – implement it to add batch support to any provider.
PRs are welcome! See VirusScanner.ConsoleTest/Program.cs for a full usage example.