The files you forgot you deployed

terraform.tfstate, .aws/credentials, sftp.json: the infra files in your web root

The .env file gets all the attention, and it earns it. But it isn't the only file that turns into a disaster when it sits in a public web root. A whole category of infrastructure and deploy config does the same thing, and these files leak something worse than app secrets. They leak the keys to the thing that hosts the app.

Here's the list, and what each one hands over to anyone who guesses the path.

The files and what's inside them

These all share a property: they're meant for your machine, your CI runner, or your deploy tooling, and they're never meant to be served to a browser. When they end up in the web root, a single GET request reads them.

FileWhat a stranger gets
terraform.tfstateYour entire infrastructure as Terraform recorded it, often with provider credentials and resource secrets stored in plaintext
.aws/credentialsAWS access key and secret, the literal keys to your account
appsettings.json.NET config, typically with the database connection string and password in it
.vscode/sftp.jsonThe deploy host, username, and password or key path for your SFTP target
docker-compose.ymlYour service topology and every env value baked into it, which is frequently database passwords and API keys
.netrcSaved login credentials for hosts your tooling talks to

The Terraform state file is the standout. terraform.tfstate is a complete, machine-readable inventory of your infrastructure: every resource, its configuration, and, depending on the providers, secrets that Terraform stored as plaintext attributes. Database passwords, generated keys, sometimes the cloud credentials themselves. It's the blueprint and the keys in one JSON file.

request
GET /terraform.tfstate HTTP/1.1 Host: yoursite.com
response
HTTP/1.1 200 OK
{ "resources": [{
"type": "aws_db_instance",
"attributes": {
"username": "admin",
"password": "prod_S3cr3t_2024" } }] }
Terraform stores resource secrets as plaintext attributes. A public state file reads them straight out.

Why these end up downloadable

Almost always the same accident: the deploy copied the whole project directory into the web root, infra files included, instead of just the built application.

A git pull on the server brings down .aws/, the .terraform/ directory, and the docker-compose.yml alongside the code. An rsync without an exclude list copies everything. A Dockerfile that does COPY . . bakes the lot into the image, and then the web server happily serves any of it because they're sitting next to index.html. The .vscode/sftp.json case is its own small tragedy: a VS Code extension stores your deploy credentials there for convenience, the folder gets committed and deployed, and now your SFTP password is a URL away. This is the same root cause as an exposed .env file and a public .git folder: the deploy shipped more than the app.

$ yoursite.com
$ curl -s https://yoursite.com/.aws/credentials
[default]
aws_access_key_id = AKIA4PED...EXMP
aws_secret_access_key = wJalrXUt...KEY
$ aws s3 ls --profile loot
your-app-prod-backups every-customer-export.csv
One readable credentials file, and the stranger is now authenticated as your AWS account.

The fix is to ship less, and block the rest

Two layers. First, don't deploy infrastructure files at all. Your web root should contain the built application and nothing else. Use a deploy that copies only the build output, or a .dockerignore and deploy exclude list that drops .aws, .terraform, *.tfstate, docker-compose.yml, .vscode, and .netrc.

Second, make the web server refuse to serve these even if one slips through, as a backstop.

# nginx: no rule, the web root serves whatever's in it
location / {
try_files $uri $uri/ =404;
}
# /terraform.tfstate and /.aws/credentials return 200
A deny rule is the safety net. The real fix is not deploying these files in the first place.

And rotate anything that was reachable. An AWS key that sat in a public .aws/credentials, a database password from a public tfstate, a deploy login from a public sftp.json, all of them are compromised the moment the file is public, regardless of how quickly you pull it. Generate new ones and store the Terraform state in a proper backend (S3 with a lock, Terraform Cloud) instead of a file in the project.

Whether any of these files answers a GET request is something a stranger finds by trying the paths, and we try the same ones. SurfaceCheckr requests the known infra and deploy config paths from outside, checks each response for the content signature that proves it's the real file and not a soft-404, and flags the ones that are downloadable. We read enough to confirm the file is there and stop; we don't pull your whole state file down. What we hand you is the list of infrastructure files a stranger can read, which is the list you want before someone else makes it.

Find it before someone else does.

Paste your domain. The grade and issue count are free, and you'll see in a couple of minutes exactly what's reachable from outside.