Serverless computing promised to eliminate infrastructure management from the developer's plate. And it delivered on that promise, mostly. You no longer worry about patching operating systems, scaling servers, or managing network configurations. But the security risks did not disappear. They shifted -- from infrastructure you control to dependencies you trust and runtime environments you cannot inspect.
For supply chain security, serverless functions are particularly interesting because they compress the attack surface into a very specific area: the application code and its dependencies.
The Serverless Attack Surface
In a traditional server deployment, the attack surface spans the OS, the runtime, the application framework, the application code, and all dependencies. In serverless, the cloud provider manages the OS and runtime. Your attack surface is primarily:
- Your function code
- Your dependencies (npm packages, pip packages, etc.)
- Function configuration (IAM roles, environment variables, triggers)
- Layers and custom runtimes (shared code and runtime extensions)
- Event sources (API Gateway, S3 events, SQS messages, etc.)
Items 2 through 4 are all supply chain concerns, and they are where most serverless security issues live.
Dependency Risks in Serverless
Serverless functions tend to have leaner dependency trees than full web applications, but the dependencies they do include carry outsized risk.
The Cold Start Pressure
Serverless billing and performance both incentivize small, fast-loading functions. This creates a tension with security: teams are reluctant to add security-focused dependencies (input validation libraries, security middleware) because they increase cold start times and package sizes. The result is often minimal input validation and a heavy reliance on the cloud provider's built-in protections.
Deployment Package as Attack Surface
When you deploy a Lambda function, you upload a ZIP file or container image containing your code and all dependencies. This deployment package is stored in S3 (for Lambda) or a container registry. If an attacker can modify the deployment package -- through a compromised CI/CD pipeline, a dependency confusion attack, or access to the storage location -- they control what runs when the function is invoked.
Unlike a long-running server, where a compromised dependency might be detected through anomalous behavior over time, a serverless function executes briefly and terminates. Exfiltration can happen in a single invocation, leaving minimal traces.
Transitive Dependencies in Small Functions
A Lambda function that imports aws-sdk and axios seems simple, but those two packages bring their own dependency trees. In a typical Node.js Lambda deployment, even a minimal function can include 50-100 transitive dependencies. Each is a potential attack vector.
The event-stream incident of 2018 demonstrated this perfectly. A popular npm package was compromised through a transitive dependency, and the malicious code specifically targeted cryptocurrency wallets. Any Lambda function that happened to include event-stream in its dependency tree was affected.
Lambda Layers and Shared Dependencies
AWS Lambda Layers allow you to package shared code, libraries, and runtimes separately from your function code. A layer is a ZIP archive that gets extracted into the function's execution environment at /opt.
Layers introduce a supply chain dependency that is often invisible. When a team uses a shared layer maintained by another team or a third party, they are implicitly trusting that layer's contents. If the layer is updated with a compromised dependency, every function using that layer is affected.
Layer Security Best Practices
- Pin layer versions. Lambda functions should reference specific layer version ARNs, not the latest version. This prevents a compromised layer update from automatically propagating.
- Scan layers as part of your SBOM program. Layers contain dependencies just like deployment packages do. They should be included in your software inventory and vulnerability scanning.
- Limit layer publishers. Use IAM policies to restrict who can publish layer versions. In a shared account model, overly permissive layer publishing permissions can allow any developer to modify shared layers.
- Maintain your own copies of third-party layers. Rather than using a layer ARN controlled by a third party, copy the layer into your own account and manage it like an internal dependency.
Environment Variable Exposure
Serverless functions commonly receive configuration through environment variables: API keys, database connection strings, service endpoints. These are visible to anyone with lambda:GetFunction permissions and are included in the function's runtime environment.
From a supply chain perspective, if a compromised dependency runs process.env and exfiltrates the results, it captures every secret configured for the function. This is trivially easy to implement in a malicious npm package's install script or in code that runs during module initialization.
Mitigations:
- Use secret managers (AWS Secrets Manager, Azure Key Vault, Google Secret Manager) instead of environment variables for sensitive values. Retrieve secrets at runtime, not at deployment.
- Apply least-privilege IAM to function roles so that even if a secret is compromised, the damage is limited.
- Monitor outbound network calls. A function making unexpected HTTP requests to external endpoints is a strong indicator of dependency compromise.
Event Source Injection
Serverless functions are triggered by events: API Gateway requests, S3 object creates, SQS messages, DynamoDB streams, CloudWatch events. Each event source is an input vector, and the event data is controlled by different actors with different trust levels.
An API Gateway event is user-controlled input and should be validated rigorously. An S3 event is triggered by object creation, which might be controlled by an upstream system or an external party. A DynamoDB stream event contains data that was previously written to the table by some other component.
The supply chain risk here is indirect. If a component upstream in your architecture is compromised and writes malicious data to an S3 bucket or DynamoDB table, the serverless function that processes that data is exposed. Traditional input validation focuses on HTTP request boundaries, but in serverless architectures, every event source is a trust boundary.
Container Image-Based Functions
AWS Lambda, Google Cloud Run, and Azure Container Apps support deploying functions as container images. This adds the container supply chain to the serverless attack surface: base image vulnerabilities, multi-stage build leaks, and image registry security.
Container-based serverless deployment should follow the same supply chain practices as any container workload:
- Use minimal base images (distroless, Alpine)
- Scan images for vulnerabilities before deployment
- Sign images and verify signatures at deployment
- Pin base image digests rather than tags
- Generate SBOMs for container images
Monitoring and Observability
Serverless functions are ephemeral, which makes traditional security monitoring difficult. There is no persistent process to attach an agent to, no file system to monitor for changes, and no long-running network connections to analyze.
Key monitoring approaches:
- Function-level logging: Log all inputs, outputs, and errors. Ship logs to a centralized SIEM.
- Outbound network monitoring: Use VPC configurations or proxy layers to monitor and control outbound network traffic from functions.
- Runtime protection: Services like AWS Lambda Extensions allow runtime agents that can detect anomalous behavior (unauthorized network calls, file system access, process execution).
- Dependency monitoring: Continuously scan deployment packages and layers against vulnerability databases. When a new CVE affects a dependency in a deployed function, you need to know immediately.
How Safeguard.sh Helps
Safeguard.sh provides supply chain visibility for serverless deployments by scanning deployment packages and container images to generate comprehensive SBOMs. This includes the function code, all direct and transitive dependencies, and Lambda layers.
When a new vulnerability is disclosed in a package that is commonly used in Lambda functions, Safeguard.sh identifies every affected function across your environment within minutes. Combined with policy gates that can block deployments containing known-vulnerable dependencies, this gives your serverless security program the same rigor that your traditional application security program provides.