
Auditing Your MCP Server for Internet Exposure, Credential Leaks, and Unauthenticated Endpoints
Why this scan wave matters right now
The reporting behind this post points to active scanning for three things at once: exposed MCP servers, AI assistant credentials, and unauthenticated LLM endpoints. That combination matters because it matches a pattern I keep seeing in real environments: an AI feature starts as a local tool, grows into a network service, and then gets a token bolted on later.
My view is simple: this is not “just more bot noise.” It is a sign that AI integrations now sit inside the public attack surface, and they are often built with less discipline than the rest of the app.
What I can confirm from the reporting: scanners are looking for exposed MCP servers, AI assistant credentials, and unauthenticated LLM endpoints. What I did not verify: the scanner operator, exact scale, or whether every target was misconfigured in the same way.
What the reporting says the scanners are looking for
The scan pattern is broad, but it hangs together:
- MCP servers that answer from the network when the team expected local-only access
- assistant-related secrets in environment files, config snippets, logs, or shell history
- LLM or agent endpoints that answer without authentication, even if they were meant for internal use only
That mix matters because the first issue gives an attacker reach, the second gives them credentials, and the third gives them something to do with either the credentials or the exposed tool surface.
Why MCP servers and LLM endpoints are unusually exposed by default
MCP servers are often built for developer convenience first. That usually means:
- local bind addresses during development
- no auth in the first working version
- broad tool descriptions so clients can discover capabilities quickly
- environment variables or config files that are easy to copy into CI, Docker, or a VM
The catch is that “easy to copy” also means “easy to publish by accident.” Once the service moves from localhost to 0.0.0.0, the threat model changes immediately. At that point, anything that exposes manifests, tool schemas, logs, or debug routes can become useful to a scanner.
Start with an inventory, not a fix
I would not start by rewriting auth middleware. I would start by finding every place the service exists.
Find every MCP server, sidecar, and experimental endpoint
On a small team, inventory is usually a mix of shell, container, and cluster checks. A quick pass might look like this:
## Find listening processes on a host
ss -ltnp
## Inspect local services
lsof -i -P -n | grep LISTEN
## Find running containers and published ports
docker ps --format 'table {{.Names}}\t{{.Ports}}\t{{.Image}}'
## In Kubernetes, look for services and ingress
kubectl get svc,ingress -A
If you have several repos, also search for likely transport and manifest routes in code and deployment manifests.
git grep -nE 'mcp|tool|manifest|health|stream|sse|0\.0\.0\.0'
This is not exhaustive, but it gives you a map. Without the map, you will fix one endpoint and leave three others exposed.
Separate local-only tools from anything reachable over the network
I treat these as two different classes:
| Class | Expected reachability | Risk profile |
|---|---|---|
| Local-only dev tool | Loopback or Unix socket only | Low if it stays local |
| Internal service | Private network or VPN | Medium if auth is weak |
| Remote-facing service | Public or reverse-proxied | High, even if “read only” |
The mistake is to assume that a service is safe because it is “just for developers.” If it listens on a routable interface, it is no longer just for developers.
Record the auth mode, transport, and host binding for each service
For each endpoint, write down three things:
- transport: HTTP, SSE, WebSocket, stdio bridge, or something else
- host binding:
127.0.0.1, private subnet,0.0.0.0, or a public ingress - auth mode: none, bearer token, session cookie, mTLS, VPN-only, or proxy-authenticated
That record becomes your baseline. If you cannot answer those three questions, you do not really know what you are shipping.
Test for internet exposure from the outside in
The outside-in test is the one scanners are already doing. You should do it yourself before they do.
Check whether the server is bound to 0.0.0.0, public IPs, or a reverse proxy
A local bind usually means you intended private access. A public bind or ingress means you need real controls.
Example:
curl -i http://127.0.0.1:3000/health
curl -i http://YOUR_PUBLIC_IP:3000/health
If the first request works and the second fails, that is usually what you want. If both work, the service is reachable from the network. If the second works from the internet, assume scanners will find it quickly.
Verify whether health, manifest, or tool routes answer without authentication
Do not stop at the health check. A lot of teams protect the action route but leave discovery routes open.
A simple audit pattern is:
BASE="https://mcp.example.com"
curl -i "$BASE/health"
curl -i "$BASE/.well-known/mcp.json"
curl -i "$BASE/tools"
curl -i "$BASE/mcp"
Your exact paths will vary. The point is to test the same classes of routes scanners usually probe: health, manifest, and tool surface.
Here is the difference I want to see:
| Request | Good response | Bad response |
|---|---|---|
| Health | 401 or 403 if private | 200 OK with no auth |
| Manifest | Auth required or no public route | Tool list returned openly |
| Tool call | Rejected without auth | State change or upstream call succeeds |
Example protected response:
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="internal"
Content-Type: application/json
{"error":"authentication required"}
Example exposed response:
HTTP/1.1 200 OK
Content-Type: application/json
{"tools":[{"name":"repo_search"},{"name":"deploy"}]}
That second response is not harmless just because it is “only metadata.” It tells an attacker what the service can do and where to focus next.
Show the difference between expected local access and actual public reachability
I like to test both from the same host:
## expected local-only
curl -i http://127.0.0.1:3000/health
## actual network path
curl -i http://$(hostname -I | awk '{print $1}'):3000/health
If loopback works and the network path also works, you have confirmed public or at least LAN reachability. If that service was meant to stay local, treat it as a configuration bug, not an abstract risk.
Look for credential leaks tied to AI assistants and MCP tooling
The scanner wave also reportedly targets assistant credentials. That matters because leaked keys turn a reachability bug into account abuse.
Common leak points in env files, config snippets, logs, and shell history
The usual places are boring, which is exactly why they get missed:
.env,.env.local, and CI environment dumps- example config files checked into repos
- application logs that print startup configuration
- notebook cells and pasted terminal transcripts
- shell history from
export TOKEN=...orcurl -H "Authorization: Bearer ..."
A few safe searches help:
## Search the repo for likely secret patterns
git grep -nE 'sk-[A-Za-z0-9]{16,}|Bearer [A-Za-z0-9._-]+|api[_-]?key|token'
## Check recent shell history carefully
tail -n 50 ~/.bash_history
tail -n 50 ~/.zsh_history
How to confirm a secret is real without pasting it into the article or sharing it broadly
Do not post the token. Do not spray it at random endpoints. Confirm it locally and quietly.
Useful checks:
- Compare the suspected value against the provider’s token format.
- Check whether the token is currently active in the provider dashboard.
- Use a throwaway validation request from your own machine.
- If the token may have leaked, revoke first and verify the revoke succeeded.
If you need to prove impact internally, capture only the minimum evidence:
- token prefix or last four characters
- account or project name
- timestamp
- the status code or error text, redacted
That is enough to show the secret was real without spreading it further.
Why assistant tokens and service keys deserve separate handling
I would not lump these together.
- assistant tokens often grant access to user-facing workflows or chat agents
- service keys often unlock backend APIs, tool calls, or deployment actions
If one leaks, you may not be able to reuse the same rotation path for the other. In practice, I rotate them separately, log them separately, and scope them separately. A shared secret pool is how a small leak turns into a broad incident.
If an assistant token or service key has been exposed, rotate it as an incident response task, not as cleanup. Assume copies already exist in logs, terminals, and pasted snippets.
Probe unauthenticated endpoints for real impact, not just presence
An open route is not always exploitable, but it is always worth classifying.
Identify which endpoints disclose capabilities, schemas, or tool names
Start by asking what the endpoint reveals before you ask what it can change.
Examples of useful questions:
- Does it list tools or actions?
- Does it return schema details or parameter names?
- Does it disclose upstream hosts, model names, or connector IDs?
- Does it echo config values, build metadata, or debug flags?
Even if the route does nothing dangerous by itself, the metadata can help an attacker chain the next step.
Check whether unauthenticated requests can trigger state changes or upstream calls
This is the line I care about most. Metadata exposure is one problem. Execution is another.
A safe test pattern is to use a disposable account or a test environment and ask:
- can I trigger a read-only call?
- can I cause a write or deploy action?
- can I make the server call an upstream API on my behalf?
If the answer is yes without auth, the impact is no longer theoretical.
Explain the difference between metadata exposure and tool execution risk
| Exposure type | What leaks | Why it matters |
|---|---|---|
| Metadata only | Tool names, schemas, route names | Helps attackers target the next step |
| Read-only access | Data, records, status | Can still expose sensitive internal info |
| Write access | Changes, jobs, tickets, deploys | Direct business impact |
| Delegated upstream calls | Third-party API usage or state change | Can amplify abuse outside your network |
My position: teams often underestimate metadata exposure. I would not dismiss it, but I would prioritize any unauthenticated route that can actually do something.
Practical hardening steps that actually shrink exposure
Bind internal services to loopback or private interfaces by default
If a service is meant to be local, make local the default. Use 127.0.0.1, not 0.0.0.0. In containers, be explicit about published ports and network policy. In Kubernetes, keep internal services off public ingress.
Put authentication in front of every remote MCP path
No remote path should be unauthenticated by accident. That includes:
- manifest routes
- health routes if they reveal too much
- debug routes
- tool execution routes
- any reverse-proxied internal admin surface
If the path is reachable remotely, it needs auth. “Read only” is not a substitute.
Add allowlists, mTLS, or VPN access where public reach is not required
If the service is only for employees or trusted clients, do not publish it to the open internet. A few good options are:
- IP allowlists for tightly controlled environments
- mTLS for service-to-service access
- VPN access for internal-only tools
- proxy authentication in front of the app
Pick one that matches the use case, then make sure the app still rejects direct access.
Rotate any secret that may have been exposed and invalidate old sessions
If a key may have leaked, assume it did. Rotate it. Then invalidate sessions, refresh tokens, and any downstream credentials that were derived from it.
The common mistake is rotating the obvious token while leaving old sessions alive in caches, proxies, or long-lived jobs.
Verify the fix with the same tests the scanner would use
Re-run the outside-in curl checks and document the blocked responses
After the fix, repeat the same requests:
curl -i http://YOUR_HOST:3000/health
curl -i https://mcp.example.com/.well-known/mcp.json
curl -i https://mcp.example.com/tools
The output I want to see is a denial, not a “mostly hidden” success.
HTTP/1.1 401 Unauthorized
Content-Type: application/json
{"error":"authentication required"}
If you get a 200 from anywhere public, keep digging.
Confirm logs, alerts, and rate limits are firing on probe traffic
A good hardening pass should also leave traces:
- auth failures should hit logs
- repeated probes should trigger alerts or rate limits
- unexpected manifest discovery should be visible in access logs
If the system silently absorbs probe traffic, you may have fixed reachability but lost observability.
Note what you did not test and where manual review is still needed
I would be explicit about gaps:
- I did not test every auth provider or proxy path.
- I did not validate third-party SaaS connectors.
- I did not inspect downstream tool permissions in this article.
- I did not attempt an authenticated abuse path.
That keeps the review honest. It also tells the next reviewer where to spend time.
What I would prioritize first in a real deployment
Fix public reachability before tuning the tool surface
If an MCP server or LLM endpoint is reachable from the internet, I would fix that before I polish prompts, tool descriptions, or schemas. Reachability is the widest opening. Close it first.
Treat leaked credentials as an incident, not just a cleanup task
A leaked assistant token is already a security event. The right response is revocation, session invalidation, log review, and a quick look for misuse. Cleanup comes after containment.
Close unauthenticated endpoints even if they look read-only
I do not buy the argument that a manifest or health route is harmless if it is public. Discovery helps attackers. In practice, I would close unauthenticated endpoints unless I had a strong reason not to.


