Skip to content

Enhance LM Studio security with user-controlled private network access - #3271

Merged
esokullu merged 17 commits into
webbrain-one:mainfrom
esokullu:main
Oct 27, 2026
Merged

esokullu merged 17 commits into
webbrain-one:mainfrom
esokullu:main

Conversation

@esokullu

Copy link
Copy Markdown
Collaborator

No description provided.

esokullu and others added 17 commits October 27, 2026 13:03
…ment

fetch_url and research_url advertised an allowPrivate argument in their
model-visible schemas and passed it straight to the URL and DNS guards, so
any page the model read could ask it to turn off loopback, RFC1918,
link-local and metadata blocking.

The argument is gone from both schemas. Private targets now open only
through a per-chat "Allow private network access" plugin setting (off by
default) that LM Studio shows to the user. fetchUrl and researchUrl take
the setting as a separate options parameter, so an allowPrivate key in
model arguments is ignored. Cloud metadata endpoints (169.254.169.254,
169.254.170.2, 169.254.170.23, 169.254.0.23, 100.100.100.200,
fd00:ec2::254, fd00:ec2::23, metadata.google.internal, metadata.goog,
metadata), including IPv4-mapped IPv6 spellings, DNS answers and redirect
targets, stay blocked even with the setting on. Refusals tell the model
to ask the user to change the setting instead of naming an argument.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
safeFetch resolved a hostname with dns.lookup, checked every address,
then called global fetch with the hostname, which resolved it again.
A TTL=0 record could answer public to the check and private to the
connection, on the first hop and on every redirect.

Each hop now goes through node:http/https with a custom lookup hook:
the socket's own resolution is checked (all addresses) and only those
addresses are dialled, so there is no second lookup to answer
differently. Requests use a fresh connection (agent: false) so a
pooled socket resolved for another request is never reused. The reply
is wrapped in a standard Response with url set; fetch's default
headers, gzip/deflate/br decoding, abort reasons and refusals (GET
with body, credentials in URL, CONNECT/TRACE) are kept, so fetch_url
and research_url are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Both loopback bridges trusted any socket that sent the extension's hello,
and a new socket replaced the connected extension before saying anything.
Any local process (or another extension) could take over the bridge,
receive private task payloads and answer with forged results.

The bridges now keep each new socket as a candidate. They send it a
random nonce, and it must return HMAC-SHA256(pairing key, label + nonce)
as well as a valid hello before it becomes the extension. Candidates get
no commands and never displace the paired extension; a wrong proof or no
proof within 10 s closes them with code 4001. The key never crosses the
socket and a recorded proof is useless on the next socket.

The key comes from WEBBRAIN_BRIDGE_KEY, WEBBRAIN_BRIDGE_KEY_FILE, or
~/.webbrain/bridge-key (created 0600 on first start and shared by both
bridges). `webbrain-mcp pairing-key` prints it. The extension gets a
Pairing key field under Settings -> Bridge (Chrome and Firefox), answers
the challenge with WebCrypto, and stops reconnecting after a refusal
until the settings change. Backends that never challenge are unaffected.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
upload_file put any absolute filePath or any browser-wide downloadId into a
page's file input after a host-wide upload grant (or with prompts off), and
the page could read the file at once. The approval never named the file.

The permission gate now resolves the source the handler will read and
approves that exact file for the page host. The card shows the file name,
local path and download address, offers Allow once / Don't allow only, and a
host-wide "always allow upload" grant no longer approves a file (a standing
deny still refuses). Files the run itself provided (a user attachment, a
download this run's tools saved, Firefox's picker) keep the ordinary gate.
Any other local file gets its own card on every call, even with prompts off
or after /dangerously-skip-permissions, and an unattended scheduled run
refuses it. The handler attaches nothing when the file it resolves is not
the approved one. Chrome and Firefox both.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… post

Colors the winning score in both result tables (light and dark themes),
adds an appendix with four inline SVG charts (difference per suite, score
per suite, vision categories, d1-2B decisions) and drops the budget figure.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EsDq6FMykCZm3KaSkXwy8c
Pin LM Studio fetches to the DNS answer the guard checked
…e-arg

Make LM Studio private-network access a user setting instead of a model argument
Pair the extension with the MCP and LM Studio bridges before trusting it
Bind upload_file approval to the exact local file
@vercel

vercel Bot commented Oct 27, 2026

Copy link
Copy Markdown

@esokullu is attempting to deploy a commit to the esokullu's projects Team on Vercel.

A member of the Team first needs to authorize it.

@esokullu
esokullu merged commit 3b149bd into webbrain-one:main Oct 27, 2026
0 of 2 checks passed
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.

2 participants