SafeWebCore.FraudDetection 1.0.0

dotnet add package SafeWebCore.FraudDetection --version 1.0.0
                    
NuGet\Install-Package SafeWebCore.FraudDetection -Version 1.0.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="SafeWebCore.FraudDetection" Version="1.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="SafeWebCore.FraudDetection" Version="1.0.0" />
                    
Directory.Packages.props
<PackageReference Include="SafeWebCore.FraudDetection" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add SafeWebCore.FraudDetection --version 1.0.0
                    
#r "nuget: SafeWebCore.FraudDetection, 1.0.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package SafeWebCore.FraudDetection@1.0.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=SafeWebCore.FraudDetection&version=1.0.0
                    
Install as a Cake Addin
#tool nuget:?package=SafeWebCore.FraudDetection&version=1.0.0
                    
Install as a Cake Tool

SafeWebCore.FraudDetection

Optional fraud-detection module for SafeWebCore.

What it adds

  • Neutral geo-cultural consistency detection (recommended): detect strong inconsistencies between observed signals (IP country, timezone, browser language, device fonts) and your configured expected/primary region.
    • Works for any region: Western Europe+NA, Gulf/Arabic, Russia/CIS, Sub-Saharan Africa, East Asia, Latin America, etc.
    • Configure via GeoCulturalConsistencyOptions + EnableGeoCulturalConsistency.
  • Legacy Western impersonation mode (fully supported for backward compatibility): uses WesternDetectorOptions + EnableWesternImpersonation.
  • Pen-test and scanner detection (OWASP ZAP, Burp Suite, Tenable/Nessus, and configurable patterns).
  • Authorized pen-test bypass via configurable header and optional secret.
  • Runtime configuration fallback:
    • Primary: options pattern from appsettings.json with IOptionsMonitor reload.
    • Secondary: optional IFraudDetectionConfigurationStore (database-backed tenant overrides).

Geo-IP Resolution & Data Enrichment

You do not need a geo-IP service for SafeWebCore.FraudDetection to work.

The detectors mainly care about these fields on ClientFingerprintData:

  • ResolvedCountryCode (strongly preferred)
  • SystemTimezone (strongly preferred)
  • IpAddress (only used as a last-resort fallback)

Resolve the country and timezone yourself as early as possible (middleware, endpoint filter, or a dedicated service) and populate ResolvedCountryCode + SystemTimezone directly.

This is the preferred pattern because:

  • You control the provider, caching, and error handling
  • No hidden side effects inside the detector
  • The detector stays focused purely on analysis (SRP)

Example:

// In middleware or a request pipeline
var ip = context.Connection.RemoteIpAddress?.ToString();
var fingerprint = await BuildClientFingerprintAsync(context);

if (!string.IsNullOrWhiteSpace(ip) && geoIpService is not null)
{
    fingerprint = fingerprint.EnrichGeoIp(geoIpService);
}

// Later...
var report = fraudDetector.Analyze(fingerprint);

You can also call the extension method manually at any point:

var enriched = fingerprint.EnrichGeoIp(geoIpService);
var report = fraudDetector.Analyze(enriched);

Convenience fallback (optional)

If you register an IGeoIpService, the detectors will automatically use it as a fallback when:

  • IpAddress is present, and
  • ResolvedCountryCode or SystemTimezone is still null
builder.Services.AddSingleton<IGeoIpService, MyGeoIpService>();

Implementing IGeoIpService

public sealed class MyGeoIpService : IGeoIpService
{
    public string? GetCountryCode(string ipAddress)
    {
        // your lookup (MaxMind GeoIP2, IP2Location, internal API, etc.)
        return null;
    }

    public string? GetTimezone(string ipAddress)
    {
        return null;
    }
}

When you can skip geo-IP entirely

If your frontend already sends reliable country and timezone values (via Intl.DateTimeFormat, FingerprintJS, etc.), just map them into ResolvedCountryCode and SystemTimezone. No IGeoIpService registration is required at all.

Registration (Minimal API)

using SafeWebCore.FraudDetection.Abstractions;
using SafeWebCore.FraudDetection.Extensions;

builder.Services.AddSingleton<IFraudDetectionConfigurationStore, ExampleDatabaseFraudDetectionConfigurationStore>();
builder.Services.AddSafeWebCoreFraudDetection(builder.Configuration);

Programmatic registration is also supported:

builder.Services.AddSafeWebCoreFraudDetection(options =>
{
    options.EnableWesternImpersonation = true;
    options.EnablePenTestDetection = true;
    options.PenTestDetection.AuthorizationHeaderName = "X-PenTest-Authorized";
});

Event-driven notification hook (custom mail module)

By default, scanner authorization-check notifications are dispatched to all registered IPenTestAuthorizationNotificationConsumer implementations. The package registers a logging consumer out of the box.

To attach your own mail module, either register a consumer directly or register an injectable mail client.

Option A: custom consumer

using SafeWebCore.FraudDetection.Abstractions;
using SafeWebCore.FraudDetection.Extensions;
using SafeWebCore.FraudDetection.Models;

builder.Services.AddSafeWebCoreFraudDetection(builder.Configuration);
builder.Services.AddPenTestAuthorizationNotificationConsumer<PenTestMailNotificationConsumer>();

public sealed class PenTestMailNotificationConsumer(IMailService mailService) : IPenTestAuthorizationNotificationConsumer
{
    public void OnAuthorizationCheck(PenTestAuthorizationNotification notification)
    {
        ArgumentNullException.ThrowIfNull(notification);

        mailService.Send(
            notification.Recipients,
            notification.Subject,
            $"Potential scanner activity from {notification.IpAddress} on {notification.RequestPath}");
    }
}

Option B: injectable mail client abstraction

using SafeWebCore.FraudDetection.Abstractions;
using SafeWebCore.FraudDetection.Extensions;
using SafeWebCore.FraudDetection.Models;

builder.Services.AddSafeWebCoreFraudDetection(builder.Configuration);
builder.Services.AddPenTestAuthorizationNotificationMailClient<SmtpPenTestNotificationMailClient>();

public sealed class SmtpPenTestNotificationMailClient(ISmtpSender smtpSender) : IPenTestAuthorizationNotificationMailClient
{
    public void SendAuthorizationCheck(PenTestAuthorizationNotification notification)
    {
        ArgumentNullException.ThrowIfNull(notification);

        smtpSender.Send(
            notification.Recipients,
            notification.Subject,
            $"Potential scanner activity from {notification.IpAddress} on {notification.RequestPath}");
    }
}

If you need full control, you can still replace IPenTestAuthorizationNotificationSender directly.

Instead of the legacy "Western impersonation" naming, you can now protect any primary region:

builder.Services.AddSafeWebCoreFraudDetection(options =>
{
    options.EnableGeoCulturalConsistency = true;
    options.EnableWesternImpersonation = false; // optional: disable legacy path
    options.EnablePenTestDetection = true;

    var geo = options.GeoCulturalConsistency;

    // Example: protect a Gulf / Arabic-speaking primary audience
    geo.ExpectedCountries = ["AE", "SA", "QA", "KW", "BH", "OM"];
    geo.InconsistentTimezones = ["Europe/Moscow", "Asia/Shanghai", "America/New_York"];
    geo.InconsistentLanguageCodes = ["ru", "zh", "ja", "ko", "en", "es"];
    geo.KnownTravelCountries = ["TR", "EG", "JO", "LB", "MA"];
    geo.TravelTimezones = ["Europe/Istanbul", "Africa/Cairo"];

    geo.MediumSuspicionThreshold = 30;
    geo.HighSuspicionThreshold = 65;
    geo.HighInconsistencyThreshold = 85;
});

Example region profiles

// Russian / CIS focused
geo.ExpectedCountries = ["RU", "BY", "KZ", "KG", "AM", "AZ"];
geo.InconsistentTimezones = ["Asia/Shanghai", "America/New_York", "Europe/London"];
geo.InconsistentLanguageCodes = ["zh", "ja", "ko", "ar", "en", "es"];

// Sub-Saharan Africa focused (example)
geo.ExpectedCountries = ["ZA", "NG", "KE", "GH", "TZ", "UG"];
geo.InconsistentTimezones = ["Europe/Moscow", "Asia/Shanghai", "America/Sao_Paulo"];
geo.InconsistentLanguageCodes = ["ru", "zh", "ar", "hi"];

// East Asia focused (example)
geo.ExpectedCountries = ["JP", "KR", "TW", "SG", "HK"];
geo.InconsistentTimezones = ["Europe/Moscow", "America/New_York", "Asia/Kolkata"];
geo.InconsistentLanguageCodes = ["ru", "ar", "hi", "es"];

Configuration section (appsettings.json) — legacy Western example

{
  "SafeWebCore": {
    "FraudDetection": {
      "EnableWesternImpersonation": true,
      "EnableGeoCulturalConsistency": false,
      "EnablePenTestDetection": true,
      "WesternImpersonation": {
        "EnableTravelMode": true,
        "HighSuspicionThreshold": 65,
        "FakeWesternThreshold": 85
      },
      "PenTestDetection": {
        "AuthorizationHeaderName": "X-PenTest-Authorized",
        "AuthorizationHeaderSecret": "replace-with-secure-secret",
        "ScannerHeaders": [ "X-ZAP-Initiator", "X-ZAP-Scan-ID", "X-Burp" ],
        "ScannerUserAgentTokens": [ "owasp zap", "burp", "tenable", "nessus" ],
        "BurstWindow": "00:00:15",
        "BurstRequestThreshold": 30,
        "ScannerScoreThreshold": 60,
        "ScannerRecommendedAction": "BlockRequest",
        "SendAuthorizationCheckEmail": true,
        "AuthorizationCheckRecipients": [ "soc@contoso.com" ],
        "NotificationCooldown": "00:10:00",
        "AuthorizationCheckSubject": "Is this an authorized penetration test?"
      }
    }
  }
}

Database-backed configuration example

using Microsoft.Extensions.Primitives;
using SafeWebCore.FraudDetection.Abstractions;
using SafeWebCore.FraudDetection.Options;

public sealed class ExampleDatabaseFraudDetectionConfigurationStore : IFraudDetectionConfigurationStore
{
    public FraudDetectionOptions? GetOptions(string? tenantId)
    {
        // Query your DB by tenantId and map to FraudDetectionOptions.
        return null;
    }

    public IChangeToken GetReloadToken() => NullChangeToken.Singleton;
}

When the store returns null, the module falls back to options pattern values.

Public API

  • IFraudDetector.Analyze(ClientFingerprintData data)
  • FraudReport now contains both neutral and legacy properties:
    • IsRegionImpersonation / IsNotInExpectedRegion (recommended, neutral)
    • IsFakeWestern / IsNotInWesternCountry (legacy, still populated for compatibility)
    • Verdict can be RegionImpersonation (neutral) or the legacy FakeWestern alias.
  • FraudDetectionOptions:
    • EnableGeoCulturalConsistency + GeoCulturalConsistency (recommended)
    • EnableWesternImpersonation + WesternImpersonation (legacy, fully supported)
  • Scanner/pen-test properties remain unchanged.

Migration from Western-only to neutral detection

If you previously used only EnableWesternImpersonation + WesternImpersonation, your code continues to work without changes.

To move to the neutral model:

  1. Set EnableGeoCulturalConsistency = true.
  2. Configure GeoCulturalConsistency.ExpectedCountries, InconsistentTimezones, InconsistentLanguageCodes, etc.
  3. Optionally set EnableWesternImpersonation = false if you no longer need the legacy path.
  4. Consume the neutral report properties (IsRegionImpersonation, IsNotInExpectedRegion, Verdict == RegionImpersonation).

Reacting to Fraud Results (v1.6+)

You can observe every fraud analysis result without changing detection logic by registering one or more IFraudEventSink implementations.

using SafeWebCore.FraudDetection.Abstractions;

public sealed class FraudTelemetrySink : IFraudEventSink
{
    private readonly ILogger<FraudTelemetrySink> _logger;

    public FraudTelemetrySink(ILogger<FraudTelemetrySink> logger) => _logger = logger;

    public void OnFraudEvent(FraudEvent fraudEvent)
    {
        var r = fraudEvent.Report;
        _logger.LogInformation("Fraud verdict {Verdict} score={Score} action={Action} tenant={Tenant}",
            r.Verdict, r.SuspicionScore, r.RecommendedAction, r.TenantId);

        // Example: send metrics, call a webhook, trigger step-up auth, etc.
    }
}

// Registration (additive, opt-in)
builder.Services.AddFraudEventSink<FraudTelemetrySink>();

A default LoggingFraudEventSink is registered automatically (emits at Information level when enabled).

FraudEvent contains:

  • Report — the full FraudReport
  • Timestamp
  • Fingerprint — intentionally null by default for privacy (you can enrich inside your own sink if needed)

This mechanism is the foundation for custom action pipelines (log, notify, webhook, step-up, block, etc.).

Notes

  • Existing Western-only registration overloads remain supported for backward compatibility.
  • The module remains fully optional and only activates when registered.
  • IFraudEventSink is additive — it does not change IFraudDetector.Analyze(...) behavior or the shape of FraudReport.
Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • net10.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.

Version Downloads Last Updated
1.0.0 0 7/25/2026