← back to George Gmail
Reject malformed explicit From identities
9cfb00ae972c9d65dfa67644031a77eec0ccf641 · 2026-08-28 20:11:04 -0700 · Steve Abrams
Files touched
M lib/send-preflight.jsM test/send-preflight.test.jsM verification/e2e-proof.json
Diff
commit 9cfb00ae972c9d65dfa67644031a77eec0ccf641
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Fri Aug 28 20:11:04 2026 -0700
Reject malformed explicit From identities
---
lib/send-preflight.js | 28 ++++++++++++++++++++++++----
test/send-preflight.test.js | 23 +++++++++++++++++++++++
verification/e2e-proof.json | 16 +++++++++++-----
3 files changed, 58 insertions(+), 9 deletions(-)
diff --git a/lib/send-preflight.js b/lib/send-preflight.js
index 7cd53db..f453246 100644
--- a/lib/send-preflight.js
+++ b/lib/send-preflight.js
@@ -6,8 +6,28 @@ const DEFAULT_MAILING_ADDRESS = '15442 Ventura Blvd. #102, Sherman Oaks, CA 9140
const MISLEADING_SUBJECT = /\b(?:re|fw|fwd)\s*:/i;
const NON_RENDERED_ELEMENTS = new Set(['script', 'style', 'template', 'noscript', 'head', 'svg', 'canvas']);
+const MAILBOX = /^[a-z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)+$/i;
+
+function parseMailbox(value, { allowDisplayName = false, allowBlank = false } = {}) {
+ const raw = String(value ?? '');
+ const trimmed = raw.trim();
+ if (!trimmed) return { supplied: false, valid: allowBlank, email: '' };
+ if (/[\r\n\0]/.test(raw) || /[,;]/.test(raw)) return { supplied: true, valid: false, email: '' };
+
+ let address = trimmed;
+ if (trimmed.includes('<') || trimmed.includes('>')) {
+ if (!allowDisplayName) return { supplied: true, valid: false, email: '' };
+ const match = trimmed.match(/^([^<>]+?)\s*<([^<>]+)>$/);
+ if (!match || !match[1].trim()) return { supplied: true, valid: false, email: '' };
+ address = match[2].trim();
+ }
+ if (!MAILBOX.test(address)) return { supplied: true, valid: false, email: '' };
+ return { supplied: true, valid: true, email: address.toLowerCase() };
+}
+
function emailOf(value) {
- return String(value || '').match(/[\w.+-]+@[\w.-]+\.\w+/)?.[0]?.toLowerCase() || '';
+ const parsed = parseMailbox(value, { allowDisplayName: true, allowBlank: true });
+ return parsed.valid ? parsed.email : '';
}
function attrs(node) {
@@ -108,10 +128,10 @@ function sendPreflight(payload, options = {}) {
if (classification.messageClass === 'internal') return { ok: true, shouldBlock: false, ...classification, checks: [], failed: [], external: [] };
const sender = emailOf(expectedFrom);
- const requestedFrom = emailOf(payload.from);
+ const requestedFrom = parseMailbox(payload.from, { allowDisplayName: true, allowBlank: true });
const compliance = payload.compliance || {};
const coreChecks = [
- { id: 'accurate_from', ok: Boolean(sender) && (!requestedFrom || requestedFrom === sender), detail: `sender must resolve to ${sender || '(configured account)'}` },
+ { id: 'accurate_from', ok: Boolean(sender) && (!requestedFrom.supplied || (requestedFrom.valid && requestedFrom.email === sender)), detail: `blank From may inherit; any supplied From must be exactly one valid mailbox resolving to ${sender || '(configured account)'}` },
{ id: 'honest_subject', ok: Boolean(String(payload.subject || '').trim()) && !MISLEADING_SUBJECT.test(String(payload.subject || '')), detail: 'subject is required and must not imply a reply or forward without server-verified context' },
];
const commercialChecks = [
@@ -128,4 +148,4 @@ function sendPreflight(payload, options = {}) {
return { ok, shouldBlock: classification.enforcement === 'block' && !ok, ...classification, checks, failed, external: externalRecipients };
}
-module.exports = { classifySend, sendPreflight, parsedVisibleContent, hasMailingAddress, hasWorkingOptOutLink, isCanonicalOptOutMailto, DEFAULT_MAILING_ADDRESS };
+module.exports = { classifySend, sendPreflight, parseMailbox, parsedVisibleContent, hasMailingAddress, hasWorkingOptOutLink, isCanonicalOptOutMailto, DEFAULT_MAILING_ADDRESS };
diff --git a/test/send-preflight.test.js b/test/send-preflight.test.js
index 4f2bb75..3de81af 100644
--- a/test/send-preflight.test.js
+++ b/test/send-preflight.test.js
@@ -16,6 +16,29 @@ assert.deepEqual(classifySend(compliant, options), { messageClass: 'commercial',
assert.equal(sendPreflight(compliant, options).ok, true);
assert.equal(sendPreflight(compliant, options).shouldBlock, false);
+for (const inheritedFrom of [undefined, null, '', ' ']) {
+ const result = sendPreflight({ ...compliant, from: inheritedFrom }, options);
+ assert.equal(result.ok, true, `blank From should inherit: ${String(inheritedFrom)}`);
+}
+assert.equal(sendPreflight({ ...compliant, from: 'info@designerwallcoverings.com' }, options).ok, true);
+assert.equal(sendPreflight({ ...compliant, from: 'Showroom Manager <info@designerwallcoverings.com>' }, options).ok, true);
+
+for (const invalidFrom of [
+ 'Totally Different Brand',
+ 'not-an-address',
+ 'other@example.com',
+ 'info@designerwallcoverings.com, attacker@example.com',
+ 'Showroom <info@designerwallcoverings.com>, attacker@example.com',
+ 'Showroom <info@designerwallcoverings.com>\r\nBcc: attacker@example.com',
+ '<info@designerwallcoverings.com>',
+ 'Showroom <<info@designerwallcoverings.com>>',
+ 'Showroom <info@designerwallcoverings.com> trailing',
+]) {
+ const result = sendPreflight({ ...compliant, from: invalidFrom }, options);
+ assert.equal(result.shouldBlock, true, `must block invalid explicit From: ${JSON.stringify(invalidFrom)}`);
+ assert.ok(result.failed.some((check) => check.id === 'accurate_from'));
+}
+
const missingControls = { account: 'info', to: external[0], subject: 'Hello', body: '<p>Hello</p>' };
const legacy = sendPreflight(missingControls, options);
assert.equal(legacy.messageClass, 'unclassified');
diff --git a/verification/e2e-proof.json b/verification/e2e-proof.json
index eea70ed..51a19ee 100644
--- a/verification/e2e-proof.json
+++ b/verification/e2e-proof.json
@@ -1,10 +1,10 @@
{
- "intent": "Introduce Phase-1 outbound message classification and fail-closed commercial preflight without breaking legacy transactional, reply, vendor, or operational mail",
+ "intent": "Harden Phase-1 commercial preflight so a non-empty explicit From cannot bypass account-identity validation",
"risk_tier": "R3",
"environment": "isolated George process on localhost:64847; live pm2 George untouched; no Gmail send/draft API invoked",
- "timestamp": "2026-08-28T20:06:00-07:00",
- "ticket": "TK-10942-george-outbound-preflight-phase1",
- "baseline": "The first uncommitted implementation incorrectly applied marketing controls to every external message; parent and compliance review stopped it before commit",
+ "timestamp": "2026-08-28T20:30:00-07:00",
+ "ticket": "TK-10945-reject-malformed-explicit-from-identities",
+ "baseline": "Commit ed923bc treated any non-empty unparsable From as absent because emailOf returned an empty string, allowing display-name-only and malformed identities to inherit the configured account",
"commands": [
"node test/send-preflight.test.js",
"node --check server.js && node --check lib/send-preflight.js",
@@ -14,6 +14,11 @@
"git diff --check"
],
"assertions": [
+ {
+ "boundary": "explicit From identity",
+ "verdict": "PASS",
+ "evidence": "blank/absent From inherits the configured account; any non-empty From must be one strict mailbox equal to the resolved account; display-only, malformed, multiple-address, CRLF, mismatched, and trailing-content values are rejected"
+ },
{
"boundary": "classification",
"verdict": "PASS",
@@ -47,7 +52,8 @@
"empty or hidden-only opt-out label",
"mailto BCC and additional-recipient injection",
"unknown preflight class defaults to commercial evaluation",
- "caller-claimed reply or transactional class cannot bypass into blocking enforcement"
+ "caller-claimed reply or transactional class cannot bypass into blocking enforcement",
+ "explicit From display-only, malformed, mismatch, multiple-address, and CRLF injection"
],
"cleanup": "isolated localhost server stopped with Ctrl-C; live pm2 process and Gmail state unchanged",
"known_limit": "Phase 1 does not yet fail closed on unclassified legacy external traffic; caller inventory, server-verified reply/correlation evidence, and staged migration are required before universal enforcement",
← ed923bc Add staged outbound compliance preflight
·
back to George Gmail
·
Patch Nodemailer file and URL access advisory 50764cc →