Skip to content

Do not open loopback computer URLs from a LAN tab - #11

Draft
matthewhand wants to merge 566 commits into
mainfrom
feat/lan-loopback-viewer-v2
Draft

Do not open loopback computer URLs from a LAN tab#11
matthewhand wants to merge 566 commits into
mainfrom
feat/lan-loopback-viewer-v2

Conversation

@matthewhand

@matthewhand matthewhand commented Aug 18, 2026

Copy link
Copy Markdown
Owner

Reviewer brief

This is a draft on matthewhand/OpenMausBot, not an upstream PR to milind-soni/OpenMausBot. One concern: noVNC / join URLs bound to 127.0.0.1 must not be opened from a LAN browser tab.

Read src/lib/loopback-viewer.ts first. The UI files only call those helpers.

Why this exists

The Local VM viewer is bound to 127.0.0.1:6080 on the host. Opening that URL from another machine hits that client's loopback, which has no viewer. Cloud desktop URLs are fine from anywhere.

What to look at

File Why it is here
src/lib/loopback-viewer.ts loopbackViewerUsable / canOpenExternalUrl. Treats IPv4-mapped forms (::ffff:127.0.0.1 and Node's [::ffff:7f00:1]) as loopback. Invalid URLs return false.
src/lib/loopback-viewer.test.ts Host vs LAN, cloud URL from LAN, IPv4-mapped, unparseable URL.
src/lib/frame-src.ts Normalizes raw base64 vs already-prefixed data: URLs so Watch/VM previews do not double-prefix.
src/components/LocalComputerSection.tsx Watch screen (harness screenshot overlay) always when ready. Open noVNC only on loopback.
src/components/ComputerPanel.tsx Join: missing URL / loopback-from-LAN / pop-up blocked become errors. Preview polls abort on unmount.
vite.config.ts OMB_PORT || OGB_PORT. No bearer inject.

Security notes (please check these)

  • This PR does not add a loopback exemption to LAN auth.
  • This PR does not inject Authorization on the Vite /api proxy.
  • Watch screen uses existing POST /api/local-computer/screenshot.
  • A loopback join URL from a LAN tab tells you to use the in-panel live preview (or open the app on the host). It does not point at App Settings Watch screen — that control is Local VM only.

How to verify

pnpm exec vitest run src/lib/loopback-viewer.test.ts src/lib/frame-src.test.ts
pnpm typecheck

Manual:

  1. On the host (http://127.0.0.1:…): Open noVNC still appears.
  2. From another machine on the LAN: Open noVNC is hidden; Watch screen shows the overlay.
  3. Cloud Open desktop still works from LAN (non-loopback URL).
  4. A loopback join URL from a LAN tab shows an error, not a dead tab.
  5. Confirm vite.config.ts has no bearer-token inject.

Out of scope

LAN auth, MCP, TTS, comm popups. README marketing PNGs are stale separately (computer-panel still shows 3-way Runs on / Routines).

Process note

Local commits: 9e83916, a3ebc17, 94ea252 in C:\\OpenMausBot-pr-lan-viewer. Do not merge to milind-soni/OpenMausBot from this draft.

@amazon-q-developer amazon-q-developer Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR implements a security improvement by preventing loopback URLs from being opened when the page is accessed over LAN. The implementation properly adds validation for loopback addresses and surfaces error messages instead of failing silently.

Key changes reviewed:

  • New loopback validation utilities with comprehensive test coverage
  • Enhanced error handling for desktop link creation and pop-up blocking
  • "Watch screen" feature added for LAN users as alternative to noVNC
  • Vite proxy configuration updated to check both OMB_PORT and OGB_PORT

Critical change requested (1):

  • Fix bracket removal regex to prevent potential security bypass

The approach is sound and addresses the stated problem effectively. Once the regex fix is applied, this will be ready to merge.


You can now have the agent implement changes and create commits directly on your pull request's source branch. Simply comment with /q followed by your request in natural language to ask the agent to make changes.

/** noVNC is bound to 127.0.0.1:6080 on purpose. Opening that URL from a
* LAN browser hits the *client's* loopback, which has no viewer. */
export function loopbackViewerUsable(hostname: string): boolean {
const host = hostname.replace(/^\[|\]$/g, "").toLowerCase();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The regex /^\[|\]$/g only removes brackets at the start OR end but doesn't handle brackets in the middle of hostnames. Consider using /[\[\]]/g to remove all brackets, preventing bypass via hostnames like [127.0.0.1]foo which would currently pass validation.

Suggested change
const host = hostname.replace(/^\[|\]$/g, "").toLowerCase();
const host = hostname.replace(/[\[\]]/g, "").toLowerCase();

milind-soni and others added 28 commits August 24, 2026 23:13
)

Adopts the open agentskills.io format (Apache-2.0 spec). Server v1:

- fetch from GitHub (owner/repo, tree/blob/raw URLs) with the registry's
  discovery walk (skills/, .claude/skills, .agents/skills), size caps
- markdown-only: scripts are recorded as skipped, never written — the
  Snyk/Koi registry audits found confirmed exfil payloads almost always
  in scripts
- imports land DISABLED with provenance (source + sha256) and static
  red-flag scan (base64 blobs, curl|sh, invisible Unicode); a person
  enables after reading the full SKILL.md
- enabled skills reach the bot like MEMORY.md does: a budgeted
  name+description index in the system prompt, files read on demand —
  plus symlinks into .claude/skills, .agents/skills and .grok/skills in
  the workspace so CLIs with native skill support load them themselves
- API: list / import / read / enable / remove under /api/bots/:id/skills

UI (import modal in bot settings) is the follow-up PR.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* feat: add secure credential request cards

* fix: make credential card resume recoverable

* fix: surface room credential resume failures

* fix: drain credential resumes after terminal failures

* fix: isolate room credential requests and decline retries
* feat: add section-scoped shared context

* fix: guard unsaved section context
* feat: preview image attachments in app

* fix: keep attachment preview keyboard-safe
* feat: add chat find and flat replies

* fix: resolve reply quotes across branch history
milind-soni#425 made sidebar mascots mount paused — and exposed that the parked
loop never draws: the SVG layers hold no expression until the first
draw() positions them, so an idle bot's avatar rendered blank. The
paused branch now paints the still face once, re-painting only when
what it shows changes (state, pinned expression, gradient), then parks
on the same 4Hz wake-poll. Animation stays opt-in; the resting pose is
simply visible again.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* Add portable BotMRR package installs

* Keep package link tests out of Vitest discovery

* Add universal BotMRR Markdown imports

* Document portable team playbooks
…i#346)

* fix(linux): fail closed on unsafe local control startup

* fix(desktop): keep optional connected apps off startup path

* fix(linux): repair inherited DEB upgrade permissions

* docs(linux): document the local-control safety hold

* fix(ci): configure dependencies in DEB upgrade smoke

* fix(linux): configure DEB Chromium sandbox

* fix(linux): restore safe Xorg local control

* fix(desktop): close review security and refresh races

* fix(linux): isolate local control safety opt-in

* docs(linux): explain private CUA cursor behavior

* fix(linux): clean CUA runtime on termination signals

* fix(linux): reap stale AppImage CUA stages

* docs(linux): clarify release CUA coverage

* test(linux): preserve packaged smoke diagnostics

* ci(linux): normalize runner package parent

* fix(linux): close final Ubuntu review gaps

* fix(ci): fail closed before Ubuntu package install

* fix(composio): enforce broker URL parity
…lind-soni#447)

* Let Antigravity models control computers (mount the computer MCP)

agy has no per-turn MCP flag and provably no project-level MCP config
(1.1.19: embedded docs list only the global
~/.gemini/config/mcp_config.json and per-plugin files; agy mcp list
ignores .gemini/{settings,mcp_config}.json in the cwd). So each turn
upserts one key — openmausbot-computer — into the global file right
before the spawn, preserving every other byte of the user's config and
tolerating malformed JSON, and removes that key on the next
computer-less turn so tools and box/control tokens cannot leak into
later turns or the user's own agy sessions.

Cloud boxes mount OpenMausBot's REST-to-MCP computer proxy (resolved
via SPAWNED_PROXIES — never relative to the module, the 0.1.24
lesson); Local VM and VPS connections pass through as the stdio Cua
command they already are.

computerMcp is advertised only by full-auto instances: print mode has
no interactive approval channel, and outside
--dangerously-skip-permissions agy auto-denies tools that would
prompt, so a non-fullAuto mount could never fire. localComputerMcp
stays unset — the host desktop requires per-action human approval,
which print mode cannot deliver in any mode.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix: isolate Antigravity computer mounts

* fix: reap settled Antigravity children

* fix: keep MCP lease until child exit

* fix: preserve Antigravity MCP ownership

* fix: clear failed Antigravity turns

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…k actions (milind-soni#441)

* feat(electron): right-click context menu with clipboard and spellcheck actions

Adds a context-menu handler wiring Undo/Redo/Cut/Copy/Paste/
Paste-and-Match-Style/Select All plus spellcheck suggestions
and copy-link, so text fields behave like native macOS apps.

* refactor(electron): skip context menu when nothing is actionable; popup targets the source frame

---------

Co-authored-by: Milind Soni <46266943+milind-soni@users.noreply.github.com>
…milind-soni#442)

* feat(ui): composer attach button and per-bot permission mode selector

Adds a paperclip button that opens a file picker feeding the shared
attachment pipeline, and an Approve-for-me / Ask-for-approval pill that
toggles autoApprove per bot without opening settings. The composer is
restructured into two rows — text on top, controls below — matching
common chat-app layouts.

* feat(ui): remove the Always allow button from approval cards

The per-bot permission mode selector in the composer (Ask for approval /
Approve for me) is the single mechanism for reducing approval prompts;
the per-program Always-allow grant duplicated it with a worse model.
Allow-once and Deny remain.

* Paint the resting face when a mascot mounts paused (milind-soni#444)

milind-soni#425 made sidebar mascots mount paused — and exposed that the parked
loop never draws: the SVG layers hold no expression until the first
draw() positions them, so an idle bot's avatar rendered blank. The
paused branch now paints the still face once, re-painting only when
what it shows changes (state, pinned expression, gradient), then parks
on the same 4Hz wake-poll. Animation stays opt-in; the resting pose is
simply visible again.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

* fix(composio): preload connected account state (milind-soni#445)

* chore(release): bump version to 0.1.33 (milind-soni#446)

* Add portable BotMRR Markdown playbooks (milind-soni#426)

* Add portable BotMRR package installs

* Keep package link tests out of Vitest discovery

* Add universal BotMRR Markdown imports

* Document portable team playbooks

* fix(composio): accept empty authorization bodies (milind-soni#451)

* ci: stop retaining disposable Linux packages (milind-soni#452)

* fix(linux): harden Ubuntu upgrades and Xorg local control (milind-soni#346)

* fix(linux): fail closed on unsafe local control startup

* fix(desktop): keep optional connected apps off startup path

* fix(linux): repair inherited DEB upgrade permissions

* docs(linux): document the local-control safety hold

* fix(ci): configure dependencies in DEB upgrade smoke

* fix(linux): configure DEB Chromium sandbox

* fix(linux): restore safe Xorg local control

* fix(desktop): close review security and refresh races

* fix(linux): isolate local control safety opt-in

* docs(linux): explain private CUA cursor behavior

* fix(linux): clean CUA runtime on termination signals

* fix(linux): reap stale AppImage CUA stages

* docs(linux): clarify release CUA coverage

* test(linux): preserve packaged smoke diagnostics

* ci(linux): normalize runner package parent

* fix(linux): close final Ubuntu review gaps

* fix(ci): fail closed before Ubuntu package install

* fix(composio): enforce broker URL parity

* Let Antigravity models control computers (mount the computer MCP) (milind-soni#447)

* Let Antigravity models control computers (mount the computer MCP)

agy has no per-turn MCP flag and provably no project-level MCP config
(1.1.19: embedded docs list only the global
~/.gemini/config/mcp_config.json and per-plugin files; agy mcp list
ignores .gemini/{settings,mcp_config}.json in the cwd). So each turn
upserts one key — openmausbot-computer — into the global file right
before the spawn, preserving every other byte of the user's config and
tolerating malformed JSON, and removes that key on the next
computer-less turn so tools and box/control tokens cannot leak into
later turns or the user's own agy sessions.

Cloud boxes mount OpenMausBot's REST-to-MCP computer proxy (resolved
via SPAWNED_PROXIES — never relative to the module, the 0.1.24
lesson); Local VM and VPS connections pass through as the stdio Cua
command they already are.

computerMcp is advertised only by full-auto instances: print mode has
no interactive approval channel, and outside
--dangerously-skip-permissions agy auto-denies tools that would
prompt, so a non-fullAuto mount could never fire. localComputerMcp
stays unset — the host desktop requires per-action human approval,
which print mode cannot deliver in any mode.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix: isolate Antigravity computer mounts

* fix: reap settled Antigravity children

* fix: keep MCP lease until child exit

* fix: preserve Antigravity MCP ownership

* fix: clear failed Antigravity turns

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Milind Soni <46266943+milind-soni@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Kesley David <39314443+KesleyDavid@users.noreply.github.com>
Co-authored-by: milind-soni <milindsoni201@gmail.com>
…tools only

Every permission event was stamped approvalScope=local-computer whenever a
bot had This computer enabled — including plain Bash/Edit asks. The server
suppresses allowKey when a scope is set, so the Always-allow button never
rendered for any tool on such bots. Now only mcp__computer* tools carry the
scope; request.resolved looks the tool up from the pending ask.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ew-live-desktop

feat(computer): open live desktop from preview
…y-foundation

fix(ios): pair through every trusted route
ericmaddox and others added 27 commits August 27, 2026 19:23
…-status-skin-tokens

fix(onboarding): use the success and warning tokens for status badges
…-rename

fix(tasks): let the header picker actually rename
…-vm-20260822

Local VM: add secure two-up workspace and Chief status awareness
feat(channels): add separate task conversations
Review routine approvals without crossing provider boundaries
feat(mcp): add Model Context Protocol (MCP) server for external agent orchestration
Users can explicitly ask a bot to list, schedule, update, pause, resume, run, or delete routines in chat. Every mutation remains inert until a durable, exact-detail confirmation card is confirmed, with ownership, stale-state, cloud-readiness, redaction, audit, recovery, and voice safeguards.
…a, model-shaped coercion, guiding errors (milind-soni#544)

Field report (0.1.38, hours after milind-soni#540 shipped): a bot's propose_routine
failed on every attempt — the model tried a 30-minute interval, was told no,
fell back to "daily", failed again, then gave up ("the routine proposal tool
is returning errors every time — it's not accepting any input").

Reproduced the whole pipeline against a live server: the endpoint and proxy
are correct for valid payloads. What actually breaks in the field is the
tool's advertised schema: schedule was a oneOf of two const-discriminated
branches — exactly the JSON-Schema keywords several agent CLIs flatten or
drop when converting MCP tools into their provider's function-call format
(codex only began preserving oneOf in June 2026; other drivers still
simplify). A model that never saw the branches guesses shapes forever, and
every guess failed with a message that never taught it the right one.

Three changes, all proxy-side — the harness dialect is untouched:

- The schedule schema is now one flat object (type enum once|weekly|daily +
  at/time/weekdays), free of oneOf/const/format, so it survives any
  conversion. Rules live in descriptions and are enforced with words.
- normalizeScheduleInput coerces what models actually send: "daily" becomes
  weekly-on-all-seven-days on the wire, JSON-string schedules are parsed,
  weekday names are case-folded and short names (mon..sun) expanded.
- Unsupported shapes now answer with instructions instead of a wall: a
  sub-day interval is named as unsupported with the closest alternatives,
  weekly-without-weekdays points at "daily", unknown types list the three
  supported shapes with examples.

Verified end-to-end against a live server: daily / stringified / short-cap
payloads now produce confirmation cards, interval and missing-weekdays get
guiding errors, and the valid-weekly path is byte-identical. Mutation check:
disabling the daily alias fails the new tests.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* feat(chat): dock the composer over the transcript

Bubbles scroll into the pill instead of clipping on a black bar.
The pill has no hairline; app-ground under it keeps text from
painting below the dock.

* docs(chat): before/after captures for the composer dock

---------

Co-authored-by: Max <ajsdaksfjhs@gmail.com>
…sion eats composition keywords (milind-soni#547)

Codified from the milind-soni#544 field failure: schedule was a oneOf of const-branches,
several engines' MCP-to-provider converters flattened it, and models guessed
shapes forever. The rule, the coercion posture, and the errors-must-teach
posture now live next to the driver SPI guidance so the next tool surface
doesn't relearn it in production.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…m-9dfe

Co-authored-by: mhand <matthewhand@users.noreply.github.com>
Fork main is now even with milind-soni/OpenMausBot main (plus this
delta doc and prior merge commits). Feature work stays on the open
draft PRs.

Co-authored-by: mhand <matthewhand@users.noreply.github.com>
Keep loopback-viewer guards. Apply them to the new openDesktop path
and keep Watch screen next to the per-bot noVNC link.

Co-authored-by: mhand <matthewhand@users.noreply.github.com>
cursor Bot pushed a commit that referenced this pull request Aug 28, 2026
Record that PRs #11#19 and review/combined-prs-1-4 were merged
forward without rewriting history, and that closed #3/#4 branches
should stay retired.

Co-authored-by: mhand <matthewhand@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.