S3 bucket misconfigurations in AI-generated infrastructure code
Por Seaworthy · 24 de junio de 2026
Photo by Taylor Vick on Unsplash
When you ask an AI assistant to "write me some Terraform to create an S3 bucket for user uploads," you usually get code that works. The bucket gets created, files go in, your app runs. What you often don't get is a bucket that's configured correctly from a security standpoint.
This isn't a knock on any particular model. It's structural. AI tools optimise for satisfying the literal request. "Create a bucket" doesn't include "and lock it down properly," so the assistant skips the locking down.
What gets left out
The AWS S3 console has spent years adding default-on protections. Block Public Access is now on by default for new buckets created through the console. The AWS CLI and SDKs, though, don't enforce this. Neither does most AI-generated Terraform.
A typical AI-generated bucket resource looks like this:
resource "aws_s3_bucket" "uploads" {
bucket = "my-app-uploads"
}
That's it. Bucket created. The AI might add a few tags if you asked nicely. What it almost certainly won't add is explicit public access blocking, versioning, server-side encryption, or access logging. Each of those omissions is exploitable in its own way.
The public access issue is the most immediate. Without an explicit aws_s3_bucket_public_access_block resource, your bucket's public access settings are inherited from the account level. If your account was itself set up with AI-generated code and that code didn't configure account-level block public access, you now have a publicly readable bucket sitting in production.
The bucket policy problem
It gets worse when you ask for permissions. When developers ask an AI assistant to "allow my Lambda to access the uploads bucket," the generated policy often ends up looking like this:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": "*",
"Action": "s3:*",
"Resource": [
"arn:aws:s3:::my-app-uploads",
"arn:aws:s3:::my-app-uploads/*"
]
}
]
}
Principal: "*" means anyone. Action: "s3:*" means everything: read, write, delete, list. This policy allows any unauthenticated request from the public internet to download, overwrite, or wipe every object in the bucket.
The AI didn't do this out of malice. You asked it to "allow access" and it generated a policy that allows access, very thoroughly.
The correct approach is to scope the principal to the specific Lambda execution role and restrict the actions to only what's needed:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::123456789012:role/my-lambda-role"
},
"Action": ["s3:GetObject", "s3:PutObject"],
"Resource": "arn:aws:s3:::my-app-uploads/*"
}
]
}
That's the difference between a bucket that can be wiped by anyone with curl and one that only your Lambda can touch.
What a secure bucket actually looks like in Terraform
Here's a minimal configuration that at least covers the basics AWS expects from production infrastructure:
resource "aws_s3_bucket" "uploads" {
bucket = "my-app-uploads"
}
resource "aws_s3_bucket_public_access_block" "uploads" {
bucket = aws_s3_bucket.uploads.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
resource "aws_s3_bucket_versioning" "uploads" {
bucket = aws_s3_bucket.uploads.id
versioning_configuration {
status = "Enabled"
}
}
resource "aws_s3_bucket_server_side_encryption_configuration" "uploads" {
bucket = aws_s3_bucket.uploads.id
rule {
apply_server_side_encryption_by_default {
sse_algorithm = "AES256"
}
}
}
That's four resources for what the AI wrote in one. Every one of those extra resources is doing real work. The public access block stops accidental exposure from a badly scoped policy. Versioning means you can recover if someone overwrites or deletes objects. Encryption at rest means the data isn't readable if storage-level access is ever compromised.
Why AI skips the security defaults
Models learn from training data. Most Terraform examples on the internet show the minimal resource definition, because tutorials aim for brevity and getting something working fast. Security configuration is covered separately in a "hardening" section that many tutorials never reach, or as an afterthought that doesn't make it into the snippets people copy.
The model isn't wrong, strictly speaking. The code does what you asked. It just doesn't do what you meant.
You can prompt your way around this to some extent: "Write production-ready Terraform for an S3 bucket with public access blocked, versioning enabled, and AES-256 encryption at rest." That usually gets you closer. But you shouldn't have to remember to ask. Security defaults should be the default, and they're not.
Checking what you already have
If you have AI-generated infrastructure code running in production right now, the fastest audit is two commands:
aws s3api get-public-access-block --bucket YOUR_BUCKET_NAME
aws s3api get-bucket-policy --bucket YOUR_BUCKET_NAME
If the first command returns any false values, public access may be possible. If the second command returns a policy with "Principal": "*", treat that as a live incident, not a future to-do.
The CapitalOne breach in 2019 exposed over 100 million customer records partly through a misconfigured S3 bucket. That wasn't AI-generated code, but the same class of mistake is now being introduced at much higher volume by developers who are shipping faster than they're reviewing.
Seaworthy is a static analysis tool that scans repositories for security and configuration issues like these, aimed at solo developers and small teams who use AI to write code and want a quick confidence check before shipping.
Este artículo fue generado por IA y resume fuentes públicamente disponibles.
Seaworthy analiza tu repositorio en busca de los problemas que se tratan en artículos como este.
Brechas de seguridad, secretos expuestos y configuraciones erróneas detectados antes de desplegar. Gratis, sin cuenta necesaria.