The sandbox opened the port
CVE-2026-65105 is a networking bug in NVIDIA NemoClaw, a tool whose whole purpose is running an AI agent inside a sandbox. The sandbox is a Docker container, containers cannot reach the host's loopback interface, so NemoClaw started the local model server on every interface instead — which switched off the one check that would have stopped a web page from talking to it. What a visiting page then took was the model's chat template, the layer that renders every system prompt before the model reads it. In Bromure Agentic Coding the local engine binds loopback on a port the guest never learns, and the guest reaches it over vsock instead of over a network.
The containment is what opened the hole. A sandbox that cannot reach your machine's loopback interface pushes the model server onto every interface instead, and then a browser tab can reach it too.
You are running your coding agent the careful way. It lives in a sandbox. The model it thinks with runs on your own machine, so no prompt leaves the building. You have done the two things everyone tells you to do.
Then you open a browser tab, and from that moment your agent starts writing code with a flaw in it that it declines to mention.
That is CVE-2026-65105, disclosed on August 25 by Elad Luz and Ofek Itach at Oasis Security, whose write-up calls it drive-by agent hijacking. They took it to NVIDIA's PSIRT before publishing, and the story ran the same day in The Hacker News, SiliconANGLE and CSO Online. NVIDIA has an advisory and a patch.
The product is NemoClaw, NVIDIA's tool for deploying an OpenClaw agent inside an OpenShell sandbox with a local Ollama backend for inference. Containment is the pitch.
The container could not reach loopback
Ollama listens on port 11434, and by default it binds 127.0.0.1 — the loopback
interface, reachable only from the machine itself.
OpenShell sandboxes are Docker containers. A container has its own network
namespace, so 127.0.0.1 inside the container is the container, not the host.
The agent in the sandbox therefore cannot reach a model server bound to the
host's loopback.
NemoClaw solved it in one environment variable. It launches Ollama with
OLLAMA_HOST=0.0.0.0:11434: every interface on the machine, including the one
facing your office wifi. The installer kept printing Using Ollama on localhost:11434 while the socket answered the world.
Then comes the step that turns a configuration choice into a vulnerability.
Ollama's API has no authentication, so it carries two guards instead. The first
is a CORS check on the Origin header. The second is a Host header allowlist
that accepts localhost, the machine's own hostname, and names ending in
.localhost, .local or .internal.
The second guard contains an exception. Ollama looks at the address it is bound
to, and if that address is not loopback, it skips the Host check entirely.
You can see why someone wrote that: an operator who binds a public interface
wants remote clients, and a Host allowlist built for loopback would turn every
one of them away. The result is that the setting which exposes the port is the
same setting that disarms the check protecting it.
That leaves the CORS check, and DNS rebinding walks through CORS by
construction. The attacker serves their page from a domain they control, on port
11434, then re-resolves that domain to 127.0.0.1. Origin and Host are now
the same attacker-chosen hostname, which Ollama reads as same-origin.
We covered the rebinding mechanism in detail last week,
so take it as given: a page you visit becomes a client for a service on your
machine, and it takes about a minute.
Rebinding is the interesting path in. It is also the harder one. A server on
0.0.0.0 answers every other device on the network segment with no browser
trick at all: the guest laptop, the smart TV, the compromised machine two desks
over.
They did not want code execution
Reaching the API is the boring half. What Oasis did once they had it is the new part.
The obvious move is to write a malicious system prompt into the model. That
fails here: OpenClaw sends its own system prompt in the messages array on every
request, and the client's prompt wins over the one stored in the model. So the
researchers went one layer down.
Ollama's /api/create endpoint accepts a template field. A chat template
is a Go template that turns the structured messages array — roles, contents,
tool definitions — into the single flat block of text the model actually reads.
It runs at inference time, after the client has handed over its messages, and it
touches all of them.
The attack is three requests. Read the real template with /api/show. Keep its
tool rendering and its special tokens exactly as they are, and change only how
system messages get rendered, appending an instruction of your choosing.
Write it back with /api/create.
The client still sends its own careful system prompt. The template wraps that prompt in the attacker's, every time, before the model sees a token of it. In Elad Luz's words, quoted by CSO Online, the instruction sits "one layer beneath anything a guardrail or an operator can see."
The edit lands in the model definition on disk, so it outlives the conversation, the reset and the restart. The model's metadata, size and listed capabilities stay the same, and nothing looks different in a list of installed models. Every consumer of that model gets it: the CLI you poke at by hand, and the agent running while you are at lunch.
Oasis lists what such an instruction is good for. None of it is dramatic enough to notice.
Write the bug in
Produce code with a subtle flaw in it — the kind that reads fine in review, because it was written to read fine in review.
Stay quiet
Never flag a security concern. An agent that used to warn you about something and no longer does is a hard thing to spot.
Steer the pick
Recommend a particular package, a particular URL, a particular configuration. You asked for a suggestion; you got one.
Send it home
Where the agent has network access, ship conversation contents and file contents to an endpoint of the attacker's choosing.
The rest of the unauthenticated API is a supporting cast: /api/generate and
/api/chat to run inference on someone else's GPU, /api/pull to fill a disk,
/api/delete to destroy the models, /api/push to publish under the victim's
ollama.com account, and /api/me to learn the hostname, public key and signed-in
username of whoever you just landed on.
The sandbox was never the blast radius
Oasis is careful about what the sandbox did and did not do. The OpenShell sandbox works: it isolates the filesystem, the network and the processes, and it keeps the endpoint clean. An agent inside an organization earns its keep by holding source control, CI/CD, internal APIs, cloud accounts, chat and a stack of MCP servers. Take the agent and you direct that access. The write-up's own summary is that the true blast radius follows the resources the agent is authorized to reach, not the boundary of its sandbox.
Which is the question a container never answers for you, and here it comes with a twist. The containment layer is what created the exposure. The Docker network namespace did its job. Reaching around it put the model server on the wifi.
Where the local model lives in a profile
Bromure Agentic Coding runs local inference too. A profile's agents can be pointed at an on-device model instead of a cloud provider, which is the same feature NemoClaw is offering. The plumbing underneath is built the opposite way round, and every step of it is a direct answer to something above.
The engine binds loopback, and never 0.0.0.0. The on-device model server
runs on the Mac, on 127.0.0.1, on a port the kernel hands out at launch
instead of a fixed number. The dynamic port is there for a dull reason: so
Bromure never fights whatever already holds the traditional 11434 on that Mac,
Ollama and LM Studio included. It also means there is no well-known port for a
page to aim at.
The guest does not reach it over a network. Inside the VM, the agent dials
127.0.0.1:11434, the address it expects. That connection goes nowhere near a
route. A bridge in the VM splices the TCP stream onto vsock, the virtio
socket transport, on port 8446, and the host end of the channel hands it to the
loopback engine. A vsock channel is a pipe between a hypervisor and one guest.
It has no IP address, no hostname and no DNS record, so there is nothing to
rebind, nothing to scan for, and no way for a browser to dial it. The guest
never learns which port the engine is on.
Nothing on your LAN can reach the VM. Under Resources → Network, the default mode is NAT: egress works, and nothing on your local network can reach the VM. That closes the half of the NemoClaw finding which needs no rebinding at all, the device on the same wifi dialling the port directly, and it closes it without you changing a setting.
There is no model definition in the VM to rewrite. Guest agents are pinned
to one model identifier, bromure-local, and they address it at bromure.llm,
a synthetic hostname with no DNS record behind it. The in-VM proxy intercepts
that name and forwards it to the host. Which model answers is a mapping you hold
on the host, in the profile's Local Models panel, and changing it is a
host-side remap: no reconfiguration inside the VM, no agent restart. The
template, the weights and the routing all live on the Mac. Nothing in the guest
holds a /api/create equivalent, because nothing in the guest holds the model.
Local inference runs the same pipeline as cloud. Because that traffic goes through Bromure's proxy rather than straight to a port, it gets the same treatment as a call to Anthropic or OpenAI: the same trace, the same logging, and the same prompt-injection detection. The Prompt Injection panel scores the file contents, web pages and tool output the agent reads with a local PromptGuard model, and scores the CLAUDE.md, AGENTS.md and GROK.md files it loads as authority with a fine-tuned ModernBERT classifier plus a deterministic scanner for invisible-Unicode payloads. Detections go to the Security Log, or pause the request, or return a hard 451, depending on which of the three responses you picked.
Now assume the model is lying to you
Take an architecture argument far enough and someone should test it. So hand the attacker the win by whatever route you prefer: a poisoned template, a bad day at a model provider, a prompt injection that got through. Your agent now works for someone else and its output still reads well. Walk those four payloads through a profile.
Steer the pick, and the pick still gets judged. This payload leans hardest
on the model's authority, and it hits the most wall. Bromure evaluates every
package fetch at the host proxy before the agent sees the response, across npm,
PyPI, Cargo, RubyGems, Maven, NuGet, Go modules and Packagist, and the proxy
does not ask who suggested the package. A two-day age gate is on by default,
which handles most of what a freshly-published typosquat needs. Turn on the OSV
lookup, point Package filtering at socket.dev or Delpi, and strip install
scripts, and a recommendation has to survive four opinions the model has no say
in. The .npmrc and pip.conf inside the VM can tighten those settings. They
cannot loosen them.
Send it home, over a wire you control. Exfiltration needs egress. Guardrails
→ Outbound connections is a pf-style rule table — an action, a protocol
(tcp, udp, web, any), a host or CIDR, a port list, and for web a list
of HTTP methods — matched top to bottom, first match wins, with Unmatched
traffic set to Allow or Deny. Set it to Deny, list the hosts your work needs,
and the attacker's endpoint is not among them. Enforcement is at the virtual
switch and the SNI layer on the host, so nothing the guest does to its own
routing changes the verdict.
Write the bug in, and then try to ship it. Guardrails also carry a mode for
each service the agent can reach: Block destructive or Read-only on
GitHub, GitLab, Bitbucket, AWS, Kubernetes, DigitalOcean, container registries
and each configured database endpoint. In read-only mode a git push is
classified as a write and blocked; a fetch is always allowed. The backdoored
commit stays in the VM.
And the access it inherits is fake. Oasis's blast-radius point is the one a
profile spends most of its effort on. The credentials in the VM are decoys:
brm_… placeholders as environment variables,
a synthetic ~/.kube/config with throwaway client certificates, a fake base64
blob in ~/.docker/config.json, and AWS requests re-signed on the host so that
anything bypassing the proxy gets InvalidSignatureException instead of access.
Private SSH keys
are never in the VM at all — the
host signs through a per-profile agent. Switch on Require approval to use for
a given credential and each substitution becomes a host dialog with a
time-bounded grant: five minutes, an hour, the rest of the session. Folder
sharing is capped at eight directories you pick by hand, so the rest of your Mac
is not there to read.
And you can see all of it. The Security Log window (Window → Security Log…) is one host-side chronological table: package verdicts, every destination the VM opened and whether it was allowed or denied, credential swaps, injection detections. Guest code cannot edit it, because guest code cannot reach it. When the day ends, Erase home… and Reset to base… under Resources → Storage take the profile back to its post-clone state.
Turn it on
If you run NemoClaw, take NVIDIA's patch — it moves Ollama back to loopback and
puts a token-gated proxy in front of anything that legitimately needs to leave
it. The Hacker News notes the Windows and WSL path is not fully covered by that
release, so check your platform against the advisory rather than assuming.
Then go and find every other model server on your machine and ask it what
address it is bound to. The answer is more often 0.0.0.0 than people expect,
because containers keep asking for it.
In a profile, the settings worth two minutes are the usual short list. Guardrails → Outbound connections with Unmatched traffic on Deny. Credentials → Require approval to use on anything that can spend money or delete data. Supply Chain → OSV vulnerability check and socket.dev or Delpi filtering if you have a key, on top of the age gate that is already on. Prompt Injection → both detectors, so the traffic to your local model gets read as carefully as the traffic to a cloud one.
Keep running the model on your own machine. That choice buys you prompts which stay local, and it is worth having. It says nothing about who else gets to talk to the server holding them, which is a question about a socket, and a socket deserves a better answer than an environment variable set to make a container happy. Install Bromure Agentic Coding, pick your model on the host, and leave the guest with a pipe instead of a port.