What is your recommended alternative to .env files?
Really depends on your threat model and use case. The problems with .env files: plain text on disk, no access control, no rotation mechanism, no audit trail, trivial to leak accidentally, secrets go into env variables (which are exposed and often leak). Which of those do you care about? What are you trying to prevent?
At the simplest level, keeping .env-ish files, use sops + age [1] or dotenvx [2] (or similar) to encrypt just the values. You keep the .env file approach, the actual secrets are encrypted, and now you can check the file in and track changes without leaking your secrets. You still have the env variable problems.
There are some options that'll use virtual files to get your secrets from a vault to your process's env variables, or you can read the secrets from a secret manager yourself into env variables, but that feels like more complexity without a lot more gain to me. YMMV.
You could use a regular password manager (your OS's keychain, 1Password and its ilk, etc) if you're just working on your own. Also in the more complexity without much gain category for me.
If you want to use a local file on disk, you could use a config file with locked down permissions, so at least it's not readable by anything that comes along. ssh style.
Better is to have your code (because we're talking about your code, I assume) read from secret managers itself. Whether that's Bitwarden, AWS / GCP / Azure (well, maybe not Azure), Hashicorp, or one of the many other enterprisey options. That way you get an audit trail and easy rotation, plus no env variables and no plain text at rest. You can still leak them, but you have fewer ways to do so.
Speaking of leaking accidentally, the two most common paths: Logging output and Docker files. The first is self explanatory, though don't forget about logging HTTP requests with auth headers that you don't want exposed. The second is missed by a lot of people. If you inject secrets into your Dockerfile via `ARG` or `ENV` that gets baked into the image and is easy to get back out. Use `--mount-type=secret` etc. (Never use the old Docker base64 stored secrets in config. That's just silly.)
There are other permutations and in-between steps, these are just the big ones. Like all security stuff, the details really depend on your specific needs. It is easy to say, though, that plain text .env files injected into env variables are at the bad end of the spectrum. Passing the secrets in as plain text args on the command line is worse, so at least you're not doing that!
1: https://github.com/getsops/sops / https://github.com/FiloSottile/age
[dead]
In the context of traditional SaaS, using dynamic secrets loaded at runtime (KMS+Dynamo, etc.).
For agentic tools and pure agents, a proxy is the safest approach. The agent can even think it has a real API key, but said key is worthless outside of the proxy setting.