OpenAI's CLI Turns the API Into Shell Commands. Set One Variable Before an Agent Drives It.
openai/openai-cli is fast, scriptable and shipping almost daily. One of its handiest features reads files off your disk, and its own docs tell you when to turn that off.
Coding agents are very good at one thing: running shell commands. Give them a tool with a clean command-line interface and they will use it more fluently than most people do, chaining it with jq, piping its output into the next step, retrying on errors.
That makes every new CLI a little more than a convenience for humans. It becomes a set of actions an agent can take. And the features that save a person typing time can do surprising things when the input comes from somewhere the person never looked. Pass @.env as an input value to OpenAI's CLI and it reads your .env file into the API request. Let an agent paste that same string from a web page, and it does exactly the same thing.
OpenAI's official command-line tool is a clean example. It drew more than 200 points on Hacker News on October 22, and it has one setting worth knowing before you hand it to an agent.
What the OpenAI CLI is
openai/openai-cli is "the official CLI for the OpenAI REST API." Every command follows the pattern openai [resource] <command> [flags...], so the API's endpoints become things you type:
openai responses create --input "Say this is a test" --model gpt-5.5
It is written in Go and installs two ways:
brew install openai/tools/openai
# or, with Go 1.26 or later
go install 'github.com/openai/openai-cli/cmd/openai@latest'
It reads your key from OPENAI_API_KEY. Output can be text, JSON, JSON Lines, YAML and a few other formats, and the README's advice for scripts is to use --format json or --format jsonl. There is also --transform, which pulls fields out of the response with GJSON paths (a small query language for JSON) so you do not need a separate parsing step.
This is not a brand-new project. The changelog has version 1.9.0 dated August 26, 2026, and version 1.40.2 dated October 22. That is more than 30 minor versions in about two months. Version 1.39.1 alone, from October 21, made the CLI require HTTPS for remote API endpoints and stopped it from accepting symlinks as download destinations. Someone is clearly hardening it as they go.
The feature that reads your files
Here is the part that matters for agents.
When a command takes JSON input, any value that starts with @ is treated as a file path. The CLI reads that file from your disk and puts its contents into the request. The CLI reference shows it working in a flag, inside a JSON blob and inside YAML sent through standard input (stdin, the stream a program reads when you pipe data into it):
openai <command> --arg @abe.jpg
openai <command> --arg '{image: "@abe.jpg"}'
Two variants control encoding, @file:// for text and @data:// for base64. The README's own example uses the plain form for a prompt:
openai responses create --model gpt-5.5 --input @prompt.txt
For a person at a keyboard, this is lovely. You keep a long prompt in a file and reference it. You attach an image without base64-encoding it by hand.
Now picture the same tool in an agent loop. The agent fetches a GitHub issue, a web page or another tool's JSON output, and pipes part of it into openai. If that data contains a string like "@.env" or "@config/secrets.yaml", the CLI does what it was built to do. It reads the file and sends its contents to the API as part of your request. If the agent then prints the response, those contents may show up in its transcript or logs.
Nobody has to break anything for this to happen. A string that starts with @ is enough. This is the same shape as prompt injection (hostile instructions hidden in content an agent reads), except the trigger is not the model. It is the tool's input parser.
The setting that turns it off
The maintainers saw this coming. The README says to "use OPENAI_UNTRUSTED_STDIN=1 when piping JSON or YAML from an untrusted source so input cannot cause local file reads."
The CLI reference spells out what changes. In that mode, values that arrive through stdin are treated as plain data, so @, @file:// and @data:// references in request bodies, headers and query parameters no longer read local files. If you do need to upload a file, you pass it as an explicit flag, and flags keep their normal behavior:
untrusted-producer | OPENAI_UNTRUSTED_STDIN=1 openai <command> --file ./upload.txt
The docs also say to leave the variable unset for trusted heredocs, the inline <<YAML ... YAML blocks you write yourself, when you want @ to read files on purpose.
That is the whole design, and it is a good one. The open question is only who decides what counts as "untrusted." The README leaves it to you.
My answer: if an agent built the input, it is untrusted. The agent may have copied it from a page you never saw. That is my rule, not OpenAI's, but it costs nothing to follow.
The second key
The CLI uses two different credentials. Regular endpoints use OPENAI_API_KEY. Admin endpoints, such as organization usage reports, need a separate admin API key in OPENAI_ADMIN_KEY:
openai admin organization usage completions \
--start-time 1735689600 \
--end-time 1735776000 \
--bucket-width 1d
Usage reports sound harmless. But admin keys are created under your organization settings (the README links to that page), and the CLI's API surface reaches the keys themselves; the 1.40.2 changelog entry, for example, is about service-account API key deletion. That is a much bigger blast radius (how much damage a leaked key can do) than a key meant for making API requests. An agent does not need it to write code.
Put this into practice
Here is a setup that keeps the CLI useful for you and boring for your agent.
1. Install and smoke-test it
brew install openai/tools/openai
export OPENAI_API_KEY=sk-...
openai responses create --input "Say this is a test" --model gpt-5.5 --format json
You should get the JSON response printed to your terminal. If the command is not found after a Go install, add Go's bin folder to your PATH; the README covers this.
2. Give the agent a wrapper, not the raw binary
Make a small script that sets the safe defaults every time, and tell your agent to call it instead of openai:
#!/usr/bin/env bash
# ~/bin/openai-agent
unset OPENAI_ADMIN_KEY
export OPENAI_UNTRUSTED_STDIN=1
exec openai "$@"
chmod +x ~/bin/openai-agent
Then add one line to your project's agent instructions (CLAUDE.md, AGENTS.md or whatever your agent reads): "Call the OpenAI API only through openai-agent." This does not stop an agent that decides to ignore instructions. It does make the safe path the easy one, and it removes the admin key from that process even if your shell has it.
3. Prove the switch works on your machine
Do not take my word for it, or the README's. Make a harmless test file and send it both ways:
echo "Reply with exactly one word: CANARY" > /tmp/canary.txt
printf 'input: "@/tmp/canary.txt"\n' | openai responses create --model gpt-5.5
printf 'input: "@/tmp/canary.txt"\n' | OPENAI_UNTRUSTED_STDIN=1 openai responses create --model gpt-5.5
Going by the docs, the first command reads the file, so the model sees the instruction and should answer CANARY. The second sends the literal text @/tmp/canary.txt, and you should get a reply about a file path instead. If both answer CANARY, your version behaves differently from the docs, and you want to know that before an agent does. Run this again after upgrades; the tool ships often.
4. Escape literal @ values
Sometimes you really do want to send a string that starts with @, such as a username. The reference shows how to escape it so the CLI does not try to open a file:
openai <command> --username '\@abe'
5. Keep debug logs private
The --debug flag prints full HTTP requests and responses, bodies included. The README warns not to share those logs if they might contain sensitive payloads. If an agent turns on --debug while chasing an error, treat whatever it wrote as sensitive.
6. Use a key you can afford to lose
Give the agent its own API key, separate from the one your production code uses. If anything leaks, you revoke one key and nothing else breaks.
Honest limitations
A few things this article cannot promise.
OPENAI_UNTRUSTED_STDIN covers stdin. Values the agent writes directly into command-line flags are a different path, and @ in a JSON flag value still reads a file by design. The wrapper does not change that. If an agent copies hostile text straight into --input, the variable will not save you, which is why the instruction to treat agent-built input as untrusted matters more than any single setting.
The wrapper is a convention. An agent with a shell can call the real openai binary if it wants to. Sandboxing (running the agent somewhere it cannot read your secrets at all) is the real fix, and it is more work.
The CLI moves fast. More than 30 minor versions in two months means flags and docs can shift under you, and the README says package-manager versions can trail GitHub releases. Check the changelog before you rely on a specific behavior.
And none of this is unique to OpenAI. Plenty of CLIs read files from @ prefixes; curl does it too. OpenAI's tool is just the one that documents the risk and ships a switch for it, which is more than most.
The bigger point
Every CLI you install is now two tools: the one you use, and the one your agent uses. They have the same flags but very different inputs. You read what you type. Your agent pipes in whatever it found.
The good news is that the OpenAI CLI already has the switch. Set OPENAI_UNTRUSTED_STDIN=1, keep the admin key out of reach, and the convenience stays yours. Then go look at the other CLIs your agent calls, and check which of them read files when they see an @.
Sources: openai/openai-cli README · CLI reference: passing files and untrusted stdin · CHANGELOG (1.9.0 to 1.40.2) · Hacker News discussion, October 22, 2026. README and changelog read at tag v1.40.2; the CLI reference read on main on October 22, 2026.