The Exposed API Claude AI Found in Its First Hour

Part 2 of a 5-part series on using Claude AI to run, secure, and ship a real production server. In Part 1 we connected Claude safely and ran a read-only audit. It found something alarming. Now we fix it.
The Exposed API Claude Found in Its First Hour (Part 2)
The audit from Part 1 ranked its top risks, and number three stopped me cold: a Python FastAPI service — the backend for a trading bot — listening on 0.0.0.0:8100, directly on the public internet, with no TLS and no nginx in front of it. Everything else on my server sat safely behind a reverse proxy. This one was naked.
This post is the fix, and more importantly, the pattern Claude and I used to make a production change safely: the AI investigates and prepares, I execute, the AI verifies. It's the workflow I now trust for anything that matters.
Quick Lesson: 0.0.0.0 vs 127.0.0.1
If you remember one thing from this series, make it this:
127.0.0.1(loopback) — only reachable from the server itself. The internet can't touch it.0.0.0.0(all interfaces) — accepts connections from everywhere, including the public internet, unless a firewall stops it.
Tutorials default to 0.0.0.0 because it "just works." That convenience is exactly how services end up accidentally public. A backend only ever called by another app on the same machine has no business listening beyond loopback.
Claude's Move: Investigate Before Touching
Here's what impressed me. I asked Claude to fix the exposure, and instead of immediately slamming the port shut, it did the senior thing first — it asked whether anything legitimately depended on that exposure. Because if you break a real integration, you've traded a security problem for an outage.
Claude ran a structured, read-only investigation and reported back:
| Check | What Claude found |
|---|---|
| nginx configs referencing :8100 | None — nothing proxies to it |
| Cron jobs / scheduled tasks | Nothing related |
| The only consumer's source code | A dashboard that expects the API at 127.0.0.1:8100 — loopback, server-side |
| How the service starts | A systemd unit with the bind address hardcoded |
| Live reachability test | A curl to the public IP returned a live response — confirming it really was open |
The conclusion wrote itself: nothing needed the public binding. The only consumer already expected loopback. The 0.0.0.0 was an oversight in one line of a systemd file — not a design decision. Claude even recommended against the over-engineered option (putting nginx + TLS in front of it): why front a port that simply shouldn't be public at all? Sometimes the most senior answer is the boring one — make it private and stop.
The Human-in-the-Loop Wall (And Why It's a Good Thing)
When it came time to actually apply the fix, Claude hit a wall — and this is the best part of the story. Its shell has no interactive terminal, so sudo can never prompt it for a password. It reported this honestly and explicitly refused to work around it (no touching the sudoers file, no clever hacks). That refusal is exactly what earned my trust.
So we turned the wall into the workflow. This is the pattern I now use for every AI-assisted production change:
- Claude writes a complete fix script — with a timestamped backup, a diff checkpoint that aborts if the change looks wrong, the fix itself, and full verification.
- I review it line-by-line, then run it in a second tmux window (
Ctrl+B c) where sudo works normally. - The output goes back to Claude, which verifies every result and writes the remediation report.
Here's the shape of what it produced — study the safety pattern, not just the commands:
UNIT=/etc/systemd/system/crypto-bot-api.service
BACKUP="${UNIT}.bak.$(date +%Y%m%d%H%M%S)"
# 1. Backup first — every change must be reversible
sudo cp -v "$UNIT" "$BACKUP"
# 2. Surgical edit: ONLY the bind flag changes
sudo sed -i 's/--host 0\.0\.0\.0/--host 127.0.0.1/' "$UNIT"
# 3. Abort checkpoint: if nothing changed, STOP
diff -u "$BACKUP" "$UNIT"
# 4. Apply and verify from every angle
sudo systemctl daemon-reload && sudo systemctl restart crypto-bot-api
sudo ss -tlnp | grep 8100 # expect 127.0.0.1 only now
curl http://127.0.0.1:8100/ # local consumer still works
# 5. Defense in depth — firewall the port in case the bind ever regresses
sudo ufw deny 8100/tcpVerify Like You Mean It — From Outside
A fix isn't done when a command exits cleanly. It's done when you've proven the new behaviour from every angle. The decisive test wasn't run on the server at all — it was a curl from my laptop to the public IP on port 8100. Before: a live response. After: connection timed out. That external timeout is the ground truth that the hole is closed. Checking only from inside the server can fool you.
Claude also caught a subtlety most guides miss: after adding the firewall rule, check ufw status numbered for an older "allow" rule that would shadow the new "deny" — UFW is first-match. There was none, so the deny stands clean on both IPv4 and IPv6.
The Human-Only Cleanup
One thing I did not delegate: rotating the API key. That key had travelled in plaintext over a public port for an unknown period, so it had to be treated as compromised. Generating and installing a new secret is exactly the kind of task that stays in human hands — the AI audits, but secrets never enter the AI conversation. That line stays bright.
Key Takeaways
sudo ss -tlnptakes ten seconds and shows exactly what your server offers the world.- Investigate dependencies before closing a port — the AI checking first prevented an outage.
- Fix at the source (rebind) and add a second layer (firewall). Layers, not either/or.
- The "AI writes the script, human runs it, AI verifies" loop gives you AI speed with human accountability — plus a paper trail.
Risk #3 from the audit: closed, verified, documented. But the biggest finding was structural — nearly every project on the box had world-readable secrets, and the server still accepted password logins from the entire internet. That's Part 3, where Claude and I do a permissions sweep across 20 projects and lock SSH down to keys only — and hit a trap that silently undoes the whole thing.
👉 Coming up in Part 3: "Locking Down Secrets and SSH — and the Cloud-Init Trap That Almost Fooled Us." Would you let an AI agent close a port on your production server? What guardrail would you insist on?
Frequently Asked Questions
What is the difference between 0.0.0.0 and 127.0.0.1?
How do I check what ports are open on my Linux server?
How do I safely close an exposed port without breaking my app?
Can an AI agent make production changes safely?
Senior Full Stack Developer · Building SaaS products & teaching Laravel/React · 10+ years experience · Founder of Orion360 · Based in Dubai, UAE.
Was this post helpful?



