How to Lock Down Ollama, vLLM and llama.cpp Endpoints Against LLMjacking
If you run Ollama, vLLM or llama.cpp’s llama-server anywhere a stranger can reach it, assume it is being scanned right now, and make sure every inference path needs a credential. On October 21, 2026 Symantec’s threat intelligence team published research on an autonomous offensive agent that searches the internet for exposed large language model endpoints, then uses the hijacked model as the “brain” of its own attack campaign: scanning, exploiting web frameworks, stealing databases and credentials, and installing cryptocurrency miners, for up to 100,000 steps without human approval. The owner of the exposed model pays for every step. This guide turns that report into a checklist: find out what you have exposed, close it per server, fix the gaps that an API key alone does not cover, and check for the indicators Symantec published.
Everything below comes from Symantec’s report, Autonomous LLMjacking: ‘Skynet’ Agent Hijacks Exposed AI Models to Run Attacks, and from each project’s own documentation: the Ollama FAQ and API reference, the llama.cpp server README, vLLM’s security guide, the AWS IMDS documentation and the nginx auth_basic module. Defaults and flags can change between releases, so confirm them against the version you actually run.
What Symantec found, in one minute
Symantec runs an “AI honeypot”: a system that pretends to be an unprotected, internet-facing AI service. In summer 2026 an attacker sent it what they thought was a request to a real model, and in doing so handed over the agent’s entire working prompt. Key points from the report:
- The target is free inference. An “exposed LLM endpoint” is a server that answers AI requests without authentication, or with authentication a stolen key can pass. Because most tools speak the same OpenAI-compatible API, one scanner can find many kinds of servers: self-hosted model servers “left open by default”, AI gateways and relays in front of paid accounts, quick internal test systems, and MCP (Model Context Protocol) servers connected to real tools.
- Scanning is constant. The honeypot recorded more than 127,000 external requests from tens of thousands of unique addresses, dominated by endpoint reconnaissance, spraying of stolen API keys, and probing for exposed configuration files.
- The model drives the attack. The captured request carried around 74 offensive tool definitions: internet-wide asset discovery through the FOFA search service, exploit modules for VS Code, Django, Jupyter, Laravel, Supabase, Next.js and Flask/Werkzeug (including working out the Werkzeug debugger PIN for remote code execution), command injection, server-side request forgery (SSRF) aimed at cloud metadata services, database dumping through Prisma Studio, and botnet command-and-control.
- A jailbreak strips safety controls. Layered instructions told the model to hide its real identity, treat reading
.envfiles and API keys as normal development work, and dismiss its own safety messages. - “Skynet mode” authorised up to 100,000 unattended steps, told the model to run tools without narration or permission, and to deploy miners hidden under
/tmp/.xin a scan, infect, mine, reinfect loop. - Day two brought a real payload. A follow-up request from a different address asked the model to decode a gzip-compressed shell script that installs the XMRig Monero miner, fails over across four mining pools, locks its own config with an immutable attribute, and keeps deleting a local service’s access logs. It looked for go2rtc directories (a WebRTC camera-streaming server common in Home Assistant setups) and hid the miner there as
.trace.
Symantec’s own summary of the risk: organisations that stand up AI services, model gateways or MCP servers “should assume they will be found and tested soon after being exposed.”
So there are two victims: you, if your model server or API key powers the agent, and whoever the agent breaks into next. Both are fixable mostly with configuration.
Step 1: Find out what is actually listening
Start on each machine that runs a model server. List listening TCP sockets and the processes behind them:
sudo ss -tlnp | grep -E '11434|8000|8080|9931'
The ports in that filter are the usual defaults: Ollama listens on 11434, the llama.cpp README documents 9931 as llama-server’s default port (its Docker examples use 8080), and vLLM’s examples use 8000. Add any port you set yourself.
Look at the local address column:
127.0.0.1:PORTor[::1]:PORTmeans loopback only. Good.0.0.0.0:PORT,*:PORTor[::]:PORTmeans every interface. That is only acceptable if a firewall or a private network you trust sits in front of it.- A specific LAN or VPN address means it is reachable from that network.
Then test from outside the machine, ideally from a host on a different network, the way a scanner would. Replace YOUR_HOST with the public IP or DNS name:
# Ollama: lists installed models if open
curl -s --max-time 5 http://YOUR_HOST:11434/api/tags
# OpenAI-compatible servers (vLLM, llama-server): lists models if open
curl -s --max-time 5 http://YOUR_HOST:8000/v1/models
curl -s --max-time 5 http://YOUR_HOST:9931/v1/models
/api/tags is Ollama’s documented “list local models” endpoint. If any of these return a model list from the outside without a credential, you have exactly the kind of endpoint the agent hunts. Fix that first, then work through the rest.
Do not forget containers. A docker run -p 8080:8080 ... publishes the port on every host interface even if the process inside thinks it is private. Publish to loopback instead with -p 127.0.0.1:8080:8080 when only local clients or a local reverse proxy need it.
Step 2: Ollama — keep it on loopback, put auth in front
The Ollama FAQ is clear about the default: “Ollama binds 127.0.0.1 port 11434 by default,” and you change that with the OLLAMA_HOST environment variable. Most exposed Ollama servers got that way because someone followed the FAQ’s own example of setting OLLAMA_HOST=0.0.0.0:11434 to reach it from another machine.
Check whether you changed it. On Linux with the systemd service:
systemctl cat ollama | grep -i OLLAMA_HOST
On macOS, where the app reads variables set with launchctl:
launchctl getenv OLLAMA_HOST
If it says 0.0.0.0, decide whether you really need remote access. If not, remove the override. On Linux, run systemctl edit ollama.service, delete the Environment="OLLAMA_HOST=0.0.0.0:11434" line from the [Service] section, then reload as the FAQ describes:
sudo systemctl daemon-reload
sudo systemctl restart ollama
If you need remote access, add authentication in front. Ollama’s FAQ doesn’t describe an API key for the local server; for exposing it, the FAQ shows a reverse proxy such as nginx forwarding to localhost:11434 with the Host header set. That proxy is where authentication belongs. A minimal version with nginx HTTP basic authentication, keeping Ollama itself on loopback:
# Create a password file (nginx accepts apr1 hashes made with openssl passwd)
printf 'alice:%s\n' "$(openssl passwd -apr1)" | sudo tee /etc/nginx/ollama.htpasswd
server {
listen 443 ssl;
server_name ollama.example.com;
# ssl_certificate / ssl_certificate_key lines go here
location / {
auth_basic "ollama";
auth_basic_user_file /etc/nginx/ollama.htpasswd;
proxy_pass http://localhost:11434;
proxy_set_header Host localhost:11434;
}
}
Serve it over TLS, because basic-auth credentials travel with every request. If only you or your team need access, a VPN or private overlay network is simpler still: keep OLLAMA_HOST on the private interface and never open the port publicly.
Tunnels count as exposure too. The FAQ shows how to publish Ollama with ngrok http 11434 --host-header="localhost:11434" or cloudflared tunnel --url http://localhost:11434 .... Those commands give you a public URL to an unauthenticated server unless you add the tunnel provider’s access controls. If you once ran one “just to test”, make sure it is gone.
Step 3: llama.cpp llama-server — bind address plus API key
llama-server is safer out of the box: the README lists the --host default as 127.0.0.1. It becomes reachable when you pass --host 0.0.0.0, which the README’s Docker examples do, since a container needs it.
When the server must be reachable, give it keys. The README documents:
--api-key KEY: “API key to use for authentication, multiple keys can be provided as a comma-separated list” (env:LLAMA_API_KEY).--api-key-file FNAME: a file with one key per line; lines starting with#are comments (env:LLAMA_ARG_API_KEY_FILE).--ssl-key-fileand--ssl-cert-filefor TLS directly on the server.
A file keeps keys out of your shell history and ps output:
umask 077
openssl rand -hex 32 > ~/.config/llama-server.keys
llama-server -m /models/model.gguf \
--host 0.0.0.0 --port 9931 \
--api-key-file ~/.config/llama-server.keys
Clients then send the key as a bearer token, as with any OpenAI-compatible API:
curl -s http://YOUR_HOST:9931/v1/models \
-H "Authorization: Bearer $(cat ~/.config/llama-server.keys)"
Run the same request without the header from outside and confirm it is refused. Note what stays open: the README says GET /health “is public (no API key check)”. That is fine for a load balancer but means a scanner can still tell a llama-server is there.
Three more README flags are worth checking on any server that isn’t strictly local:
--cors-originsdefaults to*. If the built-in web UI is the only browser client, restrict it (the special valuelocalhostreflects the Origin header only for localhost).--toolsenables experimental built-in agent tools, includingread_file,write_fileandexec_shell_command. The README says “do not enable in untrusted environments.” On a reachable server, that is the difference between someone stealing tokens and someone running shell commands.--metricsexposes a Prometheus endpoint and is disabled by default. Leave it off or keep it on an internal port.
If llama-server is giving you connection errors after you lock it down, our llama-server connection refused, 503 and 404 fixes cover the usual host, port and path mistakes.
Step 4: vLLM — the API key does not cover every endpoint
vLLM has --api-key (or the VLLM_API_KEY environment variable), and many people stop there. vLLM’s own security guide says that is not enough: the key protects “only endpoints under the /v1, /v2, /inference, and /cohere path prefixes,” and “Many other sensitive endpoints are exposed on the same HTTP server without any authentication enforcement.”
Among the endpoints the guide lists as unauthenticated even with --api-key set:
/invocations, a SageMaker-compatible endpoint that “routes to the same inference functions as/v1endpoints”. The guide calls it “particularly concerning”, because it gives free inference without a key, which is exactly what LLMjacking needs./pooling,/classify,/score,/rerankand/generative_scoringinference variants.- Operational controls such as
/pause,/abort_requestsand/update_weights(when the generate task is supported). - Utilities such as
/metrics,/tokenize,/detokenize,/healthand/version.
The guide’s recommendation is to deploy vLLM “behind a reverse proxy (such as nginx, Envoy, or a Kubernetes Gateway)” that “explicitly allowlists only the endpoints you want to expose” and blocks everything else.
Bind vLLM to loopback and set a key anyway:
export VLLM_API_KEY="$(openssl rand -hex 32)"
vllm serve <model> --host 127.0.0.1 --port 8000
Then allowlist in nginx. This example exposes only chat completions, completions and the model list, adds basic auth on top of vLLM’s own key check, and returns 404 for every other path, including /invocations:
server {
listen 443 ssl;
server_name llm.example.com;
# ssl_certificate / ssl_certificate_key lines go here
auth_basic "llm";
auth_basic_user_file /etc/nginx/llm.htpasswd;
location = /v1/chat/completions { proxy_pass http://127.0.0.1:8000; }
location = /v1/completions { proxy_pass http://127.0.0.1:8000; }
location = /v1/models { proxy_pass http://127.0.0.1:8000; }
location / { return 404; }
}
Adjust the list to the endpoints your clients actually call (embeddings, responses, messages, and so on). Test it from outside: /v1/models without credentials should be refused, and /invocations should return 404 whatever you send.
Other vLLM settings from the same guide:
- Never set
VLLM_SERVER_DEV_MODE=1in production. It enables/collective_rpc, which the guide describes as executing “arbitrary RPC methods on the engine (extremely dangerous)”, along with cache resets and sleep endpoints. - Leave
--enable-tokenizer-info-endpointand profiler endpoints off unless you need them. - Limit the
nparameter.VLLM_MAX_N_SEQUENCESdefaults to 16384; for public-facing deployments the guide suggests something like64or128. - In multi-node or distributed setups, PyTorch’s
TCPStore“by default, listens on all network interfaces”. Firewall everything except the API port, setVLLM_HOST_IPto a specific address, and never expose the internal ports.
If you followed our guide to running Kimi K3 locally or similar vLLM tutorials on a cloud GPU box, revisit the --host flag you used and the cloud firewall rules.
Step 5: Treat API keys like production credentials
LLMjacking doesn’t need an open server; a stolen key works too. Symantec’s honeypot saw a lot of “spraying of stolen API keys” and probing for exposed configuration files, and the agent’s jailbreak specifically targeted .env files. Its recommendations:
- Rotate AI API keys regularly and scope them narrowly (per project, per environment).
- Keep them out of source code and committed configuration files.
- Monitor accounts for unusual inference volume or spend, “which is often the first sign that access has been stolen.” Most providers let you set spending alerts or hard limits; turn them on.
This applies to gateways and relays as well. A LiteLLM or similar proxy that holds your provider keys is a higher-value target than a single local model, because one open endpoint exposes every account behind it. Supply-chain incidents like the LiteLLM 1.107.0 PyPI and Docker compromise and the Railway environment-variable incident are reminders that keys leak through more than one door.
Step 6: Limit what a compromised box can do
Symantec’s mitigations go past the model server, because the agent’s goal is to use your compute to attack others and to mine.
Restrict outbound traffic. Hosts that run AI tools or tool-enabled agents should not be able to scan the internet, reach a command-and-control server, or download a miner. Allow only the destinations they need (model registries, package mirrors, your own services) and deny the rest.
Enforce IMDSv2 on cloud instances. The agent’s SSRF tool targeted cloud metadata services. On AWS, require session tokens for the Instance Metadata Service and, as Symantec recommends, set the PUT response hop limit to one:
aws ec2 modify-instance-metadata-options \
--instance-id i-1234567890abcdef0 \
--http-tokens required \
--http-put-response-hop-limit 1 \
--http-endpoint enabled
Both options appear in AWS’s documentation for modifying existing instances (its own hop-limit example uses 3). If containers on the instance legitimately call IMDS, test them after the change, since a lower hop limit can affect them.
Run agents with least privilege. Give tool-enabled agents (including ones you run on local models) a human approval step for sensitive actions, and keep the software the agent tries to exploit patched and off the public internet. Symantec names VS Code servers, Django, Jupyter, Laravel, Supabase and Prisma Studio. A Jupyter server or Werkzeug debugger on the same box as an open model server is a bad combination. If you also host your own Git server, our GitLab CVE-2026-110264 patch guide covers this week’s other urgent fix.
Step 7: Check for the published indicators
If you found an endpoint open in Step 1, check whether anything has already happened. Symantec notes its honeypot never executed the payloads, so these indicators describe what the agent tries to do rather than confirmed infections, but they are concrete things to look for.
Hidden miner locations named in the report:
ls -la /tmp/.x 2>/dev/null
for d in /config /opt/go2rtc /etc/go2rtc ~/.config/go2rtc /dev/shm; do
ls -la "$d/.trace" 2>/dev/null
done
A config file locked with the immutable attribute (the script locks a go2rtc config so it can’t be deleted). An i in the attribute list means immutable:
lsattr /config/go2rtc.y*ml /opt/go2rtc/go2rtc.y*ml /etc/go2rtc/go2rtc.y*ml 2>/dev/null
File hashes published by Symantec:
fcde1323acceb46fd88f2276c010bb98839dccce03dc512c56d9529428ceec68(shell script)b720a5e7c3a5b1760d15a8833a782f4fe7fc5c62b967fe57f607e72745ec0ccf(gzip-compressed shell script)
Network indicators (defanged in the report): 86.216.147[.]228, 13.140.161[.]91, 207.180.232[.]206, and mining pools mine.c3pool[.]com:13333, auto.c3pool[.]org:80, auto.skypool[.]xyz:6666 and xmrpool[.]eu:5555. Search your firewall, DNS and proxy logs for them, and check for live connections:
sudo ss -tnp | grep -E ':13333|:6666|:5555'
Beyond the IOCs, the plainest signal is usage you didn’t cause: GPU utilisation at odd hours, request logs full of unfamiliar client IPs, or a provider bill that jumped. If you find any of these, take the endpoint offline, rotate every key the host could read, and rebuild from a clean image rather than trying to clean in place.
Quick checklist
ss -tlnpon every model host; nothing on0.0.0.0without a reason.- External
curlto/api/tagsand/v1/modelsreturns nothing useful without a credential. - Ollama:
OLLAMA_HOSTon loopback or a private interface; any remote access goes through an authenticated TLS proxy or VPN; no forgotten tunnels. - llama-server:
--api-key-fileon any reachable server;--toolsoff;--cors-originsrestricted. - vLLM:
--host 127.0.0.1,VLLM_API_KEYset, and a reverse proxy that allowlists paths and blocks/invocations; noVLLM_SERVER_DEV_MODE=1. - Docker ports published to
127.0.0.1unless they must be public. - Provider keys rotated, scoped, out of repos, with spend alerts on.
- Egress restricted; IMDSv2 required with a low hop limit.
- IOC sweep done on anything that was exposed.
FAQ
My Ollama is only on my home network. Am I affected?
Not directly by internet-wide scanning, as long as your router doesn’t forward the port and no tunnel publishes it. Anything else on that network can still use it, though, so on shared or untrusted networks, keep OLLAMA_HOST on loopback.
Is vLLM’s --api-key useless then?
No. It protects the /v1, /v2, /inference and /cohere prefixes. The problem is the endpoints outside those prefixes, which is why vLLM recommends a path allowlist in front of it.
Does putting the server on a non-default port help? Not much. Symantec describes scanners that use one set of OpenAI-compatible probes to find many kinds of targets, and internet-wide search services index whatever answers. Authentication and network restrictions are what actually stop this.
I run an MCP server for my agent. Does this apply? Yes. Symantec lists MCP servers that connect a model to real tools among the exposed services attackers look for. Apply the same rule: no unauthenticated access on any path, and don’t bind it to all interfaces.
Where can I follow updates? Symantec points to its Protection Bulletin for the latest protection updates. For the model servers, watch the security docs linked at the top of this guide, since endpoint lists and defaults change between releases. More hands-on guides are on the 7minai homepage.