API key prefixes: which service issued this key?
An API key’s first few characters usually name the service that issued it. pplx- is Perplexity, tvly- is Tavily, xai- is xAI, fw_ is Fireworks AI, sk-ant- is Anthropic, ghp_ is GitHub. If you found one in a .env, a log, a commit or an agent transcript, the table below tells you whose dashboard to open, and the sections after it cover rotation and keeping coding agents out of .env.
The prefix table
Each row is checked against the vendor’s own docs. Where we could not confirm a prefix on a vendor page, the row says so instead of guessing.
| Prefix | Issuer | What it is | Source |
|---|---|---|---|
pplx- |
Perplexity | API key | Perplexity API key management |
tvly- |
Tavily | API key | Tavily headers and authentication |
xai- |
xAI | API key | xAI accounts and authorization |
fw_ |
Fireworks AI | API key | Fireworks docs |
fpk_ |
Fireworks AI | Fire Pass key | Fireworks Fire Pass |
sk-ant-api |
Anthropic | Claude API key (sk-ant-api03-... in examples) |
Claude API authentication |
sk-ant-admin |
Anthropic | Admin API key | Claude Admin API |
sk-, sk-proj- |
OpenAI, widely reported | API key, project key | Unverified: no OpenAI page we could load states the prefix |
AIza |
Google Cloud API key | Google Cloud API keys | |
ghp_ |
GitHub | Personal access token (classic) | GitHub token expiration and revocation |
github_pat_ |
GitHub | Fine-grained personal access token | same |
gho_ / ghu_ / ghs_ / ghr_ |
GitHub | OAuth app / user-to-server / server-to-server / refresh token | GitHub’s token formats post |
sk_live_ / sk_test_ |
Stripe | Secret key, live / sandbox | Stripe API keys |
rk_live_ / rk_test_ |
Stripe | Restricted key | same |
pk_live_ / pk_test_ |
Stripe | Publishable key (safe in client code) | same |
hf_ |
Hugging Face | User access token | Hugging Face user access tokens |
r8_ |
Replicate | API token | Replicate API tokens |
ntn_ |
Notion | Integration token (since September 25, 2024) | Notion changelog |
secret_ |
Notion (older tokens), also generic | Integration token created before the switch to ntn_ |
same |
up_ |
Unverified | No vendor source found | — |
sn4_ |
Unverified | No vendor source found | — |
Notes on the rows:
sk-versussk_. Anthropic and the OpenAI-style keys use a hyphen. Stripe uses an underscore. Ask_live_key is Stripe and it is live money.secret_alone tells you little. Notion used it until September 2024, and older Notion tokens still work. Other tools use it too. Check the variable name next to it in the file.- Notion advises against validating its tokens with a regex, because the format can change. A prefix helps you triage a finding. It is not a contract.
up_andsn4_. We found no vendor page that claims either one. Don’t trust a blog that names an issuer without a link to the vendor. Look at the variable name and the code that reads it.
Why a key prefix shows up in search
People usually land here for one of three reasons:
- A scanner flagged it. GitHub secret scanning, a pre-commit hook or a CI job reports a match and shows only the first few characters.
- It showed up in an agent transcript. A coding agent opened
.envto debug something and quoted a value back. - It is in a file nobody remembers creating. An old
.envin a repo you just inherited.
The next step is the same each time: find the issuer, rotate the key, then close the leak. A key in a transcript or a public commit counts as leaked even if nobody has used it yet. Our piece on how agents leak .env secrets covers the usual paths.
How to rotate each one
Rotate means: create a new key, deploy it wherever the old one was read, then disable or delete the old one.
| Issuer | Where | Notes from the vendor docs |
|---|---|---|
| Perplexity | API portal, or the /generate_auth_token and /revoke_auth_token endpoints |
A revoked key cannot be recovered |
| Tavily | Your Tavily dashboard | Replace TAVILY_API_KEY |
| xAI | xAI Console, API Keys page | Keys are bound to a team; the full key is shown once |
| Fireworks AI | Fireworks account settings | Replace FIREWORKS_API_KEY |
| Anthropic | Claude Console, API keys page | Disable is reversible, Delete is permanent; set an expiration on the new key |
| OpenAI | OpenAI platform API keys page | Replace OPENAI_API_KEY |
| Google Cloud console, API keys | Add API and application restrictions to the new key | |
| GitHub | Your GitHub token settings | POST /credentials/revoke takes a ghp_ or github_pat_ token and needs no authentication |
| Stripe | Dashboard, API keys | Stripe says to rotate a leaked key immediately; prefer a restricted rk_ key for the replacement |
| Hugging Face | Settings → Access Tokens | POST https://huggingface.co/api/credentials/revoke revokes leaked tokens in bulk |
| Replicate | replicate.com/account/api-tokens | Disable the token, create a new one |
| Notion | Your integration’s settings | New tokens come out as ntn_ |
Two vendor tips. Stripe’s key best practices suggest searching your code, config and CI for sk_live_ and rk_live_. Anthropic’s docs suggest an expiration on every key, so a leaked one stops working on its own.
Keep coding agents out of .env
Rotation fixes the last leak. These settings make the next one less likely. Each tool’s controls are different, and each has a gap.
Claude Code
Add Read deny rules in .claude/settings.json (Claude Code permissions):
{
"permissions": {
"deny": [
"Read(./.env)",
"Read(./.env.*)",
"Read(./secrets/**)"
]
}
}
Bare filenames follow gitignore rules, so Read(.env) matches any .env at or under the current directory. Use // for an absolute path and ~/ for your home directory.
Per the docs, Read deny rules cover the built-in file tools, Bash file commands Claude Code recognizes (cat, head, tail, sed, tee) and redirect targets. They do not cover grep -r pattern . run from the directory that holds the file, or a Python or Node script that opens the file itself. For that, the docs point to the sandbox. A .claudeignore file has no effect. Our deny rules not working post lists the reported bypasses.
Codex
Codex blocks paths through a permission profile in ~/.codex/config.toml (Codex permissions):
default_permissions = "project-edit"
[permissions.project-edit]
extends = ":workspace"
[permissions.project-edit.filesystem]
glob_scan_max_depth = 3
[permissions.project-edit.filesystem.":workspace_roots"]
"." = "write"
"**/*.env" = "deny"
deny blocks both reads and writes from sandboxed commands, and deny wins over write and read on the same path. Two limits from the docs: on Linux, WSL and native Windows, deny globs are expanded when each sandboxed process starts, so a .env created later stays readable to that process; and an unbounded ** pattern needs glob_scan_max_depth. An admin can enforce reads denied for every user with deny_read under [permissions.filesystem] in requirements.toml.
Check it on your version. openai/codex#22179 reported deny globs not blocking reads in 0.130.0 under the older syntax. For a hook that blocks edits to .env, see Codex hooks examples.
Cursor
Cursor ignores .env* by default, along with everything in .gitignore (Cursor ignore files). Ignored files are blocked from Agent, Tab and @ mentions. Add more in .cursorignore:
**/.env
**/.env.*
**/*.pem
The gap, in Cursor’s words: “The terminal and MCP server tools used by Agent cannot block access to code governed by .cursorignore.” An agent that runs cat .env in the terminal still reads it. Cursor also notes that complete protection isn’t guaranteed.
OpenCode
OpenCode 2’s default policy asks before reading *.env and *.env.*, and allows *.env.example (OpenCode permissions). To deny instead of ask, add rules to opencode.jsonc. The last matching rule wins, so the exception goes last:
{
"$schema": "https://opencode.ai/config.json",
"permissions": [
{ "action": "read", "resource": "*.env", "effect": "deny" },
{ "action": "read", "resource": "*.env.*", "effect": "deny" },
{ "action": "read", "resource": "*.env.example", "effect": "allow" }
]
}
This is the V2 format: permissions, shell and subagent replace V1’s permission, bash and task. The read rule covers the read tool. A shell command that prints the file is a separate shell action, so review your shell rules too.
What this does not cover
A prefix names the issuer. It does not prove the key is live, and it does not tell you who used it. Only the vendor’s usage logs answer that.
Every agent control above lives inside one tool. Each has a documented way around it, usually the shell, and none of them carries over to the next tool. The strongest fix is to keep real secrets out of files an agent can open. Load them from a secrets manager at run time and keep only .env.example in the repo.
ACP can add a record. It checks each tool call from the harness hooks and logs it, so you can see which commands touched which files, and it redacts secrets in the stored previews. Be clear about the limit: ACP’s read-and-send detector (a sensitive file like .env named in the same command as curl, wget, scp or nc) ships off unless a preset turns it on. Don’t count on ACP to stop a .env read. The install box below sets up the logging in one line.
Frequently asked questions
pplx- leak .env: whose key is it?
A key starting with pplx- is a Perplexity API key. Perplexity’s API key management docs show generated tokens in that form. If one leaked, revoke it (the docs list a revoke_auth_token endpoint, and keys can be managed in the API portal) and generate a new one.
tvly- and xai- keys: which services?
tvly- is Tavily, the search API; its docs show Authorization: Bearer tvly-YOUR_API_KEY. xai- is xAI, the Grok API; its key management reference shows keys returned in the form xai-… Rotate both in the vendor console.
What are fw_, secret_, up_ and sn4_ keys?
fw_ is Fireworks AI; its docs refer to a standard Fireworks key as fw_…. secret_ is the older Notion integration token prefix (new Notion tokens start with ntn_), though other tools also use secret_. We could not verify an issuer for up_ or sn4_ from a vendor source, so we do not name one.
How do I prevent Codex from reading .env?
Use a permission profile in ~/.codex/config.toml whose filesystem block marks “*/.env” = “deny” under :workspace_roots, set glob_scan_max_depth, and make it the default_permissions. Deny blocks both reads and writes from sandboxed commands. Files created after a process starts are not added to its deny list.
ai coding tools env file exposure: which tools read .env by default?
Claude Code reads files in the working directory without asking unless you add a Read deny rule. Cursor ignores .env* by default for Agent and Tab, but not for terminal or MCP tools. OpenCode’s default policy asks before reading *.env. Codex blocks it only when a permission profile denies it.
Where to read more
- Stop your agent from leaking .env secrets: how the leaks happen
- Claude Code permissions: Read deny rules and what they cover
- Codex permissions: permission profiles and deny globs
- Cursor ignore files: defaults and the terminal gap
- OpenCode permissions: V2 rule format and defaults
- GitHub token expiration and revocation: every GitHub prefix
Running coding agents? ACP records each tool call its hook sees and makes the dangerous shell commands (a force-push, destructive SQL, curl | sh) wait for you. One command, free up to 5 agents:
curl -fsSL https://agenticcontrolplane.com/install.sh | bash
irm https://agenticcontrolplane.com/install.ps1 | iex