Skip to content
Open
Show file tree
Hide file tree
Changes from 48 commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
9d2ad83
Created domain typosquatting checker
zbalkan Nov 30, 2025
109df58
Added customlist capability
zbalkan Dec 30, 2025
1ef77d6
Final version
zbalkan Dec 30, 2025
3fde001
Foxed performance issue
zbalkan Dec 30, 2025
c0eb10b
Added path check
zbalkan Dec 30, 2025
216c9ac
Added exception handling for file parsing
zbalkan Dec 30, 2025
2349023
Disposed the threadlocal normalizer
zbalkan Dec 30, 2025
d345ce2
Memory management for detector
zbalkan Dec 30, 2025
9a9fc59
Marked detector as volatile
zbalkan Dec 30, 2025
62c97b4
Reused same httpclient
zbalkan Dec 30, 2025
ac3a4b9
Fixed async query
zbalkan Dec 30, 2025
d82deb5
Fixed nullability attributes
zbalkan Dec 30, 2025
381049f
Minor fix for null bloomfilter
zbalkan Dec 31, 2025
55be360
Final touches
zbalkan Dec 31, 2025
43e2701
Fixed sync-in-async issue
zbalkan Jan 1, 2026
5ef1b49
Fixed hash-check bug
zbalkan Jan 1, 2026
3be8c44
Fixed timer issue
zbalkan Jan 1, 2026
4e81914
Prioritized user provided domain list
zbalkan Jan 1, 2026
16ee898
Added "m" as a common subdomain name to cleanup
zbalkan Jan 1, 2026
a142f23
Optimized fuzzy matching
zbalkan Jan 1, 2026
1ef772e
Minor performance optimizations on hot path
zbalkan Jan 1, 2026
ccc064e
Added README
zbalkan Jan 1, 2026
038d255
Updated app description
zbalkan Jan 1, 2026
4340beb
Fixed regions
zbalkan Jan 1, 2026
c27794e
Improved null handlng
zbalkan Jan 1, 2026
f38c308
Simplified suspicious check
zbalkan Jan 1, 2026
ae1d7eb
Simplified Reason enum
zbalkan Jan 1, 2026
ea267e0
Added empty or corrupted hash file edge case handling
zbalkan Jan 1, 2026
cf26711
Guard clause for httpClient leak edge case
zbalkan Jan 1, 2026
9cc367a
Added default severity.
zbalkan Jan 1, 2026
d464196
Updated description
zbalkan Jan 1, 2026
8fb46f3
Made httpclient non-static
zbalkan Jan 1, 2026
6b724e3
Used regular for loop for small instances to minimize overload
zbalkan Jan 1, 2026
94aed97
Removed unused httpclient
zbalkan Jan 1, 2026
8375608
Solving race condition in Parallel.Foreach
zbalkan Jan 1, 2026
c57e475
Fixrd concurrency issue in swapping detector
zbalkan Jan 1, 2026
b8fbcf9
Volarile _change
zbalkan Jan 1, 2026
f4e5968
Concurrency issues, again
zbalkan Jan 1, 2026
c111cd1
Fixed regex
zbalkan Jan 1, 2026
79cf25b
Added null check
zbalkan Jan 1, 2026
2af0ae5
Shared lock issue fixed
zbalkan Jan 1, 2026
e05fbc3
Normalizatipn function fixed
zbalkan Jan 1, 2026
5b7695e
Simplification
zbalkan Jan 1, 2026
ce40194
Optimizations
zbalkan Jan 1, 2026
576be77
Used Lazy static HTTP client issues
zbalkan Jan 1, 2026
e82225b
Fixed path issues
zbalkan Jan 1, 2026
2369713
Concurrency is a headache
zbalkan Jan 1, 2026
a22f599
Used explicit type instead of var everywhere
zbalkan Jan 1, 2026
b934b24
Fixed regex issue
zbalkan Jan 2, 2026
c4502fc
Formatting
zbalkan Jan 2, 2026
5ac62ff
Refactor
zbalkan Jan 2, 2026
0ceae8f
Created statepool for allocation issues
zbalkan Jan 2, 2026
24a45a8
Added second-level sharding (length + prefix2), with a prefix1 fallba…
zbalkan Jan 2, 2026
3d2bab5
Optimized score
zbalkan Jan 2, 2026
22f012c
Null check
zbalkan Jan 2, 2026
67fe1b6
Used DomainCache for domain name normalization optimization
zbalkan Jan 2, 2026
b37f08a
Optimize Bloom filter shards
zbalkan Jan 2, 2026
78ee5a9
Used bounded statepool to prevent memory allocation issues
zbalkan Jan 2, 2026
7cf90cf
Rolled back accidental changes
zbalkan Jan 2, 2026
95fb88c
Added lock check in Clear
zbalkan Jan 2, 2026
2866ee8
Application config guards added
zbalkan Jan 2, 2026
0f26f9d
Fixed typo
zbalkan Jan 5, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
484 changes: 484 additions & 0 deletions Apps/TyposquattingDetector/App.cs

Large diffs are not rendered by default.

107 changes: 107 additions & 0 deletions Apps/TyposquattingDetector/Config.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
/*
Technitium DNS Server
Copyright (C) 2025 Shreyas Zare ([email protected])
Copyright (C) 2025 Zafer Balkan ([email protected])

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

using System.ComponentModel.DataAnnotations;
using System.IO;
using System.Text.Json.Serialization;
using System.Text.RegularExpressions;

namespace TyposquattingDetector
{
public sealed partial class App
{
private class Config
{
[JsonPropertyName("addExtendedDnsError")]
public bool AddExtendedDnsError { get; set; } = true;

[JsonPropertyName("allowTxtBlockingReport")]
public bool AllowTxtBlockingReport { get; set; } = true;

[JsonPropertyName("disableTlsValidation")]
public bool DisableTlsValidation { get; set; } = false;

[JsonPropertyName("enable")]
public bool Enable { get; set; } = true;

[JsonPropertyName("fuzzyMatchThreshold")]
[Range(75, 90, ErrorMessage = "fuzzyMatchThreshold must be between 75 and 90.")]
[Required(ErrorMessage = "fuzzyMatchThreshold is a required configuration property. The lower threshold means more false positives.")]
public int FuzzyMatchThreshold { get; set; } = 75;

[JsonPropertyName("customList")]
[RegularExpression("^(?:[a-zA-Z]:\\\\(?:[^\\\\\\/:*?\"<>|\\r\\n]+\\\\)*[^\\\\\\/:*?\"<>|\\r\\n]*|(?:\\/[^\\/\\0]+)+\\/?)$", ErrorMessage = "customList must be a valid file path with one domain per line.")]
Copy link

Copilot AI Jan 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The file path regex validation pattern attempts to validate both Windows and Unix paths but has issues. For Windows, it doesn't handle UNC paths or paths with forward slashes. For Unix, the pattern would match invalid paths. Consider using built-in Path validation methods instead of regex, or split validation by platform.

Copilot uses AI. Check for mistakes.
Copy link

Copilot AI Jan 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The regex pattern on line 49 attempts to validate file paths but has incorrect escaping. The pattern uses quadruple backslashes (\\) throughout, which will match literal double backslashes in the path string. For a C# string literal inside a RegularExpression attribute, only double backslashes (\) are needed to represent a single backslash in the regex pattern. This will cause valid Windows paths like "C:\path\file.txt" to be rejected.

Suggested change
[RegularExpression("^(?:[a-zA-Z]:\\\\(?:[^\\\\\\/:*?\"<>|\\r\\n]+\\\\)*[^\\\\\\/:*?\"<>|\\r\\n]*|(?:\\/[^\\/\\0]+)+\\/?)$", ErrorMessage = "customList must be a valid file path with one domain per line.")]
[RegularExpression(@"^(?:[a-zA-Z]:\\(?:[^\\/:*?""<>|\r\n]+\\)*[^\\/:*?""<>|\r\n]*|(?:/[^/\0]+)+/?)$", ErrorMessage = "customList must be a valid file path with one domain per line.")]

Copilot uses AI. Check for mistakes.
[CustomValidation(typeof(FileContentValidator), nameof(FileContentValidator.ValidateDomainFile))]
public string? Path { get; set; }

[JsonPropertyName("updateInterval")]
[Required(ErrorMessage = "updateInterval is a required configuration property.")]
[RegularExpression(@"^\d+[mhdw]$", ErrorMessage = "Invalid interval format. Use a number followed by 'm', 'h', or 'd' (e.g., '90m', '2h', '7d').", MatchTimeoutInMilliseconds = 3000)]
public string UpdateInterval { get; set; } = "30d";
}

public partial class FileContentValidator
{
// Optimized Regex: Compiled for performance during "Happy Path" scans
private static readonly Regex DomainRegex = FilePathPattern();

public static ValidationResult? ValidateDomainFile(string? path, ValidationContext context)
{
// 1. If path is null/empty, we assume validation is not required here
// (Use [Required] on the property if you want to force a path to be provided)
if (string.IsNullOrWhiteSpace(path)) return ValidationResult.Success;

// 2. Existence Check
if (!File.Exists(path))
return new ValidationResult($"File not found: {path}");

try
{
// 3. Stream through lines
// If the file is empty, this loop is simply skipped
foreach (string line in File.ReadLines(path))
{
string trimmedLine = line.Trim();

// Skip truly empty lines (whitespace only)
if (string.IsNullOrEmpty(trimmedLine)) continue;

// 4. Fail-Fast Logic
// If any content exists, it MUST follow the domain rules
if (trimmedLine.Contains('*') || !DomainRegex.IsMatch(trimmedLine))
{
return new ValidationResult($"Invalid content: '{trimmedLine}'. Wildcards are not allowed.");
}
}
}
catch (IOException ex)
{
return new ValidationResult($"File access error: {ex.Message}");
}

// 5. Success Path
// Reached if the file was empty OR all lines passed validation
return ValidationResult.Success;
}

[GeneratedRegex(@"^(?!-)[A-Za-z0-9-]+([\-\.]{1}[a-z0-9]+)*\.[A-Za-z]{2,63}$", RegexOptions.IgnoreCase | RegexOptions.Compiled, "en-US")]
private static partial Regex FilePathPattern();
}
}
}
51 changes: 51 additions & 0 deletions Apps/TyposquattingDetector/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# Typosquatting Detector for Technitium DNS Server

A DNS security plugin that detects and blocks look-alike domains associated with phishing and brand impersonation. The plugin evaluates similarity between queried domains and a high-reputation corpus and blocks near-miss variants before resolution.

## Detection model

The plugin builds a trusted corpus from the Majestic Million list plus an optional custom list. For each query it:

1. Normalizes to the registrable domain using Public Suffix rules.
2. Performs an O(1) Bloom filter check for known legitimate domains.
3. Runs fuzzy similarity matching against length-adjacent candidates for unknown domains.

Queries above the configured similarity threshold are classified as probable typosquats and blocked.

## Enforcement behavior

Suspicious domains receive an authoritative NXDOMAIN with SOA. Optional Extended DNS Error metadata and optional TXT blocking reports expose structured blocking details for logs and SIEM ingestion. Clean domains are not modified and resolve normally.

## Configuration

Example configuration:

```json
{
"enable": true,
"fuzzyMatchThreshold": 75,
"customList": "/path/to/custom-domains.txt",
"disableTlsValidation": false,
"updateInterval": "30d",
"allowTxtBlockingReport": true,
"addExtendedDnsError": true
}
```

Key options

* fuzzyMatchThreshold (75–90): main sensitivity control. Lower values detect more variants but increase false positives.
* customList: one domain per line; add organization and brand domains you want treated as trusted.
* updateInterval: controls when the Majestic list is reprocessed; rebuilds are skipped when the file hash is unchanged.
* allowTxtBlockingReport / addExtendedDnsError: control operator visibility of blocking decisions.
* disableTlsValidation: test or lab use only.

## Deployment and risk considerations

Start with a conservative threshold (85–90) in production and observe blocks before lowering. False positives are most likely for domains visually similar to major brands but legitimate or newly emerging services. Mitigations include raising the threshold or adding the domain to the custom list.

This plugin is intended for recursive resolvers operated by security teams where DNS blocking is an accepted control point. Communicate expected behavior to users and support staff to avoid confusion when NXDOMAIN is enforcement rather than resolution failure.

## Acknowledgements

Uses [Majestic Million dataset](https://majestic.com/reports/majestic-million), [Nager Public Suffix parser](https://github.com/nager/Nager.PublicSuffix), [BloomFilter.NetCore](https://github.com/vla/BloomFilter.NetCore) and [FuzzySharp](https://github.com/JakeBayer/FuzzySharp) libraries, and the Technitium DNS Server app framework.
Loading