Skip to content

Commit

Permalink
feat: added abuse mitigation check, added denylist for capacity/overl…
Browse files Browse the repository at this point in the history
…oaded errors
  • Loading branch information
titanism committed Feb 26, 2025
1 parent b3e433c commit 10736d4
Show file tree
Hide file tree
Showing 3 changed files with 76 additions and 1 deletion.
2 changes: 1 addition & 1 deletion ecosystem-mx.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
"deploy": {
"production": {
"user": "deploy",
"host": ["121.127.44.56","104.248.224.170"],
"host": ["121.127.44.56","138.197.213.185","104.248.224.170"],
"ref": "origin/master",
"repo": "[email protected]:forwardemail/forwardemail.net.git",
"path": "/var/www/production",
Expand Down
2 changes: 2 additions & 0 deletions helpers/get-forwarding-addresses.js
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,7 @@ async function getForwardingAddresses(
{ responseCode: 421 }
);

/*
//
// we also need to maintain a counter for the # of unique domains per email on free plan being used
// (e.g. in an attempt to mitigate spam/fraud and shadowban/silent ban users)
Expand Down Expand Up @@ -342,6 +343,7 @@ async function getForwardingAddresses(
.catch((err) => logger.fatal(err));
}
}
*/

//
// if the domain on free plan and was expired or newly created in the background
Expand Down
73 changes: 73 additions & 0 deletions helpers/on-data-mx.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ const pMap = require('p-map');
const pMapSeries = require('p-map-series');
const parseErr = require('parse-err');
const revHash = require('rev-hash');
const safeStringify = require('fast-safe-stringify');
const status = require('statuses');
const { Headers } = require('mailsplit');
const { Iconv } = require('iconv');
Expand Down Expand Up @@ -1200,6 +1201,51 @@ async function forward(recipient, headers, session, body) {
try {
let info;
try {
// check for abuse (e.g. massive amount of domains forwarding to single email addresses)
const key = `abuse_check:${revHash(recipient.to[0].toLowerCase())}`;
const cache = await this.client.get(key);
let json;
if (cache) {
try {
json = JSON.parse(cache);
if (
typeof json !== 'object' ||
typeof json.domains !== 'object' ||
!Array.isArray(json.domains) ||
typeof json.sent !== 'boolean'
)
throw new TypeError('JSON invalid');
} catch (err) {
logger.fatal(err);
json = null;
}
}

if (!json) json = { domains: [], sent: false };
const rootDomain = parseRootDomain(
parseHostFromDomainOrAddress(recipient.recipient)
);
if (!json.domains.includes(rootDomain)) json.domains.push(rootDomain);
json.email = recipient.to[0].toLowerCase();
// rudimentary email alert to admins if we detect the count was >= 20
if (!json.sent && json.domains.length >= 20) {
json.sent = true;

// log fatal error email alert to admins
const err = new TypeError(
`${recipient.to[0].toLowerCase()} forwarded to from ${
json.domains.length
} domains`
);
err.isCodeBug = true;
err.domains = json.domains;
err.recipient = recipient;
err.session = session;
logger.fatal(err);
}

await this.client.set(key, safeStringify(json), 'PX', ms('30d'));

info = await sendEmail({
session,
cache: this.cache,
Expand Down Expand Up @@ -1227,6 +1273,33 @@ async function forward(recipient, headers, session, body) {
//
err.bounceInfo = getBounceInfo(err);

//
// if user mailbox is full or if they are receiving mail too quickly
// then we should back off from retrying an denylist the recipient for 1 hour
//
if (
isEmail(recipient.to[0]) &&
((err.bounceInfo.action === 'reject' &&
err.bounceInfo.category === 'capacity' &&
err.bounceInfo.message &&
err.bounceInfo.message.toLowerCase() === 'mailbox is full') ||
(err.bounceInfo.action === 'defer' &&
err.bounceInfo.category === 'recipient' &&
err.bounceInfo.message &&
err.bounceInfo.message.toLowerCase() === 'recipient overloaded'))
) {
this.client
.set(
`denylist:${recipient.to[0].toLowerCase()}`,
true,
'PX',
ms('1h')
)
.then()
.catch((err) => logger.fatal(err));
throw err;
}

if (
!session.rewriteFriendlyFrom &&
session.dmarc?.status?.result === 'pass' &&
Expand Down

0 comments on commit 10736d4

Please sign in to comment.