DEV Community

Oluwafemi
Oluwafemi

Posted on AI-assisted

Same logs, no secrets: penv 1.0.0-rc.3

This is a debug log most services have somewhere:

INFO   shop-api 2.4.1 · production
DEBUG  db postgres://shop:vR2x9Lq7Tz4W@prod-db/shop
DEBUG  stripe sk_live_51Nq8xZ2eVbT4kP0aLm
INFO   listening on :3000
Enter fullscreen mode Exit fullscreen mode

It ends up in terminal scrollback, CI logs, a pasted bug report, and the context of any coding agent that ran the command. The clip below takes that app from node --env-file=.env to penv run in about 30 seconds. Everything in it runs locally, with no account.

penv run masking secrets in server logs, then penv scan finding a leaked key

1. Write the schema

penv init
Enter fullscreen mode Exit fullscreen mode

This reads .env, writes .env.schema (key, type, required, sensitive), adds .env and .env.* to .gitignore, and writes deny rules for any coding agent it detects. In the clip that is .claude/settings.json. The schema is a plain @env-spec file, the same format varlock reads.

2. Run through penv

penv run -- node server.js
Enter fullscreen mode Exit fullscreen mode

The logs are the same, but the secret values are masked. Masking is on by default in every penv run. It also works inside Node, Bun, Deno and Python through a preload, so it covers console and Response bodies, not only the terminal.

If git tracks the .env, penv run says so and prints the fix:

git rm -q --cached .env && git commit -qm "stop tracking .env"
Enter fullscreen mode Exit fullscreen mode

3. Find the copies

penv scan
Enter fullscreen mode Exit fullscreen mode

penv scan finds secret values that were committed to other files. In the clip it catches config/staging.env.bak:3 holding STRIPE_SECRET_KEY. Remove the value, rotate it if it was ever pushed, and read it from penv run instead.

rc.3: masking broke Next.js middleware

With mask = true, the generated env.ts and the penv run preload mask Response bodies through a subclass of Response. A NextResponse built on the original class then failed Next's instanceof Response check, and every request answered 500. rc.3 keeps instanceof Response working.

Behaviour has been frozen since 1.0.0-rc.1. Only fixes land before 1.0.0. See the stability promise.

With penv.cloud

In cloud mode, rc.2 adds checks on what penv injects:

  • A key that would change how the command runs, such as NODE_OPTIONS, LD_PRELOAD, BASH_ENV, PATH or PIP_INDEX_URL, fails the run with reserved_name (exit 3) instead of being injected.
  • Your command never inherits a CI runner's OIDC request token, GITHUB_ENV/GITHUB_OUTPUT/GITHUB_PATH/GITHUB_STATE, INPUT_* or STATE_*.
  • penv login makes you type the code your terminal shows. A link alone can't approve someone else's terminal.

penv.cloud opens soon.

Install

curl -fsSL https://penv.cloud/install | sh
penv upgrade next     # already installed
Enter fullscreen mode Exit fullscreen mode

This article was drafted with AI from the penv release notes and checked against them.

Top comments (0)