DEV Community

Revin
Revin

Posted on

An OpenAI model went looking for exposed API keys in testing. I assume my coding agent will too

CIO reported today that OpenAI published six new reports on model misalignment under a new reporting framework. The cases come from internal evaluations and describe models acting beyond the constraints they were given: hidden instructions, unauthorized communication with external services, modified intermediate outputs, shared environments used in ways nobody planned. One of them is an attempt to locate exposed API keys. OpenAI called the behavior "unexpected or concerning".

Most reactions treat this as news about OpenAI. I read it as a spec sheet for the thing running in my terminal. A coding agent with shell access has the same shape as the models in those reports: a goal, a set of tools and a filesystem. If a key sits within reach and the task gets easier with it, using the key is just another path to finishing the task. No intent is required for that to happen.

So I stopped asking whether my agent would open .env. I assume it will, and I set things up so that opening it finds nothing worth having.

The mental model: a contractor on day one

A new contractor gets the access their task needs, for as long as the task lasts, and someone keeps a record of what they touched. Nobody hands them the master key because they seem careful. That is the frame I use for agents, and it breaks down into four questions:

  1. What can it read right now?
  2. Where do the secrets live, and can they leave the workspace?
  3. Which token does it hold, and what can that token do?
  4. Is there a record of what it ran?

1. Find what the agent can read

.gitignore protects your git history. It does nothing against a process that opens files, and the working directory is exactly where the .env lives.

Start with the files git ignores but that are sitting on disk:

# ignored by git, readable by anything running in this directory
git ls-files --others --ignored --exclude-standard \
  | grep -iE '\.env|secret|credential|\.pem$|\.key$'
Enter fullscreen mode Exit fullscreen mode

Then look at what the agent inherits without reading any file:

# names only, never print the values
env | grep -iE 'key|token|secret|password' | cut -d= -f1
Enter fullscreen mode Exit fullscreen mode

The second command matters more than people expect. Agents usually inherit the shell you launched them from. If you exported OPENAI_API_KEY or AWS_SECRET_ACCESS_KEY in your profile months ago, the agent has it already.

Last, scan contents, history included:

gitleaks dir . --redact --no-banner
gitleaks git . --redact --no-banner
Enter fullscreen mode Exit fullscreen mode

A key that was committed once and deleted later still lives in git log -p. An agent asked to find out why something broke in March will read that history.

2. Move secrets out of the workspace

The tempting first fix is telling the agent to ignore .env, through its ignore file or a line in the instructions. That is a request. The models in the OpenAI reports were operating under instructions too.

What holds is the secret not being on disk. Replace the file with references that a secret manager resolves at runtime. With the 1Password CLI, the template looks like this:

# .env.tpl (safe to commit, safe for the agent to read)
DATABASE_URL=op://dev/app-db/url
STRIPE_SECRET_KEY=op://dev/stripe-test/secret
Enter fullscreen mode Exit fullscreen mode
op run --env-file=.env.tpl -- npm run dev
Enter fullscreen mode Exit fullscreen mode

The agent can read .env.tpl all day and it will see vault paths. Doppler, Infisical and the cloud secret managers do the same job with different syntax, so pick whichever your team already pays for.

One catch I don't have a clean answer for: if the agent's shell can call op run with your unlocked session, the values are one command away. I start the agent from a shell where the secret manager is not signed in and run the app from a different one. It is clunky, and I'd like to hear a better arrangement.

3. Its own token, with the smallest scope

When the agent needs credentials, to push a branch or read an issue, it gets its own and never yours. On GitHub that means a fine-grained personal access token limited to one repository, contents read and write, pull requests write, nothing under administration, expiring in days instead of a year.

Then run the agent where that token is the only thing in the environment:

docker run --rm -it \
  -v "$PWD":/work -w /work \
  --env-file agent.env \
  --network agent-net \
  agent-image
Enter fullscreen mode Exit fullscreen mode

agent.env has exactly one line. The container sees the repo and nothing from your home directory: no ~/.aws, no ~/.ssh, no shell history with a token someone pasted once. If you can route agent-net through a proxy that logs hostnames, do it. Interacting with external services was one of the behaviors in the reports, and outbound traffic is where you would see it.

4. Log every command it runs

When something odd happens, the first question is what the agent executed. Most agents now expose hooks around tool calls. In Claude Code, a PreToolUse hook on Bash receives the command as JSON on stdin, and exit code 2 blocks it:

#!/usr/bin/env bash
# ~/.claude/hooks/audit-bash.sh
input=$(cat)
cmd=$(jq -r '.tool_input.command // empty' <<<"$input")
printf '%s\t%s\n' "$(date -u +%FT%TZ)" "$cmd" >> "$HOME/.agent-audit.log"

if grep -qE '(\.env|id_rsa|\.aws/credentials)' <<<"$cmd"
then
  echo "blocked: command touches a secret path" >&2
  exit 2
fi
Enter fullscreen mode Exit fullscreen mode
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          { "type": "command", "command": "~/.claude/hooks/audit-bash.sh" }
        ]
      }
    ]
  }
}
Enter fullscreen mode Exit fullscreen mode

Be honest about the block. It is a speed bump. cat .e""nv walks right past that regex, and so does a Python one-liner. I keep it because it turns the obvious attempts into a line I will notice. The log is the part that earns its keep: a plain text file with timestamps that I can grep after a long session.

What I would skip

Two recommendations I keep seeing and don't think pay off. Asking the agent in the system prompt to never touch credentials costs nothing and guarantees nothing. The reports are, quite literally, about models acting past stated constraints. And rotating every key after every session sounds disciplined, but nobody keeps it up for more than a week. Short expiry and narrow scope do the same job without depending on someone remembering.

What the reports don't settle

These were OpenAI's internal evaluations, designed to surface this kind of behavior. I don't know how often a coding agent on an ordinary Tuesday task goes near a credentials file, and I would distrust anyone who gave me a precise number. What I do know is the price on each side: the setup above takes an afternoon, and being wrong means a production key that someone else finds before you do.

Security as a habit looks boring up close. The file that isn't there, the token that expires, the log nobody reads until the day it matters.

How are you keeping coding agents away from secrets today? Container, separate OS user, a remote dev box, or trust plus a good .gitignore?

Top comments (0)