[object Object]

← back to Bubbesblock

TK-11341: strip AdSense loader from private /inbox route (serve-time guard); revert to restore

f17e95dfe9ecaf028f712c834c1899e1eaee64b8 · 2026-09-10 11:54:08 -0700 · Steve Abrams

Serve inbox/notifications/bookmarks with any AdSense loader stripped at
serve-time (before express.static), so a stale on-disk file can never re-leak
ads on a private, login-gated page. Public content pages keep their loader.
Root cause of the live leak: commit 5a6874a's per-file fix was never deployed
to prod; this guard also hardens against that recurrence.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BQfWFUMW9XZMNwUAuHNtFq

Files touched

Diff

commit f17e95dfe9ecaf028f712c834c1899e1eaee64b8
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Thu Sep 10 11:54:08 2026 -0700

    TK-11341: strip AdSense loader from private /inbox route (serve-time guard); revert to restore
    
    Serve inbox/notifications/bookmarks with any AdSense loader stripped at
    serve-time (before express.static), so a stale on-disk file can never re-leak
    ads on a private, login-gated page. Public content pages keep their loader.
    Root cause of the live leak: commit 5a6874a's per-file fix was never deployed
    to prod; this guard also hardens against that recurrence.
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
    Claude-Session: https://claude.ai/code/session_01BQfWFUMW9XZMNwUAuHNtFq
---
 server.js | 21 ++++++++++++++++++++-
 1 file changed, 20 insertions(+), 1 deletion(-)

diff --git a/server.js b/server.js
index 6d6b143..b25e463 100644
--- a/server.js
+++ b/server.js
@@ -25,6 +25,23 @@ try { homeHistory = require('./lib/home-history.js'); } catch { /* not built yet
 
 app.use(express.json({ limit: '12kb' }));
 app.use(cookieParser(SECRET));
+
+// Private/authed pages must NEVER carry AdSense (Google policy: no ads on
+// private, login-gated content). Strip any AdSense loader at serve-time so a
+// stale on-disk file can never re-leak ads on a private route (TK-11341).
+// Runs BEFORE express.static so both /inbox and /inbox.html are covered.
+const PRIVATE_PAGES = ['inbox', 'notifications', 'bookmarks'];
+const ADSENSE_LOADER_RE = /[ \t]*<script[^>]*(?:googlesyndication\.com|adsbygoogle)[^>]*>\s*<\/script>\s*\n?/gi;
+function sendPageNoAds(res, file) {
+  fs.readFile(path.join(__dirname, 'public', file), 'utf8', (err, html) => {
+    if (err) return res.sendStatus(404);
+    res.type('html').send(html.replace(ADSENSE_LOADER_RE, ''));
+  });
+}
+app.get(PRIVATE_PAGES.flatMap(r => ['/' + r, '/' + r + '.html']), (req, res) => {
+  sendPageNoAds(res, path.basename(req.path).replace(/\.html$/, '') + '.html');
+});
+
 app.use(express.static(path.join(__dirname, 'public')));
 app.get('/healthz', (_req, res) => res.json({ ok: true, service: 'bubbesblock', db: !!pool }));
 
@@ -354,9 +371,11 @@ app.get('/api/home-history', async (req, res) => {
 });
 
 // ===================== page routes =====================
+// (Private pages inbox/notifications/bookmarks are served ad-stripped by the
+// guard registered before express.static above — TK-11341.)
 app.get('/p/:id', (_req, res) => res.sendFile(path.join(__dirname, 'public', 'post.html')));
 app.get('/opportunities', (_req, res) => res.sendFile(path.join(__dirname, 'public', 'opportunities.html')));
-for (const r of ['events', 'groups', 'bookmarks', 'search', 'inbox', 'notifications']) {
+for (const r of ['events', 'groups', 'search']) {
   app.get('/' + r, (_req, res) => res.sendFile(path.join(__dirname, 'public', r + '.html')));
 }
 for (const r of ['about', 'guidelines', 'privacy', 'help']) {

← 5a6874a Keep AdSense out of private notification pages  ·  back to Bubbesblock  ·  Add real privacy page + /privacy route (was catch-all servin 0f22c5c →