Spin up a local AWS, plant deliberately insecure resources, and run real security scanners against it. No account, no token, no cost, no risk.
There is a better way. You can run a full AWS-compatible API locally, plant whatever insecure resources you want, point your security tools at it, and throw the whole thing away when you are done. This article shows how, end to end, with commands you can copy and run right now.
We will use LocalEmu as the local cloud and two open-source scanners to audit it. Everything here runs on your laptop and costs nothing.
What is LocalEmu
LocalEmu is a free, open-source AWS cloud emulator. It speaks the AWS APIs, so the same AWS CLI, boto3, Terraform, or CDK you already use work against it unchanged. You just point them at http://localhost:4566.
It is a community fork of the LocalStack community edition, which was archived and put behind a mandatory account in March 2026. LocalEmu continues that codebase under Apache 2.0, free and tokenless. No account, no sign-up, no auth token.
That last part is exactly what makes it a great security lab: there is no real account behind it. The emulator runs under the placeholder AWS account 000000000000, so nothing you do can touch, expose, or bill a real environment.
What we are going to build
A local “ vulnerable by design ” AWS environment, then audit it:
- Start LocalEmu.
- Point the AWS CLI at it.
- Plant a public S3 bucket, a Lambda function with secrets, and a couple of exposed EC2 instances.
- Scan all three with real security scanners and read the findings and compliance scores.
- Fix an issue and re-scan to watch the score climb.
Total time: about ten minutes.
Step 1: Install and start LocalEmu
pip install localemu[runtime]
localemu start
You will see the banner and a Ready. line. By default it listens on localhost:4566. Docker is only needed for services that run a real engine in a sidecar (Lambda, ECS, EKS, RDS, EC2); everything else is pure Python.
Step 2: Point the AWS CLI at LocalEmu
The AWS CLI honors a handful of environment variables. Set the endpoint and some dummy credentials (any value works, since there is no real account):
export AWS_ENDPOINT_URL=http://localhost:4566
export AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE
export AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
export AWS_DEFAULT_REGION=us-east-1
That is it. From now on, aws ... talks to your local cloud. Confirm it:
aws sts get-caller-identity
{
"UserId": "AKIAIOSFODNN7EXAMPLE",
"Account": "000000000000",
"Arn": "arn:aws:iam::000000000000:root"
}
Step 3: Plant some insecure resources
Let us create exactly the kind of thing a security scanner should scream about.
A public, unencrypted S3 bucket:
aws s3 mb s3://acme-public-website
aws s3api put-public-access-block --bucket acme-public-website \
--public-access-block-configuration \
BlockPublicAcls=false,IgnorePublicAcls=false,BlockPublicPolicy=false,RestrictPublicBuckets=false
aws s3api put-bucket-policy --bucket acme-public-website --policy '{
"Version": "2012-10-17",
"Statement": [{
"Sid": "PublicRead", "Effect": "Allow", "Principal": "*",
"Action": "s3:GetObject", "Resource": "arn:aws:s3:::acme-public-website/*"
}]
}'
A Lambda function with secrets sitting in plaintext environment variables and a public function URL:
echo 'def handler(e, c): return {"ok": True}' > handler.py
zip function.zip handler.py
aws lambda create-function --function-name payment-processor \
--runtime python3.12 --handler handler.handler \
--role arn:aws:iam::000000000000:role/lambda-role \
--zip-file fileb://function.zip \
--environment 'Variables={DB_PASSWORD=Sup3rS3cret!,STRIPE_SECRET_KEY=sk_live_51Hxxxx}'
aws lambda create-function-url-config \
--function-name payment-processor --auth-type NONE
And two EC2 instances with public IPs, the old IMDSv1 metadata service, and a security group that opens SSH and RDP to the entire internet:
SG=$(aws ec2 create-security-group --group-name public-ssh-rdp \
--description "open" --query GroupId --output text)
aws ec2 authorize-security-group-ingress --group-id $SG --protocol tcp --port 22 --cidr 0.0.0.0/0
aws ec2 authorize-security-group-ingress --group-id $SG --protocol tcp --port 3389 --cidr 0.0.0.0/0
AMI=$(aws ec2 describe-images --query 'Images[0].ImageId' --output text)
aws ec2 run-instances --image-id $AMI --instance-type t2.micro --count 2 \
--security-group-ids $SG --associate-public-ip-address \
--metadata-options "HttpTokens=optional,HttpEndpoint=enabled"
Notice we never left the laptop. No real bucket was ever public. No real secret was ever stored. No real instance was ever exposed.
Step 4: Scan it with real security scanners
Now the fun part. Install two open-source scanners and run them against your local cloud. They use boto3, so they pick up the same AWS_ENDPOINT_URL and hit LocalEmu automatically.
pip install s3-security-scanner lambda-security-scanner ec2-security-scanner
Scan the buckets:
s3-security-scanner security
You get a per-bucket score, a summary, and a multi-framework compliance breakdown, for example:
s3-security-scanner security
╔══════════════════════════════════════════════════════════╗
║ S3 Security Scanner - AWS S3 Security Analysis Tool ║
╚══════════════════════════════════════════════════════════╝
Starting S3 security analysis...
2026-05-31 12:39:08,680 - INFO - Found 1 S3 buckets in account 000000000000
Scanning 1 bucket(s)...
2026-05-31 12:39:08,818 - WARNING - Account 000000000000 missing account-level public access block
2026-05-31 12:39:08,819 - WARNING - GuardDuty S3 protection is not enabled for threat detection
2026-05-31 12:39:08,819 - WARNING - Macie S3 discovery is not enabled for sensitive data detection
⠋ Scanning 1 buckets...2026-05-31 12:39:08,819 - INFO - Scanning bucket: acme-public-website
2026-05-31 12:39:09,050 - INFO - Exported compliance report to ./output/s3_compliance_us-east-1_20260531_123909.json
2026-05-31 12:39:09,051 - INFO - Exported JSON report to ./output/s3_scan_us-east-1_20260531_123909.json
2026-05-31 12:39:09,051 - INFO - Exported CSV report to ./output/s3_scan_us-east-1_20260531_123909.csv
2026-05-31 12:39:09,070 - INFO - Exported HTML report to ./output/s3_scan_us-east-1_20260531_123909.html
S3 Security Scan Summary - us-east-1
┏━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┓
┃ Metric ┃ Value ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━┩
│ Account ID │ 000000000000 │
│ Total Buckets │ 1 │
│ Average Security Score │ 20.0/100 │
│ Public Buckets │ 1 │
│ High Severity Issues │ 1 │
│ Medium Severity Issues │ 1 │
│ Public Objects Found │ 0 │
│ Sensitive Objects Found │ 0 │
└─────────────────────────┴──────────────┘
Lowest Scoring Buckets
┏━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━┳━━━━━━━━┓
┃ Bucket ┃ Score ┃ Issues ┃
┡━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━╇━━━━━━━━┩
│ acme-public-website │ 20/100 │ 20 │
└─────────────────────┴────────┴────────┘
Now the functions:
lambda-security-scanner security
lambda-security-scanner security
╔══════════════════════════════════════════════════════════╗
║ Lambda Security Scanner ║
║ Comprehensive Lambda Security Auditing ║
╚══════════════════════════════════════════════════════════╝
Version 1.0.0 | https://github.com/TocConsulting/lambda-security-scanner
Starting Lambda security analysis...
2026-05-31 12:42:45,525 - INFO - Found 1 Lambda functions in account 000000000000
Scanning 1 function(s)...
⠋ Scanning Lambda functions... 0/12026-05-31 12:42:45,615 - WARNING - AWS API error: An error occurred (NoSuchEntity) when calling the ListAttachedRolePolicies operation: Role lambda-role not found
⠙ Scanning Lambda functions... 0/1
Overall Metrics
┏━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━┓
┃ Metric ┃ Value ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━┩
│ Total Functions │ 1 │
│ Scanned │ 1 │
│ Errors │ 0 │
│ Average Score │ 36.0 │
│ Public Functions │ 1 │
│ Functions with Secrets │ 1 │
│ Deprecated Runtimes │ 0 │
└────────────────────────┴───────┘
Lowest Scoring Functions
┏━━━━━━━━━━━━━━━━━━━┳━━━━━━━┳━━━━━━━━┳━━━━━━━━━━━━┓
┃ Function ┃ Score ┃ Issues ┃ Runtime ┃
┡━━━━━━━━━━━━━━━━━━━╇━━━━━━━╇━━━━━━━━╇━━━━━━━━━━━━┩
│ payment-processor │ 36 │ 10 │ python3.12 │
└───────────────────┴───────┴────────┴────────────┘
The scanner flagged the public function URL, the secrets in the environment variables, and a dozen more issues, mapped against CIS, PCI-DSS, HIPAA, SOC 2, ISO, GDPR, and NIST. All on a function that exists only on your laptop.
And the instances:
ec2-security-scanner security
ec2-security-scanner security
╔══════════════════════════════════════════════════════════╗
║ EC2 Security Scanner ║
║ Comprehensive EC2 Security Auditing ║
╚══════════════════════════════════════════════════════════╝
Version 1.0.0 | https://github.com/TocConsulting/ec2-security-scanner
Starting EC2 security analysis...
2026-05-31 12:47:40,191 - INFO - Found 2 EC2 instances (filter: running) in account 000000000000
Scanning 2 instance(s)...
2026-05-31 12:47:40,192 - INFO - Running account-level security checks...
2026-05-31 12:47:40,344 - INFO - Running VPC-level checks for 1 VPCs...
Scanning 2 instances...
EC2 Security Scan Summary
┏━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┓
┃ Metric ┃ Value ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━┩
│ Region │ us-east-1 │
│ Account │ 000000000000 │
│ Total Instances │ 2 │
│ Running │ 2 │
│ Stopped │ 0 │
│ Public IP │ 2 │
│ With Secrets in UserData │ 0 │
│ Unencrypted Volumes │ 2 │
│ Critical Issues │ 2 │
│ High Issues │ 2 │
│ Avg Instance Score │ 2.0/100 │
│ Environment Score │ 30/100 │
└──────────────────────────┴──────────────┘
Environment Posture (account + VPC, counted once)
┏━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ Severity ┃ Finding ┃ Description ┃
┡━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩
│ HIGH │ NO_GUARDDUTY │ GuardDuty is not enabled for EC2 protection. │
│ HIGH │ VPC_BPA_NOT_ENABLED │ VPC Block Public Access is not blocking IGW traffic. │
│ HIGH │ NO_CLOUDTRAIL │ No active CloudTrail trail found. │
│ HIGH │ DEFAULT_SG_HAS_RULES │ VPC default security group allows traffic in: ['vpc-c19b5364e20197df2'] │
│ MEDIUM │ EBS_DEFAULT_ENCRYPTION_DISABLED │ EBS default encryption is not enabled. │
│ MEDIUM │ NO_VPC_FLOW_LOGS │ VPC flow logging is not enabled in: ['vpc-c19b5364e20197df2'] │
│ MEDIUM │ NACL_ADMIN_PORTS_OPEN │ Network ACLs allow admin ports from 0.0.0.0/0 in: ['vpc-c19b5364e20197df2'] │
└────────────┴─────────────────────────────────┴──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
Both instances scored 2 out of 100. The scanner caught the public IPs, IMDSv1 (the older, SSRF-prone metadata service), the unencrypted EBS volumes, and the security group exposing SSH and RDP to the world, plus environment-level issues like the permissive default security group, missing VPC flow logs, and no GuardDuty.
Step 5: Fix it and re-scan
This is where a local lab really pays off: the feedback loop is seconds, not a deploy cycle. Lock the bucket down:
aws s3api put-public-access-block --bucket acme-public-website \
--public-access-block-configuration \
BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true
aws s3api put-bucket-encryption --bucket acme-public-website \
--server-side-encryption-configuration \
'{"Rules":[{"ApplyServerSideEncryptionByDefault":{"SSEAlgorithm":"AES256"}}]}'
s3-security-scanner security
Watch the score climb and the “Public Buckets” count drop to zero. You just practiced detection and remediation without ever touching a real account.
Why this is genuinely useful
This is not just a party trick. A local AWS plus a scanner is a real tool:
- Learn cloud security for free. Plant a misconfiguration, see how a scanner catches it, fix it, repeat. No bill, no risk.
- Develop and test security tooling. If you write scanners, policy checks, or remediation scripts, you need predictable, reproducible inputs. Seed the exact resource you want and assert on the output.
- Run it in CI. Spin up LocalEmu in a pipeline, apply your Terraform, scan it, and fail the build on a critical finding, all before anything reaches AWS.
- Onboard and demo safely. Teach a team what “good” looks like without handing out real credentials.
- Reproduce findings. Got a weird scanner result against prod? Recreate the resource locally and debug it in isolation.
An honest caveat
LocalEmu is an emulator, not AWS. Coverage and fidelity vary by service, and it will not perfectly mirror every IAM edge case or every API quirk. You will actually see this in the EC2 scan: a few of the scanner’s checks call newer APIs the emulator has not implemented yet (snapshot block-public-access, serial console, patch state), and the scanner reports those as errors rather than crashing. That is the right behavior, and it is also a useful reminder that local results are a strong approximation, not ground truth. Treat LocalEmu as what it is: an excellent environment for learning, building tooling, and CI, and a complement to, not a replacement for, scanning your real accounts. Use it to get your tooling and your instincts sharp, then point those same tools at production with confidence.
Wrap-up
In about ten minutes, with no AWS account and no spend, we stood up a local cloud, planted realistic misconfigurations, caught them with real security scanners across ten compliance frameworks, and remediated one in a seconds-long loop. That is a security lab you can keep on your laptop and rebuild from scratch any time.
Spin one up and try breaking it. It is the safest place to do so.
Links
- LocalEmu: https://github.com/localemu/localemu
- S3 Security Scanner: https://github.com/TocConsulting/s3-security-scanner
- Lambda Security Scanner: https://github.com/TocConsulting/lambda-security-scanner
- EC2 Security Scanner: https://github.com/TocConsulting/ec2-security-scanner
LocalEmu is an independent project and is not affiliated with or endorsed by LocalStack Inc.











Top comments (0)