← back to Slack Idea Board
Idea-board: localhost-only spawn guard (public tunnel previews/copies, never spawns)
fac3b236a8406c8b160856a52555b99d1fb5ce21 · 2026-08-06 17:01:07 -0700 · Steve Abrams
Files touched
M public/index.htmlM server.js
Diff
commit fac3b236a8406c8b160856a52555b99d1fb5ce21
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Thu Aug 6 17:01:07 2026 -0700
Idea-board: localhost-only spawn guard (public tunnel previews/copies, never spawns)
---
public/index.html | 3 ++-
server.js | 9 +++++++++
2 files changed, 11 insertions(+), 1 deletion(-)
diff --git a/public/index.html b/public/index.html
index 89ee279..8662a7e 100644
--- a/public/index.html
+++ b/public/index.html
@@ -160,7 +160,8 @@ async function launchAction(id, step, label, btn){
if(Date.now() + RETRY_MS > deadline) break; // give up → falls through to the throttle toast
for(let s=Math.round(RETRY_MS/1000); s>0; s--){ setLbl(`throttled · retry ${s}s`); await sleep(1000); }
}
- if(j.launched){ toast(`🚀 <b>${esc(label||step)}</b> launched in a new iTerm2 tab<br><code>${esc((j.prompt||'').slice(0,160))}…</code>`); }
+ if(j.launched){ toast(`🚀 <b>${esc(label||step)}</b> launched in a new iTerm2 window<br><code>${esc((j.prompt||'').slice(0,160))}…</code>`); }
+ else if(j.localOnly){ toast('🔒 '+esc(j.reason||'spawn is localhost-only')+'<br>Tip: Shift-click to copy the command instead.'); }
else if(j.throttled){ toast('⏳ Still throttled after retries — '+esc(j.reason||'try again shortly')); }
else if(j.needsConfirm){ toast('Launch blocked — server requires confirm'); }
else { toast('Launch failed: '+esc(j.error||'unknown')); }
diff --git a/server.js b/server.js
index 741df64..efef8e0 100644
--- a/server.js
+++ b/server.js
@@ -232,6 +232,15 @@ const server = http.createServer(async (req, res) => {
res.writeHead(200, { 'Content-Type': 'application/json' });
return res.end(JSON.stringify({ launched: false, dryRun: !!j.dryRun, needsConfirm: !j.dryRun, step, cwd, prompt }));
}
+ // Guard 0 — spawn is LOCALHOST-ONLY. The board is exposed to the internet via a Cloudflare
+ // tunnel (basic-auth only), so a real `claude --model opus` terminal spawn on this Mac must
+ // never be triggerable from the public URL. Preview/copy still works everywhere; only the
+ // actual spawn is gated. The tunnel is identified by the proxy headers Cloudflare injects.
+ const viaProxy = !!(req.headers['cf-ray'] || req.headers['x-forwarded-for'] || req.headers['x-forwarded-host']);
+ if (viaProxy) {
+ res.writeHead(403, { 'Content-Type': 'application/json' });
+ return res.end(JSON.stringify({ launched: false, localOnly: true, reason: 'spawn is localhost-only — open http://localhost:9820 on the Mac to launch', step }));
+ }
const key = (j.id || '') + '|' + step;
const now = Date.now();
// Guard 2 — per-idea in-flight + cooldown dedupe: drop a repeat of the same idea within the cooldown.
← 0638b1f Idea-board: launch in a NEW iTerm2 window (not a tab); lift
·
back to Slack Idea Board
·
creds-safe fetch guard: resolve relative fetch vs credential 48fce21 →