Serverless removes the servers you patch but not the code, permissions and event sources an attacker can abuse. A function as a service (FaaS) on AWS Lambda, Azure Functions or Google Cloud Run functions (formerly Cloud Functions) runs with a cloud identity, accepts input from many triggers that are not HTTP requests, pulls in third-party packages and scales, and bills, automatically. Those properties move risk into execution roles, event validation, secrets handling, dependencies and cost limits. This guide covers the serverless threat model, function permissions, event injection, front-door options, secrets, dependencies, denial of wallet, logging and infrastructure-as-code checks, with a rollout plan and metrics. It is written for cloud security engineers, platform teams and developers who build or review functions.
Key takeaways
- Every function is a non-human identity: its execution role, managed identity or service account decides the blast radius of any code flaw, so one narrowly scoped identity per function is the most important serverless control.
- All event data is untrusted: object keys, queue messages, stream records and email bodies reach code that often has no gateway in front of it, so injection must be handled in the function.
- Patching is shared, not gone: the provider patches managed runtimes, but you own dependencies, container base images and migrating off deprecated runtimes.
- Cost is an attack surface: automatic scaling turns floods, recursive triggers and stolen credentials into bills. Concurrency caps, maximum instances, timeouts and budget alerts are security controls.
- Visibility must be designed in: functions are short-lived, so logs, traces and control plane events are the only evidence you will have.
The serverless threat model and who patches what
The NCSC places serverless "between PaaS and SaaS" in its shared responsibility model. The provider runs the host, operating system and runtime; the customer owns code, configuration, identity, event sources and data. AWS states that "Lambda is responsible for curating and publishing security updates for all supported managed runtimes" and applies them automatically by default, but runtime support ends: after deprecation "AWS may no longer apply security patches", creation of new functions is blocked at least 30 days later and updates at least 60 days later.
| Item | Provider | Customer |
|---|---|---|
| Host, hypervisor, OS of the execution environment | Patches and isolates | Nothing to patch; choose isolation tier where offered |
| Managed language runtime (zip deployment) | Applies runtime security updates | Migrate before deprecation dates |
| Container image functions | Publishes updated base images | Rebuild and redeploy from the latest base image; AWS notes deprecation notifications are not available for container image functions |
| Function code and dependencies | None | Secure coding, dependency updates, code signing |
| Identity, triggers, network, secrets | Provides the controls | Configures every one of them |
The resulting attack surface is different from a web server's. ATT&CK v20 tracks T1648 Serverless Execution, where adversaries "may abuse serverless computing, integration, and automation services to execute arbitrary code", often after stealing cloud credentials (T1078.004), so creating or modifying functions is itself something to detect.
- Warm reuse and cold starts: providers reuse a warm execution environment across invocations, so global variables, cached tokens and files in /tmp can persist from one request to the next. Do not keep one caller's data in shared state, and clear temporary files after use. A cold start loads the latest code and runtime; a long-lived warm instance does not pick up a fix until it is recycled.
- Runtime protection: agent-style protection on functions is delivered as layers, extensions or wrapped images that add latency and must themselves be trusted code. Most estates get more value from least privilege, posture checks and control plane detection first. See the CNAPP Guide.
Where identity fits: function permissions
A Lambda execution role "grants the function permission to access AWS services and resources", and Lambda "automatically assumes your execution role when you invoke your function". The temporary credentials sit in the reserved environment variables AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY and AWS_SESSION_TOKEN, which means any code injection or dependency compromise that can read the environment can steal the role's credentials. Every function is therefore a workload NHI with a credential worth stealing.
- One identity per function: shared roles across functions mean the least-protected function defines the access of all of them. Use one execution role, managed identity or service account per function.
- Start narrow, then prove it: AWS advises adjusting the role "to include only the required permissions" before production and using IAM Access Analyzer, which reviews CloudTrail activity "and generates a policy template with only the permissions that the function used". Scope actions to specific resource ARNs, not "*".
- Avoid default identities: on Cloud Run the Compute Engine default service account is used if you specify none, and Google warns it "might automatically be granted the Editor role on your project". Use a user-managed service account with minimal permissions and never set GOOGLE_APPLICATION_CREDENTIALS, since tokens come from the metadata server.
- Use managed identities on Azure: Microsoft notes that with a managed identity "you don't need to provision or rotate any secrets", and recommends managed identities instead of secrets for trigger and binding connections.
- Control who can invoke: the resource-based policy on a Lambda function and invoker roles on Cloud Run decide which principals and services can call the function. Restrict service principals with source account or source ARN conditions.
- Review deployers as privileged: anyone who can update function code or pass a role to a new function can act as that role. See the Cloud Workload Identity Guide and the Cloud PAM and CIEM Guide.
Event injection
Functions are triggered by far more than HTTP. Any field an outside party can influence, from a file name uploaded to storage to the subject line of an email, can reach code that builds a query, a shell command or a prompt. Gateways and WAFs only see HTTP, so most event sources arrive unfiltered.
| Trigger | Attacker-controlled data | Typical sink | Control |
|---|---|---|---|
| HTTP via API gateway or function URL | Body, headers, path, query | SQL, NoSQL, templates | Schema validation at the gateway and in code; parameterised queries |
| Object storage events | Object key, metadata, file content | Shell commands, file parsers, image libraries | Never pass keys to a shell; allowlist characters; parse in a sandboxed library |
| Queues, topics, streams | Message body from any authorised producer | Deserialisers, downstream APIs | Validate against a schema; avoid unsafe deserialisation |
| Database change streams | Records written by users | Second-order injection | Treat stored data as untrusted when reprocessed |
| Email and IoT messages | Headers, body, device payloads | Parsers, rules engines | Strict parsing, size limits, sender authentication |
| Functions calling an LLM | Any of the above, inside a prompt | Tool calls with the function's role | Separate instructions from data; limit the role. See the Prompt Injection Guide |
- Validate at both layers: request validation at the gateway reduces noise, but the function must still validate, because the same function is often wired to a second trigger later.
- Do not trust the trigger's identity: a message arriving on an internal queue proves only that some principal could write to that queue.
- Fail closed and quietly: return generic errors, log details server-side, and route failed events to a dead-letter queue for review rather than retrying indefinitely.
Front door: gateways, function URLs and keys
- Lambda function URLs: AuthType AWS_IAM requires signed requests; with NONE, "Lambda doesn't perform any authentication", and a public resource policy lets "any unauthenticated user with your function URL" invoke it. AWS notes that from October 2025 new function URLs require both lambda:InvokeFunctionUrl and lambda:InvokeFunction permissions. Use the lambda:FunctionUrlAuthType condition key to deny NONE through an SCP.
- Azure Functions keys: Microsoft says access keys "can help prevent unwanted access", but "the only way to truly secure your function endpoints is by implementing positive authentication of clients". Set functionsRuntimeAdminIsolationEnabled to true to disable /admin endpoints, redirect HTTP to HTTPS, avoid wildcard CORS origins, and keep the default-enabled FTP endpoint disabled when unused.
- Cloud Run: require authentication for invocation unless the service is deliberately public, and restrict ingress to internal traffic or a load balancer where possible.
- Gateway fronting: an API gateway adds OAuth or JWT authorisers, throttling, usage plans, request validation and WAF integration. See the OAuth 2.0 and OpenID Connect Guide.
| Choice | Choose A when | Choose B when |
|---|---|---|
| A: API gateway / B: function URL | Public or partner traffic needing OAuth, throttling, WAF and quotas | Internal service-to-service calls with AWS_IAM signing and low volume |
| A: Zip on managed runtime / B: container image | You want the provider to patch the runtime | You need custom system libraries and can rebuild images on a schedule |
| A: Secrets manager / B: environment variable | Any credential, key or token | Non-secret configuration only |
| A: Private networking / B: public endpoints | Function reaches internal databases or regulated data | Function calls only public APIs and cold-start latency matters |
Secrets handling
Environment variables are encrypted at rest, and a customer-managed KMS key can be configured, but AWS states "we recommend that you use AWS Secrets Manager instead of environment variables to store database credentials and other sensitive information like API keys or authorization tokens". Environment variables are visible to principals who can read the function configuration and are easy to leak through debug output; the 230 Million AWS environments campaign shows how exposed .env-style configuration becomes cloud access.
- AWS: the Parameters and Secrets Lambda extension serves secrets on a local HTTP port (2773 by default), caches for 300 seconds and holds up to 1,000 secrets. Lower SECRETS_MANAGER_TTL if you rotate often, or request the AWSCURRENT staging label. Grant secretsmanager:GetSecretValue on named secrets only.
- Azure: "Never store secrets in your function code"; use Key Vault references in application settings and managed identities for connections.
- Google Cloud: reference Secret Manager secrets from the service configuration and grant the function's service account access to specific secrets only.
- Prefer no secret at all: where the downstream service accepts the function's cloud identity, use that instead of a key. See the Secrets Management Guide.
Dependencies and code integrity
A small function often ships more third-party code than first-party code, and a malicious package runs with the function's role. Credential-stealing worms such as the ChainDrop npm worm target exactly the CI and cloud credentials that functions and their pipelines hold.
- Pin and scan: lock files, software composition analysis in the pipeline and an SBOM per deployment package or image, including layers.
- Sign code: Lambda code signing with AWS Signer checks integrity, expiry, signing profile mismatch and revocation at deployment. The default UntrustedArtifactOnDeployment value is Warn; set Enforce to block. It only prevents new unsigned deployments, and layers must also be signed.
- Rebuild on a schedule: container image functions and vendored dependencies age even when code does not change. See the Open Source Security Guide and the SLSA and Build Provenance Guide.
Timeouts, concurrency and denial of wallet
Denial of wallet is the serverless form of denial of service: instead of the service falling over, it scales and the organisation pays. Stolen credentials used to run compute (T1496 Resource Hijacking) have the same effect; in the AWS crypto-mining campaign compromised IAM credentials had miners running within minutes.
- Cap concurrency: Lambda reserved concurrency sets both the minimum and maximum concurrent instances for a function; setting it to 0 throttles the function completely, a useful kill switch. Account-level concurrency is shared, so one runaway function can starve others.
- Cap instances: Cloud Run sets a default maximum of 100 instances per revision, which Google says can be used to "control your costs" and to limit connections to a backing service; it can be exceeded briefly during spikes.
- Short timeouts: set each function's timeout close to its normal duration, not the platform maximum, so slow-loris style requests and hung dependencies cost less.
- Break recursive loops: a function triggered by a bucket must not write back to the same bucket or prefix without a guard.
- Throttle at the edge: gateway rate limits and usage plans per client, plus budget alerts and cost anomaly detection routed to the security team, not just finance.
Logging, tracing and detection
Lambda reports request count, duration and errors to CloudWatch automatically, and supports CloudWatch Logs, X-Ray tracing, Lambda Insights and CloudTrail for API calls. Function invocations are CloudTrail data events, which are not recorded unless a trail is configured for them. Azure Functions integrates with Application Insights and Defender for Cloud; Cloud Run writes to Cloud Logging and Cloud Audit Logs.
| Signal | Source | Why it matters |
|---|---|---|
| New function, new function URL, AuthType NONE | CloudTrail management events, Activity log, Admin Activity audit logs | Persistence and execution through serverless (T1648) |
| Code, layer or environment variable change outside the pipeline | UpdateFunctionCode, UpdateFunctionConfiguration events | Backdoored function running with a trusted role |
| Execution role policy or trust change | IAM events | Privilege escalation through the function's identity (T1098.003) |
| Role credentials used from outside the function | CloudTrail source IP and user agent, provider threat detection | Stolen function credentials |
| Throttles, error spikes, concurrency at cap | CloudWatch metrics, Azure Monitor, Cloud Monitoring | Flooding, injection probing or denial of wallet |
Log a correlation ID per event, never log secrets or full payloads containing personal data, and set log retention explicitly. See the Log Management and Retention Guide and the Detection Engineering Guide.
Infrastructure as code checks
Functions are almost always deployed from SAM, Serverless Framework, Terraform, Bicep or CloudFormation, which makes the template the best place to stop misconfiguration. Scan templates in pull requests (open-source scanners such as Checkov cover these formats) and fail the build on:
- wildcard actions or resources in execution role policies, or shared roles across functions;
- function URLs with AuthType NONE or public invoke permissions without an approved exception;
- literal secrets in environment variables;
- missing reserved concurrency or maximum instances, and timeouts at the platform maximum;
- deprecated runtimes, and missing code signing configuration where it is mandated.
Protect the pipeline that deploys functions as carefully as the functions. See the CI/CD Pipeline Identity Security Guide.
OWASP and CSA serverless guidance
The OWASP Serverless Top 10 project page is labelled an incubator project, and its main output is the "OWASP Top 10: Serverless Interpretation" from 2018; the page lists no later edition. The Cloud Security Alliance's "The 12 Most Critical Risks for Serverless Applications" (2019) is described as "a security awareness and education guide". Both remain useful for awareness and threat modelling, but they predate features such as function URLs, managed identities for bindings and code signing, so pair them with current provider documentation.
Implementing serverless security
- Inventory: list every function, its runtime, trigger, execution identity, public exposure and owner across all accounts and regions.
- Fix the worst first: public unauthenticated endpoints, wildcard roles, default service accounts and deprecated runtimes.
- Split identities: one role per function, generated from observed activity, deployed through code.
- Move secrets: from environment variables to a secrets manager or identity-based access, then rotate what was exposed.
- Add guardrails in audit mode: template scanning and policies that report on AuthType NONE, missing concurrency caps and unsigned code; review for two to four weeks, then enforce for new deployments first.
- Turn on telemetry: data events or invocation logs for sensitive functions, tracing, metric alarms and budget alerts.
- Rehearse: practise setting concurrency to zero, revoking a function's role sessions and redeploying from a known-good build; that is also your rollback path.
Small teams can rely on managed runtimes, provider templates and native scanning; larger estates need a platform team that publishes a hardened function template and owns the guardrails.
Detection and measurement
- Least privilege: percentage of functions with a dedicated identity and no wildcard actions.
- Exposure: number of unauthenticated public functions, each with an approved exception.
- Runtime currency: functions on deprecated or soon-to-be-deprecated runtimes, and container images older than your rebuild window.
- Secrets hygiene: functions with secrets in environment variables, trending to zero.
- Cost control coverage: functions with concurrency or instance caps, and mean time from cost anomaly to triage.
- Drift: function changes made outside the deployment pipeline per month.
Framework mapping
| Area | NIST CSF 2.0 | SP 800-53 Rev. 5 | CIS Controls v8.1 | ISO/IEC 27001:2022 |
|---|---|---|---|---|
| Function inventory | ID.AM-02 | CM-8 | 2.1 | A.5.9 |
| Execution identity and least privilege | PR.AA-05 | AC-6 | 5.4, 6.8 | A.5.15, A.8.2 |
| Input validation and secure coding | PR.PS-06 | SI-10, SA-11 | 16.1, 16.12 | A.8.28 |
| Dependencies and code integrity | ID.RA-01, PR.PS-02 | SI-7, SR-3, RA-5 | 2.2, 16.4, 7.4 | A.8.8, A.8.25 |
| Secrets | PR.DS-01 | IA-5, SC-12 | 3.11 | A.5.17, A.8.24 |
| Concurrency and cost limits | PR.IR-04 | SC-5, SC-6 | Not mapped | A.8.6 |
| Logging and monitoring | DE.CM-09, PR.PS-04 | AU-2, AU-12, SI-4 | 8.2, 8.5 | A.8.15, A.8.16 |
ATT&CK v20: T1648 Serverless Execution, T1078.004 Cloud Accounts, T1190 Exploit Public-Facing Application, T1098.003 Additional Cloud Roles, T1530 Data from Cloud Storage and T1496 Resource Hijacking.
Common failure patterns
- One broad role for every function: convenient in development, then promoted to production, so any injectable function can read every table and bucket.
- Keys in environment variables and templates: visible in configuration, committed to repositories and copied into logs, as in the .env campaign above.
- Public by accident: a function URL with AuthType NONE or an anonymous HTTP trigger created for a test and never removed.
- Validation only at the gateway: the function is later wired to a queue or bucket event that bypasses it.
- No cost limits: no concurrency caps or budget alerts, so a flood or stolen key shows up first on the invoice.
- Abandoned functions: old functions on deprecated runtimes with live triggers and roles nobody reviews.
Practitioner checklist
- Inventory all functions with runtime, trigger, identity, exposure and owner.
- Give each function its own least-privilege identity; remove default service accounts and wildcard policies.
- Require authentication on every function endpoint unless an approved exception exists; deny AuthType NONE by policy.
- Validate every event source in code, not only at the gateway.
- Move secrets to a secrets manager or identity-based access and rotate anything previously in environment variables.
- Pin, scan and sign deployment packages, layers and images; enforce code signing where mandated.
- Set timeouts, concurrency or instance caps, gateway throttling and budget alerts for every production function.
- Enable invocation logging, tracing and alerts on function creation, code changes and role changes.
- Scan serverless templates in pull requests and block high-risk findings.
- Track runtime deprecation dates and migrate before creation and update blocks apply.
Standards and references
- OWASP: Serverless Top 10 project
- Cloud Security Alliance: The 12 Most Critical Risks for Serverless Applications
- MITRE ATT&CK: T1648 Serverless Execution
- AWS: Defining Lambda function permissions with an execution role
- AWS: Lambda runtimes and deprecation policy
- AWS: Control access to Lambda function URLs
- Microsoft: Securing Azure Functions
- Google Cloud: Introduction to service identity for Cloud Run
Related NHI Mgmt Group resources: Cloud Workload Identity Guide · Service Account Security Guide · Secrets Management Guide · CNAPP Guide · CI/CD Pipeline Identity Security Guide