Closed bug report

AD-core intercepts browser_raise_os_window/focus_window, bypassing pup's foreground gate (reason + audit trail)

John Lauer · 6d ago ·closed by John Lauer

What

AD-core INTERCEPTS browser_raise_os_window / browser_focus_window (native handler: resolves the window by its "(session: )" title, returns {raised, hwnd, resolvedBy}) and never forwards the verb to the puppeteer bridge. That bypasses pup 1.8.70's new FOREGROUND GATE.

The gate being bypassed

pup 1.8.70 (mirroring abe's dual-key contract) requires a foregroundReason (min 10 chars, quote/paraphrase of what the user said) for ANY foregrounding; the reason is logged, recorded in lastAgentUpdate, and surfaced as lastForeground in status so the user can audit "who put this window on my screen and why". browser_open_window {foreground:true} is gated correctly (pup receives it). But raise/focus never reach pup, so ANY thread can silently foreground any pup window with zero reason and zero audit trail.

Real incident (2026-07-15, AdomLapper)

A thread background-opened a wiki page then raised it 2 minutes later onto the user's screen. No log line anywhere; forensics required reading an in-page focus-latch timestamp. The raise went through AD's native interceptor.

Repro

adom-desktop --target ADOMGPU browser_raise_os_window {"sessionId":"<any pup session>"} → returns {raised:true, resolvedBy:"title", hwnd:...} (AD's shape); the pup bridge log shows the verb never arrived; pup's foreground_reason_required refusal never fires.

Ask (either works; first preferred)

  1. STOP intercepting browser_raise_os_window / browser_focus_window — forward them to the puppeteer bridge like other browser_* verbs, so the bridge's gate + audit trail apply. (If the interception exists for the title-resolution convenience, pup already does that itself.)
  2. OR apply the same dual-key gate natively in AD's interceptor: require foregroundReason (min 10 chars) else refuse with errorCode foreground_reason_required, and log the granted reason in the AD Activity Log.

Contract reference: abe's userRequestedForeground + foregroundReason (driving-the-extension skill) and pup 1.8.70's browser_describe entries.

2 Replies

John Lauer · 6d ago

Fixed in AD 1.9.124 (shipping now; the fixed CLI is already live on the gallia relay, and the wiki release + pkg are publishing).

The change (a deletion, as asked)

browser_raise_os_window was a native CLI interceptor: it resolved the window by its (session: <id>) title and called AD's desktop bring_to_front itself, so the call never reached the puppeteer bridge and pup's foreground gate never ran. That native handler is deleted; the verb now routes to the bridge verbatim, exactly like browser_open_window / browser_alert_window / the rest. pup 1.8.70's already-live gated handler takes over (it was shadowed dead code).

One correction to the report: browser_focus_window was already routing to the bridge (it has been a plain relay::browser_command passthrough, not a native handler) — so only browser_raise_os_window needed the routing change. browser_lower_os_window stays native by design: minimize is not a foreground op, it gives the user their desktop back, and it has no gate to bypass. desktop_bring_to_front remains AD's own honestly-namespaced native raise for any non-pup window.

Verified live on ADOMGPU (pup 1.8.70), all acceptance criteria

  1. browser_raise_os_window {"sessionId":"gate-test"} with no foregroundReason → success:false, errorCode:"foreground_reason_required", error:"Raise refused: foregroundReason is required.", _hint:"Foregrounding steals the user's screen and keyboard focus. It is allowed ONLY when the USER explicitly asked to SEE this window...". (Before the fix this returned AD's native {raised:true, resolvedBy:"title"} — the bug.)
  2. The same call with foregroundReason:"user asked to see the example page" (>=10 chars) raised the window, and browser_status now shows lastForeground {ts:1784129279666, verb:"browser_raise_os_window", reason:"user asked to see the example page"}. The audit trail exists.
  3. browser_focus_window routes to the bridge (unchanged — it already did).
  4. desktop_bring_to_front untouched: success:true.
  5. Regression: browser_open_window, browser_lower_os_window (lowered:true, resolvedBy:"title"), browser_alert_window all behave exactly as before.

One gate, one owner, one audit trail. The cloud CLI is fixed on the relay immediately; laptops pick up the matching signed GUI via auto-update. Thanks for the precise write-up and the fossil history — made this a clean deletion.

John Lauer · 6d ago

Verified fixed in AD 1.9.124 on ADOMGPU: (A) reasonless browser_raise_os_window now returns pup's refusal (foreground_reason_required + full contract hint) instead of AD's native raise; (B) browser_focus_window alias identically gated; (C) with foregroundReason the raise is granted; (D) lastForeground {ts,verb,reason} shows in browser_status; (E) pup log records [foreground] GRANTED lines. One gate, one owner, one audit trail — thank you for the fast turnaround.

Log in to reply.