spamscanner 7.0.8 → 7.0.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/arf.cjs +1 -1
- package/dist/cjs/arf.cjs.map +2 -2
- package/dist/cjs/index.cjs +41 -21
- package/dist/cjs/index.cjs.map +3 -3
- package/dist/esm/arf.js +1 -1
- package/dist/esm/arf.js.map +2 -2
- package/dist/esm/cli.js +38 -18
- package/dist/esm/cli.js.map +3 -3
- package/dist/esm/index.js +38 -18
- package/dist/esm/index.js.map +3 -3
- package/dist/types/parse.d.ts +11 -0
- package/dist/types/tokenizer.d.ts +3 -2
- package/package.json +1 -1
package/dist/esm/index.js
CHANGED
|
@@ -2102,11 +2102,12 @@ function gatewayTarget(url) {
|
|
|
2102
2102
|
}
|
|
2103
2103
|
if (host === "urldefense.com" && url.pathname.startsWith("/v3/__")) {
|
|
2104
2104
|
const rest = url.href.slice(url.href.indexOf("/v3/__") + 6);
|
|
2105
|
-
const end = rest.
|
|
2105
|
+
const end = rest.lastIndexOf("__;");
|
|
2106
2106
|
return end === -1 ? null : rest.slice(0, end);
|
|
2107
2107
|
}
|
|
2108
2108
|
return null;
|
|
2109
2109
|
}
|
|
2110
|
+
var MAX_GATEWAYS = 16;
|
|
2110
2111
|
function unwrapLink(address2) {
|
|
2111
2112
|
let url;
|
|
2112
2113
|
try {
|
|
@@ -2114,7 +2115,7 @@ function unwrapLink(address2) {
|
|
|
2114
2115
|
} catch {
|
|
2115
2116
|
return null;
|
|
2116
2117
|
}
|
|
2117
|
-
for (let depth = 0; depth <
|
|
2118
|
+
for (let depth = 0; depth < MAX_GATEWAYS; depth++) {
|
|
2118
2119
|
const target = gatewayTarget(url);
|
|
2119
2120
|
if (!target) {
|
|
2120
2121
|
break;
|
|
@@ -3432,8 +3433,13 @@ function oleHasMacros(buffer) {
|
|
|
3432
3433
|
return false;
|
|
3433
3434
|
}
|
|
3434
3435
|
var PDF_ACTIVE = linear(String.raw`\/(?:JavaScript|JS|Launch)\b`);
|
|
3435
|
-
var
|
|
3436
|
-
|
|
3436
|
+
var escaped = (character) => {
|
|
3437
|
+
const hex = character.codePointAt(0).toString(16);
|
|
3438
|
+
return `(?:${character}|#${hex[0]}[${hex[1].toLowerCase()}${hex[1].toUpperCase()}])`;
|
|
3439
|
+
};
|
|
3440
|
+
var WORD_ESCAPE = String.raw`#(?:3\d|4[1-9a-fA-F]|5[\daA]|5[fF]|6[1-9a-fA-F]|7[\daA])`;
|
|
3441
|
+
var PDF_ACTIVE_ESCAPED = new RegExp(`/(?:${["JavaScript", "JS", "Launch"].map((name) => [...name].map((character) => escaped(character)).join("")).join("|")})(?![\\w]|${WORD_ESCAPE})`);
|
|
3442
|
+
var pdfActive = (text) => PDF_ACTIVE.test(text) || PDF_ACTIVE_ESCAPED.test(text);
|
|
3437
3443
|
function inspectAttachments(attachments = []) {
|
|
3438
3444
|
const findings = [];
|
|
3439
3445
|
for (const attachment of attachments) {
|
|
@@ -3491,7 +3497,7 @@ function inspectAttachments(attachments = []) {
|
|
|
3491
3497
|
add("macro", `Attachment "${name}" is an Office document with a VBA macro project`, { extension });
|
|
3492
3498
|
}
|
|
3493
3499
|
} else if (detected?.type === "pdf" || extension === "pdf") {
|
|
3494
|
-
if (
|
|
3500
|
+
if (pdfActive(content.toString("latin1"))) {
|
|
3495
3501
|
add("pdf_active", `PDF "${name}" contains JavaScript or a launch action`, { extension: "pdf" });
|
|
3496
3502
|
}
|
|
3497
3503
|
} else if (detected?.type === "rtf" && /\\object\b|\\objdata\b|\\objupdate\b/.test(content.toString("latin1"))) {
|
|
@@ -3539,7 +3545,7 @@ function boundAddressField(line, room) {
|
|
|
3539
3545
|
const limit = Math.min(line.length, MAX_ADDRESS_FIELD, room);
|
|
3540
3546
|
let end = start;
|
|
3541
3547
|
let expecting = "";
|
|
3542
|
-
let
|
|
3548
|
+
let escaped2 = false;
|
|
3543
3549
|
let separators = 0;
|
|
3544
3550
|
let groups = 0;
|
|
3545
3551
|
let index = start;
|
|
@@ -3548,8 +3554,8 @@ function boundAddressField(line, room) {
|
|
|
3548
3554
|
if ((chr === "," || chr === ";") && ++separators > MAX_ADDRESS_SEPARATORS) {
|
|
3549
3555
|
break;
|
|
3550
3556
|
}
|
|
3551
|
-
if (
|
|
3552
|
-
|
|
3557
|
+
if (escaped2) {
|
|
3558
|
+
escaped2 = false;
|
|
3553
3559
|
} else if (expecting !== "" && chr === expecting) {
|
|
3554
3560
|
expecting = "";
|
|
3555
3561
|
end = chr === ";" ? index + 1 : end;
|
|
@@ -3557,7 +3563,7 @@ function boundAddressField(line, room) {
|
|
|
3557
3563
|
expecting = ADDRESS_OPERATORS.get(chr);
|
|
3558
3564
|
end = chr === "," || chr === ";" ? index : end;
|
|
3559
3565
|
} else if (expecting === '"' && chr === "\\") {
|
|
3560
|
-
|
|
3566
|
+
escaped2 = true;
|
|
3561
3567
|
}
|
|
3562
3568
|
if (chr === ":" && expecting === ";" && ++groups > MAX_ADDRESS_GROUPS) {
|
|
3563
3569
|
break;
|
|
@@ -3700,9 +3706,14 @@ var SIGNATURE_FIELDS = /* @__PURE__ */ new Set(["dkim-signature", "arc-message-s
|
|
|
3700
3706
|
var AUTH_FIELDS = /* @__PURE__ */ new Set([...KEPT_FIELDS, "arc-seal", "arc-message-signature"]);
|
|
3701
3707
|
var MAX_SIGNATURES = 10;
|
|
3702
3708
|
var MAX_DKIM_SIGNATURES = 8;
|
|
3709
|
+
function isAttachedMessage(attachment) {
|
|
3710
|
+
const type = String(attachment.contentType).toLowerCase();
|
|
3711
|
+
return type === "message/rfc822" || type === "message/global" || /\.(?:eml|u8msg)$/i.test(String(attachment.filename));
|
|
3712
|
+
}
|
|
3703
3713
|
var MAX_NESTED_DEPTH = 3;
|
|
3704
3714
|
var MAX_ATTACHMENTS = 100;
|
|
3705
3715
|
var MAX_NESTED_BYTES = 25 * 1024 * 1024;
|
|
3716
|
+
var MAX_ATTACHED_MESSAGES = 100;
|
|
3706
3717
|
var PARSE_TIMEOUT = 6e4;
|
|
3707
3718
|
async function simpleParse(raw, { timeout = PARSE_TIMEOUT, parser = simpleParser } = {}) {
|
|
3708
3719
|
const input = Readable.from([finishable(unfoldHeaders(raw))], { objectMode: false, autoDestroy: false });
|
|
@@ -3760,18 +3771,18 @@ function headerValue2(line) {
|
|
|
3760
3771
|
let key = null;
|
|
3761
3772
|
let inKey = false;
|
|
3762
3773
|
let quoted = false;
|
|
3763
|
-
let
|
|
3774
|
+
let escaped2 = false;
|
|
3764
3775
|
let value = "";
|
|
3765
3776
|
for (const chr of line.slice(line.indexOf(":") + 1).replaceAll(/\r?\n[ \t]*/g, " ")) {
|
|
3766
3777
|
if (inKey && chr === "=") {
|
|
3767
3778
|
key = value.trim().toLowerCase();
|
|
3768
3779
|
inKey = false;
|
|
3769
3780
|
value = "";
|
|
3770
|
-
} else if (inKey ||
|
|
3781
|
+
} else if (inKey || escaped2) {
|
|
3771
3782
|
value += chr;
|
|
3772
|
-
|
|
3783
|
+
escaped2 = false;
|
|
3773
3784
|
} else if (chr === "\\") {
|
|
3774
|
-
|
|
3785
|
+
escaped2 = true;
|
|
3775
3786
|
} else if (chr === '"') {
|
|
3776
3787
|
quoted = !quoted;
|
|
3777
3788
|
} else if (chr === ";" && !quoted) {
|
|
@@ -3819,7 +3830,7 @@ function unfoldHeaders(raw) {
|
|
|
3819
3830
|
if (blank && type.boundary) {
|
|
3820
3831
|
boundaries.push(Buffer2.from(type.boundary));
|
|
3821
3832
|
}
|
|
3822
|
-
head = blank &&
|
|
3833
|
+
head = blank && ["message/rfc822", "message/global"].includes(type.value) && ["", "7bit", "8bit", "binary"].includes(encoding) && first.get("content-disposition")?.value === "inline";
|
|
3823
3834
|
};
|
|
3824
3835
|
for (let start = 0; start < raw.length; ) {
|
|
3825
3836
|
const lf = raw.indexOf(10, start);
|
|
@@ -3956,6 +3967,7 @@ async function expandAttachments(attachments = [], errors = [], parse = parseMes
|
|
|
3956
3967
|
let budget = MAX_NESTED_BYTES;
|
|
3957
3968
|
let nested = 0;
|
|
3958
3969
|
let tooMany = false;
|
|
3970
|
+
let opened = 0;
|
|
3959
3971
|
const visit = async (list, prefix, depth) => {
|
|
3960
3972
|
for (const attachment of list) {
|
|
3961
3973
|
if (prefix) {
|
|
@@ -3972,17 +3984,22 @@ async function expandAttachments(attachments = [], errors = [], parse = parseMes
|
|
|
3972
3984
|
const filename = prefix ? `${prefix}/${name}` : name || "attached message";
|
|
3973
3985
|
all.push(prefix ? { ...attachment, filename: `${prefix}/${name}` } : attachment);
|
|
3974
3986
|
const { content } = attachment;
|
|
3975
|
-
const attached =
|
|
3987
|
+
const attached = isAttachedMessage(attachment);
|
|
3976
3988
|
if (attached && Buffer2.isBuffer(content) && depth >= MAX_NESTED_DEPTH) {
|
|
3977
3989
|
unopened.push({ filename, reason: `nested more than ${MAX_NESTED_DEPTH} deep` });
|
|
3978
3990
|
continue;
|
|
3979
3991
|
}
|
|
3992
|
+
if (attached && Buffer2.isBuffer(content) && opened >= MAX_ATTACHED_MESSAGES) {
|
|
3993
|
+
unopened.push({ filename, reason: `more than ${MAX_ATTACHED_MESSAGES} attached messages` });
|
|
3994
|
+
continue;
|
|
3995
|
+
}
|
|
3980
3996
|
if (attached && Buffer2.isBuffer(content) && content.length > budget) {
|
|
3981
3997
|
unopened.push({ filename, reason: `past ${MAX_NESTED_BYTES / 1024 / 1024} MB of attached messages` });
|
|
3982
3998
|
continue;
|
|
3983
3999
|
}
|
|
3984
4000
|
if (attached && Buffer2.isBuffer(content)) {
|
|
3985
4001
|
budget -= content.length;
|
|
4002
|
+
opened++;
|
|
3986
4003
|
let inner;
|
|
3987
4004
|
try {
|
|
3988
4005
|
inner = await parseWithin(content, parse, deadline);
|
|
@@ -5668,9 +5685,12 @@ ${visibleText(html.slice(0, 1e5)).text}`)) {
|
|
|
5668
5685
|
hit("SEXTORTION_SUBJECT", 2, "Subject used by sextortion and account takeover scams, without a demand for payment", "SCAM");
|
|
5669
5686
|
}
|
|
5670
5687
|
const markup = html.slice(0, 1e5);
|
|
5688
|
+
const readable = readableText(`${subject}
|
|
5689
|
+
${text.slice(0, 5e4)}
|
|
5690
|
+
${visibleText(withoutHelp(markup)).text}`);
|
|
5671
5691
|
if (isInjection(`${subject}
|
|
5672
5692
|
${text.slice(0, 5e4)}
|
|
5673
|
-
${withoutHelp(markup)}`) || attributeText(markup).some(({ text: text2, start, end, help }) => (!help || isOrder(text2)) && isInjection(unquoted(text2), AI_ACTION_OBJECT, () => shownNear(markup, start, end)))) {
|
|
5693
|
+
${withoutHelp(markup)}`) || isInjection(readable) || attributeText(markup).some(({ text: text2, start, end, help }) => (!help || isOrder(text2)) && isInjection(unquoted(text2), AI_ACTION_OBJECT, () => shownNear(markup, start, end)))) {
|
|
5674
5694
|
hit("PROMPT_INJECTION", 3, "Contains instructions addressed to an AI filter", "SPAM");
|
|
5675
5695
|
}
|
|
5676
5696
|
if (session.resolvedClientHostname?.endsWith(".outbound.protection.outlook.com")) {
|
|
@@ -7048,7 +7068,7 @@ function scoreResults(results, options) {
|
|
|
7048
7068
|
var package_default = {
|
|
7049
7069
|
name: "spamscanner",
|
|
7050
7070
|
description: "Spam, phishing and malware filter for email in any language: a trainable classifier, link and attachment checks, SPF/DKIM/DMARC, and an optional AI second opinion from any LLM, including local Ollama models. Works as a library, CLI, Postfix milter or HTTP API.",
|
|
7051
|
-
version: "7.0.
|
|
7071
|
+
version: "7.0.9",
|
|
7052
7072
|
author: "Forward Email (https://forwardemail.net)",
|
|
7053
7073
|
bin: {
|
|
7054
7074
|
spamscanner: "./dist/esm/cli.js"
|
|
@@ -9098,7 +9118,7 @@ var SpamScanner = class {
|
|
|
9098
9118
|
message: `Attachment "${filename}" contains ${result.viruses.join(", ")}`
|
|
9099
9119
|
});
|
|
9100
9120
|
viruses.push(found);
|
|
9101
|
-
if (
|
|
9121
|
+
if (isAttachedMessage(attachment)) {
|
|
9102
9122
|
carriers.set(found, attachment.filename || "attached message");
|
|
9103
9123
|
}
|
|
9104
9124
|
}
|