Remix.run Logo
Show HN: enveil – hide your .env secrets from prAIng eyes(github.com)
104 points by parkaboy 7 hours ago | 59 comments
londons_explore an hour ago | parent | next [-]

Does this actually work?

I assume an AI which wanted to read a secret and found it wasn't in .env would simply put print(os.environ) in the code and run it...

That's certainly what I do as a developer when trying to debug something that has complex deployment and launch scripts...

PufPufPuf 33 minutes ago | parent [-]

Good point. You would need to inject the secrets in an inaccessible part of the pipeline, like an external proxy.

hardsnow 6 hours ago | parent | prev | next [-]

Alternative, and more robust approach is to give the agent surrogate credentials and replace them on the way out in a proxy. If proxy runs in an environment to which agent has no access to, the real secrets are not available to it directly; it can only make requests to scoped hosts with those.

I’ve built this in Airut and so far seems to handle all the common cases (GitHub, Anthropic / Google API keys, and even AWS, which requires slightly more work due to the request signing approach). Described in more detail here: https://github.com/airutorg/airut/blob/main/doc/network-sand...

sesm an hour ago | parent | next [-]

That's great for API credentials but some secrets are ment for local use, like encryption keys.

petesergeant an hour ago | parent | prev | next [-]

This is cool! Solving the same problem (authority delegation to resources like Github and Gmail) but in a slightly different way at https://agentblocks.ai

NitpickLawyer 5 hours ago | parent | prev [-]

How does this work with SSL? Do you need to provision certs on the agent VM?

hardsnow 5 hours ago | parent [-]

Yep - requires the client to trust the SSL cert of the proxy. Cooperative clients that support eg HTTP_PROXY may be easier to support, but for Airut I went for full transparent mitmproxy. All DNS A requests resolve to the proxy IP and proxy cert is injected to the container where Claude Code runs as trusted CA. As a bonus this closes DNS as potential exfiltration channel.

zith 33 minutes ago | parent | prev | next [-]

I must have missed some trends changing in the last decade or so. People have production secrets in the open on their development machines?

Or what type of secrets are stored in the local .env files that the LLM should not see?

I try to run environments where developers don't get to see production secrets at all. Of course this doesn't work for small teams or solo developers, but even then the secrets are very separated from development work.

Malcolmlisk 19 minutes ago | parent | next [-]

Usually, some people change their .env files in the root of the project to inject the credentials into the code. Those .env files have the credentials in plain text. This is "safe" since .gitignore ignores that file, but sometimes it doesn't (user error) and we've seen tons of leaks because of that. Those are the variables and files the llms are accessing and leaking now.

tuvistavie 21 minutes ago | parent | prev | next [-]

I think having API keys for some third-party services (whatever LLM provider, for example) in a .env file to be able to easily run the app locally is pretty common. Even if they are dev-only API keys, still not great if they leak.

portly 19 minutes ago | parent | prev [-]

Sometimes it can be handy for testing some code locally. Especially in some highly automated CICD setups it can be a pain to just try out if the code works, yes it is ironic.

Zizizizz 5 hours ago | parent | prev | next [-]

https://github.com/getsops/sops

This software has done this for years

_pdp_ 2 hours ago | parent [-]

Came to say this.

ctmnt 4 hours ago | parent | prev | next [-]

This suffers from all the usual flaws of env variable secrets. The big one being that any other process being run by the same user can see the secrets once “injected”. Meaning that the secrets aren’t protected from your LLM agent at all.

So really all you’re doing is protecting against accidental file ingestion. Which can more easily be done via a variety of other methods. (None of which involve trusting random code that’s so fresh out of the oven its install instructions are hypothetical.)

There are other mismatches between your claims / aims and the reality. Some highlights: You’re not actually zeroizing the secrets. You call `std::process::exit()` which bypasses destructors. Your rotation doesn’t rotate the salt. There are a variety of weaknesses against brute forcing. `import` holds the whole plain text file in memory.

Again, none of these are problems in the context of just preventing accidental .env file ingestion. But then why go to all this trouble? And why make such grand claims?

Stick to established software and patterns, don’t roll your own. Also, don’t use .env if you care about security at all.

My favorite part: I love that “wrong password returns an error” is listed as a notable test. Thanks Claude! Good looking out.

robbomacrae 3 hours ago | parent | next [-]

This is amazing. I agree with your take except "You’re not actually zeroizing the secrets"... I think it is actually calling zeroize() explicitly after use.

Can I get your review/roast on my approach with OrcaBot.com? DM me if I can incentivize you.. Code is available:

https://github.com/Hyper-Int/OrcaBot

enveil = encrypt-at-rest, decrypt-into-env-vars and hope the process doesn't look.

Orcabot = secrets never enter the LLM's process at all. The broker is a separate process that acts as a credential-injecting reverse proxy. The LLM's SDK thinks it's talking to localhost (the broker adds the real auth header and forwards to the real API). The secret crosses a process boundary that the LLM cannot reach.

anoncow 3 hours ago | parent | prev [-]

What is your recommended alternative to .env files?

jumploops 3 hours ago | parent [-]

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.

wswin 42 minutes ago | parent [-]

These are from AWS right, what about simple, no cloud setups with just docker compose or even bare proccesses on a VPS?

tiku an hour ago | parent | prev | next [-]

Ive made different solution for my Laravel projects, saving them to the db encrypted. So the only thing living in the .env is db settings. 1 unencrypted record in the settings table with the key.

Won't stop any seasoned hacker but it will stop the automated scripts (for now) to easily get the other keys.

monster_truck 6 minutes ago | parent | prev | next [-]

How did this get to the front page? We shouldn't be encouraging bad practices or drawing attention to people who make embarrassing mistakes

collimarco 40 minutes ago | parent | prev | next [-]

Is this a real protection? The AI agent could simply run: enveil run -- printenv

PufPufPuf 35 minutes ago | parent | next [-]

It prompts for password every time. Which is also the main problem here imo, it would get old quickly.

olmo23 37 minutes ago | parent | prev [-]

it would be prompted for the master password again, according to the website

frumiousirc 27 minutes ago | parent | prev | next [-]

    MY_API_KEY=$(pass my/api/key | head -1) python manage.py runserver
pedropaulovc 6 hours ago | parent | prev | next [-]

1Password has this feature in beta. [1]

[1]: https://developer.1password.com/docs/environments/

jen729w 4 hours ago | parent [-]

You can already put op:// references in .env and read them with `op run`.

1P will conceal the value if asked to print to output.

I combine this with a 1P service account that only has access to a vault that contains my development secrets. Prod secrets are inaccessible. Reading dev secrets doesn't require my fingerprint; prod secrets does, so that'd be a red flag if it ever happened.

In the 1P web console I've removed 'read' access from my own account to the vault that contains my prod keys. So they're not even on this laptop. (I can still 'manage' which allows me to re-add 'read' access, as required. From the web console, not the local app.)

I'm sure it isn't technically 'perfect' but I feel it'd have to be a sophisticated, dedicated attack that managed to exfiltrate my prod keys.

enjoykaz 3 hours ago | parent | prev | next [-]

The JSONL logs are the part this doesn't address. Even if the agent never reads .env directly, once it uses a secret in a tool call — a curl, a git push, whatever — that ends up in Claude Code's conversation history at `~/.claude/projects/*/`. Different file, same problem.

handfuloflight 4 hours ago | parent | prev | next [-]

How does this compare with https://dotenvx.com/?

reacharavindh 3 hours ago | parent [-]

Thanks for this! I’ve been looking for a better solution to the .env files and this is ideal, covers all my needs.

Zizizizz 5 hours ago | parent | prev | next [-]

https://github.com/jdx/fnox

A recent project by the creator of mise is related too

hjkl_hacker 6 hours ago | parent | prev | next [-]

This doesn’t really fix that it can echo the secrets and read the logs. `enveil run — printenv`

Datagenerator 6 hours ago | parent [-]

Not the author but No, the decryption would ask the secret again? The readme mentions it's wiped from memory after use.

nvader 5 hours ago | parent | prev | next [-]

In the vein of related work, there is https://github.com/imbue-ai/latchkey which injects secrets into cURL commands issued by your agent.

chickensong 3 hours ago | parent | prev | next [-]

Is configuration management dead? Sandbox the agent and provision unique credentials to that environment.

md- 3 hours ago | parent | prev | next [-]

as you have stated 'And yes, this project was built almost entirely with Claude Code with a bunch of manual verification and testing.' this code is not copyright protected, therefore you are not allowed to apply a MIT LICENSE to this project.

jshmrsn 2 hours ago | parent | next [-]

That has not been established in the courts, at least not precisely enough to assert that for sure this project isn’t copyrightable.

“ But the decision does raise the question of how much human input is necessary to qualify the user of an AI system as the “author” of a generated work. While that question was not before the court, the court’s dicta suggests that some amount of human input into a generative AI tool could render the relevant human an author of the resulting output.”

“Thaler did not address how much human authorship is necessary to make a work generated using AI tools copyrightable. The impact of this unaddressed issue is worth underscoring.”

https://www.mofo.com/resources/insights/230829-district-cour...

xml 2 hours ago | parent | prev [-]

    > this code is not copyright protected, therefore you are not allowed to apply a MIT LICENSE to this project.
Why not? You still can (and probably should) disclaim warranty and whether the code is copyright protected may vary by jurisdiction.

(Not sure if claiming copyright without having it has any legal consequences though.)

thomc an hour ago | parent | prev | next [-]

Another thing to look at is the built-in sandboxing and permissions for your agent. Claude Code for example has the /sandbox command which uses Bubblewrap on Linux or Seatbelt on macOS for OS level sandboxing. Combine that with global default deny permissions for read & edit on your SSH, GPG keys and other secrets. You need both otherwise Claude can run bash commands which bypass the permissions.

m-hodges 5 hours ago | parent | prev | next [-]

This looks interesting. For agent-fecfile I used the system keyring + an out-of-process proxy (MCP Server) to try to maximize portability.¹

¹ https://github.com/hodgesmr/agent-fecfile?tab=readme-ov-file...

SteveVeilStream 6 hours ago | parent | prev | next [-]

Sometimes I need to give Claude Code access to a secret to do something. (e.g. Use the OpenAI API to generate an image to use in the application.) Obviously I rotate those often. But what is interesting is what happens if I forget to provide it the secret. It will just grep the logs and try to find a working secret from other projects/past sessions (at least in --dangerously-skip-permissions mode.)

WalterGR 5 hours ago | parent [-]

What software do you use that logs credentials?

SteveVeilStream 5 hours ago | parent [-]

Claude Code does it. Check out the JSONL files.

NamlchakKhandro 5 hours ago | parent | prev | next [-]

this won't solve the problem.

Instead you need to do what hardsnow is doing: https://news.ycombinator.com/item?id=47133573

Or what the https://github.com/earendil-works/gondolin is doing

yanosh_kunsh 5 hours ago | parent | prev | next [-]

I think it would be best if AI agents would honor either .gitignore or .aiexclude (https://developers.google.com/gemini-code-assist/docs/create...).

iamflimflam1 5 hours ago | parent [-]

The problem is, you cannot force the agent to do anything.

A suitably motivated AI will work around any instructions or controls you put in place.

yanosh_kunsh 2 hours ago | parent | next [-]

You are absolutely correct, but I don't need it to be 100% bulletproof.

I'm using opencode as a coding agent and I've added a custom plugin that implements an .aiexclude check (gist (https://gist.github.com/yanosh-k/09965770f37b3102c22bdf5c59a...)) before tool calls. No matter how good the checks are, on the 5th or 6th attempt a determined prompt can make the agent read a secret — but that only happens if reading secrets is the explicit goal. When I'm not specifically prompting it to extract secrets, the plugin reliably prevents the agent from reading them during normal coding work.

My threat model isn't a motivated attacker — it's accidental ingestion.

That's also why I think this should be a built-in feature of coding agents — though I understand the hesitation: if it can't guarantee 100% coverage, shipping it as a native safeguard risks giving users a false sense of security, which may be harder to manage than not having it at all.

wdroz 2 hours ago | parent | prev | next [-]

We could simply make the "view file" tool not able to see .env. Same for other "grep-like" tools.

handfuloflight 4 hours ago | parent | prev | next [-]

You can force what is not able to git upstream.

jen729w 4 hours ago | parent | prev [-]

It doesn’t even need to be motivated: just forgetful.

kittikitti 38 minutes ago | parent | prev | next [-]

This works by obfuscating the keys in memory with a root-access risk model. It will work but as I've been told when I tried the same thing for another purpose, this is security by annoyance. It sounds harsh but the same gatekeepers mentioned that this was only a psychological trick.

I dislike the gatekeepers so I will follow this implementation and see where it goes. Maybe they like you better.

umairnadeem123 7 hours ago | parent | prev | next [-]

this solves a real problem. i run coding agents that have access to my workspace and the .env files are always the scariest part. even with .gitignore, the agent can still read them and potentially include secrets in context that gets sent to an API.

the approach of encrypting at rest and only decrypting into environment variables at runtime means the agent never sees the raw secrets even if it reads every file in the project. much better than the current best practice of just hoping your .gitignore is correct and your AI tool respects it.

one suggestion: it would be useful to have a "dry run" mode that shows which env vars would be set without actually setting them. helps verify the config is correct before you realize three services are broken because a typo in the key name.

l332mn 6 hours ago | parent | prev | next [-]

I use bubblewrap to sandbox the agent to my projects folder, where the ai gets free read/write reign. Non-synthetic env cars are symlinked into my projects folder from outside that folder.

navigate8310 5 hours ago | parent | prev | next [-]

I use the combination of sops and age combined with pre-commit hooks to encrypt.env files. Works tremendously well.

anshumankmr 6 hours ago | parent | prev | next [-]

What about something like Hashicorp secrets? We have a the hashicorp secrets in launch.json and load the values when the process is initialized (yeah it is still not great)

syabro 5 hours ago | parent | prev | next [-]

I'm using https://www.litellm.ai/ as a proxy

oulipo2 2 hours ago | parent | prev | next [-]

The way I did it now is to put everything in 1Password and just use the `op://vault/item/field` references in .env or configs

stephenr 5 hours ago | parent | prev | next [-]

> can read files in your project directory, which means a plaintext .env file is an accidental secret dump waiting to happen

It's almost like having a plaintext file full of production secrets on your workstation is a bad fucking idea.

So this is apparently the natural evolution of having spicy autocomplete become such a common crutch for some developers: existing bad decisions they were ignoring cause even bigger problems than they would normally, and thus they invent even more ridiculous solutions to said problems.

But this isn't all just snark and sarcasm. I have a serious question.

Why, WHY for the love of fucking milk and cookies are you storing production secrets in a text file on your workstation?

I don't really understand the obsession with a .ENV file like that (there are significantly better ways to inject environment variables) but that isn't the point here.

Why do you have live secrets for production systems on your workstation? You do understand the purpose of having staging environments right? If the secrets are to non-production systems and can still cause actual damage, then they aren't non-production after all are they?

Seriously. I could paste the entirety of our local dev environment variables into this comment and have zero concerns, because they're inherently to non-production systems:

- payment gateway sandboxes;

- SES sending profiles configured to only send mail to specific addresses;

- DB/Redis credentials which are IP restricted;

For production systems? Absolutely protect the secrets. We use GPG'd files that are ingested during environment setup, but use what works for you.

Datagenerator 6 hours ago | parent | prev | next [-]

Looks good. Almost stopped reading due the npm example, grasped it was just a use case, kept reading.

Kernel keyring support would be the next step?

PASS=$(keyctl print $(keyctl search @s user enveil_key))

frgturpwd 5 hours ago | parent | prev [-]

I prefer waiting till it gets me in trouble. So far, it having access to all my .env secrets seems to work out okay.