Learning objectives
After reading this article you will be able to:
- Contain a breach in the right order, without destroying evidence
- Diagnose the most common causes of a hacked app
- Rebuild the app, restore data and prevent another breach
A breach rarely shows up as a clear error message. The sign is usually indirect: the site shows a page you never made, CPU sits at 100% with no traffic, the AI API bill comes in high, an admin account nobody created appears, or the database disappears and a ransom note takes its place.
In that moment, the urge is to delete whatever looks wrong and move on. That is the most common mistake: you destroy the clues about how the attacker got in and leave the door open for them to come back. The order below follows the incident response guidance in NIST SP 800-61: contain first, then investigate, recover and prevent.
How do you know the app was really hacked?#
Look for these signs, from most to least common in small apps:
- Changed or new files that are not in your repository, such as scripts injected into the HTML or a
.phpfile in a Node.js app. - High, constant CPU with no rise in visits. Cryptocurrency miners are the typical payload after a breach.
- Strange accounts and charges: new admin users, heavy usage on paid APIs, bulk emails sent through your provider.
- Deleted or encrypted data, sometimes with a new table or collection holding a ransom demand.
- Successful requests for sensitive files in your access logs, like this one (example):
203.0.113.7 - - [12/Oct/2026:03:14:07 +0000] "GET /.env HTTP/1.1" 200 1532 "-" "Mozilla/5.0"The 200 in that line means the server handed out your .env file with all of your secrets. Check right now whether your site exposes files that should never be public. The expected answer is 404 or 403:
for path in .env .git/config; do
printf '%s -> ' "$path"
curl -s -o /dev/null -w "%{http_code}\n" "https://www.yoursite.com/$path"
doneIf you have access logs in a file, search for requests that succeeded:
grep -E '"GET /\.(env|git)' access.log | grep -E '" 200 'What should you do in the first hour?#
Follow this order. Do not start with cleanup.
- Contain. Stop the app or block public access. An app offline for an hour costs less than an active attacker for another hour.
- Preserve evidence. Before deleting anything, download the logs and a copy of the files on the server. Without that material, you will not find the cause.
- Rotate every secret. Database passwords, bot tokens, API keys, session and JWT signing keys, email and storage credentials. Treat everything the app could read as compromised, including its environment variables.
- Lock down the surrounding accounts. Change the password and turn on two-factor authentication on GitHub, your domain registrar and your hosting dashboard. End open sessions.
- Invalidate user sessions. Rotating the session or JWT signing key logs everyone out, including the attacker.
For bot tokens and API keys, the detailed steps are in leaked your bot token and API key exposed on GitHub.
How do attackers usually get in?#
These are the most common causes in small apps, from most to least frequent.
Leaked secrets#
A .env committed to a public repository, served by the site itself or included in a shared ZIP. Check your Git history and run the curl test above. A leaked secret can explain the whole breach: whoever had the database password needed no flaw in your code.
A database exposed with no password#
A database open to the internet without authentication gets broken into quickly by bots that scan addresses. The Redis administration docs warn that, by default, Redis requires no authentication and listens on all network interfaces. MongoDB binds only to localhost by default, according to its security docs, but many people switch it to 0.0.0.0 to "make it work". Check that your database requires a password and only accepts connections from your app.
A vulnerable dependency#
An outdated library with a known flaw. Run your language's audit and compare the dates the flaws were published with the date of the breach:
npm audit --audit-level=high
pip-audit -r requirements.txtnpm audit asks the registry for known vulnerabilities in your dependencies and exits with an error when it finds something at the given level. pip-audit does the same for Python.
A flaw in your code#
SQL injection from concatenating text into a query, an upload that accepts any extension and saves to a public folder, a system command built from user input, an admin route with no permission check. Look in your access logs for routes that got strange requests shortly before the first signs.
A weak password or a compromised account#
An admin panel with a short password and no attempt limit, or the GitHub account of someone on your team. If the attacker made commits or deploys, your Git history and deploy logs show when.
To compare what is running with what is in your repository, use a copy downloaded from the server:
git diff --no-index --stat ./my-repo/src ./server-copy/srcAny file that appears only in the server copy deserves attention.
How do you clean up and get back online?#
Do not try to "fix" the hacked server file by file. Rebuild:
- Fix the cause in the code: update the dependency, close the database, remove the exposed file, fix the query.
- Deploy fresh from the repository instead of reusing the files that were on the server.
- Use the new secrets, never the old ones.
- Restore the data from a copy made before the breach, if the database was changed. See how to back up a database to set up a routine that makes this possible.
- Monitor closely over the next few days: access logs, CPU usage and third-party service accounts.
Do you have to notify anyone?#
If users' personal data may have leaked, probably yes. In the EU, the GDPR requires the controller to notify the supervisory authority within 72 hours of becoming aware of a breach, unless it is unlikely to result in a risk to people. Other countries and states have their own breach notification laws, and the risk assessment is yours to make. If in doubt, get legal advice.
Tell your users directly too: explain what happened, which data was affected and what they should do, such as changing their password on other services where they reused it.
How do you prevent another breach?#
- Secrets out of the code and out of any folder the site serves.
- A database reachable only by the app, always with a password and TLS.
- Dependencies audited on every deploy.
- Two-factor authentication on every account that can deploy.
- A WAF in front of the app to block known attack patterns; see what a WAF is.
- Tested copies of your data, stored off the server.
The deploy security checklist brings these items together with code examples.
How to do this on Square Cloud#
On Square Cloud, the order in this article maps to steps in the dashboard. To preserve evidence, copy the logs from the dashboard or with the CLI's app logs command (logs from before a restart stay visible) and take a manual snapshot of the application before you touch any file; manual snapshots are available on every plan. The browser file manager shows the application's disk, which helps you find what isn't in your repository. To rotate secrets, edit the environment variables in the dashboard, the API, the CLI or the VS Code extension; the change applies on the next restart, without a rebuild.
When you rebuild, deploy from your repository into a new application rather than over the old one: the disk persists across deploys and a commit only overwrites the files in the ZIP, so whatever the attacker left would still be there. For data, databases get a daily automatic snapshot on every plan that includes databases, and applications from the Pro plan. Each daily snapshot is kept for 7 days, so pick one from before the breach. A database snapshot restores into the same database, another of the same engine or a new database, and can also be downloaded as a .zip.
Applications run in isolated containers without root access, and one cannot read another's variables or files, even in the same account. Every plan includes a WAF and L3/L4/L7 DDoS protection. Those layers limit the damage, but they do not fix a flaw in your code or a leaked secret: incident response is still yours. See how environment variables and snapshots work.
Infrastructure
L3/L4/L7 DDoS mitigation and a WAF on every plan
FAQs
Is it enough to delete the strange files and restart?
No. The attacker may have left another way in, and the credentials they saw are still valid. Rotate every secret and rebuild the app from the code in your repository.
Do I have to tell anyone if user data leaked?
Probably. Under the GDPR, a personal data breach that poses a risk to people must be reported to the supervisory authority within 72 hours of becoming aware of it. Other jurisdictions have their own rules, so get legal advice if in doubt.
How do I know whether the attack is still going on?
After rotating secrets, watch your access logs, CPU usage and third-party service accounts. New access with rotated credentials means the way in is still open.
Does restoring an old copy fix it?
It fixes the data, if the copy is from before the breach. But if the flaw that allowed the attack is still in the code, the app can be hacked again. Fix the cause before going back online.
More in Security
See all 10 articles- What is a brute force attack and how to defend against itA brute force attack tests passwords in bulk until one works. Learn the types (spraying, credential stuffing), how they work and the defenses, with code that runs.
- How to protect your app from DDoS attacksTo protect against DDoS, combine edge mitigation (CDN and WAF) with rate limiting, timeouts and caching in your app. Step-by-step guide with working code.
- API key exposed on GitHub: how to revoke and prevent itPushed an API key to GitHub? Revoke it first, then clean the history and rotate the value. Here are the steps and how to stop it from happening again.
- Leaked your bot token? What to do right nowIf your bot token leaked, reset it in the Discord Developer Portal (or with /token in BotFather) first. Learn the right order and how to check the damage.