Skip to content

Repository files navigation

ComputerysProfanityFilter

A .NET Standard 2.1 (C# version 9 for unity) profanity filter that censors known terms while recognizing common obfuscations such as character substitutions, punctuation, inserted whitespace, repeated letters, and leetspeak. The default term list is reasonable and covers swears, hate speech, and self-harm.

This library is currently used in STRAFTAT (as of the 1.4.9 update) to filter text chat, player names, lobby names, etc, albeit with a slightly cut down word list (swears removed).

Installation

NuGet

Install the package with the .NET CLI:

dotnet add package ComputerysProfanityFilter

Or add ComputerysProfanityFilter through the NuGet package manager in your IDE.

Unity Package Manager

In Unity, open Window > Package Manager, select +, then choose Add package from git URL..., then enter:

https://github.com/C0mputery/ComputerysProfanityFilter.git?path=/ComputerysProfanityFilter#upm-1.1.0

The #upm-1.1.0 suffix pins the package to version 1.1.0; change it to a newer upm-x.y.z tag to update. The UPM package requires Unity 2021.3 or newer.

Basic usage

Create one ProfanityList and reuse it when filtering messages. After construction, the instance can be used concurrently from multiple threads. Censor returns the original string unchanged when it finds no match.

usingComputerysProfanityFilter;ProfanityListprofanityFilter=newProfanityList();stringcensored=profanityFilter.Censor("Please don't be an a$$hole.");// censored: "Please don't be an #######."

Use the optional second argument to choose the replacement character:

stringcensored=profanityFilter.Censor("Please don't be an a$$hole.",'*');// censored: "Please don't be an *******."

Matching flexable, case-insensitive, it'll normalizes many substitutions (1337 speak, common enlgish rules), ignores selected punctuation within words, and collapses consecutive repeated letters. Entire matches, including their intervening punctuation, are replaced with the censor character.

Matching flexable, case-insensitive, it'll normalizes many substitutions (1337 speak, common enlgish rules). Punctuation and whitespace may be inserted within terms without preventing a match. Consecutive repeated letters are collapsed, so variants such as foooool can match fool. Matchs are as terms rather than arbitrary substrings, avoiding the Scunthorpe problem, cunt does not match inside Scunthorpe. Recognized punctuation can also act as a boundary between terms. Entire matches, including intervening whitespace and punctuation, are replaced with the censor character.

Detecting profanity

Use HasProfanity when you only need a yes/no result:

boolcontainsProfanity=filter.HasProfanity("That is a$$hole behavior.");

Use DetectAllProfanities when you need the locations and configured terms that matched:

varmatches=filter.DetectAllProfanities("Sh!t happens.");ProfanityMatchmatch=matches[0];// match.Start: 0// match.End: 4 (exclusive)// match.Term: "shit"

Start is the zero-based inclusive index in the original input. End is the zero-based exclusive index. A match's Term is the configured term, even when the input uses an expanded or obfuscated form. Matches are returned in source order. Allow terms are excluded from all three operations.

Custom term list

Pass your own terms and normalization rules to the constructor. By default, supplied terms are expanded into several common English forms, so use base terms where appropriate. Set expandTermForms: false to match only the supplied forms after normalization.

usingSystem.Collections.Generic;usingComputerysProfanityFilter;ProfanityListfilter=newProfanityList(terms:new[]{"spoiler","example phrase"},allowTerms:new[]{"allowed word"},expandTermForms:false,expectedCharacters:DefaultProfanityList.ExpectedCharacters,joinerCharacters:DefaultProfanityList.JoinerCharacters,boundaryCharacters:new[]{'.','-','_'},characterMap:newDictionary<char,string>{['4']="a",['@']="a"},sequenceMap:newDictionary<string,string>());stringcensored=filter.Censor("S-p-o-i-l-e-r ahead");// censored: "############# ahead"

Default configuration

DefaultProfanityList exposes the built-in settings as read-only collections for reuse in custom filters: Terms, AlwaysCensorTerms, AllowTerms, ExpectedCharacters, JoinerCharacters, BoundaryCharacters, CharacterMap, and SequenceMap. Allow terms are compared to the original matched text using lowercase-invariant normalization only.

Allocation behavior

Censor is zero allocate on the heap when no term matches, it'll return the same string instance that was passed in. Only when a match is found will it rent a buffer from ArrayPool<char>.Shared and returns a string that is the same length as the input. The rented buffers can be reused across calls after the pool is warm, although rent may allocate when the pool has no suitable buffer available (and there are some other caveats with ArrayPool, but) Under typical use conditions, it will only allocate a string of the same size input.

Comparisons

FeatureComputerysProfanityFilterProfanity.DetectorBogaNet.BadWordFilterDotnetBadWordDetectorCensoredmk.profanity
DistributionNuGet + UPM (see above)NuGetNuGetNuGetNuGetNuGet
Matching approachDeterministic normalized term matchingDictionary/regex scanning + Scunthorpe heuristicRegex corpus + optional normalization; optional simple matchingML.NET binary classifiersEscaped wildcard patterns compiled to regexTokenized exact list + optional Levenshtein
Case-insensitive matching◐ It's ML
Word-boundary◐ It's ML
Leetspeak normalization◐ It's ML
Inserted punctuation handling
Inserted spaces handling◐ It's ML
Repeated-letter handling◐ It's ML
Multi-word phrase support
Match positions
Custom list
Allow list
Configurable censor character
Arbitrary custom-language terms/rules✗ It's ML
Concurrency/thread safety✓ Explicitly documentedMutable, not documentedMutable, not documented✗ Uses PredictionEngineMutable, not documentedMutable, not documented
External dependenciesNoneNoneBogaNet.* infrastructure packagesMicrosoft.ML 1.7.0NoneNone
Trimming/AOT compatibility
Bundled profanity data✓ 145 terms (2,573 with variations)✓ 1,617 terms, the list is weird, read it, very much quantity over quality)✓ ~4,600 entries (claims 5,000+ regexes)✓ 4 ML Models✓ 2,915 words
Bundled languagesEnglishEnglish2540English
Fuzzy matching◐ It's ML
Confidence
Mutable
Patterns✓ Regex✓ Wildcards
URL/email moderation
Other symbol/content filters
Async rule loading
Target framework.NET Standard 2.1 and .NET 10.NET Standard 2.0.NET 8.NET 8.NET Standard 1.0.NET Standard 2.1
LicenseApache-2.0MITMITApache-2.0MITMIT

Benchmarks

BenchmarkDotNet 0.15.8 results with .NET 10.0.10 on an AMD Ryzen 7 5800X (Windows 11). The suite compares this library with Profanity.Detector, BogaNet.BadWordFilter, DotnetBadWordDetector, Censored, and mk.profanity, using equivalent vocabularies where possible. Inputs are exactly 100, 1,000, 10,000, or 100,000 characters, made by repeating a fixed 10-message chat corpus containing ordinary, profane, and obfuscated text.

Benchmark run date: August 21, 2026.

Censoring

Results are grouped by vocabulary so each comparison uses the same term list. Times are mean time per Censor operation; allocation is managed allocation per operation.

Expanded default vocabulary (2,573 terms)

Mean Time100 chars1,000 chars10,000 chars100,000 chars
ComputerysProfanityFilter2.468 us29.422 us302.077 us2.981 ms
Profanity.Detector109.157 us1.165 ms113.736 ms1.943 s
BogaNet.BadWordFilter151.999 us1.464 ms14.817 ms166.816 ms
DotnetBadWordDetector12.619 us118.059 us1.175 ms12.425 ms
Censored8.154 ms8.146 ms9.929 ms25.445 ms
mk.profanity51.646 us391.885 us3.838 ms39.767 ms
Managed Allocation100 chars1,000 chars10,000 chars100,000 chars
ComputerysProfanityFilter-2 KB20 KB195 KB
Profanity.Detector8 KB268 KB68.00 MB2.40 GB
BogaNet.BadWordFilter520 B29 KB2.04 MB205.42 MB
DotnetBadWordDetector3 KB24 KB222 KB2.15 MB
Censored14.09 MB14.17 MB14.29 MB15.52 MB
mk.profanity4 KB19 KB151 KB1.42 MB

Profanity.Detector raw vocabulary (1,617 terms)

Mean time100 chars1,000 chars10,000 chars100,000 chars
ComputerysProfanityFilter2.903 us33.929 us336.551 us3.429 ms
Profanity.Detector72.138 us751.393 us73.684 ms1.379 s
BogaNet.BadWordFilter1.188 ms9.846 ms99.245 ms1.041 s
DotnetBadWordDetector13.296 us117.788 us1.173 ms12.609 ms
Censored2.872 ms3.163 ms4.114 ms12.828 ms
mk.profanity41.420 us340.211 us3.452 ms34.629 ms
Managed Allocation100 chars1,000 chars10,000 chars100,000 chars
ComputerysProfanityFilter-2 KB20 KB195 KB
Profanity.Detector5 KB290 KB49.33 MB2.11 GB
BogaNet.BadWordFilter520 B49 KB2.98 MB294.18 MB
DotnetBadWordDetector3 KB24 KB222 KB2.15 MB
Censored5.65 MB5.70 MB5.92 MB8.13 MB
mk.profanity4 KB20 KB152 KB1.42 MB

CPU-stress input

This benchmark uses a repeated аss… input. CPU-stress numbers re-run August 21, 2026.

Mean Time100 chars1,000 chars10,000 chars100,000 chars
ComputerysProfanityFilter6.768 us73.650 us761.768 us7.955 ms
Managed Allocation100 chars1,000 chars10,000 chars100,000 chars
ComputerysProfanityFilter224 B2 KB20 KB195 KB

Construction

Construction measurements create one filter instance. This system doesn't have free construction, so be a little bit wary of that. Make sure that you cache your instance after it's created.

Engine and vocabularyMeanAllocated
ComputerysProfanityFilter / expanded default (2,573 terms)4.616 ms5.39 MB
Profanity.Detector / same expanded vocabulary (2,573 terms)5.490 us32.98 KB
ComputerysProfanityFilter / Profanity.Detector raw vocabulary (1,617 terms)1.364 ms2.90 MB
Profanity.Detector / raw default vocabulary (1,617 terms)4.399 us25.58 KB
BogaNet.BadWordFilter / English sources2.225 ms1.64 MB
DotnetBadWordDetector / default model29.899 ms2.91 MB
Censored / equivalent expanded vocabulary770.0 ns20.18 KB
mk.profanity / equivalent expanded vocabulary761.4 ns20.30 KB

License

Copyright 2026 Christopher Rohland. This project is licensed under the Apache License 2.0. See the LICENSE file for the full license text.

Releases

Packages

Contributors

Languages