Introduction
Serverless computing changed how teams build software. Instead of renting a server and keeping it patched, you write a small function, deploy it, and the cloud provider runs it only when needed — you pay for the seconds it runs, not for idle time. For a small team or a startup, that is a huge advantage.
It is easy to assume serverless also means someone else handles security. The provider does handle the physical servers, the operating system, and the runtime. But security in the cloud is always shared: the provider secures the platform, and you secure what you build on it — your code, permissions, data, and configuration.
A serverless app can be attacked in familiar ways and in new ones: a function with too much permission can become a doorway into your database, a leaked API key can be copied from a log file, and a single bad dependency can affect hundreds of functions.
This guide explains serverless security in plain language, with practical steps for developers and distributed teams. You do not need a security department to follow it — just a clear checklist, applied every time you deploy a function.
How Serverless Works — and Where the Risks Sit
In a serverless app, your code is split into small functions, each doing one job: handle a signup, resize an image, process a payment, or send an email. An event triggers the function — an HTTP request, a file upload, a message in a queue, or a timer — and the provider starts it, runs it, and shuts it down again.
Because functions are small and short-lived, there is no server to log into and no long-running process to keep alive. That removes some old risks, but the risks do not disappear — they move. The most important new risks are:
- Too much permission. A function that only needs to read one table is often given access to the whole database.
- Too many entry points. Ten functions can mean ten public endpoints, each one a door an attacker can try.
- Hidden secrets. API keys and database passwords are often stored in environment variables or in code, where they can leak.
- Invisible dependencies. Each function pulls in libraries, and each library can contain a vulnerability.
- Event-driven attacks. An attacker can trigger a function thousands of times, running up your bill or overwhelming a downstream service.
All of these risks have practical, cheap fixes, which the rest of this guide works through one by one.
Least-Privilege IAM Roles
Identity and access management — IAM — is the most important part of serverless security. Every function runs with a role that decides what it can touch. If that role can do everything, anyone who compromises the function can do everything too.
The rule is least privilege: give each function only the permissions it needs. A function that sends welcome emails needs permission to send email, not to delete database tables. A function that reads product data needs read access to one table, not write access to all of them. Write down what the function does, list the services it touches and whether it reads, writes, or deletes, then create a role with exactly those permissions. Avoid the wildcard permission that grants access to everything, even in testing, because test settings have a habit of reaching production unchanged.
Separate roles by function, not by project: if two functions share one broad role, a flaw in one gives an attacker the permissions of both. Review roles monthly and remove permissions you no longer use, before a tight role slowly drifts into a wide-open one.
Secrets and Environment Variables
Every serverless app has secrets: database passwords, API keys, signing keys, and third-party tokens. How you store them matters as much as how you write your code.
The most common mistake is putting a secret in the code itself, or in an environment variable copied into logs, error messages, or version control. A secret in a log file is no longer a secret, and a secret committed to a repository can be found by anyone who can see it, now or in the future. Use a proper secrets manager instead, let the function fetch each secret at runtime, and never print a secret in a log.
Rotate secrets every few months, and immediately if you suspect a leak. Give each environment its own secrets: development, staging, and production should not share API keys, so a leaked test key only exposes test data.
API Gateway and Authentication
Most serverless functions are reached through an API gateway — the front door of your app. Securing it is essential, because it is the part anyone on the internet can reach.
First, require authentication wherever it is needed. A public endpoint returning product listings may not need a login, but one that changes data, reads a user profile, or triggers a payment must check who is calling. Use your provider's authentication system or a well-tested identity service, rather than writing your own login check. Second, use HTTPS everywhere, with no plain HTTP fallback — a request sent over plain HTTP can be read or changed on the way.
Third, add rate limiting at the gateway, so one user sending hundreds of requests per second is slowed down or blocked. This stops brute-force logins and attackers triggering expensive functions to run up your bill. Finally, keep public endpoints few: if a function is only used by another function, do not expose it to the internet at all.
Input Validation: Never Trust a Request
A serverless function receives its input from an event — a web request, a file upload, or a message — and that input comes from outside your system, so it cannot be trusted. An attacker can send any data they like, including data designed to break your code or trick it into doing something else.
Validate every input before you use it: check that a field is the right type, length, and format. If you expect a number, reject text; if you expect an email address, check that it looks like one; if a field is not needed, ignore it. Be especially careful with data that reaches a database, a shell command, or another service: use parameterised queries and never build a query by joining user input into a string. That single habit prevents the most common injection attack.
Limit the size of incoming data — reject oversized uploads and unexpectedly long fields, so a small request cannot become a denial-of-service problem. Return safe error messages: say a request failed, but do not reveal database names, file paths, or stack traces. Log the full error for yourself, and show the user only a short, generic message.
Logging and Monitoring
Serverless functions are short-lived, which makes logging more important, not less: when a function runs for two seconds and disappears, the log is the only record of what happened. Log the useful facts for every important action — who called the function, what they asked for, whether it succeeded, and how long it took — but never log secrets, passwords, tokens, or full personal data.
Centralise your logs in one searchable place, and alert on the events that matter: repeated failed logins, a sudden spike in requests, errors from a function that normally works, or a function reaching a service it should never touch. Monitor cost too — a sudden jump in your bill can mean an attacker is triggering your functions thousands of times, so set a billing alert and investigate it the same day. Keep logs for a few weeks, then delete them; old logs are a liability if they contain personal data.
Dependency and Supply-Chain Risks
A serverless function is rarely just your code. It usually includes libraries for handling HTTP requests, talking to a database, or validating data — code written by someone else, each piece able to contain a vulnerability. If twenty functions all use the same vulnerable library, one flaw affects all twenty: that is the supply-chain risk.
Keep your dependencies up to date. Check for known vulnerabilities in your build pipeline, not just when you remember, and update the same day a serious flaw is announced in a library you use.
Use as few dependencies as you can — a ten-line helper you write yourself is safer than a large library you will never fully understand. Lock your dependency versions, test updates, and upgrade deliberately, so a new release cannot introduce a problem unnoticed.
Cold Starts, Timeouts, and Resource Limits
Serverless functions have limits traditional servers do not. A function that has not run for a while takes longer to start — a cold start. One that runs too long is stopped by a timeout, and one that uses too much memory is killed. These limits are part of the design, but they can become security problems if ignored.
Set timeouts deliberately: a payment function should finish in seconds, not minutes, and one that regularly hits its timeout is a bug to fix, not a limit to raise. Set memory limits to fit the job — maximum memory wastes money and gives an attacker more room for expensive work.
Design for retries, but limit them. Providers often retry a failed function automatically, which helps with a temporary network error but is dangerous if the function charges a card or sends an email. Make important actions idempotent — safe to repeat — so a retry does not charge a customer twice, and set a maximum number of retries so a failing function does not loop forever.
Handle cold starts by keeping functions small and fast to load, and loading heavy libraries only when they are needed.
Common Mistakes to Avoid
Most serverless security problems come from a small set of repeated mistakes. The first is giving every function the same broad role, usually the provider's default. It is quick to set up, and it works — until it is abused. Create a separate, narrow role for each function.
The second mistake is storing secrets in environment variables and then logging everything. Environment variables are convenient, but they are not a safe place for a secret unless they come from a secrets manager, and logs must never contain secret values.
The third mistake is exposing internal functions to the internet. If a function is only called by another function, keep it private. Public endpoints should be the exception, not the default.
The fourth mistake is trusting input. Every request, file, and message from outside your system should be validated before it is used. No exception for requests that come from your own app, because an attacker can send the same request directly.
The fifth mistake is ignoring the bill. In serverless, a sudden cost increase is a warning. Set billing alerts, and treat an unexpected spike as a security event until you know otherwise.
The sixth mistake is never updating dependencies: a library that was safe a year ago may have a known vulnerability today, so check regularly and update promptly when a serious flaw is found.
Conclusion
Serverless security is not about buying a special product. It is about applying a short list of habits every time you deploy a function: narrow permissions, secrets in a secrets manager, a guarded front door, validated input, useful logs, updated dependencies, and sensible limits.
The cloud provider secures the platform; you secure what you build on it. Start with the highest-impact items — least-privilege roles and secrets — and work through the rest one by one. A serverless app built this way is safer, easier to understand, cheaper to run, and simpler to fix. Security is not a feature you add at the end: in serverless, it is part of how you write each function.
Frequently Asked Questions
Is serverless more secure than a traditional server? Serverless removes some old risks, such as keeping a server patched, because the provider handles the platform. But it adds new risks around permissions, secrets, and entry points. It is not automatically more or less secure — it depends on how carefully you configure it.
What is the single most important serverless security step? Use least-privilege IAM roles: give each function only the permissions it needs. This limits the damage from almost every other attack, because a compromised function can only do what its role allows.
Should I store API keys in environment variables? Only if they come from a secrets manager, and only if you never log them. Do not put a secret in your code, in a plain variable copied into logs, or in version control. Use a secrets manager, rotate keys regularly, and use different keys per environment.
How do I know if my serverless app is being attacked? Watch for repeated failed logins, a sudden spike in requests or cost, errors from functions that normally work, and functions reaching services they should never touch. Alert on these events, and investigate a billing spike the same day you see it.
Comments
Post a Comment