[object Object]

← back to Dw Signup Fulfillment

prevent customer data in George failure logs

9e5aa29b6e58f1e13b91eb366c8fb0e634893f0d · 2026-09-02 10:56:27 -0700 · Steve Abrams

Files touched

Diff

commit 9e5aa29b6e58f1e13b91eb366c8fb0e634893f0d
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Wed Sep 2 10:56:27 2026 -0700

    prevent customer data in George failure logs
---
 lib/email.js  | 21 ++++++++++-----------
 lib/verify.js |  8 ++++----
 2 files changed, 14 insertions(+), 15 deletions(-)

diff --git a/lib/email.js b/lib/email.js
index b00efa7..d215204 100644
--- a/lib/email.js
+++ b/lib/email.js
@@ -34,16 +34,15 @@ function georgePost(payload) {
     }, res => {
       let d = ''; res.on('data', c => d += c);
       res.on('end', () => {
-        // Always surface the HTTP status + a short body preview so a caller can LOG *why*
-        // a send failed (e.g. 401). George's response body never contains our auth secret.
+        // Surface only status and a sanitized error code. Never retain George's raw
+        // response body because a downstream service could echo customer content.
         const httpStatus = res.statusCode;
-        const bodyPreview = String(d).slice(0, 300);
         let parsed = null;
         try { parsed = JSON.parse(d); } catch { /* George returned non-JSON */ }
-        const base = { ok: httpStatus < 400, status: httpStatus, httpStatus, bodyPreview };
-        resolve(parsed && typeof parsed === 'object'
-          ? { ...base, ...parsed, status: httpStatus, httpStatus, bodyPreview }
-          : base);
+        const candidate = parsed && typeof parsed === 'object'
+          ? (parsed.error || parsed.code || parsed.message || '') : '';
+        const errorCode = String(candidate).replace(/[^a-zA-Z0-9_.: -]/g, '').slice(0, 120);
+        resolve({ ok: httpStatus < 400, status: httpStatus, httpStatus, errorCode });
       });
     });
     req.on('error', e => resolve({ ok: false, status: 0, error: e.message }));
@@ -86,12 +85,12 @@ async function sendEmail({ to, subject, html, source }) {
   }
   const result = await georgePost(payload);
   // CREDENTIAL-SAFE failure/success logging — the fix for the swallowed George outage.
-  // Logs ONLY the George HTTP status, its error/body preview, and the operational
-  // recipient/source. NEVER logs the Authorization header, the Basic-auth, or the send token.
+  // Logs ONLY George's HTTP status, sanitized error code, and operational source.
+  // Never logs recipient/customer content, response bodies, auth, or the send token.
   if (result && result.ok === false) {
-    log(`SEND FAILED via George: to=${to} source=${src} status=${result.status != null ? result.status : '?'} error=${result.error || ''} body=${String(result.bodyPreview || '').replace(/\s+/g, ' ').slice(0, 160)}`);
+    log(`SEND FAILED via George: source=${src} status=${result.status != null ? result.status : '?'} errorCode=${result.errorCode || result.error || ''}`);
   } else {
-    log(`sent via George: to=${to} source=${src} status=${result && result.status != null ? result.status : '?'}`);
+    log(`sent via George: source=${src} status=${result && result.status != null ? result.status : '?'}`);
   }
   // Fire-and-forget office copy — a separate message; its failure never affects the real send.
   if (officeCopyActive(src) && to !== OFFICE_COPY_TO) {
diff --git a/lib/verify.js b/lib/verify.js
index aa6f028..356cdcd 100644
--- a/lib/verify.js
+++ b/lib/verify.js
@@ -78,11 +78,11 @@ async function startVerification({ email: to, customerId, firstName }) {
   const tpl = email.verifyEmail({ firstName: first, url, count: config.FREE_SAMPLE_COUNT });
   const mail = await email.sendEmail({ to: addr, subject: tpl.subject, html: tpl.html, source: 'retail-verify' });
   // CREDENTIAL-SAFE: never let a failed send hide again. Log + PROPAGATE the George reason/
-  // status so retail-webhook.js records *why* the letter didn't go out. Never logs the verify
-  // token (carried in `url`), the auth header, or the George body beyond a short preview.
+  // status so retail-webhook.js records *why* the letter didn't go out. Never logs the
+  // recipient, verify token (carried in `url`), auth header, or George response body.
   if (mail && mail.ok === false) {
-    console.warn(`[verify] verify-email SEND FAILED to=${addr} status=${mail.status != null ? mail.status : '?'} error=${mail.error || ''} body=${String(mail.bodyPreview || '').replace(/\s+/g, ' ').slice(0, 160)}`);
-    return { ok: false, reason: 'send_failed', status: (mail.status != null ? mail.status : null), error: mail.error || null, sent: { to: addr, subject: tpl.subject, dryRun: mail.dryRun || false } };
+    console.warn(`[verify] verify-email SEND FAILED status=${mail.status != null ? mail.status : '?'} errorCode=${mail.errorCode || mail.error || ''}`);
+    return { ok: false, reason: 'send_failed', status: (mail.status != null ? mail.status : null), errorCode: mail.errorCode || mail.error || null, sent: { to: addr, subject: tpl.subject, dryRun: mail.dryRun || false } };
   }
   // verifyUrl carries the bearer token — callers must redact it before logging.
   return { ok: true, sent: { to: addr, subject: tpl.subject, dryRun: mail.dryRun || false }, verifyUrl: url };

← b7b23da TK-11114: un-swallow George send failures (credential-safe l  ·  back to Dw Signup Fulfillment  ·  auto-data-snapshot: 2026-09-02T11:28:31 (2 data files) — arm e8c174a →