Lambda MicroVMs Bedrock Kimi K2.5 OWASP Top 10 GitHub Actions

AI Code Review Sandbox — installing untrusted packages in throwaway Firecracker VMs

I built a security scanner that actually installs untrusted packages, runs real security tools against them, and uses AI to generate a report — all inside an isolated Lambda MicroVM that boots in about a second and is destroyed the moment the scan finishes. Each scan gets its own VM. No shared state, no residual risk. Around $0.01 per scan.

↗ View on GitHub
VR
Vishnu Rachapudi Cloud & AI Engineer · AWS Community Builder (Security) · 14× AWS Certified
June 2026·10 min read
Section 01

The problem

Supply chain attacks keep increasing. Typosquatting, malicious install scripts, dependency confusion — the attack surface is the package manager itself. You can't really know if a package is safe without installing it. But installing untrusted code on your machine, or in a shared CI runner, is exactly the thing you're trying to avoid.

Existing tools either scan metadata without installing (incomplete) or run in shared environments (risky). I wanted something that does both: actually installs the package and runs real security tools against it — but in a throwaway environment that's destroyed immediately after.

💡
The key insight: Lambda MicroVMs give you a full Firecracker VM that boots in ~1 second from a snapshot, runs for up to 8 hours, and is completely destroyed on termination. No shared state. No residual risk. Perfect for running untrusted code.
Section 02

Architecture

The whole thing is serverless. A browser or CLI hits a Lambda Function URL, the orchestrator Lambda spawns a MicroVM, the scan runs inside that VM, results go to Bedrock for analysis, and the VM is torn down.

🌐 Browser / CLI
Submit package name or repo URL
λ Lambda Function URL
HTTPS endpoint · no API Gateway · no 30s timeout
λ Orchestrator (Python 3.11)
Spawns the VM, runs the scan, calls Bedrock, tears it down
↓ spawns & calls ↓
🖥️ Lambda MicroVM
Firecracker isolation
  • pip / npm install
  • bandit · pip-audit
  • ruff · npm audit
  • OWASP dependency-check
  • destroyed after scan
🤖 Bedrock Kimi K2.5
AI analysis of raw results
  • CVSS severity scoring
  • OWASP Top 10 mapping
  • supply-chain risk flags
  • structured JSON output
↓ report stored in ↓
🗄️ S3 (Reports)
Versioned JSON + Markdown · served via presigned URL

The flow, step by step:

  1. User submits a scan target (package name or repo URL + type) via the web UI or CLI
  2. The orchestrator Lambda receives it through a Function URL
  3. It spawns a Lambda MicroVM from a pre-built image
  4. The MicroVM installs the package and runs the security tools in sequence
  5. Raw results go to Bedrock Kimi K2.5 for analysis
  6. The MicroVM is terminated — zero residual state
  7. A structured JSON report is returned to the user

AWS services used

ServiceRoleWhy this choice
Lambda MicroVMsIsolated scan executionFirecracker VM per scan, full filesystem, sub-second boot
LambdaOrchestratorStateless, cheap, scales to zero
Function URLAPI endpointNo API Gateway overhead, no 30s timeout
BedrockAI analysisKimi K2.5 — ~$0.003/review, 256K context
S3Frontend + reportsStatic hosting, versioned report storage
CloudFrontCDNCache frontend, global edge delivery
Lambda LayerDependenciesrequests + boto3 with MicroVMs API support
Section 03

Why Lambda MicroVMs

The obvious question: why not just run these scans in a regular Lambda function or an ECS container?

ApproachIsolationBootFilesystemCost
Regular LambdaShared runtime~100msNoneCheapest
ECS FargateContainer-level30–60sFullModerate
Lambda MicroVMsVM-level (Firecracker)~1sFullPer second

MicroVMs hit the sweet spot: hardware-level isolation (a real separate kernel), sub-second boot from snapshot, a full filesystem to actually install into, and automatic destruction on termination. You get the security of a VM with the operational model of serverless.

⚠️
Key gotcha: the Lambda runtime's bundled boto3 doesn't include the lambda-microvms service yet. You have to ship a newer boto3 in a Lambda Layer for the MicroVMs API to work. This cost me hours of debugging.

The MicroVM image

Everything starts from a pre-built MicroVM image — the base environment with the scan tools baked in. You spawn VMs from it on demand.

MicroVM Images list showing code-review-sandbox image in Created state, version 1.0
The code-review-sandbox MicroVM image — version 1.0, Created state, ready to spawn VMs from.

A running MicroVM

When a scan starts, a VM spins up with its own HTTPS endpoint and network namespace. Note the operational settings — max lifetime of 28,800 seconds (8 hours), but I terminate after 60 seconds of idle, and the scan itself takes ~1–2 minutes.

MicroVM details page showing a running microvm with endpoint, auto-resume enabled, 8 hour max lifetime, terminate after 60 seconds
A running MicroVM — unique endpoint on lambda-microvm.us-east-1.on.aws, auto-resume on, terminate-after 60s. Each VM needs an auth token to reach it.

Lifecycle

RunMicroVM → PENDING → RUNNING → [run scan] → TerminateMicroVM → TERMINATED

Each VM:
  • has its own network namespace
  • gets a unique HTTPS endpoint (mvm-xxx.lambda-microvm.us-east-1.on.aws)
  • requires an auth token (X-aws-proxy-auth header)
  • can run up to 8 hours (I terminate after ~1–2 minutes)
  • is completely destroyed — no snapshot, no residual storage

During testing I spawned and tore down 15 of them — every one isolated, every one destroyed after its scan.

MicroVMs list showing 15 instances all in Terminated state, all from the code-review-sandbox image
15 MicroVMs spawned and terminated during testing — each isolated, each destroyed once its scan finished.
Section 04

The scanning pipeline

Each ecosystem has its own steps, but the pattern is the same: install → analyze → collect results.

PyPI

1. pip install {package}=={version}    # actually installs in the VM
2. pip-audit                            # known CVEs
3. bandit -r /path/to/package/          # static security analysis
4. ruff check /path/to/package/         # code quality
5. OWASP Top 10 pattern checks          # injection, crypto, SSRF, etc.

npm

1. npm install {package}@{version}      # installs with scripts
2. npm audit                            # known vulnerabilities
3. check suspicious install scripts     # postinstall / preinstall hooks
4. analyze dependency tree depth        # transitive risk

Maven

1. download artifact from Maven Central
2. OWASP dependency-check               # NVD CVE database
3. analyze transitive dependency tree
4. license compatibility check          # copyleft detection

The important part: all of this runs inside the MicroVM. If a package has a malicious setup.py that tries to exfiltrate data or open a reverse shell, it only affects its own VM. The orchestrator talks to the VM over HTTPS with an auth token. Even if the package compromises the VM's runtime, it can't escape the Firecracker boundary.

Here's the raw scan output collected inside the VM for a PyPI scan of requests — you can see the actual install logs, exit codes, and per-step timing.

Raw JSON scan results for a PyPI scan of the requests package showing install_package and package_metadata steps with stdout, exit codes and durations
Raw scan output for the requests package — install logs, exit codes, and per-step durations, all captured inside the MicroVM.
Section 05

AI-powered review with Kimi K2.5

Raw scan output is useful but noisy. pip-audit returns CVE IDs, bandit returns line numbers, OWASP checks return patterns. A human still has to synthesize all of it into a risk decision. That's the job the AI reviewer does.

Why Kimi K2.5

ModelCost / reviewContextCode analysis
Claude 3.5 Sonnet~$0.02200KExcellent
GPT-4o~$0.015128KExcellent
Kimi K2.5~$0.003256KVery good

Kimi K2.5 is 5–7× cheaper than the alternatives and has the largest context window. For code security analysis it's more than enough. The reviewer prompt is heavily structured with:

💡
The prompt handles edge cases explicitly: if a scan tool produced no output, mark it not_assessed rather than assuming it passed. If exit code is non-zero but output is empty, flag it as scan_error. This eliminates false negatives where a crashed tool looks like a clean result.

What the report looks like

The AI turns all that raw output into a risk verdict, a quality grade, and concrete recommendations — each mapped to an OWASP category where relevant. Here's a scan of one of my healthcare Lambda repos: LOW risk, quality B, with five specific recommendations.

Scan Complete UI in dark mode showing LOW risk, Quality B, 5 issues, and recommendations including adding a LICENSE file and structured logging mapped to OWASP A09
Scan result — LOW risk, quality B, 5 recommendations. The AI summary explains the 6 CVEs are in build tools (pip, wheel) and do not affect runtime.

The underlying JSON keeps every finding structured — CVE IDs, severity, affected component, the fix, and the OWASP category.

Raw JSON report for a git scan showing scan_id, target with checks, risk_level low, ai_summary, and security findings with CVE IDs and OWASP mapping
The structured JSON behind the report — each finding has an ID, severity, component, fix, and OWASP category. Served via an S3 presigned URL.

It works the same for any repo. Here's a scan of a different project that surfaced one HIGH-severity finding and mapped recommendations to OWASP A02, A03, and A09.

Scan Complete UI showing 1 HIGH severity finding, Quality B with 7 issues, recommendations mapped to OWASP A02 A03 A09 for the aws-transform-containers repo
A different repo — 1 HIGH finding, quality B, recommendations mapped to OWASP A02 (crypto), A03 (input validation), and A09 (logging).
Section 06

Frontend & UX

The frontend is a single HTML file — no build step, no framework — deployed to S3 behind CloudFront. It calls the Lambda Function URL directly (no API Gateway proxy); CORS is handled by the Function URL config with AUTH_TYPE=NONE.

Dark mode, mid-scan:

Scanner UI in dark mode with Git Repo tab selected, repository URL filled in, all five checks enabled, and a Scanning in isolated MicroVM message
The scanner in dark mode — Git Repo tab, all five checks on, scanning in an isolated MicroVM.

And the same thing in light mode:

Scanner UI in light mode with Git Repo tab selected and all checks enabled, theme persisted
Light mode — same layout, theme persisted in localStorage.

The dependency layer

The whole MicroVMs API only works because of a Lambda Layer carrying a newer boto3. Without it, boto3.client('lambda-microvms') doesn't exist.

Lambda Layer code-review-deps version 3 details showing ARN, python3.11 compatible runtime
Lambda Layer code-review-deps:3 — ships requests plus a boto3 new enough to include the MicroVMs API.
Section 07

CI/CD with GitHub OIDC

Deployment is fully automated via GitHub Actions. No long-lived AWS credentials — it uses OIDC federation for keyless auth.

# .github/workflows/deploy.yml (simplified)
on:
  push:
    branches: [main]

permissions:
  id-token: write   # required for OIDC

jobs:
  deploy:
    steps:
      - uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::ACCOUNT:role/Github-OIDC-Role
      - run: |
          # build layer, zip code, deploy
          aws lambda update-function-code ...

The pipeline builds the layer (requests + latest boto3), packages the orchestrator, deploys to Lambda, and syncs the frontend to S3 with a CloudFront invalidation. Total deploy time: ~45 seconds.

Section 08

Cost breakdown

ComponentPer scanNotes
Lambda Orchestrator~$0.0001512MB, ~5s execution
Lambda MicroVM~$0.005~60–120s runtime
Bedrock Kimi K2.5~$0.003~2000 tokens output
S3 storage~$0.0001negligible per report
Total~$0.008 – $0.013under a penny

At 1000 scans/month, total cost is roughly $10. The MicroVM is the most expensive piece because it runs for a minute or two, but per-second pricing means you never pay for idle time.

Section 09

What I learned

Lambda MicroVMs are real — but new

The API exists (boto3.client('lambda-microvms')), but the runtime's bundled boto3 is too old to include it. You must ship a newer boto3 via a Lambda Layer. The CLI (aws lambda-microvms) works if you have the latest AWS CLI.

Function URLs over API Gateway

For a project like this, Function URLs are vastly simpler than API Gateway — no 30-second timeout, built-in CORS, no routing rules. The tradeoff: no request validation, no usage plans, no custom domain without CloudFront.

CORS is always harder than you think

I went through it all: double CORS headers (Lambda and Function URL both setting them), OPTIONS in AllowMethods triggering a validation error (max 6 chars per method), and the classic "works in curl, fails in the browser" loop. Final fix: let the Function URL own all CORS and remove the headers from the Lambda code entirely.

The prompt matters more than the model

Switching from a generic "analyze this" prompt to a structured one — CVSS criteria, OWASP mapping, explicit edge-case handling — improved output quality far more than switching to a more expensive model would have.