Add custom filter for DWS and Tanishq Dubey trademarks

- Create custom_filter.go with DWS/Tanishq Dubey term detection
- Block variations including:
  - DWS, Dubey Web Services, DWS Engineering LLC
  - Tanishq Dubey, tdubey
  - Leet speak variations (dub3y, t4nishq, dw5, etc.)
  - Combined terms (dubeydns, dws-ddns, etc.)
- Update frontend to show 'reserved' message for blocked terms
- Filters are case-insensitive and handle separators

Protects brand identity and personal name from being used
in user subdomains
This commit is contained in:
2026-02-01 17:17:52 -05:00
parent f96aaf1e96
commit c5279243c0
3 changed files with 169 additions and 1 deletions

View File

@@ -0,0 +1,147 @@
package handlers
import (
"strings"
"unicode"
)
// CustomFilter contains terms related to DWS and Tanishq Dubey that should be blocked
type CustomFilter struct {
blockedTerms []string
}
// NewCustomFilter creates a new custom filter with DWS and personal name variations
func NewCustomFilter() *CustomFilter {
return &CustomFilter{
blockedTerms: []string{
// DWS variations
"dws",
"dubey",
"dubeyweb",
"dubeywebservices",
"dubeyweb services",
"dubey-engineering",
"dubeyengineering",
"dwsengineering",
"dws-engineering",
"dws-engineering-llc",
"dwsengineeringllc",
"dwsllc",
"dws-llc",
"webservices",
"web-services",
"dubeycorp",
"dubey-corp",
"dubeyinc",
"dubey-inc",
// Tanishq Dubey variations
"tanishq",
"tanishqdubey",
"tanishq-dubey",
"tdubey",
"t-dubey",
"tanishq-d",
"tdub",
"tanish",
"dubey-t",
"dubeytanishq",
"dubey-tanishq",
// Leet speak variations
"dub3y",
"dub3yweb",
"t4nishq",
"t4n1shq",
"tan1shq",
"dub3y3ng1n33r1ng",
"dw5",
"dw$",
"dub3yc0rp",
// Common combinations
"dubeydns",
"dubey-ddns",
"dwsdns",
"dws-ddns",
"tanishqdns",
"tanishq-ddns",
"tdubeydns",
"tdubey-ddns",
},
}
}
// IsBlocked checks if the given text contains any blocked terms
func (cf *CustomFilter) IsBlocked(text string) bool {
normalized := cf.normalize(text)
for _, term := range cf.blockedTerms {
if strings.Contains(normalized, term) {
return true
}
}
return false
}
// normalize prepares text for comparison by:
// - Converting to lowercase
// - Removing common separators
// - Converting leet speak to normal text
func (cf *CustomFilter) normalize(text string) string {
// Convert to lowercase
text = strings.ToLower(text)
// Remove separators
replacer := strings.NewReplacer(
"-", "",
"_", "",
".", "",
" ", "",
)
text = replacer.Replace(text)
// Convert leet speak
text = cf.leetToNormal(text)
return text
}
// leetToNormal converts common leet speak characters to normal letters
func (cf *CustomFilter) leetToNormal(text string) string {
replacements := map[rune]rune{
'0': 'o',
'1': 'i',
'3': 'e',
'4': 'a',
'5': 's',
'6': 'g',
'7': 't',
'8': 'b',
'9': 'g',
'@': 'a',
'$': 's',
'!': 'i',
'|': 'i',
'+': 't',
}
result := make([]rune, len(text))
for i, char := range text {
if replacement, ok := replacements[char]; ok {
result[i] = replacement
} else {
result[i] = unicode.ToLower(char)
}
}
return string(result)
}
// GetBlockedTerms returns a copy of the blocked terms list (for testing/debugging)
func (cf *CustomFilter) GetBlockedTerms() []string {
terms := make([]string, len(cf.blockedTerms))
copy(terms, cf.blockedTerms)
return terms
}

View File

@@ -16,7 +16,10 @@ import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
var profanityDetector = goaway.NewProfanityDetector() var (
profanityDetector = goaway.NewProfanityDetector()
customFilter = NewCustomFilter()
)
type WebHandler struct { type WebHandler struct {
db *database.DB db *database.DB
@@ -57,6 +60,11 @@ func (h *WebHandler) ClaimSpace(c *gin.Context) {
return return
} }
if customFilter.IsBlocked(subdomain) {
c.JSON(http.StatusBadRequest, gin.H{"error": "Subdomain is reserved"})
return
}
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel() defer cancel()
@@ -103,6 +111,15 @@ func (h *WebHandler) CheckSubdomain(c *gin.Context) {
return return
} }
if customFilter.IsBlocked(subdomain) {
c.JSON(http.StatusOK, gin.H{
"available": false,
"subdomain": subdomain,
"reason": "reserved",
})
return
}
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel() defer cancel()

View File

@@ -37,6 +37,10 @@ document.addEventListener('DOMContentLoaded', function() {
availabilityStatus.textContent = '✗ Contains inappropriate content'; availabilityStatus.textContent = '✗ Contains inappropriate content';
availabilityStatus.className = 'status taken'; availabilityStatus.className = 'status taken';
claimBtn.disabled = true; claimBtn.disabled = true;
} else if (data.reason === 'reserved') {
availabilityStatus.textContent = '✗ This subdomain is reserved';
availabilityStatus.className = 'status taken';
claimBtn.disabled = true;
} else { } else {
availabilityStatus.textContent = '✗ Already taken'; availabilityStatus.textContent = '✗ Already taken';
availabilityStatus.className = 'status taken'; availabilityStatus.className = 'status taken';