Skip to content
Published on

AWS Lambda MicroVMs — Running Agent-Written Code Under VM Isolation, and the New Problem Snapshots Create

Share
Authors

Introduction — Where Should You Run the Code an Agent Writes

Getting an LLM to write code is the easy part now. The hard part is running it. Code a model just generated hasn't been reviewed by definition, and one successful prompt injection turns it into code the user never intended. Where you run that code isn't a matter of taste — it's a question of where you draw the isolation boundary.

Containers don't fit well here, for one reason: they share a kernel. However tightly you lock down namespaces, cgroups, and seccomp, tenant A's code and tenant B's code still knock on the same kernel's system-call interface. One break in that interface and the whole boundary collapses. So anyone who takes this problem seriously has ended up going further down — to hardware virtualization, or to a userspace kernel.

That's the context for AWS shipping Lambda MicroVMs on June 22, 2026. It isn't a new isolation technology — Firecracker has existed since 2018, and Lambda itself already runs on top of it. What's new is how it's sold. AWS pulls Firecracker's isolation, snapshotting, and lifecycle out as raw primitives you handle directly, while AWS still operates the hypervisor.

This post isn't a product pitch — it's about the boundary. It looks at exactly what this boundary blocks, what it doesn't, and what new problem the implementation choice of snapshotting brings along.

What Lambda MicroVMs Actually Are

The model described by the AWS Compute Blog (July 10, 2026, Kulkarni, Agarwal, Madan) is this:

  • MicroVM image — you zip up a Dockerfile and code, upload it to S3, and Lambda runs that Dockerfile, boots the application, and takes a Firecracker snapshot of the fully initialized state. It's a versioned artifact.
  • MicroVM — an individual instance launched on demand from that image. You get one per session, per user, per job.

The API is small: create-microvm-image, run-microvm, suspend-microvm, resume-microvm, terminate-microvm, delete-microvm-image.

aws lambda-microvms run-microvm \
  --image-identifier arn:aws:lambda:us-east-1:123456789012:microvm-image:analytics-notebook \
  --idle-policy '{"maxIdleDurationSeconds":300,"suspendedDurationSeconds":28800,"autoResumeEnabled":true}' \
  --maximum-duration-in-seconds 28800 \
  --region us-east-1

Here's just the verifiable spec, sourced from the sizing table in the developer guide.

Baseline              → Peak (auto vertical scale, up to 4x)    Max disk
0.5 GB / 0.25 vCPU   → 2 GB / 1 vCPU            8 GB
1 GB   / 0.5 vCPU    → 4 GB / 2 vCPU            8 GB
2 GB   / 1 vCPU (default) → 8 GB / 4 vCPU           8 GB
4 GB   / 2 vCPU      → 16 GB / 8 vCPU           16 GB
8 GB   / 4 vCPU      → 32 GB / 16 vCPU          32 GB

One common misreading worth clearing up here. The "32GB / 16vCPU" figure quoted around the web isn't a baseline — it's the peak of the largest baseline. Set only the memory, and vCPU follows at a ratio of 1 per 2GB, with the peak auto-scaling up to 4x the baseline. The AWS news blog phrases it as "up to 16 vCPUs, 32 GB of memory, and 32 GB of disk per MicroVM," and that's what it means.

The rest of the boundary conditions:

  • ARM64 only. No x86.
  • 5 regions — Northern Virginia, Ohio, Oregon, Ireland, Tokyo.
  • Up to 8 hours (28,800 seconds). That's both the session lifetime and the idle-suspend retention limit.
  • Each MicroVM gets a dedicated HTTPS endpoint, supporting HTTP/1.1, HTTP/2, WebSocket, gRPC, and SSE. Auth uses a JWE token issued via create-microvm-auth-token, carried in the X-aws-proxy-auth header. The token embeds allowed ports and an expiration.

There's one thing the VM boundary makes possible that's a genuine advantage. Set additionalOsCapabilities to ["ALL"] and things like filesystem mounts, network namespace creation, and running eBPF open up. The documentation's own words are precise here — "Capabilities are applied within the VM isolation boundary and do not affect the host or other MicroVMs." You can hand out something close to root inside the sandbox and still keep the host safe, because there's a hypervisor underneath. Containers can't make this trade.

The Truth Behind the Fast Start — Resuming from a Snapshot Instead of Booting

The substance of "near-instant startup" is: it doesn't boot. It's the same technique Lambda SnapStart used — resuming from a Firecracker snapshot.

At image-build time, here's what Lambda does, in order: it launches a new MicroVM from the base image, runs the Dockerfile, starts the app via ENTRYPOINT/CMD, confirms initialization is complete through lifecycle hooks, and then takes a snapshot of the disk and memory state. There are two hooks.

/ready    → /aws/lambda-microvms/runtime/v1/ready
            Whether the app is in a state safe to snapshot. 503 = retry, 200 = snapshot.
/validate → /aws/lambda-microvms/runtime/v1/validate
            After the build, confirms a new MicroVM launched from that image resumes correctly.

The /validate hook has a hidden use documented by AWS — run a mock payload through it here, and Lambda traces which regions of the snapshot are actually touched, then optimizes fetching those regions at startup. Snapshot restore ultimately comes down to lazily pulling in memory pages, and knowing ahead of time which pages are hot speeds that up.

The documentation spells out what the snapshot contains — the memory state of every running process (including background daemons, cron jobs, and child processes), the disk state, and initialized network connections and file descriptors.

Read that last item again. This is where the real argument of this post begins.

The Price of Snapshots — When the Thing That Should Be Unique Isn't

The Lambda MicroVMs model is "one image → N MicroVMs." That means hundreds of VMs repeatedly resume from a single snapshot. That's the reason it's fast, and the reason it's a problem.

Here's what Firecracker's own documentation calls this pattern, verbatim.

When snapshots are used in a such a manner that a given guest's state is resumed from more than once, guest information assumed to be unique may in fact not be; this information can include identifiers, random numbers and random number seeds, the guest OS entropy pool, as well as cryptographic tokens. Without a strong mechanism that enables users to guarantee that unique things stay unique across snapshot restores, we consider resuming execution from the same state more than once insecure.

It states flatly that resuming from the same state more than once is "insecure." And the exact shape of "potentially insecure use" the Firecracker docs give as an example — resuming microVMs B and C separately from a single snapshot S — is the normal operating behavior of Lambda MicroVMs itself.

AWS doesn't hide this either. From the snapshot documentation:

If your code generates unique content during image version build phase (unique IDs, secrets, or entropy for pseudorandomness), that content is shared across all MicroVMs run from the same image version.

Let's look at exactly where this bites.

Environment variables. Per the documentation, environment variables are set at image-build time (up to 50) and get injected into the container during snapshot construction. That means any secret you put in an environment variable is shared by every tenant's MicroVM booted from that image. Plug an API key into env the way you would in a container, and the entire multi-tenant sandbox sees the same key. To inject at runtime, you have to use the dynamic payload of run-microvm.

Random numbers and cryptographic tokens. Session IDs, seeds, and tokens created at build time get cloned across the board. AWS's prescription is "generate after the MicroVM starts, not during the build," and it provides a /run lifecycle hook for resetting these.

OpenSSL. This is the most concrete trap. The documentation says to use AWS's provided base container image, public.ecr.aws/lambda/microvms:al2023-minimal. The reason: it contains an "AWS-patched version of OpenSSL that is compatible with snapshotting." If you use your own base image, you have to add the openssl-snapsafe-libs package from Amazon Linux 2023 yourself.

Why does OpenSSL get singled out? The next section answers that.

What VMGenID Fixes, and What It Doesn't

There's a kernel-side fix. VMGenID is a virtual device that lets a guest detect "I was just resumed from a snapshot." It exposes a 16-byte random identifier to the guest, and the hypervisor changes that value on every snapshot resume. Firecracker always enables this device, and on resume it writes a new 16-byte value and injects an interrupt into the guest before resuming its vCPU. Linux, starting at 5.18 (via ACPI; 6.10 for DeviceTree), detects the change in this value and reseeds its internal kernel CSPRNG.

That's why the per-language CSPRNGs AWS points you to — Java 11+'s SecureRandom, Node.js's crypto.randomBytes, Python 3.12+'s Secrets.SystemRandom, .NET 8+'s Cryptography.RandomNumberGenerator — are safe. What they have in common is that all of them draw entropy from the kernel through /dev/random or /dev/urandom. Since the kernel gets reseeded, so do they.

The problem is anything that bypasses the kernel. Here's Firecracker's documentation, verbatim:

State other than the guest kernel entropy pool, such as unique identifiers, cached random numbers, cryptographic tokens, etc will still be replicated across multiple microVMs resumed from the same snapshot. Users need to implement mechanisms for ensuring de-duplication of such state, where needed.

The emphasis on will is in the original. VMGenID fixes only the kernel entropy pool. If userspace keeps its own separate entropy pool — which is exactly what OpenSSL does — that stays cloned. That's why the separate openssl-snapsafe-libs package exists, and why AWS recommends using its base image.

Firecracker's entropy documentation is more candid about this limit — on the problem of userspace libraries each keeping their own entropy pool, it states there is "no generic solution in the current programming model, and all we can do is recommend against using them in logic that precedes a snapshot."

There's ongoing standardization work to notify userspace too. Userspace can watch the vm_generation_counter of the VMClock device via mmap() or get notified via poll(), and this support merged into Linux kernel v7.0 via a patch an Amazon engineer (itazur@amazon.com) sent on January 30, 2026. It's backported into Amazon Linux's microvm kernel 5.10 and 6.1 series. But this only pays off if "the library has been modified to watch this device" — and right now, the odds that whatever library you're using has been modified that way are low.

To summarize, here's how the responsibility for uniqueness on snapshot resume breaks down.

Kernel entropy pool               → auto-reseeded by VMGenID (Linux 5.18+)         [solved]
CSPRNGs using /dev/urandom        → go through the kernel, so safe automatically   [solved]
Self-entropy pools (e.g. OpenSSL) → need a patched build (openssl-snapsafe-libs)   [partially solved]
App-generated IDs, tokens, seeds  → nobody fixes this for you. Regenerate in the /run hook [your responsibility]
Build-time environment variables  → baked into the snapshot as-is. Avoid via runtime injection [your responsibility]

The last two lines are the most important part of this post. The VM isolation boundary does nothing for this problem. Blocking a kernel escape between tenants and tenants seeing the same seed are entirely different layers of the problem.

What the VM Boundary Doesn't Block

Say you've solved the snapshot problem entirely. There's still what the VM boundary doesn't block, and in practice this is where the more frequent incidents happen.

The default is open internet. Straight from the networking documentation — "By default, Lambda MicroVMs have public internet access on the egress path." Picture an agent that's been prompt-injected reading credentials or user data inside the sandbox and shipping them out with curl. The VM boundary blocks none of that. From the hypervisor's point of view, that's just the guest doing normal network I/O. To control egress, you have to route traffic through your own VPC with a Lambda Network Connector and put it through security groups and NACLs. The real line of defense isn't the hypervisor — it's the network policy you write.

This isn't unique to AWS. Anthropic's self-hosted sandbox security documentation states the same boundary more bluntly — "Without egress restrictions, a compromised tool execution can reach arbitrary external hosts." And on the list of "what Anthropic can't do for you," there's this: "Isolate tools inside your sandbox. Anthropic's security boundary stops at the sandbox." For what it's worth, the AWS Compute Blog explicitly describes the pattern of using Lambda MicroVMs as a self-hosted sandbox provider for Claude Managed Agents — the agent loop lives on Anthropic's side, tool execution in your MicroVM.

IAM is still on you. One good detail the Compute Blog points out: you can separate the build-time IAM role from the runtime IAM role. You need this if you want fine-grained per-tenant permissions. VM isolation is worthless if the execution role is over-privileged.

Prompt injection isn't an isolation problem. There's no way for the hypervisor to notice that the model was tricked into doing something "permitted." The VM boundary is a tool for shrinking blast radius, not a tool for stopping an agent from making a bad call.

An Honest Look at the Numbers

AWS does not give a startup-latency number. Go through the Compute Blog, the news blog, and the developer guide, and all you get is phrasing like "near-instant startup," "resume within seconds," "starts within seconds." No millisecond figure appears anywhere. So I won't invent one here — it's a number you have to measure yourself, against your own image size and working set. Given that snapshot restore scales with page fetches, there's no reason a multi-gigabyte working set and a 200MB image would resume in the same time.

Firecracker's scale numbers are vendor claims. "15 trillion+ monthly invocations" (developer guide), "tens of trillions of requests each month for over 1.5 million customers" (Compute Blog) — both are figures AWS states about its own service, and the measurement conditions aren't published. They're sufficient as qualitative evidence that Firecracker has been proven at scale, but not more than that.

Cost you can actually compute. The published us-east-1 ARM rates from the Lambda pricing page are:

vCPU              $0.0000276944 / vCPU-second
Memory            $0.0000036667 / GB-second
Snapshot write    $0.0038  / GB  (suspend)
Snapshot read     $0.00155 / GB  (start / resume)
Snapshot storage  $0.08    / GB-month

Keep the default baseline (2GB / 1vCPU) running continuously and that's $0.0000350278 a second. Roughly $0.126 an hour, about $3.03 a day, about $91 over 30 days. That's just the baseline floor — usage above baseline and snapshot/data-transfer costs are separate. For what it's worth, the practitioner estimate cited in InfoQ's coverage also comes out to $3.03 a day — the same number independently derived from the public rates, so this figure holds up. The same article also cites the observation that this is more than 9x Fargate Spot.

That has a design implication. Under this pricing model, leaving an idle MicroVM running is just money. That's why suspend isn't optional — it's mandatory — and why idle-policy is a first-class citizen of the API. Suspend it and the compute charge stops, leaving only the storage rate. On the flip side, for a workload that runs a large volume of short-lived VMs — like an RL environment — you need to factor in that every start incurs a snapshot-read charge.

Operational overhead comes with numbers attached too. Base images have a deprecation cycle — DEPRECATED (60 days) → EXPIRING (30 days, can no longer create new images) → EXPIRED (can neither build nor run). Make an image and forget about it, and eventually it stops launching. Rebuilding is a recurring chore.

The Alternative — How the gVisor Side Handles This

It's only fair to look at whoever's solving the same problem with a different boundary. Google shipped GKE Agent Sandbox on May 21, 2026. Instead of hardware virtualization, it uses gVisor — a kernel that intercepts system calls in userspace — paired with default-deny Kubernetes network policies. There's an interface for plugging in other sandboxes like Kata Containers too, and it's an open-source project under Kubernetes SIG.

The numbers Google claims are "300 sandbox allocations per second per cluster, sub-second latency, 90% of allocations completing within 200 milliseconds." That's a vendor-measured figure, and conditions like cluster configuration aren't specified in the blog post. "Up to 30% better price-performance than other hyperscalers on Axion" is likewise Google's own comparison.

The difference in the nature of the boundaries is worth stating clearly. gVisor reimplements system calls in userspace to shrink the surface touching the host kernel — thin and fast, but since it's ultimately software impersonating a kernel interface, that software itself becomes the attack surface. Firecracker is a real VM on top of KVM, so the guest gets its own kernel — heavier for it, and it brings along the snapshot problem that's taken up half of this post. Neither side gets to claim "more secure" without conditions attached. The one certain thing is that default-deny egress is something both camps have to handle separately.

When Not to Use It

Don't use it if:

  • The code you're running is your own code. With no trust boundary, there's no reason to pay per-tenant VM prices. A $3-a-day floor and an 8-hour cap aren't justified.
  • The workload is stateless and short-lived. Snapshot lifecycle, uniqueness handling, and suspend policy are all complexity that exists to preserve state — if you're not going to use that, a plain Lambda function is the right call.
  • You need x86. It's ARM64 only.
  • You need somewhere outside the 5 regions.
  • Sessions need to run longer than 8 hours. That's a hard cap with no workaround.
  • You can't lock down egress. Running untrusted code on top of an open-internet default means you bought VM isolation and left the door that actually matters open. This isn't so much "just don't use it" as "if you're not going to do this, there's no reason to pay for VM isolation at all."

When it earns its cost is, by contrast, clear — you need to run user- or agent-generated code isolated per tenant, sessions are long, stateful, and have idle periods, you need to grant high OS privileges inside the sandbox (like a scanner would), and you don't want to operate Firecracker infrastructure yourself. That combination is exactly the spot AWS is aiming at, and it fits that spot well.

Closing

Lambda MicroVMs is a good product. Just don't confuse what's being sold with what you're buying. What's sold is outsourcing hypervisor operations — using Firecracker's isolation and snapshotting through an API instead of running it yourself. What you're buying is a strong inter-tenant kernel boundary, and that boundary is real, and containers can't sell it to you.

What you're not buying is just as clear. Making sure VMs resumed from a snapshot don't see the same secrets and the same seeds is still on you. VMGenID only fixes the kernel entropy pool, and Firecracker's own documentation states plainly that everything above that layer "gets replicated." Egress is on you too — the default is open internet. So is IAM. So is prompt injection.

So the real lesson of this product sits below the isolation technology itself. An implementation choice made for speed creates a new trust assumption. The moment you use a snapshot to skip booting, the 40-year-old assumption that "every process starts in a unique state" quietly breaks. The hypervisor doesn't know that. The only one who does is you, and only if you read the documentation to the end.

References