← back to George Gmail
Add staged outbound compliance preflight
ed923bcd01f2e1bb0d765f8bf0b2a948e5df1961 · 2026-08-28 20:07:51 -0700 · Steve Abrams
Files touched
A lib/send-preflight.jsM server.jsA test/send-preflight.test.js
Diff
commit ed923bcd01f2e1bb0d765f8bf0b2a948e5df1961
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Fri Aug 28 20:07:51 2026 -0700
Add staged outbound compliance preflight
---
lib/send-preflight.js | 131 ++++++++++++++++++++++++++++++++++++++++++++
server.js | 26 ++++++++-
test/send-preflight.test.js | 77 ++++++++++++++++++++++++++
3 files changed, 233 insertions(+), 1 deletion(-)
diff --git a/lib/send-preflight.js b/lib/send-preflight.js
new file mode 100644
index 0000000..7cd53db
--- /dev/null
+++ b/lib/send-preflight.js
@@ -0,0 +1,131 @@
+'use strict';
+
+const parse5 = require('parse5');
+
+const DEFAULT_MAILING_ADDRESS = '15442 Ventura Blvd. #102, Sherman Oaks, CA 91403';
+const MISLEADING_SUBJECT = /\b(?:re|fw|fwd)\s*:/i;
+const NON_RENDERED_ELEMENTS = new Set(['script', 'style', 'template', 'noscript', 'head', 'svg', 'canvas']);
+
+function emailOf(value) {
+ return String(value || '').match(/[\w.+-]+@[\w.-]+\.\w+/)?.[0]?.toLowerCase() || '';
+}
+
+function attrs(node) {
+ return Object.fromEntries((node.attrs || []).map(({ name, value }) => [name.toLowerCase(), value]));
+}
+
+function isHiddenElement(node) {
+ if (!node.tagName) return false;
+ const tag = node.tagName.toLowerCase();
+ if (NON_RENDERED_ELEMENTS.has(tag)) return true;
+ const attributes = attrs(node);
+ if (Object.hasOwn(attributes, 'hidden')) return true;
+ if (String(attributes['aria-hidden'] || '').trim().toLowerCase() === 'true') return true;
+ if (tag === 'input' && String(attributes.type || '').trim().toLowerCase() === 'hidden') return true;
+ const style = String(attributes.style || '')
+ .replace(/\/\*[\s\S]*?\*\//g, '')
+ .replace(/\s+/g, '')
+ .toLowerCase();
+ return /(?:^|;)display:none(?:!important)?(?:;|$)/.test(style)
+ || /(?:^|;)visibility:hidden(?:!important)?(?:;|$)/.test(style);
+}
+
+function parsedVisibleContent(body) {
+ const fragment = parse5.parseFragment(String(body || ''));
+ const text = [];
+ const anchors = [];
+ function visit(node, hidden = false, activeAnchor = null) {
+ const nowHidden = hidden || isHiddenElement(node);
+ if (!nowHidden && node.nodeName === '#text') {
+ const value = node.value || '';
+ text.push(value);
+ if (activeAnchor) activeAnchor.text.push(value);
+ }
+ let childAnchor = activeAnchor;
+ if (!nowHidden && String(node.tagName || '').toLowerCase() === 'a') {
+ const href = attrs(node).href;
+ if (href) {
+ childAnchor = { href: href.trim(), text: [] };
+ anchors.push(childAnchor);
+ }
+ }
+ for (const child of node.childNodes || []) visit(child, nowHidden, childAnchor);
+ if (node.content) visit(node.content, true, null);
+ }
+ visit(fragment);
+ return {
+ visibleText: text.join(' ').replace(/\s+/g, ' ').trim(),
+ anchors: anchors.map(({ href, text: label }) => ({ href, visibleLabel: label.join(' ').replace(/\s+/g, ' ').trim() })),
+ };
+}
+
+function hasMailingAddress(body, mailingAddress = DEFAULT_MAILING_ADDRESS) {
+ const wanted = String(mailingAddress || '').replace(/[^a-z0-9]/gi, '').toLowerCase();
+ const actual = parsedVisibleContent(body).visibleText.replace(/[^a-z0-9]/gi, '').toLowerCase();
+ return Boolean(wanted) && actual.includes(wanted);
+}
+
+function isCanonicalOptOutMailto(url, expectedFrom) {
+ if (url.protocol !== 'mailto:' || url.hash) return false;
+ let recipient;
+ try { recipient = decodeURIComponent(url.pathname); } catch { return false; }
+ if (recipient.toLowerCase() !== emailOf(expectedFrom)) return false;
+ const entries = [...url.searchParams.entries()];
+ if (entries.length !== 1 || entries[0][0] !== 'subject') return false;
+ const subject = entries[0][1];
+ return Boolean(subject) && !/[\r\n\0]/.test(subject) && /(?:unsubscribe|opt\s*out|stop\s+receiving)/i.test(subject);
+}
+
+function hasWorkingOptOutLink(body, expectedFrom) {
+ const { anchors } = parsedVisibleContent(body);
+ return anchors.some(({ href, visibleLabel }) => {
+ try {
+ if (!/(?:unsubscribe|opt\s*out|stop\s+receiving)/i.test(visibleLabel)) return false;
+ const url = new URL(href);
+ return isCanonicalOptOutMailto(url, expectedFrom);
+ } catch {
+ return false;
+ }
+ });
+}
+
+function classifySend(payload, { externalRecipients = [], endpointEvaluation = false } = {}) {
+ if (!externalRecipients.length) return { messageClass: 'internal', basis: 'server-derived-internal', enforcement: 'exempt' };
+ const explicit = String(payload.message_class || '').trim().toLowerCase();
+ if (explicit === 'commercial' || payload.compliance_required === true) {
+ return { messageClass: 'commercial', basis: explicit === 'commercial' ? 'caller-explicit-commercial' : 'caller-compliance-required', enforcement: 'block' };
+ }
+ if (explicit === 'transactional' || explicit === 'reply') {
+ return { messageClass: explicit, basis: `caller-explicit-${explicit}-unverified`, enforcement: 'report-only' };
+ }
+ if (endpointEvaluation) return { messageClass: 'commercial', basis: 'preflight-endpoint-conservative-default', enforcement: 'block' };
+ return { messageClass: 'unclassified', basis: explicit ? 'caller-unknown-class' : 'legacy-external-unclassified', enforcement: 'report-only' };
+}
+
+function sendPreflight(payload, options = {}) {
+ const { expectedFrom = '', externalRecipients = [], endpointEvaluation = false, mailingAddress = DEFAULT_MAILING_ADDRESS } = options;
+ const classification = classifySend(payload, { externalRecipients, endpointEvaluation });
+ if (classification.messageClass === 'internal') return { ok: true, shouldBlock: false, ...classification, checks: [], failed: [], external: [] };
+
+ const sender = emailOf(expectedFrom);
+ const requestedFrom = emailOf(payload.from);
+ const compliance = payload.compliance || {};
+ const coreChecks = [
+ { id: 'accurate_from', ok: Boolean(sender) && (!requestedFrom || requestedFrom === sender), detail: `sender must resolve 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 = [
+ { id: 'physical_address', ok: hasMailingAddress(payload.body, mailingAddress), detail: 'body must contain the configured physical postal address' },
+ { id: 'working_opt_out', ok: hasWorkingOptOutLink(payload.body, sender), detail: 'body must contain a visible, valid opt-out anchor' },
+ { id: 'dnc_scrubbed', ok: compliance.dnc_scrubbed === true, detail: 'compliance.dnc_scrubbed must be true' },
+ { id: 'age_gate_excluded', ok: compliance.age_gated_excluded === true, detail: 'compliance.age_gated_excluded must be true' },
+ ];
+ const checks = classification.messageClass === 'transactional' || classification.messageClass === 'reply'
+ ? coreChecks
+ : [...coreChecks, ...commercialChecks];
+ const failed = checks.filter((check) => !check.ok);
+ const ok = failed.length === 0;
+ return { ok, shouldBlock: classification.enforcement === 'block' && !ok, ...classification, checks, failed, external: externalRecipients };
+}
+
+module.exports = { classifySend, sendPreflight, parsedVisibleContent, hasMailingAddress, hasWorkingOptOutLink, isCanonicalOptOutMailto, DEFAULT_MAILING_ADDRESS };
diff --git a/server.js b/server.js
index e8ecb97..50f8a36 100644
--- a/server.js
+++ b/server.js
@@ -9,6 +9,7 @@ const path = require('path');
const fs = require('fs');
const { google } = require('googleapis');
const nodemailer = require('nodemailer');
+const { sendPreflight } = require('./lib/send-preflight');
const PORT = process.env.PORT || 9850;
const AGENT_NAME = 'George';
@@ -1194,13 +1195,28 @@ function externalSendGuard(req) {
+ 'External recipients: ' + external.join(', ') };
}
+function runSendPreflight(req, payload, expectedFrom, externalRecipients) {
+ const result = sendPreflight(payload, { expectedFrom, externalRecipients });
+ const action = result.shouldBlock ? 'send-PREFLIGHT-BLOCKED' : (!result.ok && result.enforcement === 'report-only' ? 'send-PREFLIGHT-REPORT' : null);
+ if (action) try { audit(req.body?.account || '?', action, { to: payload.to, messageClass: result.messageClass, basis: result.basis, failed: result.failed.map(c => c.id) }); } catch {}
+ return result;
+}
+
+app.post('/api/send-preflight', (req, res) => {
+ const account = resolveAccount(req);
+ if (!account.gmail) return res.status(400).json({ error: `unknown account: ${account.key}` });
+ const external = _recips(req.body?.to, req.body?.cc, req.body?.bcc).filter(a => !_isInternal(a));
+ const result = sendPreflight(req.body || {}, { expectedFrom: account.label, externalRecipients: external, endpointEvaluation: true });
+ return res.status(result.ok ? 200 : 422).json(result);
+});
+
app.post('/api/send', async (req, res) => {
try {
const { from, to, subject, body, cc, bcc, threadId: bodyThreadId, inReplyTo: bodyInReplyTo, replyToMessageId, references } = req.body;
if (!to || !body) return res.status(400).json({ error: 'to and body required' });
{ const _g = externalSendGuard(req); if (!_g.ok) { try { audit(req.body.account || '?', 'send-BLOCKED', { to, external: _g.external, reason: _g.reason }); } catch (e) {} return res.status(403).json({ error: _g.reason, blocked: true, external: _g.external }); } }
- const { gmail: g, key: account } = resolveAccount(req);
+ const { gmail: g, key: account, label: accountLabel } = resolveAccount(req);
if (!g) return res.status(400).json({ error: `unknown account: ${account}` });
let threadId = bodyThreadId || null;
@@ -1218,6 +1234,10 @@ app.post('/api/send', async (req, res) => {
}
if (!finalSubject) return res.status(400).json({ error: 'subject required (or pass replyToMessageId)' });
+ const external = _recips(to, cc, bcc).filter(a => !_isInternal(a));
+ const preflight = runSendPreflight(req, { ...req.body, subject: finalSubject }, accountLabel, external);
+ if (preflight.shouldBlock) return res.status(422).json({ error: 'send compliance preflight failed', blocked: true, messageClass: preflight.messageClass, basis: preflight.basis, failed: preflight.failed, checks: preflight.checks });
+
const source = inferSource(req.body, req);
const taggedBody = withSourceFooter(body, source);
const encoded = buildRawMessage({ from, to, cc, bcc, subject: finalSubject, body: taggedBody, inReplyTo, references: refsArr });
@@ -1369,6 +1389,10 @@ app.post('/api/send-with-attachment', async (req, res) => {
if (!authClient || !gmailClient) {
return res.status(500).json({ error: `account not configured: ${acctKey}` });
}
+ const accountInfo = resolveAccount(req);
+ const external = _recips(to, cc, bcc).filter(a => !_isInternal(a));
+ const preflight = runSendPreflight(req, req.body, accountInfo.label, external);
+ if (preflight.shouldBlock) return res.status(422).json({ error: 'send compliance preflight failed', blocked: true, messageClass: preflight.messageClass, basis: preflight.basis, failed: preflight.failed, checks: preflight.checks });
// Build MIME with nodemailer (handles UTF-8 subjects, multipart, base64 chunking correctly)
const profile = await gmailClient.users.getProfile({ userId: 'me' });
diff --git a/test/send-preflight.test.js b/test/send-preflight.test.js
new file mode 100644
index 0000000..4f2bb75
--- /dev/null
+++ b/test/send-preflight.test.js
@@ -0,0 +1,77 @@
+'use strict';
+
+const assert = require('node:assert/strict');
+const { classifySend, sendPreflight } = require('../lib/send-preflight');
+
+const external = ['customer@real-domain.test'];
+const options = { expectedFrom: 'info@designerwallcoverings.com', externalRecipients: external };
+const compliant = {
+ account: 'info', from: 'Designer Wallcoverings <info@designerwallcoverings.com>', to: external[0], message_class: 'commercial',
+ subject: 'Fall wallcovering edit',
+ body: '<p>Designer Wallcoverings<br>15442 Ventura Blvd. #102, Sherman Oaks, CA 91403</p><a href="mailto:info@designerwallcoverings.com?subject=unsubscribe">Unsubscribe</a>',
+ compliance: { dnc_scrubbed: true, age_gated_excluded: true },
+};
+
+assert.deepEqual(classifySend(compliant, options), { messageClass: 'commercial', basis: 'caller-explicit-commercial', enforcement: 'block' });
+assert.equal(sendPreflight(compliant, options).ok, true);
+assert.equal(sendPreflight(compliant, options).shouldBlock, false);
+
+const missingControls = { account: 'info', to: external[0], subject: 'Hello', body: '<p>Hello</p>' };
+const legacy = sendPreflight(missingControls, options);
+assert.equal(legacy.messageClass, 'unclassified');
+assert.equal(legacy.enforcement, 'report-only');
+assert.equal(legacy.ok, false);
+assert.equal(legacy.shouldBlock, false);
+
+const explicitCommercial = sendPreflight({ ...missingControls, message_class: 'commercial' }, options);
+assert.equal(explicitCommercial.enforcement, 'block');
+assert.equal(explicitCommercial.shouldBlock, true);
+
+const endpointDefault = sendPreflight(missingControls, { ...options, endpointEvaluation: true });
+assert.equal(endpointDefault.messageClass, 'commercial');
+assert.equal(endpointDefault.basis, 'preflight-endpoint-conservative-default');
+assert.equal(endpointDefault.enforcement, 'block');
+assert.equal(endpointDefault.shouldBlock, true);
+
+const transactional = sendPreflight({ ...missingControls, message_class: 'transactional' }, options);
+assert.equal(transactional.enforcement, 'report-only');
+assert.equal(transactional.shouldBlock, false);
+
+const forgedReply = sendPreflight({ ...compliant, subject: 'Re: prior note', threadId: 'caller-controlled' }, options);
+assert.ok(forgedReply.failed.some((check) => check.id === 'honest_subject'));
+assert.equal(forgedReply.shouldBlock, true);
+
+const internal = sendPreflight({ ...missingControls, message_class: 'commercial' }, { expectedFrom: options.expectedFrom, externalRecipients: [] });
+assert.equal(internal.messageClass, 'internal');
+assert.equal(internal.basis, 'server-derived-internal');
+assert.equal(internal.enforcement, 'exempt');
+
+for (const fakeOptOut of [
+ '<!-- <a href="mailto:info@designerwallcoverings.com?subject=unsubscribe">unsubscribe</a> -->',
+ 'mailto:info@designerwallcoverings.com?subject=unsubscribe',
+ '<a href="https://invalid.example/unsubscribe">unsubscribe</a>',
+]) {
+ const result = sendPreflight({ ...compliant, body: '<p>15442 Ventura Blvd. #102, Sherman Oaks, CA 91403</p>' + fakeOptOut }, options);
+ assert.ok(result.failed.some((check) => check.id === 'working_opt_out'));
+}
+
+for (const parserBypass of [
+ '<div hidden>15442 Ventura Blvd. #102, Sherman Oaks, CA 91403</div><a href="mailto:info@designerwallcoverings.com?subject=unsubscribe">Unsubscribe</a>',
+ '<div style="display:none">15442 Ventura Blvd. #102, Sherman Oaks, CA 91403</div><a href="mailto:info@designerwallcoverings.com?subject=unsubscribe">Unsubscribe</a>',
+ '<script>15442 Ventura Blvd. #102, Sherman Oaks, CA 91403 <a href="mailto:info@designerwallcoverings.com?subject=unsubscribe">Unsubscribe</a>',
+]) {
+ const result = sendPreflight({ ...compliant, body: parserBypass }, options);
+ assert.ok(result.failed.some((check) => check.id === 'physical_address' || check.id === 'working_opt_out'));
+}
+
+for (const optOutBypass of [
+ '<a href="mailto:info@designerwallcoverings.com?subject=unsubscribe"></a>',
+ '<a href="mailto:info@designerwallcoverings.com?subject=unsubscribe"><span hidden>Unsubscribe</span></a>',
+ '<a href="mailto:info@designerwallcoverings.com?subject=unsubscribe&bcc=attacker@example.org">Unsubscribe</a>',
+ '<a href="mailto:info@designerwallcoverings.com,attacker@example.org?subject=unsubscribe">Unsubscribe</a>',
+]) {
+ const result = sendPreflight({ ...compliant, body: '<p>15442 Ventura Blvd. #102, Sherman Oaks, CA 91403</p>' + optOutBypass }, options);
+ assert.ok(result.failed.some((check) => check.id === 'working_opt_out'));
+}
+
+console.log('George send preflight classification: PASS');
← 615c560 auto-data-snapshot: 2026-08-28T20:07:34 (3 data files) — pac
·
back to George Gmail
·
Reject malformed explicit From identities 9cfb00a →