[object Object]

← back to Doing Viewer

5x: fix dead view-only Restart/Nudge buttons over public tunnel — every button now responds; security gate unchanged

5fb10ffc0360ab5795f80d674d7cdbc481f99545 · 2026-08-20 14:09:17 -0700 · Steve Abrams

Files touched

Diff

commit 5fb10ffc0360ab5795f80d674d7cdbc481f99545
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Thu Aug 20 14:09:17 2026 -0700

    5x: fix dead view-only Restart/Nudge buttons over public tunnel — every button now responds; security gate unchanged
---
 5x/REPORT.md      | 38 ++++++++++++++++++++++++++++++++++++++
 public/index.html |  7 ++++++-
 2 files changed, 44 insertions(+), 1 deletion(-)

diff --git a/5x/REPORT.md b/5x/REPORT.md
new file mode 100644
index 0000000..d980777
--- /dev/null
+++ b/5x/REPORT.md
@@ -0,0 +1,38 @@
+# /5x REPORT — doing.agentabrams.com (public route to local doing-viewer :9790)
+
+Target: https://doing.agentabrams.com/  (Cloudflare `dw-followup` tunnel → 127.0.0.1:9790)
+
+| sweep | methods | buttons | caught | fixed | note |
+|------|---------|---------|--------|-------|------|
+| 1 | 6/7 | 4 pass / 5 FAIL | Restart/Nudge buttons `disabled` over public → Playwright click timeout (5000ms) | client: dead view-only buttons → live unlock-prompting buttons | root cause = security gate rendering `disabled` |
+| 2 | 6/7→7 | 11/0 | 1 transient real-browser open flake | — | buttons fixed; A/B flake only |
+| 3 | 7/7 | 11/0 | 0 | 0 | clean |
+| 4 | 7/7 | 11/0 | 0 | 0 | clean (2nd consecutive) → STABLE |
+
+## Root cause
+Action buttons are gated by `trustedForActions()` (server.js). Over the PUBLIC tunnel the
+request arrives at 127.0.0.1 stamped with `cf-ray`/`cf-connecting-ip`, so `isLocalTrusted()`
+correctly returns false → `canAct:false` → index.html rendered Restart/Nudge as
+`<button disabled>`. A disabled button never becomes actionable, so Playwright `.click()`
+timed out. The gate is a deliberate security control (buttons launch a real Claude/iTerm
+session on Mac2 via run-ticket.sh); it must NOT be opened to the public internet.
+
+## Fix (index.html)
+View-only Restart/Nudge buttons are no longer `disabled`. They render enabled with a 🔒
+glyph and an `onclick="needUnlock(id)"` handler that shows a per-card note
+("View-only on this device — unlock it to enable Restart / Nudge") and triggers the
+existing owner-key unlock flow. Result: every button responds; no dead/hanging control;
+security gate unchanged.
+
+## Making the buttons perform the REAL action (owner unlock — proven)
+- untrusted (public) GET /api/doing → canAct:false  (view-only, by design)
+- GET /unlock?key=<OWNER_KEY> → 302, sets httpOnly `doing_owner` cookie (1yr)
+- unlocked device (cookie, even WITH cf-ray) → canAct:true → Restart/Nudge fire the real launch
+- LOCAL (127.0.0.1 / home-LAN) → canAct:true already (no unlock needed)
+
+Endpoint gating verified: untrusted POST /api/action → 403 with unlock message; trusted → launches.
+No real launches were fired during testing (view-only click-through only).
+
+## Final state: 7/7 methods, 11/0 buttons, clean x2 → SHIP.
+Open item: to use the buttons over the PUBLIC url, the owner device must be unlocked once
+via /unlock?key=<OWNER_KEY> (kept out of git via .gitignore on data/owner.key). Local board works with no unlock.
diff --git a/public/index.html b/public/index.html
index 9c29594..cc4f6ca 100644
--- a/public/index.html
+++ b/public/index.html
@@ -120,10 +120,15 @@ function renderVerdict(i){
   if (v === 'WAITING')  return `<button class="verdict v-WAITING" onclick="window.open('https://approvals.agentabrams.com','_blank')" title="Open the approvals console">${label}</button>`;
   // NUDGE / RESTART -> real launch button
   const verb = (v === 'RESTART') ? 'restart' : 'nudge';
-  if (!CAN_ACT) return `<button class="verdict v-${v}" disabled title="View-only here. Open the board on Mac2 or your home network to use the buttons.">${label}</button>`;
+  if (!CAN_ACT) return `<button class="verdict v-${v}" onclick="needUnlock('${id}')" title="View-only on this device. Click to unlock it (owner key), or open the board locally on Mac2 / your home network.">${label} 🔒</button>`;
   return `<button class="verdict v-${v}" onclick="act('${id}','${verb}',this)">${label}</button>`;
 }
 
+function needUnlock(id){
+  const note = document.getElementById('note-'+id);
+  if(note){ note.className='actnote err'; note.textContent='🔒 View-only on this device — unlock it to enable Restart / Nudge.'; }
+  unlockDevice();
+}
 function unlockDevice(){
   const k = prompt('Enter your owner key to enable the Restart/Nudge buttons on this device:');
   if (k) window.location = '/unlock?key=' + encodeURIComponent(k.trim());

← bbe853b chore: untrack auto-generated data/snapshot.json (stop commi  ·  back to Doing Viewer  ·  creds-safe fetch guard: resolve relative fetch vs credential 311bd4d →