Production-ready safety and diagnostics infrastructure for ASP.NET Core applications.
Backend Safety Kit is designed to remove repetitive backend infrastructure work by providing secure, predictable, configurable defaults through a small NuGet package.
A typical ASP.NET Core API repeatedly needs the same infrastructure:
- Global exception handling
- Consistent error responses
- Correlation/request IDs
- Structured request logging
- Sensitive-data protection
- Request timing and diagnostics
- Safe HTTP defaults
- Health and diagnostics integration
These concerns are important, but they should not force every development team to implement the same middleware, options, validation, logging rules, and tests from scratch.
Backend Safety Kit packages those concerns into a reusable layer while staying out of business logic.
Security architecture and private vulnerability-reporting guidance are documented in SECURITY.md.
The repository validates public API compatibility against each pull request's base commit, treats missing XML documentation on public APIs as a build error, and inspects the contents and metadata of packed NuGet artifacts.
Framework support is documented in the framework support policy. XML documentation and package validation are described in NuGet release readiness, with debugging symbols and Source Link covered in symbols and Source Link.
The common case should be close to this:
dotnet add package BackendSafetyKit.AspNetCoreThen:
builder.Services.AddBackendSafety();
var app = builder.Build();
app.UseBackendSafety();The package should work safely with minimal configuration. Advanced applications can configure individual features when needed.
Individual ASP.NET Core middleware features can also be disabled explicitly when an application does not need them:
builder.Services.AddBackendSafety(options =>
{
options.Features.EnableRequestLogging = false;
options.Features.EnableHttpSecurity = false;
});The default is to enable all middleware features. Disabling a feature removes its middleware from the request pipeline rather than leaving a no-op middleware in place. Sensitive-data masking remains available as a protection layer for diagnostics and is not silently disabled by feature toggles.
Configuration is validated during application startup. Invalid Backend Safety options fail through ASP.NET Core hosting startup validation instead of being silently accepted. The same validation is also used by the built-in health check, which reports an unhealthy state without exposing configuration values.
Unhandled exceptions are captured at one consistent boundary.
Goals:
- Return safe HTTP errors.
- Do not expose stack traces in production by default.
- Preserve exceptions for diagnostics.
- Support custom exception-to-status-code mappings.
- Work with controllers and minimal APIs.
Handled API exceptions are returned as standards-aligned ProblemDetails responses using the application/problem+json content type.
The default response includes:
typetitlestatusinstancewhen enabledtraceIdwhen enabled
Exception messages are not returned by default.
Example:
{
"type": "about:blank",
"title": "An unexpected error occurred.",
"status": 500,
"instance": "/api/orders/42",
"traceId": "00-..."
}Applications can customize safe details, titles, application-specific error codes, and arbitrary ProblemDetails extensions:
builder.Services.AddBackendSafety(options =>
{
options.ExceptionHandling.Map<KeyNotFoundException>(404);
options.ProblemDetails
.MapErrorCode<KeyNotFoundException>("order_not_found")
.MapTitle<KeyNotFoundException>("Order was not found.");
options.ProblemDetails.Customize = context =>
{
context.Detail = "The requested order does not exist.";
context.Extensions["errors"] = new { orderId = "order-42" };
};
});Exception details can be enabled explicitly for development environments with IncludeExceptionDetailInDevelopment. Stack traces are never added automatically.
Every request receives one effective identifier.
By default the middleware uses this precedence:
X-Correlation-ID
|
+--> valid --> use it
|
+--> missing/invalid
|
v
X-Request-ID
|
+--> valid --> use it
|
+--> missing/invalid --> generate ID
The effective identifier is:
- Stored in the current request through ICorrelationIdAccessor.
- Assigned to ASP.NET Core's HttpContext.TraceIdentifier.
- Added to the standard logging scope for downstream logs.
- Returned through X-Correlation-ID by default.
- Configurable without changing application code.
Incoming values are untrusted input. IDs longer than the configured limit, IDs containing unsupported characters, and multi-valued ID headers are ignored and safely replaced or resolved through the fallback header.
Example:
builder.Services.AddBackendSafety(options =>
{
options.Correlation.MaxLength = 128;
options.Correlation.IncludeResponseHeader = true;
});
var app = builder.Build();
app.UseBackendSafety();Custom header names and ID generation are supported through CorrelationIdOptions.
The kit provides a dependency-free System.Diagnostics.ActivitySource named BackendSafetyKit for request tracing. The ASP.NET Core middleware creates a server activity when distributed tracing is enabled and no upstream Activity.Current already exists.
Incoming W3C traceparent and tracestate headers are used as the remote parent context. Invalid or multi-valued trace headers are ignored rather than preventing request processing.
The feature is enabled by default:
builder.Services.AddBackendSafety(options =>
{
options.Features.EnableDistributedTracing = true;
options.DistributedTracing.ActivityName =
"BackendSafetyKit.HttpRequest";
});The core package does not export telemetry or require OpenTelemetry. The optional BackendSafetyKit.OpenTelemetry package can subscribe this activity source to an OpenTelemetry provider.
An existing server activity is reused, preventing duplicate request spans when the hosting stack or an observability integration has already created Activity.Current. Disabling distributed tracing removes the package middleware without affecting correlation IDs or request handling.
The library writes one structured completion log for each request through standard Microsoft.Extensions.Logging abstractions.
The completion event schema is stable:
| Event | Event ID | Meaning |
|---|---|---|
BackendSafetyKit.HttpRequest.Completed |
2001 | Successful request completion |
BackendSafetyKit.HttpRequest.ClientError |
2002 | HTTP 400-499 completion |
BackendSafetyKit.HttpRequest.ServerError |
2003 | HTTP 500+ completion or an unhandled exception |
All completion events expose the same structured fields:
- HTTP method and path
- HTTP status code
- elapsed duration
- correlation/request ID
- request host when enabled
- failure indicator
- unhandled exception type when one escapes the pipeline
- explicitly allowlisted request/response headers
Completion log severity is configurable per response class. The secure defaults remain information, warning, and error:
builder.Services.AddBackendSafety(options =>
{
options.RequestLogging.SuccessfulRequestLogLevel =
RequestLoggingLogLevel.Information;
options.RequestLogging.ClientErrorLogLevel =
RequestLoggingLogLevel.Warning;
options.RequestLogging.ServerErrorLogLevel =
RequestLoggingLogLevel.Error;
});Set any category to RequestLoggingLogLevel.None to disable only that category. The event IDs and event names remain stable so log consumers can filter by schema rather than message text.
Security defaults are intentionally restrictive:
- Request bodies are never read or logged.
- Query strings are never logged.
- Authorization, Cookie, Proxy-Authorization, and Set-Cookie are denied by default.
- Header logging is disabled until a header is explicitly added to an allowlist.
- Logged metadata values are length-limited.
Header allowlists and denylists can be configured when an application has a safe diagnostic need:
builder.Services.AddBackendSafety(options =>
{
options.RequestLogging.AllowedRequestHeaders.Add("X-Tenant");
options.RequestLogging.AllowedResponseHeaders.Add("Content-Type");
options.RequestLogging.MaxMetadataValueLength = 256;
});
A denylist entry takes precedence over an allowlist entry. Remove a default denylist entry only when the application explicitly accepts the data-handling risk.
The kit provides a non-mutating masking component through ISensitiveDataMasker. It can redact sensitive scalar values and copy nested dictionaries, collections, and public object properties into a safe representation.
The default rules cover common sensitive names such as:
- Passwords
- API keys
- Access tokens
- Refresh tokens
- Client secrets
- Authorization and proxy-authorization values
- Cookies and set-cookie values
- Common authentication token headers
Matching is case-insensitive and exact by default. Applications can add contains, starts-with, or ends-with rules for domain-specific names:
builder.Services.AddBackendSafety(options =>
{
options.SensitiveDataMasking.AddRule("CustomerSecret");
options.SensitiveDataMasking.AddRule(
"credential",
SensitiveDataMatchMode.Contains);
});Full redaction is the default:
customer-secret-42 -> [REDACTED]
Partial masking is disabled by default and requires explicit opt-in on both the options and the individual rule:
builder.Services.AddBackendSafety(options =>
{
options.SensitiveDataMasking.AllowPartialMasking = true;
options.SensitiveDataMasking.AddRule(
"accessToken",
SensitiveDataMatchMode.Exact,
SensitiveDataMaskMode.Partial,
visiblePrefixLength: 2,
visibleSuffixLength: 4);
});The masker never changes the original application object. When structured data is masked, a new dictionary/list representation is returned.
For null, string, and simple scalar inputs, ISensitiveDataMasker.Mask returns the safe result directly without allocating the reference-tracking set used for nested object graphs. Field-based masking rules are evaluated before that fast path, so secret values remain redacted.
The request logging middleware uses the masker for allowlisted headers. This provides a second protection layer when an application explicitly removes a header from the logging denylist.
Request timing is measured with a monotonic clock so elapsed duration is not affected by wall-clock changes.
The timing middleware provides:
- Request duration
- Configurable slow-request threshold
- Optional slow-request logging at a configurable log level
- Correlation/request identifier
- A completion callback for applications that want to export diagnostics elsewhere
Slow-request logging is disabled by default. A hook can be registered without introducing a metrics provider:
builder.Services.AddBackendSafety(options =>
{
options.RequestTiming.SlowRequestThreshold =
TimeSpan.FromMilliseconds(750);
options.RequestTiming.EnableSlowRequestLogging = true;
options.RequestTiming.SlowRequestLogLevel =
RequestTimingLogLevel.Warning;
options.RequestTiming.OnCompleted = timing =>
{
// Export timing.Duration to your own metrics system.
};
});The completion callback receives only safe diagnostic metadata: method, path, status code, correlation ID, duration, and slow-request state. Request bodies and query strings are not included.
The ASP.NET Core request-timing middleware can publish a dependency-free, structured diagnostic event to each registered IBackendSafetyDiagnosticSink. Register a sink in dependency injection; no exporter, telemetry SDK, or network connection is added by the core package:
using BackendSafetyKit;
using BackendSafetyKit.Abstractions;
public sealed class ApplicationDiagnosticSink : IBackendSafetyDiagnosticSink
{
public void Write(BackendSafetyDiagnosticEvent diagnosticEvent)
{
// Send the approved event to your application's existing diagnostics system.
Console.WriteLine($"{diagnosticEvent.Timestamp:O} {diagnosticEvent.Severity} {diagnosticEvent.Name}");
}
}
builder.Services.AddSingleton<IBackendSafetyDiagnosticSink, ApplicationDiagnosticSink>();The public event contract exposes stable event names through BackendSafetyDiagnosticEventNames and stable property names through BackendSafetyDiagnosticPropertyNames:
| Event | Severity | When emitted |
|---|---|---|
BackendSafetyKit.HttpRequest.Completed |
Information, or Warning when slow | A request completes with a status below 400 |
BackendSafetyKit.HttpRequest.ClientError |
Warning | A request completes with a 4xx status code |
BackendSafetyKit.HttpRequest.ServerError |
Error | A request completes with a 5xx status code |
Each event contains a UTC timestamp and the following allowlisted properties: normalized HTTP method, response status code, duration in milliseconds, and whether the slow-request threshold was reached. Paths, query strings, correlation IDs, request/response headers, bodies, and exception details are deliberately excluded to reduce data-leakage and cardinality risks.
BackendSafetyDiagnosticEvent.Properties is a read-only snapshot. Exceptions from custom sinks are isolated: they do not fail or interrupt the request and are logged as event ID 3004. Events are published by the request-timing middleware, so disabling Features.EnableRequestTiming also disables this event stream.
The kit exposes a dependency-free System.Diagnostics.Metrics.Meter named BackendSafetyKit.
HTTP request metrics are recorded from the existing request timing middleware:
| Instrument | Type | Unit | Meaning |
|---|---|---|---|
backend_safety_kit.http.server.request.count |
Counter | {request} |
Completed HTTP requests |
backend_safety_kit.http.server.request.duration |
Histogram | s |
Completed request duration |
backend_safety_kit.http.server.request.error.count |
Counter | {request} |
Completed requests with a 5xx status |
backend_safety_kit.http.server.request.slow.count |
Counter | {request} |
Requests at or above the configured slow threshold |
Metric tags are deliberately limited to the HTTP method and response status code. Paths, query strings, correlation IDs, headers, request bodies, and other potentially high-cardinality or sensitive values are not emitted as metric tags.
The core package does not export telemetry and does not require OpenTelemetry. The optional BackendSafetyKit.OpenTelemetry package can subscribe the meter to an OpenTelemetry provider.
Install the optional integration package:
dotnet add package BackendSafetyKit.OpenTelemetryRegister Backend Safety Kit's existing tracing and metrics sources with OpenTelemetry:
using BackendSafetyKit.OpenTelemetry.DependencyInjection;
builder.Services.AddBackendSafetyOpenTelemetry();
builder.Services.AddOpenTelemetry()
.WithTracing(tracing =>
{
// Add your preferred exporter here.
})
.WithMetrics(metrics =>
{
// Add your preferred exporter here.
});Tracing and metrics are enabled by default. Each signal can be disabled independently:
builder.Services.AddBackendSafetyOpenTelemetry(options =>
{
options.EnableTracing = true;
options.EnableMetrics = false;
});The integration package does not add an exporter, make network calls, or enable automatic ASP.NET Core instrumentation. It only registers the Backend Safety Kit ActivitySource and Meter with the OpenTelemetry SDK, which leaves exporter, resource, sampling, and transport choices with the application.
The package currently targets OpenTelemetry 1.19.1.
The kit applies a deliberately small set of HTTP hardening defaults through HttpSecurityOptions.
The default behavior is intentionally conservative:
X-Content-Type-Options: nosniffis enabled.- Request body size is not changed unless
MaxRequestBodySizeis explicitly configured. X-Frame-Options,Referrer-Policy, andContent-Security-Policyare disabled by default because they can affect legitimate browser-facing applications.- Existing response headers are preserved and are never overwritten by the package.
Content Security Policy can be enabled in report-only mode before enforcement. Report-only policies let browsers report violations without blocking resources, which makes them useful while learning which scripts, styles, images, and connections an application actually needs:
builder.Services.AddBackendSafety(options =>
{
options.HttpSecurity.EnableContentSecurityPolicyReportOnlyHeader = true;
options.HttpSecurity.ContentSecurityPolicyReportOnly =
"default-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'none'";
});The package does not collect CSP reports or configure a reporting endpoint. Add your own report-to or report-uri directive only after selecting and securing a reporting endpoint. Once the policy is verified against real application behavior, consider enabling Content-Security-Policy enforcement separately. The enforcing and report-only headers can coexist during staged policy rollout. Both remain disabled by default, and application-supplied headers take precedence.
Example:
builder.Services.AddBackendSafety(options =>
{
options.HttpSecurity.EnableFrameOptionsHeader = true;
options.HttpSecurity.FrameOptions = "SAMEORIGIN";
options.HttpSecurity.EnableReferrerPolicyHeader = true;
options.HttpSecurity.ReferrerPolicy = "strict-origin-when-cross-origin";
options.HttpSecurity.EnableContentSecurityPolicyHeader = true;
options.HttpSecurity.ContentSecurityPolicy =
"default-src 'self'";
options.HttpSecurity.MaxRequestBodySize = 10 * 1024 * 1024;
});A configured request body limit is applied through ASP.NET Core's IHttpMaxRequestBodySizeFeature. If the active server does not expose that feature or the server has already made the limit read-only, configuration fails instead of silently doing nothing.
When a configured limit is exceeded by a declared Content-Length, the middleware returns 413 Payload Too Large before invoking the application endpoint. Requests with an unknown/chunked length are still constrained by the server-level body-size feature as the body is read. A null limit leaves both checks unchanged; size enforcement is not inferred from a global default.
HTTPS redirection and HSTS are available as explicit opt-ins. They remain disabled by default because the HTTPS port and proxy topology are properties of the host deployment:
builder.Services.AddBackendSafety(options =>
{
options.HttpSecurity.EnableHttpsRedirection = true;
options.HttpSecurity.HttpsPort = 443;
options.HttpSecurity.EnableHsts = true;
options.HttpSecurity.HstsMaxAge = TimeSpan.FromDays(365);
options.HttpSecurity.HstsIncludeSubDomains = true;
// Enable preload only when every subdomain is HTTPS-ready and the domain is eligible.
options.HttpSecurity.HstsPreload = false;
});HTTPS redirects use a method-preserving 307 response by default (308 can be selected explicitly), and require a valid configured HTTPS port. HSTS is emitted only on HTTPS responses, preserves an application-supplied Strict-Transport-Security header, and validates preload requirements. When TLS terminates at a reverse proxy, configure trusted forwarded headers before UseBackendSafety(); otherwise the application may see the original request as HTTP and produce redirect loops. Do not trust forwarded headers from arbitrary clients.
The package does not configure authentication, authorization, CORS, cookies, or proxy trust. Those behaviors depend on the host application's deployment and security architecture.
Existing ProblemDetails behavior remains safe by default: exception details are not returned unless explicitly enabled for development, and development-only details are still suppressed outside the Development environment.
The kit can integrate with the standard ASP.NET Core Health Checks infrastructure without adding external services.
Register the package diagnostic check with:
builder.Services.AddBackendSafety();
builder.Services.AddBackendSafetyHealthChecks();Then expose it through the standard ASP.NET Core endpoint mapping:
app.MapHealthChecks("/health");The built-in check validates the package configuration without returning configuration values or exception details.
A healthy check reports:
Backend Safety Kit configuration is valid.
Invalid configuration reports an unhealthy result:
Backend Safety Kit configuration is invalid.
The underlying validation exception is written to structured logging (event ID 4001) for operators, but it is not included in the health response.
Readiness or liveness filtering can use standard Health Checks tags:
builder.Services.AddBackendSafetyHealthChecks(
"backend-safety-readiness",
"ready");The package does not create authentication, authorization, CORS, or external dependency health checks. Applications can compose the package check with their own checks using the standard AddHealthChecks and MapHealthChecks APIs.
The repository includes a runnable sample API and an ASP.NET Core integration test suite.
Run the sample locally:
dotnet run --project samples/BackendSafetyKit.SampleApiThe minimal application setup is:
builder.Services.AddBackendSafety();
builder.Services.AddBackendSafetyHealthChecks();
var app = builder.Build();
app.UseBackendSafety();
app.MapHealthChecks("/health");The integration tests boot the sample with WebApplicationFactory and exercise the HTTP pipeline without external infrastructure.
The default request pipeline includes all enabled middleware features. Individual features can be disabled through BackendSafetyOptions.Features; disabled middleware is not added to the pipeline.
The implemented request pipeline is:
Incoming Request
|
v
Correlation / Request ID
|
v
Distributed Trace Context
|
v
Structured Request Logging
|
v
Request Timing / Diagnostics
|
v
Secure HTTP Defaults
|
v
Exception Boundary
|
v
Application Pipeline
|
+---- exception ----> ProblemDetails
|
v
Completion Log
|
v
Outgoing Response
The structured logging middleware wraps request timing so the completion log uses the same monotonic duration measurement produced by the timing middleware. The timing middleware wraps the exception boundary so handled failures receive their final HTTP status code.
This ordering is a compatibility contract:
- Correlation/request ID runs first so every downstream middleware sees the effective identifier.
- Structured logging wraps timing so completion events can include the final timing result.
- Request timing wraps HTTP security and exception handling so timing observes the final handled status code.
- HTTP security wraps the exception boundary so configured security headers still apply to handled responses.
- Exception handling is the innermost package boundary before the application pipeline.
Regression tests cover these relationships together so future middleware changes cannot silently reorder the safety boundaries.
Backend Safety Kit does not enable or trust forwarded headers automatically. A reverse proxy can affect the apparent request scheme and client address, so configure ASP.NET Core's forwarded-header middleware explicitly and trust only the proxy addresses or networks that you control.
Configure the selected headers and trusted proxy before building the app:
using System.Net;
using Microsoft.AspNetCore.HttpOverrides;
builder.Services.Configure<ForwardedHeadersOptions>(options =>
{
options.ForwardedHeaders =
ForwardedHeaders.XForwardedFor |
ForwardedHeaders.XForwardedProto;
// Replace this example with the address of your actual trusted proxy.
options.KnownProxies.Add(IPAddress.Parse("10.0.0.10"));
options.ForwardLimit = 1;
});
var app = builder.Build();
// Must run before middleware that uses Request.Scheme or RemoteIpAddress.
app.UseForwardedHeaders();
app.UseBackendSafety();Security rules:
- Configure
KnownProxiesorKnownNetworksfor your actual deployment topology; the example address is illustrative. - Do not clear the trusted-proxy lists or accept forwarded headers from arbitrary internet clients just to make the headers work.
- Enable only the forwarded headers the proxy is expected to set. If you need
X-Forwarded-Host, validate allowed hosts and configure it deliberately. - Place
UseForwardedHeaders()beforeUseBackendSafety()and any other middleware that depends on the scheme, client IP, HTTPS redirection, or generated absolute URLs. - When a proxy chain exists, set
ForwardLimitto the intended number of trusted hops and define trust for each hop. Do not treat the rightmost or leftmost address as trustworthy without validating the chain.
The library intentionally leaves proxy trust to the host application. It does not rewrite forwarded headers, infer trusted proxy addresses, or make network calls.
Backend Safety Kit treats application options as startup configuration. Configure the options while registering the services and treat them as immutable after the application starts.
The built-in service lifetimes are intentional:
IOptions<BackendSafetyOptions>uses the standard ASP.NET Core options lifetime for application configuration.ISensitiveDataMaskeris registered as a singleton and snapshots its masking configuration when it is created, so request execution does not depend on a mutable options object.ICorrelationIdAccessoris scoped so correlation IDs remain isolated to the current request scope.- Middleware instances do not store request-specific state in static or shared mutable fields; request state is kept in the current
HttpContext.
This design allows the runtime services to be used concurrently without cross-request state leakage. Runtime mutation of the registered options is not supported.
The project deliberately separates framework-independent code from ASP.NET Core integration.
Contains reusable, framework-independent primitives and contracts.
Examples:
- Options contracts
- Sensitive-data masking rules
- Diagnostic abstractions
- Shared policies
- Reusable utilities
Contains ASP.NET Core-specific behavior.
Examples:
- Dependency injection extensions
- Middleware
- HTTP context integration
- ProblemDetails integration
- Request/response diagnostics
Contains the optional OpenTelemetry SDK integration.
It registers the package's existing:
BackendSafetyDiagnostics.ActivitySourceBackendSafetyMetrics.Meter
It does not add exporters or automatic ASP.NET Core instrumentation, so applications retain control over resource attributes, sampling, exporters, and transport.
Provider-specific integrations remain separate packages so the core dependency graph stays small.
- Redis diagnostics —
BackendSafetyKit.Redisprovides a bounded asynchronous Redis Streams sink for safe diagnostic events. See the Redis integration guide. - Entity Framework Core diagnostics —
BackendSafetyKit.EntityFrameworkCoreemits safe SaveChanges completion/failure events without logging SQL or entity values. See the EF Core integration guide.
Install only the provider integration an application actually uses. Neither optional package is required by the core package, and neither creates provider infrastructure or makes hidden network calls.
The core packages must not become dependency magnets.
Backend Safety Kit should be useful immediately after installation.
Rules:
- Prefer BCL and Microsoft.Extensions abstractions.
- Keep third-party dependencies minimal.
- Do not require Redis, PostgreSQL, SQL Server, or an external SaaS for core functionality.
- Optional infrastructure integrations belong in separate packages.
- No telemetry.
- No hidden network calls.
- No tracking or analytics.
Security is a primary responsibility of the library.
The project follows these rules:
- Never expose stack traces in production by default.
- Never log secrets by default.
- Never log request bodies by default.
- Treat incoming IDs and headers as untrusted input.
- Apply sensible size limits.
- Do not leak internal implementation details through errors.
- Do not silently weaken application authentication or authorization.
- Every security-sensitive behavior must have regression tests.
When convenience and security conflict, the secure behavior wins unless the consumer explicitly opts into a different behavior.
The manually reviewed public surface and compatibility rules are tracked in docs/public-api-review.md.
Public APIs should be:
- Small
- Discoverable
- Strongly typed
- Nullable-aware
- Documented
- Predictable
Prefer simple registration and focused options:
builder.Services.AddBackendSafety(options =>
{
// feature-specific settings
});Avoid:
- Global mutable state
- Static service locators
- Hidden singleton state
- Reflection on hot paths
- Magic configuration where typed options are practical
- APIs that expose internal middleware implementation details
Backend Safety Kit runs on the request path, so performance matters.
A separate BenchmarkDotNet project is available for repeatable local measurements of sensitive-data masking and request-pipeline overhead. See BENCHMARKS.md for run instructions. Benchmark results are environment-dependent and are not treated as CI performance guarantees.
Rules:
- Avoid unnecessary allocations.
- Avoid reflection on every request.
- Do not serialize data that is not needed.
- Do not read request/response bodies unless explicitly enabled.
- Avoid expensive work for disabled features.
- Never make network calls in the core request pipeline.
- Benchmark expensive code when a feature introduces measurable overhead.
Safety should not come at the cost of careless runtime overhead.
Tests are part of every feature.
Use unit tests for framework-independent behavior such as:
- Masking
- Identifier validation
- Exception mappings
- Options validation
- Reusable utilities
Use real ASP.NET Core test hosts for:
- Middleware ordering
- HTTP responses
- ProblemDetails
- Headers
- Exception handling
- Logging
- Timing
- Health/diagnostics
Every security bug or information-disclosure bug becomes a permanent regression test.
Tests should be deterministic, isolated, readable, and independent of external infrastructure.
The sample API is part of the product documentation.
It should demonstrate:
- Minimal installation
- Minimal registration
- A successful endpoint
- An endpoint that throws
- Correlation ID behavior
- Slow-request diagnostics
- Health/diagnostics behavior
The sample should remain simple enough for a developer discovering the package for the first time.
CI validates the project in this order:
restore
|
v
build
|
v
unit tests
|
v
integration tests
|
v
pack
Future CI may add:
- Formatting
- Static analysis
- CodeQL
- Dependency/security scanning
- Package validation
Before a stable release:
- Package metadata must be complete.
- Package README must render correctly.
- Public APIs should have XML documentation.
- Semantic versioning must be followed.
- Package dependencies must be reviewed.
- Security scanning must run in CI.
- Package contents must be checked before publishing.
- Release notes must explain user-visible changes.
The package must earn trust rather than simply compile.
Use Semantic Versioning:
MAJOR.MINOR.PATCH
Until the first stable release, the package may remain in the 0.x range.
Breaking public API changes require deliberate review.
Issues define the development order.
For each implementation issue:
- Create a focused feature branch.
- Implement only the issue's scope.
- Add or update tests.
- Update documentation when behavior changes.
- Run the relevant test suite.
- Open a pull request.
- Review the pull request.
- The repository maintainer performs the final merge.
PRs should not be merged automatically.
Avoid unrelated refactoring inside feature issues.
A feature is done when:
- Implementation is complete.
- Public API is intentional.
- Expected and failure behavior are tested.
- Security implications are considered.
- Documentation is updated.
- Sample usage is updated when relevant.
- CI is green.
- No unnecessary dependency was introduced.
- The change stays within the issue scope.
The initial implementation order is intentionally dependency-driven:
#1 Solution and package foundation
|
v
#2 Global exception handling
|
v
#3 ProblemDetails
|
v
#4 Correlation / Request IDs
|
v
#5 Structured request logging
|
v
#6 Sensitive-data masking
|
v
#7 Request timing and diagnostics
|
v
#8 Secure HTTP defaults
|
v
#9 Health and diagnostics integration
|
v
#10 Integration tests + sample application
Backend Safety Kit should feel like a small package that quietly removes a large amount of repetitive backend work.
A developer should be able to:
- Install it quickly.
- Understand exactly what it changes.
- Trust the defaults.
- Configure only what is necessary.
- Integrate it without restructuring the application.
- Remove it without rewriting business logic.
The ideal reaction is:
"Why was I implementing all of this manually in every API?"
The license will be finalized before the first stable NuGet release.