Coding agents
penv notices when an agent is driving your terminal and masks every value it prints, and only a person can release one.
An agent runs as you, so it can read whatever you can read. penv treats it as someone in its own right: the session is detected and every value is masked, and a reveal goes to a person before any value prints.
The command line behavior on this page is penv 1.0.0-alpha.3. Read your own with penv --version.
How penv knows
Detection is a ladder, tried in order, because vendors set colliding variables. The first rung that matches wins.
| Rung | What penv looks at |
|---|---|
| 1 to 7 | A vendor's own marker, such as CLAUDECODE, CODEX_THREAD_ID or CURSOR_AGENT |
| 8 | The rest of the vendor markers, plus shared conventions such as AI_AGENT and AGENT against a known list |
| 9 | The chain of parent processes, looking for a known executable |
A session that is not a terminal and whose GIT_EDITOR cannot prompt anybody tightens the policy without naming an agent: masking goes on and nothing else changes.
Detection changes defaults and adds friction. It is never the last line of defense, and penv says so rather than selling it as one. --agent declares a session by hand when you want the same treatment without a marker.
What changes when an agent is driving
| Behavior | A person | An agent session |
|---|---|---|
| Output | Aligned text at a terminal | JSON |
| Sensitive values in output | Shown | Masked |
penv reveal | Prints the value | Asks a person, exits 4 |
penv pull | Writes .env | Refused with exit code 2 |
The pull refusal names both ways on. A person can run it themselves with --i-am-human, or penv run can hand the values to the child process with no file written at all.
Every cloud request also carries the harness name and the session id, so the record of who did what can answer "what did that session touch". See the record of who did what.
Masking
penv run pipes the child's output and scrubs every sensitive value out of it before it reaches your screen or the agent's transcript. The scrubber matches across chunk boundaries, so a value split down the middle by a buffer is still caught.
It looks for more than the value as written. Each secret is matched as raw text, as base64 in both alphabets, padded and unpadded and at each offset a prefix can push it to, as upper and lower hex, percent-encoded, and JSON-escaped.
The list of secrets is every value in the resolved environment except the keys your schema marks public. A key that is present but missing from the schema is masked too, and penv check names it as drift.
--no-mask works only when stdin and stdout are both terminals and no agent is detected. Under an agent penv ignores the flag and says so.
Reveal goes to a person
An agent may ask for a value. Only a person can release one.
The agent runs penv reveal STRIPE_SECRET_KEY. penv creates an approval request carrying the key, this machine, the harness and the session id.
penv exits with code 4 and hands back the request id, the console page and the expiry. The value is not in that answer.
{
"error": "approval_required",
"message": "STRIPE_SECRET_KEY needs a person to approve the reveal.",
"approval": "apr_example",
"url": "https://penv.cloud/approvals/apr_example",
"expiresAt": "2026-09-10T12:40:00Z",
"fix": "A person approves at https://penv.cloud/approvals/apr_example, then run penv reveal STRIPE_SECRET_KEY --approval apr_example."
}You open that page, see the key, the device, the harness and the session, and approve or deny. The request expires 10 minutes after it was made.
penv reveal STRIPE_SECRET_KEY --approval apr_example prints the value once. A second attempt on the same id is refused.
Asking twice for the same key in one session reuses the open request rather than filling your console with duplicates. A request nobody has answered is exit 4 again. A denial is exit 2. An id that was spent or that expired tells you to ask for a new one.
An id that names a different key never gets that far. penv reveal KEY --approval <id> reads the approval before it redeems anything, and refuses with approval_mismatch at exit 4 when the approval names another key, so the id is left unspent and no key is opened that nobody released.
An approval leaves four rows in the record:
secret.reveal_requested, the agent's asksecret.reveal_approvedorsecret.reveal_denied, the answer in the consolesecret.read, the read itselfsecret.reveal, naming the approver beside the session that asked
A redemption the server turns away adds a fifth, secret.reveal_refused.
Guards
penv guard writes the rules each harness enforces, from your schema, into that harness's own config. It merges rather than overwrites, and it never weakens a rule you already had.
Eight harnesses ship with penv: Claude Code, Codex, Cursor, Amp, Copilot CLI, Gemini, Cline and Windsurf. Each one is a folder in the binary, so what a harness enforces is data rather than code.
The deny patterns are .env and .env.*, which covers a new environment file the day someone makes one. .env.schema is allowed by name, because your agent should read it.
The hook penv installs is the penv binary itself, invoked as penv hook claude-code. It is never a script that needs an interpreter, because a missing interpreter fails open and a fail-open guard is worse than none.
Run penv guard --check to see what is covered on this machine. On native Windows there is no Claude Code sandbox, and the check says so instead of implying coverage it does not have.
What penv claims, and what it does not
penv guard --check prints the claim, and it differs by state. In cloud mode:
penv keeps secrets out of the files, the repo and the shell history your coding agent reads, and out of the output it captures. It cannot stop a process running as you from looking, so every value is short-lived, scoped and attributable to the session that used it.
In local mode:
penv validates your .env, keeps values out of your agent's output, and blocks it from reading the file where its harness allows.
Next: encryption in the cloud.
Local mode and cloud mode
penv is in one of two states, decided by the schema header and your credential, and cloud mode keeps an encrypted copy on your machine so runs stay fast.
Encryption in the cloud
Every value is encrypted under its own key, and that key is wrapped by a workspace key held in a key management service.