← back to Dw Pitch Followup
yolo contrarian-gate fixes: socket-anchored /api/send guard (#1) + suppress audit log (#2)
02b62f2d4c2228f3964479ca4796951595f03f8b · 2026-07-12 12:04:27 -0700 · Steve Abrams
Files touched
Diff
commit 02b62f2d4c2228f3964479ca4796951595f03f8b
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Sun Jul 12 12:04:27 2026 -0700
yolo contrarian-gate fixes: socket-anchored /api/send guard (#1) + suppress audit log (#2)
---
server.js | 13 +++++++++++--
1 file changed, 11 insertions(+), 2 deletions(-)
diff --git a/server.js b/server.js
index d292385..675dc32 100644
--- a/server.js
+++ b/server.js
@@ -441,7 +441,12 @@ app.post('/api/send', async (req, res) => {
// users may view + draft, but firing client email is restricted to a direct
// local-loopback operator (someone physically at this Mac). Basic Auth alone is not a
// strong enough gate to hand the internet a send-as-info@ button.
- if (req.get('cf-connecting-ip') || req.get('x-forwarded-for')) {
+ // Anchor on the UNFORGEABLE socket origin, not on client-settable headers (contrarian
+ // Hole #1): only a direct local-loopback connection may fire a send. Tunnel requests
+ // arrive from cloudflared (non-loopback socket + cf-connecting-ip); loopback callers
+ // never carry those proxy headers. Header check kept as belt-and-suspenders.
+ const sendRemote = req.socket && req.socket.remoteAddress;
+ if (!LOOPBACK.has(sendRemote) || req.get('cf-connecting-ip') || req.get('x-forwarded-for')) {
return res.status(403).json({ error: 'Sending is disabled over the public tunnel. Open this tool locally to send — drafting and preview still work remotely.', tunnelBlocked: true });
}
const { to, subject, body, samples, list, account: acctNo, company } = req.body || {};
@@ -515,7 +520,11 @@ app.get('/api/suppress', (_req, res) => res.json({ suppressed: [...loadSuppress(
app.post('/api/suppress', (req, res) => {
const email = String(req.body?.email || '').trim();
if (!email) return res.status(400).json({ error: 'email required' });
- res.json(addSuppress([email]));
+ const result = addSuppress([email]);
+ // Audit trail (contrarian Hole #2): manual suppression is a mutating action any authenticated
+ // user can reach — record who/when so a silent suppress of a real buyer is traceable.
+ try { fs.appendFileSync(path.join(DATA_DIR, 'suppress-log.jsonl'), JSON.stringify({ ts: new Date().toISOString(), email: email.toLowerCase(), ip: clientIp(req), added: result.added }) + '\n'); } catch {}
+ res.json(result);
});
// GET /api/scan-unsubscribes — find clients who replied "UNSUBSCRIBE" to info@ and suppress them.
← 211aef3 yolo: baseline security headers (nosniff, frame-DENY, no-ref
·
back to Dw Pitch Followup
·
chore: v0.3.0 (session close — public tunnel + auth hardenin 93dde31 →