The pitch for serverless has always been “stop managing infrastructure and just write code.” That’s genuinely liberating — until you realize the security model shifts underneath you in ways that aren’t always obvious. There’s no server to patch, sure, but there’s also no perimeter firewall, no long-lived process to monitor, and often dozens (or hundreds) of individual functions, each one a separate potential entry point with its own permissions, dependencies, and event triggers.
I’ve reviewed serverless applications where a single Lambda function had permissions to read every table in DynamoDB “just in case,” triggered by an API Gateway endpoint with no input validation at all. That’s the kind of gap serverless security is really about closing.
Why Serverless Changes the Security Model
In a traditional server-based application, you might have one attack surface: the server itself, with a handful of open ports. In serverless, every function is its own miniature application, and your real attack surface becomes the sum of:
- Every event source that can trigger a function (API Gateway, queues, storage events, schedulers)
- Every dependency each function imports
- Every IAM/permission set attached to each function individually
- Every environment variable and secret each function has access to
flowchart TD
A[Event Sources<br/>API Gateway, Queue, Storage, Schedule] --> B[Function Trigger]
B --> C{Input Validation}
C -- Invalid --> D[Reject Request]
C -- Valid --> E[Function Executes<br/>with Scoped IAM Role]
E --> F[Access Secrets Manager /<br/>Key Vault for Credentials]
F --> G[Downstream Resource<br/>Database, Storage, API]
E --> H[Logs + Traces<br/>sent to Monitoring]
Core Serverless Security Best Practices
1. Apply Least Privilege Per Function
Every function should have its own dedicated execution role, scoped to exactly the resources it needs — not a shared, broadly permissioned role reused across your entire application. A function that only reads from one S3 bucket should not have write access to every bucket in the account.
2. Validate All Input at the Function Boundary
Serverless functions are frequently triggered by external, untrusted input — API requests, queue messages, uploaded files. Treat every trigger as untrusted and validate rigorously, the same way you would for any public-facing endpoint, since injection-style attacks don’t disappear just because there’s no traditional server involved.
3. Manage Secrets Properly
Never hardcode API keys or database credentials in function code or plaintext environment variables. Use AWS Secrets Manager, Azure Key Vault, or GCP Secret Manager, and grant each function scoped access only to the specific secrets it needs.
4. Scan Function Dependencies
Serverless functions still pull in third-party packages, and those packages carry the exact same supply chain risks as any other application. Run SCA scans on function dependencies as part of your deployment pipeline, not after the fact.
5. Set Sensible Timeouts and Memory Limits
Overly generous timeout and memory settings can be abused for denial-of-wallet attacks (an attacker triggering expensive, long-running invocations to drive up your cloud bill) or to give malicious code more room to operate. Tune limits to what the function actually needs.
6. Monitor and Log Every Invocation
Because there’s no persistent server to inspect after the fact, logging and tracing (CloudWatch, Application Insights, Cloud Logging, or a distributed tracing tool) become your primary forensic record. Make sure logs capture enough context to reconstruct what happened during an incident.
7. Secure Event Sources, Not Just the Function
An API Gateway in front of your function needs its own security controls — rate limiting, authentication, and request validation — independent of whatever validation happens inside the function itself.
8. Avoid Overly Broad Trigger Permissions
Don’t let a storage bucket trigger every function in your account on every object upload. Scope event source mappings tightly to reduce the chance of unintended or malicious invocation chains.
9. Keep Cold-Start Optimization From Undermining Security
Some teams cache credentials or connections globally across invocations to speed up cold starts. Be careful this doesn’t result in credentials or sensitive data persisting longer than intended in a function’s execution environment.
10. Treat Function Code Like Any Other Application Code
Run SAST scanning and code review on serverless functions just as rigorously as on a traditional monolith or microservice — the “it’s just a small function” mindset is exactly how insecure patterns creep in unnoticed.
Step-by-Step: Hardening a New Serverless Function
- Define a dedicated IAM role/execution identity for the function, granting only the specific permissions it needs.
- Add input validation at the very top of the function, rejecting malformed or unexpected payloads before any business logic runs.
- Pull secrets from a secrets manager, not environment variables, using scoped access tied to the function’s identity.
- Set a timeout and memory limit appropriate to the function’s actual workload, not a generous default.
- Add dependency scanning to your deployment pipeline so vulnerable packages are caught before the function ships.
- Enable structured logging and tracing, including correlation IDs, so an incident can be reconstructed after the fact.
- Restrict the event source so only the specific triggers you intend can invoke the function.
Best Practices Checklist
- Give every function its own least-privilege execution role
- Validate all input at the function boundary, treating every trigger as untrusted
- Store secrets in a managed secrets service, never in code or plaintext env vars
- Scan function dependencies for known vulnerabilities on every deployment
- Set appropriate timeout and memory limits per function
- Enable detailed logging and tracing for every invocation
- Secure event sources (API Gateway, queues) independently of the function itself
- Scope event triggers tightly to avoid unintended invocation paths
- Apply SAST scanning and code review to function code like any other application code
Common Mistakes
Reusing one broad IAM role across every function. This is the serverless equivalent of running every microservice as root — convenient, and dangerous the moment any one function is compromised.
Skipping input validation because “it’s just an internal function.” Internal-facing functions can still be reached through misconfigured permissions or chained attacks; assuming a function is unreachable by an attacker is a common and costly mistake.
Hardcoding secrets for “quick testing” that never gets cleaned up. Test shortcuts have a habit of shipping to production, especially in fast-moving serverless deployments.
Ignoring dependency bloat in function packages. Large deployment packages with unnecessary dependencies increase both attack surface and cold-start time, with no real benefit.
No centralized view across hundreds of functions. As serverless applications grow, teams often lose track of which functions have which permissions — periodic permission audits are essential, not optional, for good DevSecOps hygiene.
Frequently Asked Questions
Q: Is serverless inherently more or less secure than containers or VMs? Neither inherently — serverless removes OS-level patching responsibility (a real win), but shifts risk toward permission sprawl, event source security, and dependency management, which require just as much discipline.
Q: How do I prevent denial-of-wallet attacks on serverless functions? Set conservative concurrency limits, timeouts, and memory allocations, and enable billing alerts. Some providers also support reserved/provisioned concurrency limits that cap how far a function can scale.
Q: Do serverless functions need a Web Application Firewall (WAF)? If a function is exposed through an API Gateway or similar HTTP-facing service, yes — a WAF in front of that gateway adds a valuable layer of protection against common web attack patterns before requests ever reach your code.
Q: How often should I audit function permissions? Regularly, and ideally automatically. As functions evolve, their actual permission needs often shrink or change; a scheduled audit (or continuous least-privilege tooling) catches permissions that have outlived their purpose.
Conclusion
Serverless doesn’t eliminate your attack surface — it fragments it into many smaller pieces, each with its own permissions, triggers, and dependencies. Securing it well means applying the same fundamentals you’d apply anywhere else — least privilege, input validation, secrets management, dependency scanning — but doing so consistently across potentially hundreds of individual functions instead of one central server. Get the per-function discipline right, and serverless can genuinely be one of the more secure ways to run production workloads.
