Move filter to separate config and add more scanning (#603)

* Create .gitattributes

Added a .gitattributes file that excludes the local customWordFilter.txt file from merged updates, allowing server operators to maintain their own word filter list and not have it overwritten.

* Update .gitignore

Added chatCensoredList to gitignore

* Update .gitignore

* Dynamic censor list file changes

Removed .gitattributes file, attempted to make chatCensoredList.txt into a dynamic file loaded at runtime instead.

* Added additional censorship coverage

Censorship now covers:
Level titles
Level descriptions
Reviews
Comments

* Delete chatCensoredList.txt

* Update .gitignore

Co-authored-by: Josh <josh@slendy.pw>

* Update filter verbiage

* Update ProjectLighthouse.Servers.GameServer/Controllers/CommentController.cs

Co-authored-by: Josh <josh@slendy.pw>

* Update ProjectLighthouse/Helpers/CensorHelper.cs

Co-authored-by: Josh <josh@slendy.pw>

* Add CensorConfiguration and add more filters

Co-authored-by: Josh <josh@slendy.pw>
This commit is contained in:
HomicidalChicken 2023-01-19 22:24:45 -05:00 committed by GitHub
parent 850cfcefb3
commit 326b9e5529
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 70 additions and 53 deletions

View file

@ -119,7 +119,9 @@ public class CommentController : ControllerBase
targetId = await this.database.UserIdFromUsername(username!);
}
bool success = await this.database.PostComment(token.UserId, targetId, type, comment.Message);
string filteredText = CensorHelper.FilterMessage(comment.Message);
bool success = await this.database.PostComment(token.UserId, targetId, type, filteredText);
if (success) return this.Ok();
return this.BadRequest();

View file

@ -116,13 +116,13 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.";
return this.Ok();
}
string scannedText = CensorHelper.ScanMessage(message);
string filteredText = CensorHelper.FilterMessage(message);
string username = await this.database.UsernameFromGameToken(token);
if (ServerConfiguration.Instance.LogChatFiltering)
Logger.Info($"{username}: {message} / {scannedText}", LogArea.Filter);
Logger.Info($"{username}: {message} / {filteredText}", LogArea.Filter);
return this.Ok(scannedText);
return this.Ok(filteredText);
}
}

View file

@ -107,12 +107,16 @@ public class PublishController : ControllerBase
return this.BadRequest();
}
slot.Description = CensorHelper.FilterMessage(slot.Description);
if (slot.Description.Length > 512)
{
Logger.Warn($"Rejecting level upload, description too long ({slot.Description.Length} characters)", LogArea.Publish);
return this.BadRequest();
}
slot.Name = CensorHelper.FilterMessage(slot.Name);
if (slot.Name.Length > 64)
{
Logger.Warn($"Rejecting level upload, title too long ({slot.Name.Length} characters)", LogArea.Publish);

View file

@ -94,6 +94,8 @@ public class ReviewController : ControllerBase
Review? newReview = await this.DeserializeBody<Review>();
if (newReview == null) return this.BadRequest();
newReview.Text = CensorHelper.FilterMessage(newReview.Text);
if (newReview.Text.Length > 512) return this.BadRequest();
Review? review = await this.database.Reviews.FirstOrDefaultAsync(r => r.SlotId == slotId && r.ReviewerId == token.UserId);

View file

@ -69,7 +69,9 @@ public class SlotPageController : ControllerBase
return this.Redirect("~/slot/" + id);
}
// Prevent potential xml injection and censor content
msg = SanitizationHelper.SanitizeString(msg);
msg = CensorHelper.FilterMessage(msg);
await this.database.PostComment(token.UserId, id, CommentType.Level, msg);
Logger.Success($"Posted comment from {token.UserId}: \"{msg}\" on user {id}", LogArea.Comments);

View file

@ -43,7 +43,9 @@ public class UserPageController : ControllerBase
return this.Redirect("~/user/" + id);
}
// Prevent potential xml injection and censor content
msg = SanitizationHelper.SanitizeString(msg);
msg = CensorHelper.FilterMessage(msg);
await this.database.PostComment(token.UserId, id, CommentType.Profile, msg);
Logger.Success($"Posted comment from {token.UserId}: \"{msg}\" on user {id}", LogArea.Comments);

View file

@ -15,7 +15,7 @@ public class FilterTestPage : BaseLayout
#if DEBUG
public IActionResult OnGet(string? text = null)
{
if (text != null) this.FilteredText = CensorHelper.ScanMessage(text);
if (text != null) this.FilteredText = CensorHelper.FilterMessage(text);
this.Text = text;
return this.Page();

View file

@ -29,9 +29,11 @@ public class SlotSettingsPage : BaseLayout
if (avatarHash != null) this.Slot.IconHash = avatarHash;
name = SanitizationHelper.SanitizeString(name);
name = CensorHelper.FilterMessage(name);
if (this.Slot.Name != name && name.Length <= 64) this.Slot.Name = name;
description = SanitizationHelper.SanitizeString(description);
description = CensorHelper.FilterMessage(description);
if (this.Slot.Description != description && description.Length <= 512) this.Slot.Description = description;
labels = LabelHelper.RemoveInvalidLabels(SanitizationHelper.SanitizeString(labels));

View file

@ -33,6 +33,7 @@ public class UserSettingsPage : BaseLayout
if (avatarHash != null) this.ProfileUser.IconHash = avatarHash;
biography = SanitizationHelper.SanitizeString(biography);
biography = CensorHelper.FilterMessage(biography);
if (this.ProfileUser.Biography != biography && biography.Length <= 512) this.ProfileUser.Biography = biography;

View file

@ -0,0 +1,37 @@
using System.Collections.Generic;
using LBPUnion.ProjectLighthouse.Types;
using YamlDotNet.Serialization;
namespace LBPUnion.ProjectLighthouse.Configuration;
public class CensorConfiguration : ConfigurationBase<CensorConfiguration>
{
public override int ConfigVersion { get; set; } = 1;
public override string ConfigName { get; set; } = "censor.yml";
public override bool NeedsConfiguration { get; set; } = false;
public FilterMode UserInputFilterMode { get; set; } = FilterMode.None;
public List<string> FilteredWordList { get; set; } = new()
{
"cunt",
"fag",
"faggot",
"horny",
"kook",
"kys",
"loli",
"nigga",
"nigger",
"penis",
"pussy",
"retard",
"retarded",
"vagina",
"vore",
"restitched",
"h4h",
};
public override ConfigurationBase<CensorConfiguration> Deserialize(IDeserializer deserializer, string text) => deserializer.Deserialize<CensorConfiguration>(text);
}

View file

@ -1,5 +1,4 @@
using LBPUnion.ProjectLighthouse.Configuration.ConfigurationCategories;
using LBPUnion.ProjectLighthouse.Types;
using YamlDotNet.Serialization;
namespace LBPUnion.ProjectLighthouse.Configuration;
@ -12,7 +11,7 @@ public class ServerConfiguration : ConfigurationBase<ServerConfiguration>
// This is so Lighthouse can properly identify outdated configurations and update them with newer settings accordingly.
// If you are modifying anything here, this value MUST be incremented.
// Thanks for listening~
public override int ConfigVersion { get; set; } = 16;
public override int ConfigVersion { get; set; } = 17;
public override string ConfigName { get; set; } = "lighthouse.yml";
public string WebsiteListenUrl { get; set; } = "http://localhost:10060";
@ -32,8 +31,6 @@ public class ServerConfiguration : ConfigurationBase<ServerConfiguration>
public bool CheckForUnsafeFiles { get; set; } = true;
public bool LogChatFiltering { get; set; } = false;
public FilterMode UserInputFilterMode { get; set; } = FilterMode.None;
public AuthenticationConfiguration Authentication { get; set; } = new();
public CaptchaConfiguration Captcha { get; set; } = new();
public DigestKeyConfiguration DigestKey { get; set; } = new();

View file

@ -18,15 +18,13 @@ public static class CensorHelper
"UwU", "OwO", "uwu", "owo", "o3o", ">.>", "*pounces on you*", "*boops*", "*baps*", ":P", "x3", "O_O", "xD", ":3", ";3", "^w^",
};
private static readonly string[] censorList = ResourceHelper.ReadManifestFile("chatCensoredList.txt").Replace("\r", "").Split("\n");
public static string ScanMessage(string message)
public static string FilterMessage(string message)
{
if (ServerConfiguration.Instance.UserInputFilterMode == FilterMode.None) return message;
if (CensorConfiguration.Instance.UserInputFilterMode == FilterMode.None) return message;
int profaneIndex = -1;
int profaneIndex;
foreach (string profanity in censorList)
foreach (string profanity in CensorConfiguration.Instance.FilteredWordList)
do
{
profaneIndex = message.ToLower().IndexOf(profanity, StringComparison.Ordinal);
@ -45,7 +43,7 @@ public static class CensorHelper
sb.Append(message.AsSpan(0, profanityIndex));
switch (ServerConfiguration.Instance.UserInputFilterMode)
switch (CensorConfiguration.Instance.UserInputFilterMode)
{
case FilterMode.Random:
for(int i = 0; i < profanityLength; i++)
@ -70,14 +68,7 @@ public static class CensorHelper
case FilterMode.Asterisks:
for(int i = 0; i < profanityLength; i++)
{
if (message[i] == ' ')
{
sb.Append(' ');
}
else
{
sb.Append('*');
}
sb.Append(message[i] == ' ' ? ' ' : '*');
}
break;
@ -89,6 +80,8 @@ public static class CensorHelper
}
break;
case FilterMode.None: break;
default: throw new ArgumentOutOfRangeException(nameof(message));
}
sb.Append(message.AsSpan(profanityIndex + profanityLength));
@ -103,14 +96,10 @@ public static class CensorHelper
string[] emailArr = email.Split('@');
string domainExt = Path.GetExtension(email);
string maskedEmail = string.Format("{0}****{1}@{2}****{3}{4}",
emailArr[0][0],
emailArr[0].Substring(emailArr[0].Length - 1),
emailArr[1][0],
emailArr[1]
// Hides everything except the first and last character of the username and domain, preserves the domain extension (.net, .com)
string maskedEmail = $"{emailArr[0][0]}****{emailArr[0][^1..]}@{emailArr[1][0]}****{emailArr[1]
.Substring(emailArr[1].Length - domainExt.Length - 1,
1),
domainExt);
1)}{domainExt}";
return maskedEmail;
}

View file

@ -46,10 +46,6 @@
<EmbeddedResource Include="gitUnpushed.txt">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</EmbeddedResource>
<None Remove="chatCensoredList.txt" />
<EmbeddedResource Include="chatCensoredList.txt">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>

View file

@ -1,17 +0,0 @@
cunt
fag
faggot
horny
kook
kys
loli
nigga
nigger
penis
pussy
retard
retarded
vagina
vore
restitched
h4h