Learning objectives
After reading this article you will be able to:
- Review the security items that matter before a bot, site or API goes live
- Apply security headers and input validation in an Express API
- Check with simple commands whether a secret ended up in Git
On this page
- How do you keep secrets out of your code?
- How do you keep dependencies safe?
- How do you set up HTTPS and security headers?
- How do you validate input and limit access?
- What should you log, and what should you never log?
- What do you do when a secret leaks?
- How do you verify it worked, and which mistakes should you avoid?
- How to do this on Square Cloud
- FAQs
Many incidents on small bots, sites and APIs come from basic failures: a token in the repository, an outdated dependency, an endpoint without authentication. You do not need to be a security specialist to close those doors. You just need to walk through a list before deploying and revisit it now and then.
This checklist covers what a solo developer can do in an afternoon. The examples use a recent Node.js version and Express 5, but the items apply to any language. If your question is only about the Discord token, read how to protect your bot token.
The checklist, in order:
- Secrets out of the code and out of Git.
- Up-to-date dependencies.
- HTTPS and security headers.
- Authentication, authorization and input validation.
- Least privilege for tokens, keys and database users.
- Logs without sensitive data and errors that leak no details.
- A plan for when a secret leaks.
These items cover much of the OWASP Top 10:2025, such as Broken Access Control (A01), Security Misconfiguration (A02), Software Supply Chain Failures (A03) and Authentication Failures (A07).
How do you keep secrets out of your code?#
Bot tokens, database passwords, API keys and webhook secrets enter the application through environment variables, never written in the code. Access to the repository should not, by itself, grant access to production. If the concept is new, read what environment variables are.
For local development, use a .env file and add it to .gitignore before the first commit:
echo ".env" >> .gitignore
git ls-files .env # should print nothing
git log --all --oneline -- .env # should print nothingThe first command confirms the file is not tracked. The second shows whether it was ever committed, on any branch. If a commit shows up, the secret is in the history: revoke it and issue a new one. The OWASP secrets guide is clear that rewriting Git history has side effects and does not replace rotation.
Three more habits:
- Do not bake secrets into Docker images with
ENVorARG. Inject them at run time. - Use a different secret per environment (test and production).
- Turn on GitHub secret scanning, which looks for exposed credentials across the full history on all branches and raises an alert. It runs automatically on public repositories.
How do you keep dependencies safe?#
Your code is small, but node_modules is not. Every package is third-party code running with your application's permissions, and new vulnerabilities appear without you changing anything.
npm audit --omit=dev # report on production dependencies only
npm audit fix # applies compatible fixesnpm audit sends your dependency list to the registry and returns known vulnerabilities, exiting with a non-zero code if it finds any. Fixes that require changing a version range need --force, which can break your code, so run your tests afterward.
To avoid relying on memory, enable Dependabot security updates on GitHub: it opens pull requests with the minimum patched version when an alert is raised. Keep package-lock.json in the repository so the deploy installs the exact versions you tested, and install with npm ci.
Be wary of packages with names similar to popular ones and of brand-new packages with few downloads: that is how supply chain attacks get in.
How do you set up HTTPS and security headers?#
All traffic coming from outside should use HTTPS. Without it, passwords and tokens travel in plain text and any network on the path can read or alter the response. On a host with automatic certificates, you never issue one by hand. See how SSL/TLS works.
After HTTPS come the headers. The helmet package sets several of them at once, including Strict-Transport-Security, X-Content-Type-Options: nosniff, Content-Security-Policy and Referrer-Policy, and removes X-Powered-By, which reveals that you use Express.
npm install express@5 helmetimport express from "express";
import helmet from "helmet";
const { API_TOKEN } = process.env;
if (!API_TOKEN) {
console.error("API_TOKEN is missing");
process.exit(1);
}
const app = express();
app.use(helmet());
app.use(express.json({ limit: "100kb" }));
// authentication before any private route
app.use((req, res, next) => {
const token = (req.headers.authorization ?? "").replace(/^Bearer /, "");
if (token !== API_TOKEN) return res.status(401).json({ error: "Unauthorized" });
next();
});
app.post("/items", (req, res) => {
const { name } = req.body ?? {};
if (typeof name !== "string" || name.length === 0 || name.length > 100) {
return res.status(400).json({ error: "name must be 1 to 100 characters" });
}
res.status(201).json({ name });
});
// generic error: the detail goes to the log, not to the client
app.use((err, _req, res, _next) => {
console.error(err);
res.status(500).json({ error: "Internal Server Error" });
});
app.listen(Number(process.env.PORT ?? 3000));Strict-Transport-Security only takes effect on HTTPS responses: the browser remembers, for the number of seconds in max-age, that the site must only be reached over HTTPS. Helmet's default is one year with includeSubDomains. The default Content-Security-Policy is strict, so a site that loads scripts and images from other domains must adjust it. For an API that only returns JSON, it rarely gets in the way.
For the rest of the API (rate limiting, CORS, constant-time token comparison), follow the API production checklist.
How do you validate input and limit access?#
Treat everything from outside as hostile: body, query string, headers, uploaded file names and even the messages users send to your bot. Check type, length and format, and use parameterized queries instead of building SQL by concatenating strings.
Authenticating is not enough: Broken Access Control tops the OWASP Top 10:2025. If GET /orders/42 returns order 42 to any authenticated user, anyone can change the number and read other people's orders. Every route that touches a resource must check that the user owns it.
The same applies to bot commands: a command that deletes messages or changes settings must check the caller's role or permission instead of trusting that the command is hidden.
Least privilege. Each credential should be able to do only what its use requires. The OWASP secrets guide recommends giving each secret the minimum privileges and limiting CI/CD credentials to the secrets the job actually uses.
- Bot: request only the permissions and intents it needs, never "Administrator" for convenience.
- Database: an application user that cannot create or drop tables, separate from the migration user.
- API keys: one key per service, so you can revoke one without breaking the others.
- People: someone who only reads logs does not need access to deploy.
What should you log, and what should you never log?#
Log events that help an investigation: failed logins, 401 and 403 responses, 500 errors and administrative actions. Logging and alerting failures are their own category in the OWASP Top 10:2025, because without records you do not notice an attack.
Never log passwords, tokens, card numbers or the whole request body. And do not return a stack trace to the client: it reveals paths, libraries and versions. The example above logs the full error on the server and replies only "Internal Server Error".
What do you do when a secret leaks?#
Act in this order: revoke, replace, investigate.
- Revoke the secret at the source (Discord portal, database dashboard, API provider). Deleting the commit does not help.
- Generate a new one and update the environment variable on your host.
- Restart the application so it reads the new value.
- Investigate the service's logs for suspicious use during the window the secret was exposed.
- Fix the cause:
.gitignore, secret scanning and a review of who had access.
How do you verify it worked, and which mistakes should you avoid?#
With the application live, check the headers and authentication:
curl -sI https://your-app.example.com | grep -iE "strict-transport|x-content-type|x-powered-by"
curl -i https://your-app.example.com/items -X POST -H "Content-Type: application/json" -d '{"name":"a"}'Expected: strict-transport-security and x-content-type-options present, x-powered-by absent and the second call returning 401. Also run npm audit --omit=dev and git log --all --oneline -- .env.
The most common mistakes:
- Committing
.env"just once": the history keeps it forever. - A bot token in a screenshot or error log pasted into a forum or support server.
- CORS with
*on an authenticated API. - An admin panel without authentication, assuming an unknown URL is enough.
- Ignoring
npm auditwarnings for months. - The same database password in test and production.
How to do this on Square Cloud#
Square Cloud is self-service hosting: you deploy and define the configuration, so the checklist is still yours, but several items have something to lean on.
- Secrets (item 1): per-application environment variables, in the dashboard, the API, the CLI or VS Code. The dashboard imports your local
.env, and a change applies on the next restart with no rebuild. For a GitHub Action deploy, the only credential is an API key stored as a repository secret. - Dependencies (item 2): at start, when there is no
node_modules, the platform runsnpm installwithNODE_ENV=production, so devDependencies never reach production andnpm audit --omit=devcovers exactly what runs. Ship yourpackage-lock.jsonso the install uses the versions you tested. - HTTPS (item 3): every web application gets a
*.squareweb.appsubdomain with HTTPS. The certificate renews itself, HTTP redirects to HTTPS, and Helmet's headers, such as HSTS, pass through intact. - Network and isolation: every plan includes a WAF and L3/L4/L7 DDoS protection, and applications run in isolated containers without root access; one cannot read another's variables or files, even in the same account. Those layers do not replace authentication, validation and rate limiting in your code.
- Leaks (item 7): change the variable and restart the application from the dashboard, the CLI or the API, watching it come back in the real-time logs.
After the deploy, run the two curl commands from the verification section against your squareweb.app subdomain. See the details in network and protection and environment variables.
Infrastructure
L3/L4/L7 DDoS mitigation and a WAF on every plan
FAQs
What is the most common security mistake when deploying a bot or API?
Leaving a token, password or API key in the code or the repository. In a public repository anyone can copy it, so treat the secret as leaked and generate a new one.
Does deleting the .env file fix it if I already committed it?
No. The secret stays in the Git history. Revoke it and issue a new credential; cleaning the history helps, but it does not replace rotating the secret.
Do I need HTTPS for a bot that only talks to Discord or Telegram?
The bot itself only makes outbound connections, which already use TLS. HTTPS becomes mandatory when it receives traffic, such as a webhook, a web dashboard or an API.
How often should I run npm audit?
On every deploy and automatically on the repository, for example with Dependabot. New vulnerabilities are published constantly, even when you do not touch your code.
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.
- My app was hacked: what to do nowIf your app was hacked, contain access, rotate every secret and preserve the logs before cleaning up. See the right order, how to find the cause and how to recover.
- 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.