Skip to content

Commit 244eb83

Browse files
authored
Merge pull request #8 from ByteGuard-HQ/feature/antimalware-abstraction
Add antimalware support through abstraction
2 parents 2f841c3 + f132074 commit 244eb83

24 files changed

+495
-126
lines changed

README.md

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ It helps you enforce consistent file upload rules by checking:
77
- File size limits
88
- File signatures (magic numbers) to detect spoofed types
99
- Specification conformance for Office Open XML / Open Document Formats (`.docx`, `.xlsx`, `.pptx`, `.odt`)
10+
- Malware scan result using a varity of scanners (_requires the addition of a specific ByteGuard.FileValidator scanner package_)
1011

1112
> ⚠️ **Important:** This library should be part of a **defense-in-depth** strategy.
1213
It does not replace antivirus scanning, sandboxing, or other security controls.
@@ -17,6 +18,7 @@ It does not replace antivirus scanning, sandboxing, or other security controls.
1718
- ✅ Validate files by **size**
1819
- ✅ Validate files by **signature (_magic-numbers_)**
1920
- ✅ Validate files by **specification conformance** for archive-based formats (_Open XML and Open Document Formats_)
21+
-**Ensure no malware** through a variety of antimalware scanners
2022
- ✅ Validate using file path, `Stream`, or `byte[]`
2123
- ✅ Configure which file types to support
2224
- ✅ Configure whether to **throw exceptions** or simply return a boolean
@@ -25,23 +27,26 @@ It does not replace antivirus scanning, sandboxing, or other security controls.
2527
## Getting Started
2628

2729
### Installation
28-
This package is published and installed via NuGet.
30+
This package is published and installed via [NuGet](https://www.nuget.org/packages/ByteGuard.FileValidator).
2931

3032
Reference the package in your project:
3133
```bash
3234
dotnet add package ByteGuard.FileValidator
3335
```
3436

37+
### Antimalware scanners
38+
In order to use the antimalware scanning capabilities, ensure you have a ByteGuard.FileValidator antimalware package referenced as well. Youo can find the relevant scanner package on NuGet under the namespace `ByteGuard.FileValidator.Scanners`.
3539
## Usage
3640

3741
### Basic validation
3842

3943
```csharp
4044
var configuration = new FileValidatorConfiguration
4145
{
42-
SupportedFileTypes = [FileExtensions.Pdf, FileExtensions.Jpg, FileExtensions.Png],
43-
FileSizeLimit = ByteSize.MegaBytes(25),
44-
ThrowExceptionOnInvalidFile = false
46+
SupportedFileTypes = [FileExtensions.Pdf, FileExtensions.Jpg, FileExtensions.Png],
47+
FileSizeLimit = ByteSize.MegaBytes(25),
48+
ThrowExceptionOnInvalidFile = false,
49+
AntimalwareScanner = new SpecificAntimalwareScanner(scannerOptions)
4550
};
4651

4752
var fileValidator = new FileValidator(configuration);
@@ -52,10 +57,11 @@ var isValid = fileValidator.IsValidFile("example.pdf", fileStream);
5257

5358
```csharp
5459
var configuration = new FileValidatorConfigurationBuilder()
55-
.AllowFileTypes(FileExtensions.Pdf, FileExtensions.Jpg, FileExtensions.Png)
56-
.SetFileSizeLimit(ByteSize.MegaBytes(25))
57-
.SetThrowExceptionOnInvalidFile(false)
58-
.Build();
60+
.AllowFileTypes(FileExtensions.Pdf, FileExtensions.Jpg, FileExtensions.Png)
61+
.SetFileSizeLimit(ByteSize.MegaBytes(25))
62+
.SetThrowExceptionOnInvalidFile(false)
63+
.AddAntimalwareScanner(new SpecificAntimalwareScanner(scannerOptions))
64+
.Build();
5965

6066
var fileValidator = new FileValidator(configuration);
6167
var isValid = fileValidator.IsValidFile("example.pdf", fileStream);
@@ -72,13 +78,15 @@ The `FileValidator` class provides methods to validate specific aspects of a fil
7278
> 2. File size validation
7379
> 3. Signature (magic-number) validation
7480
> 4. Optional Open XML / Open Document Format specification conformance validation (for supported types)
81+
> 5. Optional antimalware scanning with a compatible scanning package
7582
7683
```csharp
7784
bool isExtensionValid = fileValidator.IsValidFileType(fileName);
7885
bool isFileSizeValid = fileValidator.HasValidSize(fileStream);
7986
bool isSignatureValid = fileValidator.HasValidSignature(fileName, fileStream);
8087
bool isOpenXmlValid = fileValidator.IsValidOpenXmlDocument(fileName, fileStream);
8188
bool isOpenDocumentFormatValid = fileValidator.IsValidOpenDocumentFormat(fileName, fileStream);
89+
bool isMalwareClean = fileValidator.IsMalwareClean(fileName, fileStream);
8290
```
8391

8492
### Example
@@ -92,7 +100,8 @@ public async Task<IActionResult> Upload(IFormFile file)
92100
{
93101
SupportedFileTypes = [FileExtensions.Pdf, FileExtensions.Docx],
94102
FileSizeLimit = ByteSize.MegaBytes(10),
95-
ThrowExceptionOnInvalidFile = false
103+
ThrowExceptionOnInvalidFile = false,
104+
AntimalwareScanner = new SpecificAntimalwareScanner(scannerOptions)
96105
};
97106

98107
var validator = new FileValidator(configuration);
@@ -132,9 +141,10 @@ The following file extensions are supported by the `FileValidator`:
132141

133142
`IsValidFile` always validates:
134143

135-
- File extension (against `SupportedFileTypes`)
136-
- File size (against `FileSizeLimit`)
137-
- File signature (magic number)
144+
- File extension (_against `SupportedFileTypes`_)
145+
- File size (_against `FileSizeLimit`_)
146+
- File signature (_magic number_)
147+
- Malware scan result (_if an antimalware scanner has been configured_)
138148

139149
For some formats, additional checks are performed:
140150

@@ -143,11 +153,13 @@ For some formats, additional checks are performed:
143153
- File size
144154
- Signature
145155
- Specification conformance
156+
- Malware scan result
146157

147158
- **Other binary formats** (e.g. images, audio, video such as `.jpg`, `.png`, `.mp3`, `.mp4`):
148159
- Extension
149160
- File size
150161
- Signature
162+
- Malware scan result
151163

152164
## Configuration Options
153165

@@ -158,6 +170,7 @@ The `FileValidatorConfiguration` supports:
158170
| `SupportedFileTypes` | Yes | N/A | A list of allowed file extensions (e.g., `.pdf`, `.jpg`).<br>Use the predefined constants in `FileExtensions` for supported types. |
159171
| `FileSizeLimit` | Yes | N/A | Maximum permitted size of files.<br>Use the static `ByteSize` class provided with this package, to simplify your limit. |
160172
| `ThrowExceptionOnInvalidFile` | No | `true` | Whether to throw an exception on invalid files or return `false`. |
173+
| `AntimalwareScanner` | No | N/A | An antimalware scanner used to scan the given file for potential malware. |
161174

162175
### Exceptions
163176

@@ -171,6 +184,7 @@ When `ThrowExceptionOnInvalidFile` is set to `true`, validation functions will t
171184
| `InvalidSignatureException` | Thrown when the file's signature does not match the expected signature for its type. |
172185
| `InvalidOpenXmlFormatException` | Thrown when the internal structure of an Open XML file is invalid (`.docx`, `.xlsx`, `.pptx`, etc.). |
173186
| `InvalidOpenDocumentFormatException` | Thrown when the specification conformance of an Open Document Format file is invalid (`.odt`, etc.). |
187+
| `MalwareDetectedException` | Thrown when the configured antimalware scanner detected malware in the file from a scan result. |
174188

175189
## When to use this package
176190

src/ByteGuard.FileValidator/ByteGuard.FileValidator.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
4-
<TargetFrameworks>netstandard2.0;net48;net8.0;net9.0;net10.0</TargetFrameworks>
4+
<TargetFrameworks>netstandard2.0;net8.0;net9.0;net10.0</TargetFrameworks>
55
<Authors>ByteGuard Contributors, detilium</Authors>
66
<Description>ByteGuard File Validator is a security-focused .NET library for validating user-supplied files, providing a configurable API to help you enforce safe and consistent file handling across your applications.</Description>
77
<PackageProjectUrl>https://github.com/ByteGuard-HQ/byteguard-file-validator-net</PackageProjectUrl>

src/ByteGuard.FileValidator/ByteSize.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
using System;
2-
using System.Globalization;
1+
using System.Globalization;
32

43
namespace ByteGuard.FileValidator
54
{

src/ByteGuard.FileValidator/Configuration/ConfigurationValidator.cs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
1-
using System;
2-
using System.Linq;
3-
using ByteGuard.FileValidator.Exceptions;
1+
using ByteGuard.FileValidator.Exceptions;
42

53
namespace ByteGuard.FileValidator.Configuration
64
{
@@ -49,4 +47,4 @@ public static void ThrowIfInvalid(FileValidatorConfiguration configuration)
4947
}
5048
}
5149
}
52-
}
50+
}
Lines changed: 6 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
using System.Collections.Generic;
2-
using ByteGuard.FileValidator.Exceptions;
1+
using ByteGuard.FileValidator.Scanners;
32

43
namespace ByteGuard.FileValidator.Configuration
54
{
@@ -27,33 +26,13 @@ public class FileValidatorConfiguration
2726
public long FileSizeLimit { get; set; } = -1;
2827

2928
/// <summary>
30-
/// Maximum file size limit in string representation (e.g. "25MB", "2 GB", etc.).
29+
/// Whether to throw an exception if an unsupported/invalid file is encountered. Defaults to <c>true</c>.
3130
/// </summary>
32-
/// <remarks>
33-
/// Defines the file size limit of files. See <see cref="ByteSize"/> for conversion help.
34-
/// Will be ignored if <see cref="FileSizeLimit"/> is defined.
35-
/// Spacing (<c>"25 MB"</c> vs. <c>"25MB"</c>) is irrelevant.
36-
/// <para>Supported string representation are:
37-
/// <ul>
38-
/// <li><c>B</c>: Bytes</li>
39-
/// <li><c>KB</c>: Kilobytes</li>
40-
/// <li><c>MB</c>: Megabytes</li>
41-
/// <li><c>GB</c>: Gigabytes</li>
42-
/// </ul>
43-
/// </para>
44-
/// </remarks>
45-
public string FriendlyFileSizeLimit { get; set; }
31+
public bool ThrowExceptionOnInvalidFile { get; set; } = true;
4632

4733
/// <summary>
48-
/// Whether to throw an exception if an unsupported/invalid file is encountered. Defaults to <c>true</c>.
34+
/// Optional antimalware scanner to use during file validation.
4935
/// </summary>
50-
/// <remarks>
51-
/// Will throw the following exceptions:
52-
/// <ul>
53-
/// <li><see cref="UnsupportedFileException"/> if the given file type is not supported according to the <see cref="SupportedFileTypes"/>.</li>
54-
/// <li><see cref="InvalidSignatureException"/> if the file signature does not match the valid signatures for the given file type.</li>
55-
/// </ul>
56-
/// </remarks>
57-
public bool ThrowExceptionOnInvalidFile { get; set; } = true;
36+
public IAntimalwareScanner? AntimalwareScanner { get; set; } = null;
5837
}
59-
}
38+
}

src/ByteGuard.FileValidator/Configuration/FileValidatorConfigurationBuilder.cs

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using System.Collections.Generic;
1+
using ByteGuard.FileValidator.Scanners;
22

33
namespace ByteGuard.FileValidator.Configuration
44
{
@@ -10,6 +10,7 @@ public class FileValidatorConfigurationBuilder
1010
private readonly List<string> supportedFileTypes = new List<string>();
1111
private bool throwOnInvalidFiles = true;
1212
private long fileSizeLimit = ByteSize.MegaBytes(25);
13+
private IAntimalwareScanner? antimalwareScanner = null;
1314

1415
/// <summary>
1516
/// Allow specific file types (extensions) to be validated.
@@ -43,6 +44,22 @@ public FileValidatorConfigurationBuilder SetFileSizeLimit(long inFileSizeLimit)
4344
return this;
4445
}
4546

47+
/// <summary>
48+
/// Add an antimalware scanner.
49+
/// </summary>
50+
/// <param name="scanner">Antimalware scanner to use.</param>
51+
/// <exception cref="ArgumentNullException">Thrown when the provided scanner is null.</exception>
52+
public FileValidatorConfigurationBuilder AddAntimalwareScanner(IAntimalwareScanner scanner)
53+
{
54+
if (scanner == null)
55+
{
56+
throw new ArgumentNullException(nameof(scanner));
57+
}
58+
59+
antimalwareScanner = scanner;
60+
return this;
61+
}
62+
4663
/// <summary>
4764
/// Build configuration.
4865
/// </summary>
@@ -53,12 +70,13 @@ public FileValidatorConfiguration Build()
5370
{
5471
SupportedFileTypes = supportedFileTypes,
5572
ThrowExceptionOnInvalidFile = throwOnInvalidFiles,
56-
FileSizeLimit = fileSizeLimit
73+
FileSizeLimit = fileSizeLimit,
74+
AntimalwareScanner = antimalwareScanner
5775
};
5876

5977
ConfigurationValidator.ThrowIfInvalid(configuration);
6078

6179
return configuration;
6280
}
6381
}
64-
}
82+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
namespace ByteGuard.FileValidator.Exceptions
2+
{
3+
/// <summary>
4+
/// Exception type used specifically when a an antimalware scanner failed to scane the file.
5+
/// </summary>
6+
public class AntimalwareScannerException : Exception
7+
{
8+
/// <summary>
9+
/// Default expcetion message for this expection type, if no custom message is provided.
10+
/// </summary>
11+
private const string defaultMessage = "An error occurred while scanning the file with the antimalware scanner.";
12+
13+
/// <summary>
14+
/// Initializes a new instance of the <see cref="AntimalwareScannerException"/> class indicating that an error occured during the scan.
15+
/// </summary>
16+
public AntimalwareScannerException()
17+
: this(defaultMessage)
18+
{
19+
}
20+
21+
/// <summary>
22+
/// Initializes a new instance of the <see cref="AntimalwareScannerException"/> class indicating that an error occured during the scan.
23+
/// </summary>
24+
/// <param name="message">Custom exception message.</param>
25+
public AntimalwareScannerException(string message) : base(message)
26+
{
27+
}
28+
29+
/// <summary>
30+
/// Initializes a new instance of the <see cref="AntimalwareScannerException"/> class indicating that an error occured during the scan.
31+
/// </summary>
32+
/// <param name="message">Custom exception message.</param>
33+
/// <param name="innerException">Inner exception.</param>
34+
public AntimalwareScannerException(string message, Exception innerException) : base(message, innerException)
35+
{
36+
}
37+
38+
/// <summary>
39+
/// Initializes a new instance of the <see cref="AntimalwareScannerException"/> class indicating that an error occured during the scan.
40+
/// </summary>
41+
/// <param name="innerException">Inner exception.</param>
42+
public AntimalwareScannerException(Exception innerException) : base(defaultMessage, innerException)
43+
{
44+
}
45+
}
46+
}
Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,33 @@
1-
using System;
2-
3-
namespace ByteGuard.FileValidator.Exceptions
1+
namespace ByteGuard.FileValidator.Exceptions
42
{
53
/// <summary>
64
/// Exception type used specifically when a given file does not contain any content.
75
/// </summary>
86
public class EmptyFileException : Exception
97
{
8+
/// <summary>
9+
/// Construct a new <see cref="EmptyFileException"/> to indicate that the provided file does not have any content.
10+
/// </summary>
1011
public EmptyFileException()
1112
: this("The provided file does not have any content.")
1213
{
1314
}
1415

16+
/// <summary>
17+
/// Construct a new <see cref="EmptyFileException"/> to indicate that the provided file does not have any content.
18+
/// </summary>
19+
/// <param name="message">Custom exception message.</param>
1520
public EmptyFileException(string message) : base(message)
1621
{
1722
}
1823

24+
/// <summary>
25+
/// Construct a new <see cref="EmptyFileException"/> to indicate that the provided file does not have any content.
26+
/// </summary>
27+
/// <param name="message">Custom exception message.</param>
28+
/// <param name="innerException">Inner exception.</param>
1929
public EmptyFileException(string message, Exception innerException) : base(message, innerException)
2030
{
2131
}
2232
}
23-
}
33+
}
Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,24 @@
1-
using System;
2-
using System.Runtime.Serialization;
3-
4-
namespace ByteGuard.FileValidator.Exceptions
1+
namespace ByteGuard.FileValidator.Exceptions
52
{
63
/// <summary>
74
/// Exception type used specifically when a given file exceeds the configured file size limit.
85
/// </summary>
96
public class InvalidFileSizeException : Exception
107
{
8+
/// <summary>
9+
/// Construct a new <see cref="InvalidFileSizeException"/> to indicate that the provided file does not adhere to the configured limit.
10+
/// </summary>
1111
public InvalidFileSizeException()
1212
: base("File size is larger than the configured file size limit.")
1313
{
1414
}
1515

16+
/// <summary>
17+
/// Construct a new <see cref="InvalidFileSizeException"/> to indicate that the provided file does not adhere to the configured limit.
18+
/// </summary>
19+
/// <param name="message">Custom exception message.</param>
1620
public InvalidFileSizeException(string message) : base(message)
1721
{
1822
}
1923
}
20-
}
24+
}

0 commit comments

Comments
 (0)