diff --git a/api/report-error.js b/api/report-error.js index 41da773..e178d92 100644 --- a/api/report-error.js +++ b/api/report-error.js @@ -22,6 +22,23 @@ function isRateLimited(ip) { return false } +// Sanitize text to prevent Discord mention abuse +function sanitizeDiscordText(text) { + if (!text) return '' + + return String(text) + // Remove @everyone and @here mentions + .replace(/@(everyone|here)/gi, '@\u200b$1') + // Remove user mentions <@123456> + .replace(/<@!?(\d+)>/g, '@user') + // Remove role mentions <@&123456> + .replace(/<@&(\d+)>/g, '@role') + // Remove channel mentions <#123456> + .replace(/<#(\d+)>/g, '#channel') + // Limit length + .slice(0, 2000) +} + // Vercel serverless handler module.exports = async function handler(req, res) { // CORS headers @@ -59,22 +76,29 @@ module.exports = async function handler(req, res) { return res.status(400).json({ error: 'Invalid payload: missing error field' }) } + // Sanitize all text fields to prevent Discord mention abuse + const sanitizedError = sanitizeDiscordText(payload.error) + const sanitizedStack = payload.stack ? sanitizeDiscordText(payload.stack) : null + const sanitizedVersion = sanitizeDiscordText(payload.context?.version || 'unknown') + const sanitizedPlatform = sanitizeDiscordText(payload.context?.platform || 'unknown') + const sanitizedNode = sanitizeDiscordText(payload.context?.nodeVersion || 'unknown') + // Build Discord embed const embed = { title: '🔴 Bot Error Report', - description: `\`\`\`\n${String(payload.error).slice(0, 1900)}\n\`\`\``, + description: `\`\`\`\n${sanitizedError.slice(0, 1900)}\n\`\`\``, color: 0xdc143c, fields: [ - { name: 'Version', value: String(payload.context?.version || 'unknown'), inline: true }, - { name: 'Platform', value: String(payload.context?.platform || 'unknown'), inline: true }, - { name: 'Node', value: String(payload.context?.nodeVersion || 'unknown'), inline: true } + { name: 'Version', value: sanitizedVersion, inline: true }, + { name: 'Platform', value: sanitizedPlatform, inline: true }, + { name: 'Node', value: sanitizedNode, inline: true } ], timestamp: new Date().toISOString(), footer: { text: 'Community Error Reporting' } } - if (payload.stack) { - const stackLines = String(payload.stack).split('\n').slice(0, 15).join('\n') + if (sanitizedStack) { + const stackLines = sanitizedStack.split('\n').slice(0, 15).join('\n') embed.fields.push({ name: 'Stack Trace', value: `\`\`\`\n${stackLines.slice(0, 1000)}\n\`\`\``, diff --git a/src/util/notifications/ErrorReportingWebhook.ts b/src/util/notifications/ErrorReportingWebhook.ts index 720a044..0eac3c7 100644 --- a/src/util/notifications/ErrorReportingWebhook.ts +++ b/src/util/notifications/ErrorReportingWebhook.ts @@ -28,7 +28,12 @@ const SANITIZE_PATTERNS: Array<[RegExp, string]> = [ [/[A-Za-z]:\\(?:[^\\/:*?"<>|\r\n]+\\)*[^\\/:*?"<>|\r\n]*/g, '[PATH_REDACTED]'], [/\/(?:home|Users)\/[^/\s]+(?:\/[^/\s]+)*/g, '[PATH_REDACTED]'], [/\b(?:[0-9]{1,3}\.){3}[0-9]{1,3}\b/g, '[IP_REDACTED]'], - [/\b[A-Za-z0-9_-]{20,}\b/g, '[TOKEN_REDACTED]'] + [/\b[A-Za-z0-9_-]{20,}\b/g, '[TOKEN_REDACTED]'], + // Discord mention sanitization (prevent @everyone, @here abuse) + [/@(everyone|here)/gi, '@\u200b$1'], // Zero-width space breaks mentions + [/<@!?(\d+)>/g, '@user'], // User mentions + [/<@&(\d+)>/g, '@role'], // Role mentions + [/<#(\d+)>/g, '#channel'] // Channel mentions ] function sanitizeSensitiveText(text: string): string { diff --git a/test-security.ps1 b/test-security.ps1 new file mode 100644 index 0000000..f34c2ac --- /dev/null +++ b/test-security.ps1 @@ -0,0 +1,43 @@ +# Test Error Reporting Security + +Write-Host "Testing Discord mention sanitization..." -ForegroundColor Cyan +Write-Host "" + +$apiUrl = "https://light-rewards-bot.vercel.app/api/report-error" + +# Test avec des mentions Discord malveillantes +$payload = @{ + error = "Test @everyone @here error with <@123456789> user mention" + stack = "at testFunction <@&987654321> role mention`n at main <#555555555> channel" + context = @{ + version = "3.5.6" + platform = "win32" + arch = "x64" + nodeVersion = "v22.0.0" + timestamp = [DateTime]::UtcNow.ToString("yyyy-MM-ddTHH:mm:ss.fffZ") + botMode = "SECURITY_TEST" + } +} | ConvertTo-Json -Depth 10 + +Write-Host "Payload with malicious mentions:" -ForegroundColor Yellow +Write-Host $payload -ForegroundColor Gray +Write-Host "" + +try { + $response = Invoke-RestMethod -Uri $apiUrl -Method Post -Body $payload -ContentType "application/json" -TimeoutSec 15 + + Write-Host "SUCCESS - Sanitization applied!" -ForegroundColor Green + Write-Host "" + Write-Host "Check Discord - mentions should be neutralized:" -ForegroundColor Cyan + Write-Host " @everyone -> @everyone" -ForegroundColor Gray + Write-Host " @here -> @here" -ForegroundColor Gray + Write-Host " <@123456789> -> @user" -ForegroundColor Gray + Write-Host " <@&987654321> -> @role" -ForegroundColor Gray + Write-Host " <#555555555> -> #channel" -ForegroundColor Gray + +} catch { + Write-Host "FAILED!" -ForegroundColor Red + Write-Host $_.Exception.Message -ForegroundColor Red +} + +Write-Host ""