RED: covers EVID-03 (normalized headers, structured auth verdicts, Received chain, URLs, attachment metadata incl. related flag), EVID-04 (no-network spy, truncated body preview), and the DoS size guard (oversized buffer rejected before simpleParser). New synthetic fixtures: rich multipart, auth-results-original, inline/CID attachment, long-body, fuzzy-URL, and an oversized-buffer generator. None of these exports exist on eml-parser.ts yet.
227 lines
7.9 KiB
TypeScript
227 lines
7.9 KiB
TypeScript
/**
|
|
* Synthetic fixtures for eml-parser.test.ts. Nothing here is real customer
|
|
* content — all addresses, subjects, and bodies are invented for testing
|
|
* only (per this milestone's explicit synthetic-fixture-only constraint).
|
|
*/
|
|
|
|
import type { Attachment } from '@/lib/types/autotask';
|
|
|
|
function makeAttachment(overrides: Partial<Attachment> & { id: number }): Attachment {
|
|
return {
|
|
attachmentType: 'FILE_ATTACHMENT',
|
|
fullPath: '',
|
|
title: '',
|
|
publish: 1,
|
|
contentType: 'message/rfc822',
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
/** Tier 1: exact `rfc.eml` present among message/rfc822 attachments (Microsoft "Report Message" flow). */
|
|
export const RFC_EML_TIER_ATTACHMENTS: Attachment[] = [
|
|
makeAttachment({ id: 1, title: 'rfc.eml', fullPath: 'rfc.eml' }),
|
|
makeAttachment({ id: 2, title: 'OriginatingEmail.eml', fullPath: 'OriginatingEmail.eml' }),
|
|
];
|
|
|
|
/** Same tier, but with mixed case to verify case-insensitive matching. */
|
|
export const RFC_EML_TIER_ATTACHMENTS_UPPERCASE: Attachment[] = [
|
|
makeAttachment({ id: 1, title: 'RFC.EML', fullPath: 'RFC.EML' }),
|
|
makeAttachment({ id: 2, title: 'OriginatingEmail.EML', fullPath: 'OriginatingEmail.EML' }),
|
|
];
|
|
|
|
/** Tier 2: KnowBe4 PhishER flow — versioned filename, no literal rfc.eml. */
|
|
export const KNOWBE4_TIER_ATTACHMENTS: Attachment[] = [
|
|
makeAttachment({
|
|
id: 10,
|
|
title: 'phish_alert_sp2_2.0.0.0.eml',
|
|
fullPath: 'phish_alert_sp2_2.0.0.0.eml',
|
|
}),
|
|
makeAttachment({ id: 11, title: 'OriginatingEmail.eml', fullPath: 'OriginatingEmail.eml' }),
|
|
];
|
|
|
|
/** Tier 3: only OriginatingEmail.eml present (older tickets, majority-case fallback). */
|
|
export const ORIGINATING_ONLY_ATTACHMENTS: Attachment[] = [
|
|
makeAttachment({ id: 20, title: 'OriginatingEmail.eml', fullPath: 'OriginatingEmail.eml' }),
|
|
];
|
|
|
|
/** Ambiguous: two non-OriginatingEmail message/rfc822 candidates, no rfc.eml — falls back to OriginatingEmail.eml. */
|
|
export const AMBIGUOUS_WITH_FALLBACK_ATTACHMENTS: Attachment[] = [
|
|
makeAttachment({ id: 30, title: 'weird_name_1.eml', fullPath: 'weird_name_1.eml' }),
|
|
makeAttachment({ id: 31, title: 'weird_name_2.eml', fullPath: 'weird_name_2.eml' }),
|
|
makeAttachment({ id: 32, title: 'OriginatingEmail.eml', fullPath: 'OriginatingEmail.eml' }),
|
|
];
|
|
|
|
/** Ambiguous with no fallback available — selectOriginalMessage should return null. */
|
|
export const AMBIGUOUS_NO_FALLBACK_ATTACHMENTS: Attachment[] = [
|
|
makeAttachment({ id: 40, title: 'weird_name_1.eml', fullPath: 'weird_name_1.eml' }),
|
|
makeAttachment({ id: 41, title: 'weird_name_2.eml', fullPath: 'weird_name_2.eml' }),
|
|
];
|
|
|
|
/** No .eml attachments at all — selectOriginalMessage should return null. */
|
|
export const NO_EML_ATTACHMENTS: Attachment[] = [
|
|
makeAttachment({
|
|
id: 50,
|
|
title: 'screenshot.png',
|
|
fullPath: 'screenshot.png',
|
|
contentType: 'image/png',
|
|
}),
|
|
];
|
|
|
|
/** Empty attachment list. */
|
|
export const EMPTY_ATTACHMENTS: Attachment[] = [];
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Synthetic raw .eml buffers for parseEml (EVID-03/EVID-04). All addresses,
|
|
// bodies, and content below are invented for testing only — no real
|
|
// customer email content per this milestone's Out of Scope constraint.
|
|
// ---------------------------------------------------------------------------
|
|
|
|
/**
|
|
* Rich multipart fixture: text + html bodies (each carrying the same URL),
|
|
* one non-inline base64 attachment, and an Authentication-Results header
|
|
* with spf=pass, dkim=fail, dmarc=none.
|
|
*/
|
|
export const RICH_MULTIPART_EML = Buffer.from(
|
|
`From: "Attacker Corp" <attacker@evil-example.test>
|
|
To: victim@wulfconsulting.test
|
|
Cc: cc-user@wulfconsulting.test
|
|
Reply-To: reply@evil-example.test
|
|
Return-Path: <bounce@evil-example.test>
|
|
Subject: Urgent: verify your account
|
|
Date: Mon, 15 Jul 2026 12:00:00 +0000
|
|
Message-ID: <rich1@evil-example.test>
|
|
Authentication-Results: mx.wulfconsulting.test; spf=pass smtp.mailfrom=evil-example.test; dkim=fail header.d=evil-example.test; dmarc=none header.from=evil-example.test
|
|
Received: from mx1.example.test by mx2.example.test; Mon, 15 Jul 2026 11:59:00 +0000
|
|
Received: from mx0.example.test by mx1.example.test; Mon, 15 Jul 2026 11:58:00 +0000
|
|
MIME-Version: 1.0
|
|
Content-Type: multipart/mixed; boundary="BOUNDARY1"
|
|
|
|
--BOUNDARY1
|
|
Content-Type: multipart/alternative; boundary="BOUNDARY2"
|
|
|
|
--BOUNDARY2
|
|
Content-Type: text/plain; charset="UTF-8"
|
|
|
|
Please visit http://evil-example.test/verify to verify your account.
|
|
|
|
--BOUNDARY2
|
|
Content-Type: text/html; charset="UTF-8"
|
|
|
|
<html><body><p>Please visit <a href="http://evil-example.test/verify">this link</a> to verify your account.</p></body></html>
|
|
|
|
--BOUNDARY2--
|
|
|
|
--BOUNDARY1
|
|
Content-Type: application/pdf; name="invoice.pdf"
|
|
Content-Disposition: attachment; filename="invoice.pdf"
|
|
Content-Transfer-Encoding: base64
|
|
|
|
SGVsbG8gV29ybGQh
|
|
|
|
--BOUNDARY1--
|
|
`
|
|
);
|
|
|
|
/** Same as RICH_MULTIPART_EML but also carries Authentication-Results-Original. */
|
|
export const RICH_MULTIPART_WITH_AUTH_ORIGINAL_EML = Buffer.from(
|
|
`From: "Attacker Corp" <attacker@evil-example.test>
|
|
To: victim@wulfconsulting.test
|
|
Subject: Urgent: verify your account (remediated)
|
|
Date: Mon, 15 Jul 2026 12:00:00 +0000
|
|
Message-ID: <rich2@evil-example.test>
|
|
Authentication-Results: mx.wulfconsulting.test; spf=fail smtp.mailfrom=evil-example.test; dkim=fail header.d=evil-example.test; dmarc=fail header.from=evil-example.test
|
|
Authentication-Results-Original: mx.wulfconsulting.test; spf=pass smtp.mailfrom=evil-example.test; dkim=pass header.d=evil-example.test; dmarc=pass header.from=evil-example.test
|
|
MIME-Version: 1.0
|
|
Content-Type: text/plain; charset="UTF-8"
|
|
|
|
Body text for the remediated-header fixture.
|
|
`
|
|
);
|
|
|
|
/**
|
|
* Inline/CID attachment fixture: html references cid:sig123, wrapped in
|
|
* multipart/related so mailparser marks the image attachment `related: true`
|
|
* (Pitfall 5 — inline parts must be kept, not dropped).
|
|
*/
|
|
export const INLINE_ATTACHMENT_EML = Buffer.from(
|
|
`From: sender@evil-example.test
|
|
To: victim@wulfconsulting.test
|
|
Subject: Newsletter with inline logo
|
|
Date: Mon, 15 Jul 2026 12:00:00 +0000
|
|
Message-ID: <inline1@evil-example.test>
|
|
MIME-Version: 1.0
|
|
Content-Type: multipart/related; boundary="RELBOUND"
|
|
|
|
--RELBOUND
|
|
Content-Type: multipart/alternative; boundary="ALTBOUND"
|
|
|
|
--ALTBOUND
|
|
Content-Type: text/plain; charset="UTF-8"
|
|
|
|
Plain text body with an inline logo.
|
|
|
|
--ALTBOUND
|
|
Content-Type: text/html; charset="UTF-8"
|
|
|
|
<html><body><p>Hello</p><img src="cid:sig123"></body></html>
|
|
|
|
--ALTBOUND--
|
|
|
|
--RELBOUND
|
|
Content-Type: image/png; name="sig.png"
|
|
Content-Disposition: inline
|
|
Content-ID: <sig123>
|
|
Content-Transfer-Encoding: base64
|
|
|
|
iVBORw0KGgo=
|
|
|
|
--RELBOUND--
|
|
`
|
|
);
|
|
|
|
/** Long-body fixture — long enough to force buildBodyPreview truncation. */
|
|
const LONG_PARAGRAPH =
|
|
'This is a long paragraph of synthetic phishing-style body text repeated ' +
|
|
'several times to exceed the body preview truncation threshold. '.repeat(20);
|
|
|
|
export const LONG_BODY_EML = Buffer.from(
|
|
`From: sender@evil-example.test
|
|
To: victim@wulfconsulting.test
|
|
Subject: Long body fixture
|
|
Date: Mon, 15 Jul 2026 12:00:00 +0000
|
|
Message-ID: <long1@evil-example.test>
|
|
MIME-Version: 1.0
|
|
Content-Type: text/plain; charset="UTF-8"
|
|
|
|
${LONG_PARAGRAPH}
|
|
`
|
|
);
|
|
|
|
/** Fuzzy (scheme-less) www. URL fixture, to exercise linkify-it's fuzzyLink mode. */
|
|
export const FUZZY_URL_EML = Buffer.from(
|
|
`From: sender@evil-example.test
|
|
To: victim@wulfconsulting.test
|
|
Subject: Fuzzy URL fixture
|
|
Date: Mon, 15 Jul 2026 12:00:00 +0000
|
|
Message-ID: <fuzzy1@evil-example.test>
|
|
MIME-Version: 1.0
|
|
Content-Type: text/plain; charset="UTF-8"
|
|
|
|
Visit www.evil-example.com/login to reset your password.
|
|
`
|
|
);
|
|
|
|
/** Oversized buffer — exceeds MAX_EML_BYTES, must be rejected before simpleParser runs. */
|
|
export function makeOversizedEmlBuffer(maxBytes: number): Buffer {
|
|
const header = `From: sender@evil-example.test
|
|
To: victim@wulfconsulting.test
|
|
Subject: Oversized fixture
|
|
Date: Mon, 15 Jul 2026 12:00:00 +0000
|
|
Message-ID: <oversized1@evil-example.test>
|
|
MIME-Version: 1.0
|
|
Content-Type: text/plain; charset="UTF-8"
|
|
|
|
`;
|
|
const padding = 'A'.repeat(maxBytes + 1024 - header.length);
|
|
return Buffer.from(header + padding);
|
|
}
|