<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: penv</title>
    <description>The latest articles on DEV Community by penv (@penvhq).</description>
    <link>https://dev.to/penvhq</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4119548%2F79de780b-39c8-4b06-8697-d5dac0cd0448.jpg</url>
      <title>DEV Community: penv</title>
      <link>https://dev.to/penvhq</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/penvhq"/>
    <language>en</language>
    <item>
      <title>Your coding agent can read your .env. Here's what we did about it.</title>
      <dc:creator>penv</dc:creator>
      <pubDate>Thu, 10 Sep 2026 15:41:20 +0000</pubDate>
      <link>https://dev.to/penvhq/your-coding-agent-can-read-your-env-heres-what-we-did-about-it-5go9</link>
      <guid>https://dev.to/penvhq/your-coding-agent-can-read-your-env-heres-what-we-did-about-it-5go9</guid>
      <description>&lt;p&gt;Run &lt;code&gt;claude&lt;/code&gt;, &lt;code&gt;codex&lt;/code&gt;, &lt;code&gt;cursor&lt;/code&gt;, &lt;code&gt;amp&lt;/code&gt;, whatever. It starts a shell as your user. Your user can read &lt;code&gt;.env&lt;/code&gt;. So the agent can read &lt;code&gt;.env&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That is the whole problem. Not a bug in any harness, not a jailbreak, just the permission model working as designed. And it means that whenever an agent greps around your repo for context, or a tool call echoes the environment, or a stack trace lands in a transcript, your Stripe key can go with it.&lt;/p&gt;

&lt;p&gt;Most harnesses have a deny list you can configure. Almost nobody does, the syntax differs per tool, and a deny rule on &lt;code&gt;.env&lt;/code&gt; does nothing about &lt;code&gt;.env.local&lt;/code&gt;, &lt;code&gt;.env.production&lt;/code&gt;, or the fact that &lt;code&gt;printenv&lt;/code&gt; in a hook still works.&lt;/p&gt;

&lt;p&gt;We built &lt;a href="https://github.com/Itzfeminisce/penvhq" rel="noopener noreferrer"&gt;penv&lt;/a&gt; because we were tired of hand-writing those configs and then not trusting them.&lt;/p&gt;

&lt;h2&gt;
  
  
  What penv actually does
&lt;/h2&gt;

&lt;p&gt;One static Rust binary. No runtime, no daemon, no account required for the first minute.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://penv.cloud/install | sh   &lt;span class="c"&gt;# or: npm i -g @penvhq/cli&lt;/span&gt;
penv init                                    &lt;span class="c"&gt;# reads .env, writes .env.schema, gitignores .env&lt;/span&gt;
penv run &lt;span class="nt"&gt;--&lt;/span&gt; pnpm dev                         &lt;span class="c"&gt;# validates, injects, masks&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;init&lt;/code&gt; writes exactly one file into your repo, &lt;code&gt;.env.schema&lt;/code&gt;. It looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# @schema=1&lt;/span&gt;

&lt;span class="c"&gt;# @type=url&lt;/span&gt;
&lt;span class="nv"&gt;DATABASE_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;

&lt;span class="c"&gt;# @type=string&lt;/span&gt;
&lt;span class="nv"&gt;STRIPE_SECRET_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;

&lt;span class="c"&gt;# @type=port @sensitive=false&lt;/span&gt;
&lt;span class="nv"&gt;PORT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;3000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No values that could be secrets are ever copied in. Every key is sensitive and required by default; a bundler prefix like &lt;code&gt;NEXT_PUBLIC_&lt;/code&gt; or an obviously dull value like &lt;code&gt;3000&lt;/code&gt; flips that, and a key &lt;em&gt;named&lt;/em&gt; like a secret (&lt;code&gt;STRIPE_SECRET_KEY&lt;/code&gt;, &lt;code&gt;..._ANON_KEY&lt;/code&gt;) stays sensitive no matter what it holds. The decorators follow the &lt;code&gt;@env-spec&lt;/code&gt; vocabulary, so if you have used varlock you can already read it.&lt;/p&gt;

&lt;p&gt;The schema is committed. &lt;code&gt;.env&lt;/code&gt; is not. That split is what makes the rest possible.&lt;/p&gt;

&lt;h2&gt;
  
  
  What that gives you against an agent
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Validation before exec.&lt;/strong&gt; &lt;code&gt;penv run&lt;/code&gt; checks every value against the schema before your process starts. A missing or malformed key fails with exit code 3, not a runtime error forty seconds in.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Masking.&lt;/strong&gt; When penv detects an agent session (it reads the harness env vars first, then walks process ancestry), it scrubs every sensitive value from the child's stdout and stderr — raw, hex, base64, and URL-encoded forms. The agent sees &lt;code&gt;***&lt;/code&gt;, not the key.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Harness guards, generated.&lt;/strong&gt; &lt;code&gt;penv guard&lt;/code&gt; writes what each harness actually enforces, from the schema:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;deny rules and a sandbox block for Claude Code&lt;/li&gt;
&lt;li&gt;a permission profile for Codex&lt;/li&gt;
&lt;li&gt;deny rules and fail-closed hooks for Cursor&lt;/li&gt;
&lt;li&gt;the equivalents for Copilot CLI, Gemini, Cline, Windsurf, and Amp&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The hook is the penv binary itself, never a shell script that fails open when something goes wrong.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Typed access.&lt;/strong&gt; &lt;code&gt;penv gen ts&lt;/code&gt; or &lt;code&gt;penv gen py&lt;/code&gt; writes a typed accessor for your language so code stops doing &lt;code&gt;process.env.FOO!&lt;/code&gt; and reads through a validated object. Targets are folders with a template; adding a language is adding a folder.&lt;/p&gt;

&lt;h2&gt;
  
  
  The claim, and only the claim
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;penv guard --check&lt;/code&gt; prints one sentence, and we keep it exactly true:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;penv keeps secrets out of the files, the repo, the shell history and the captured output an agent reads.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is the layer where leaks actually happen — a grep, a transcript, a pasted stack trace — and it is the layer penv closes. penv is not a sandbox and does not claim to be one; the process you launch still runs as you. So the cloud side is built for that: every value it issues is short-lived, scoped to the session, and attributable to it. A value that escapes is already expired and already traced.&lt;/p&gt;

&lt;h2&gt;
  
  
  The cloud is the upgrade, not the product
&lt;/h2&gt;

&lt;p&gt;Everything above works with zero account. When you have a team:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;penv login   &lt;span class="c"&gt;# device code in the browser&lt;/span&gt;
penv push    &lt;span class="c"&gt;# values go to penv.cloud, .env is deleted&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From there &lt;code&gt;.env&lt;/code&gt; is a view you can regenerate, not the source of truth. A teammate clones and runs &lt;code&gt;penv run -- pnpm dev&lt;/code&gt;; that is the onboarding. CI presents its OIDC token and gets a fifteen-minute credential. A server with nothing to present enrols a keypair once. &lt;code&gt;penv reveal KEY&lt;/code&gt; under an agent session needs a human to click approve in the console — the agent can ask, it cannot self-serve.&lt;/p&gt;

&lt;h2&gt;
  
  
  Status, honestly
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;@penvhq/cli&lt;/code&gt; is at &lt;code&gt;1.0.0-alpha.3&lt;/code&gt;. It is published, signed, builds for macOS/Linux/Windows on x86_64 and arm64, and we use it daily. We do not expect further breaking changes before RC, but it is an alpha. Local mode needs nothing from us.&lt;/p&gt;

&lt;p&gt;penv.cloud is in private beta. We are letting early users in from the &lt;a href="https://penv.cloud" rel="noopener noreferrer"&gt;waitlist&lt;/a&gt; in small batches, and the teams we onboard during the beta keep enterprise-tier benefits at launch — if you want your team on it, sign up.&lt;/p&gt;

&lt;p&gt;If you run a coding agent against a repo with a &lt;code&gt;.env&lt;/code&gt; in it — and you almost certainly do — try &lt;code&gt;penv init&lt;/code&gt; on it and read the schema it writes. If it guesses a key wrong, that is a bug report we want.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Source: &lt;a href="https://github.com/Itzfeminisce/penvhq" rel="noopener noreferrer"&gt;https://github.com/Itzfeminisce/penvhq&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Install + waitlist: &lt;a href="https://penv.cloud" rel="noopener noreferrer"&gt;https://penv.cloud&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;npm: &lt;a href="https://www.npmjs.com/package/@penvhq/cli" rel="noopener noreferrer"&gt;https://www.npmjs.com/package/@penvhq/cli&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;MIT.&lt;/p&gt;

</description>
      <category>security</category>
      <category>ai</category>
      <category>rust</category>
      <category>devops</category>
    </item>
  </channel>
</rss>
