From a real incident

How AI-built apps get breached

On 1 November 2026 a service we run was attacked: secrets on several servers and part of the funds in an operating wallet were taken. The cause was not advanced hacking — it was basic hygiene that almost everyone skips when building apps with AI. We are publishing this because the same thing can happen to you. We fixed the cause and rotated every secret right after.

Five lessons

1. An "install / extension" feature is remote code execution

The admin screen's plugin / adapter / package install feature was open to any logged-in user, install scripts were allowed to run, and the app ran as root. After signing up, the attacker "installed" hundreds of malicious packages and each one ran commands on the server. Results were smuggled back inside error responses.

Ask your AI: "Only admins may install or run external packages, URLs or files; disable install scripts; run the app as a dedicated non-root user."

2. One breached server opens all the others through one SSH key

An SSH private key left one server, and the same key was authorized on five other servers, so the attacker logged in as root on each in turn. Trust created for convenience multiplied the damage.

Use a different key per server, restrict each key with from= allowed IPs, and shrink the list of servers any one host can reach.

3. Secrets sit in far more places than you think, in plain text

Not just .env. Passwords were in backup folders, shell history, editor history, the allow-rule files of AI coding tools, and example files. AI tools keep making convenience copies; changing one leaves the others.

Search everything for the secret's value to find leftover copies, rotate, then search again and confirm zero.

4. Reusing one password or account collapses everything at once

One database superuser was shared by nine apps, so one compromised app exposed all data. Some passwords were weak or equal to the service name.

Create one DB account per app with only the rights it needs. Use random passwords of 32+ characters.

5. Put limits and locks on wallet and exchange keys

If a decrypted wallet key exists on a server, funds move the moment the server falls. Limits enforced by the app meant nothing to an attacker holding the key. The alert was also buried among routine warnings such as "low gas".

For exchange keys: withdrawals off plus an IP allowlist. Keep only the minimum in a hot wallet and make fund-movement alerts impossible to miss.

5-minute self-check (read-only, on your own server)

These checks change nothing. If a result looks wrong, rotate your secrets first, then investigate.

Ports your server exposes to the internet

ss -tlnp   # anything on 0.0.0.0 you do not recognize is suspect

Unknown SSH keys

cat ~/.ssh/authorized_keys   # any line you did not add: investigate now

Recent login sources

last -i | head -30   # any unfamiliar IP?

Is the app running as root

ps -eo user,comm | grep -E 'node|python|java' | sort | uniq -c

Secrets embedded in code or config

grep -rIlE 'BEGIN (RSA|OPENSSH|EC) PRIVATE KEY|AKIA[0-9A-Z]{16}|sk-[A-Za-z0-9]{20,}' --exclude-dir=node_modules --exclude-dir=.git .

Do the attacker sources below appear in your logs

grep -hE '23\.95\.130\.140|107\.174\.144\.145|31\.58\.136\.212' /var/log/nginx/access.log* /var/log/auth.log* 2>/dev/null | head

Attack sources observed in our logs

These are sources from which the behavior described was observed against our servers. They may be rented servers, VPN exits or other victims' machines, so we do not identify an owner or culprit, and an IP may later be reassigned to someone else. Whether to block is your call.

Source IPObserved (UTC)Observed behavior
23.95.130.1402026-11-01 05:50 UTC ~ 2026-11-01 22:20 UTCReceiver of an SSH private key exfiltrated over plain HTTP (port 9999). The same IP then logged in as root to several servers with that key, and later retried the install endpoint and probed for local-file read / internal-address access.
107.174.144.1452026-11-01 01:34 UTC ~ 2026-11-01 08:40 UTCAbout 200 automated sign-up / login / password-reset requests against a membership site, then 36 SSH logins to a server using an attacker-planted key (backdoor).
31.58.136.2122026-11-01 01:34 UTC ~ 2026-11-01 02:12 UTCSame time window and same pattern (python-requests / curl): about 40 automated sign-up / login requests, plus scanner paths such as /scim/Users.

If you find them in your logs: (1) do not just block, (2) review logins, installs and file changes around those times, and (3) rotate SSH keys, passwords and API keys on that host. Blocking does not undo what was already taken.

Share your own case

Tell the story of when an AI-built app of yours got hacked, so fewer people repeat the same mistake. We do not accept specific IP addresses, personal data (names, emails, phone numbers), links, keys or passwords, or wallet addresses — a submission containing them is rejected (we do not mask-and-publish). Posts are user-submitted and not verified by us.

Cases submitted by visitors

No cases yet. Be the first to share one.

Honest limits

This is what we learned from one incident and it does not cover every threat. For the investigation and to prevent recurrence we left out our server addresses, the vulnerable product's name and the loss amount. A clean self-check is not proof that you are safe.

Request a free check of my site Back to free credits