7 Lambda Function URL Controls That Block Public Abuse

A Lambda function URL set to AuthType NONE is a public HTTP endpoint: once the attached resource-based policy grants public access, any caller on the internet who learns the URL can invoke your code and spend your budget. The control point is policy, not code — and since October 2025 AWS requires both lambda:InvokeFunctionUrl and lambda:InvokeFunction permissions on every new function URL, the permission layer is exactly where an open endpoint becomes a governed one. The seven controls below come from current AWS documentation and the MITRE ATT&CK knowledge base; most can be applied in a single afternoon.
What AuthType NONE exposes
With AuthType AWS_IAM, Lambda authenticates every request against IAM: callers need permission in an identity-based policy, a resource-based policy, or both before the handler runs. With AuthType NONE, that gate disappears — AWS warns that when a public resource-based policy is in place, any unauthenticated user with your function URL can invoke your function. Two details make this worse than it sounds. The console and SAM create the public policy for you automatically, so the exposure is a default, not a decision. And if you later delete the URL, Lambda does not delete the associated resource-based policy — stale public grants outlive the endpoint they were created for. If you must serve browsers directly, treat NONE as meaning authentication happens elsewhere: validate a token or signature inside the handler and assume the URL will be enumerated eventually.
Seven controls to enforce
| Control | What to set | Why it matters |
|---|---|---|
| 1. Default to AWS_IAM | AuthType AWS_IAM on every function URL | Only signed principals can invoke; anonymous scanners get nothing |
| 2. Scope grants to the URL | Add the lambda:InvokedViaFunctionUrl condition key | Grantees can call through the URL only, not via the API or SDK |
| 3. Deny open URLs org-wide | Service control policy denying URL config where auth is not AWS_IAM | One misconfigured console click cannot expose the whole account |
| 4. Monitor external access | Enable IAM Access Analyzer findings | Flags public and cross-account permissions, at no charge |
| 5. Move secrets out of env vars | Fetch credentials from Secrets Manager at runtime | Env vars are configuration, not a vault, and share a hard size cap |
| 6. Size /tmp deliberately | Set ephemeral storage to what the workload needs | Oversized scratch space enlarges what hijacked code can stage |
| 7. Control what runs and leaves | Pinned images plus default-deny egress | A compromised handler reaches nothing worth stealing |
Row 5 deserves emphasis because it fails quietly: the total size of all environment variables on a function cannot exceed 4 KB, and AWS itself recommends Secrets Manager instead of environment variables for database credentials, API keys and authorization tokens. Environment variables travel with every version of the function configuration, surface in consoles and SDK calls, and are readable by anything running inside the handler — including a dependency you did not audit. Secrets Manager keeps the value out of configuration entirely and lets you rotate it without redeploying code.
Row 6 is a sizing decision rather than a security feature: ephemeral storage can be sized between 512 MB and 10,240 MB in 1-MB increments, and each execution environment gets its own encrypted volume under /tmp. Match the allocation to the workload — a maximum allocation for an API handler that writes nothing to disk only enlarges the staging space available to an injected process.
Row 7 is where serverless meets supply chain: pull base images and layers through verification rather than by mutable tag, applying the same image verification layers that protect container deployments, and send outbound traffic through an allow list so a hijacked handler can neither exfiltrate data nor reach mining pools.
Least privilege for the role
The execution role defines what your function can touch once it runs, and it is the blast radius of every other failure on this page. Lambda assumes the role automatically on every invocation, so the role policy is the only thing standing between a compromised handler and your data stores. Work it as a loop, not a one-off:
- Inventory every permission the role carries today, including inherited managed policies.
- Capture real usage over a window that exercises all code paths — scheduled jobs, retries and rare branches included.
- Replace broad statements with a generated policy granting only the observed actions on the observed resources.
- Re-test the function end to end; a failure here is a finding, not a reason to widen the policy back.
- Repeat the cycle quarterly and whenever the function’s dependencies change.
The discipline matters more than the tooling: a role scoped to one bucket prefix and one table turns a leaked credential from an account-wide incident into a contained one, and it makes the Access Analyzer findings from row 4 actionable instead of noise.
Blocking metadata credential theft
Serverless does not exempt you from the classic cloud credential attack. MITRE ATT&CK sub-technique T1552.005 documents adversaries querying the cloud instance metadata API at the de facto standard address 169.254.169.254 to collect credentials and other sensitive data, and tracked malware families including Hildegard and Shai-Hulud have queried AWS and GCP metadata endpoints for instance and service credentials. In a Lambda context the runtime injects the execution role’s access keys into the execution environment as environment variables, which means application-layer SSRF or a malicious dependency can attempt the same harvest your EC2 hardening was built to stop. Layer the defense: validate and restrict every URL the handler fetches, run with a role whose keys are worthless outside one function, and apply the IMDSv2 enforcement practices across the rest of your estate so one platform’s weakness does not become the standard everywhere.