Back to Intelligence

Securing API Keys in LLM Coding Agent Sessions: Lessons from llm-keys-ui 0.1 and Secrets Hygiene for AI-Driven Development

SA
Security Arsenal Team
September 20, 2026
10 min read

Simon Willison — creator of the llm CLI tooling ecosystem — just released llm-keys-ui 0.1, a small plugin that solves a problem every security team should be paying attention to in 2026: developers are pasting API keys directly into LLM coding agent sessions. Willison uses Codex Remote to drive coding agents on remote machines from his phone, and rather than pasting provider API keys (OpenAI, Anthropic, etc.) into the ChatGPT app session, he built a workflow where the agent runs uvx --with llm-keys-ui llm keys-ui --all, which spins up a local web UI. The operator then opens the URL and enters the key through a browser form — keeping the secret out of the agent's conversational context entirely.

This matters far beyond one developer's workflow. Autonomous and semi-autonomous coding agents (Codex, Claude Code, Copilot agents, Aider, and dozens of others) are now running with shell access across enterprise fleets. Every secret pasted into an agent session is a secret that lands in provider-side conversation logs, local transcript files, telemetry pipelines, and potentially the context windows of third-party models. If that transcript is later leaked, synced to cloud storage, or fed into another model as context, the key is compromised. In our IR casework this year, exposed LLM provider keys and cloud credentials harvested from developer tooling logs have become a real — and growing — initial-access vector. This post breaks down the risk, why Willison's approach is the right pattern, and gives your SOC concrete detection content for finding secrets that have already leaked into agent-adjacent artifacts.

Technical Analysis

The Problem: Secrets in Agent Context Are Secrets at Rest Everywhere

When a developer pastes an API key into an LLM coding agent session, the key propagates to multiple persistence layers, most of which are outside the developer's threat model:

  1. Provider-side session logs. Codex Remote sessions, ChatGPT conversations, and equivalent products retain conversation history server-side. A key in the prompt is a key in the vendor's log pipeline.
  2. Local transcript and history files. Coding agents write session transcripts, rollout files, and shell histories to disk (e.g., ~/.bash_history, agent-specific session directories, ~/.zsh_history).
  3. Process command lines. Keys passed as CLI arguments (llm keys set openai --value sk-... or export OPENAI_API_KEY=sk-... typed into a shell the agent spawned) are visible in ps output, audit logs, and EDR process telemetry to any local user or process for the lifetime of the process.
  4. Downstream context reuse. Agent transcripts are routinely pasted into bug reports, shared with colleagues, or fed into other models — replicating the secret each time.

The affected surface is not a single product or version — it is the entire class of LLM coding agent workflows running on macOS, Linux, and Windows developer workstations and remote build machines. There is no CVE here; this is an architectural exposure pattern, which is exactly what makes it dangerous: there is nothing to patch, only behavior to change and telemetry to hunt.

How llm-keys-ui Mitigates It

The plugin's design is instructive for defenders because it demonstrates the correct control pattern:

  • The agent runs uvx --with llm-keys-ui llm keys-ui --all and reports back a localhost URL.
  • The human operator opens that URL in a browser and types the key into a form.
  • The key is written to the llm tool's on-disk key store (keys.json) via the local UI — it never transits the agent's prompt, completion, or transcript.

This cleanly separates the agent's context (untrusted, logged, model-visible) from the secret provisioning path (human-operated, local, short-lived). It is the same principle as OAuth device authorization flows and out-of-band secret delivery: keep credentials out of the channel that gets logged.

Why Defenders Should Treat This as an Active Risk Now

Exploitation status: there is no vulnerability to exploit in llm-keys-ui itself — but the behavior it prevents is being exploited opportunistically. Throughout 2025 and into 2026 we have seen infostealer families and post-compromise automation explicitly harvest .env files, shell histories, and — increasingly — agent transcript directories from developer machines, because that is where cloud and LLM provider keys now concentrate. A single leaked sk-... key yields immediate API abuse (fraudulent inference billed to your account), and a leaked cloud key yields full environment compromise. The window between exposure and abuse for cloud and AI provider keys is routinely measured in minutes via automated scanning of public artifacts.

Detection & Response

The defensive play has two halves: (1) find keys that have already leaked into command lines, shell histories, and transcript files, and (2) alert on new leakage in near-real-time via process command-line telemetry.

SIGMA Rules

YAML
---
title: API Key Material Observed in Process Command Line
id: 3f9c2a71-8b4d-4e6a-b1f2-7c5d9e0a2b48
status: experimental
description: Detects LLM provider and cloud API keys passed directly on process command lines, indicating secrets exposed to process lists, EDR telemetry, and agent transcripts.
references:
  - https://simonwillison.net/2026/Sep/20/llm-keys-ui/
  - https://attack.mitre.org/techniques/T1552/001/
author: Security Arsenal
date: 2026/09/22
tags:
  - attack.credential_access
  - attack.t1552.001
logsource:
  category: process_creation
  product: windows
detection:
  selection_openai:
    CommandLine|contains:
      - 'sk-proj-'
      - 'sk-ant-'
      - 'sk-live-'
  selection_other_providers:
    CommandLine|contains:
      - 'AIzaSy'
      - 'gsk_'
      - 'xai-'
  selection_key_flags:
    CommandLine|contains:
      - 'llm keys set'
      - '--api-key '
      - 'API_KEY='
  condition: 1 of selection_*
falsepositives:
  - CI/CD pipelines with keys injected via secrets managers (tune by build agent hostname)
  - Developers running llm keys set interactively (migrate to llm keys-ui workflow)
level: high
---
title: Shell History or Agent Transcript Access by Unusual Process
id: 8e1b4c62-2d7f-4a3b-9c6e-5f0a1b8d3e27
status: experimental
description: Detects non-shell processes reading shell history files or LLM agent transcript directories, consistent with infostealer and post-compromise secret harvesting.
references:
  - https://attack.mitre.org/techniques/T1552/003/
  - https://attack.mitre.org/techniques/T1005/
author: Security Arsenal
date: 2026/09/22
tags:
  - attack.credential_access
  - attack.t1552.003
  - attack.collection
  - attack.t1005
logsource:
  category: file_event
  product: windows
detection:
  selection_paths:
    TargetFilename|contains:
      - '\.bash_history'
      - '\.zsh_history'
      - '\.config\llm\'
      - '\.codex\'
      - '\.claude\'
      - '\AppData\Roaming\llm\'
  filter_legit:
    Image|endswith:
      - '\Code.exe'
      - '\explorer.exe'
  condition: selection_paths and not filter_legit
falsepositives:
  - Backup and indexing software (tune by signer)
  - Developer editors opening history files manually
level: medium

KQL Hunt — Microsoft Sentinel / Defender

This query hunts process command lines containing LLM provider key formats or key-setting invocations across Windows and Linux endpoints ingested via Defender or Syslog. Run it over 30 days to establish the blast radius, then convert to a scheduled analytics rule.

KQL — Microsoft Sentinel / Defender
let KeyPatterns = dynamic(["sk-proj-", "sk-ant-", "sk-live-", "AIzaSy", "gsk_", "xai-", "llm keys set", "OPENAI_API_KEY=sk-", "ANTHROPIC_API_KEY=sk-"]);
let DefenderHits = DeviceProcessEvents
| where TimeGenerated > ago(30d)
| where ProcessCommandLine has_any (KeyPatterns)
| project TimeGenerated, DeviceName, AccountName, FileName, ProcessCommandLine, InitiatingProcessFileName, Source = "Defender";
let SyslogHits = Syslog
| where TimeGenerated > ago(30d)
| where Facility == "auth" or SyslogMessage has_any (KeyPatterns)
| where SyslogMessage has_any (KeyPatterns)
| project TimeGenerated, DeviceName = HostName, AccountName = HostIP, FileName = ProcessName, ProcessCommandLine = SyslogMessage, InitiatingProcessFileName = "", Source = "Syslog";
union DefenderHits, SyslogHits
| extend SuspectedKey = extract(@"(sk-[A-Za-z0-9_\-]{20,}|AIzaSy[A-Za-z0-9_\-]{30,}|gsk_[A-Za-z0-9]{20,})", 1, ProcessCommandLine)
| summarize FirstSeen = min(TimeGenerated), LastSeen = max(TimeGenerated), Hosts = dcount(DeviceName) by SuspectedKey, Source
| order by FirstSeen desc

The SuspectedKey extraction lets you pivot per-key: each distinct key value appearing in telemetry is a rotation candidate. Pair results with your LLM provider's usage dashboards — unexpected inference spend against a key that appears in process telemetry is confirmation of abuse.

Velociraptor VQL Hunt

Use this artifact to sweep a developer fleet for exposed keys at rest — shell histories, llm key stores, and agent transcript directories — plus live processes carrying keys on their command lines.

VQL — Velociraptor
-- Hunt for exposed LLM API keys in shell history, llm key stores,
-- agent transcript dirs, and live process command lines
LET key_regex = '(sk-(proj|ant|live)-[A-Za-z0-9_\-]{20,}|AIzaSy[A-Za-z0-9_\-]{30,}|gsk_[A-Za-z0-9]{20,})'

LET history_hits = SELECT FullPath, 
       parse_string_with_regex(string=read_file(filename=FullPath, length=2000000), 
       regex=key_regex).g1 AS SuspectedKey
FROM glob(globs=[
  '/home/*/.bash_history',
  '/home/*/.zsh_history',
  '/root/.bash_history',
  '/Users/*/.zsh_history',
  '/home/*/.config/llm/keys.json',
  '/Users/*/.config/io.datasette.llm/keys.json'
])
WHERE SuspectedKey

LET proc_hits = SELECT Pid, Name, Username, Exe,
       parse_string_with_regex(string=CommandLine, regex=key_regex).g1 AS SuspectedKey,
       CommandLine
FROM pslist()
WHERE CommandLine =~ key_regex

SELECT * FROM history_hits
UNION ALL
SELECT Exe AS FullPath, SuspectedKey FROM proc_hits

Note: the llm tool stores keys in a local keys.json — that is by design and not itself a finding, but its presence on shared or ephemeral agent-runner machines (rather than individual developer workstations) should trigger review of who else can read it and whether disk encryption and file permissions (0600) are enforced.

Verification and Hardening Script

Run this on Linux/macOS developer machines and agent runners to locate exposed keys, fix permissions on the llm key store, and scrub histories. Anything it finds should be treated as compromised and rotated.

Bash / Shell
#!/usr/bin/env bash
# audit-llm-secrets.sh — find and remediate exposed LLM API keys
set -euo pipefail

KEY_RE='sk-(proj|ant|live)-[A-Za-z0-9_-]{20,}|AIzaSy[A-Za-z0-9_-]{30,}|gsk_[A-Za-z0-9]{20,}'
REPORT="$HOME/llm-secret-audit-$(date +%Y%m%d).txt"

echo "[*] Scanning shell histories and config for exposed keys..."
grep -rEn "$KEY_RE" \
  "$HOME/.bash_history" "$HOME/.zsh_history" \
  "$HOME/.config/llm/" "$HOME/.codex/" "$HOME/.claude/" \
  2>/dev/null | sed -E 's/(sk-[A-Za-z0-9_-]{8})[A-Za-z0-9_-]+/\1...REDACTED/g' > "$REPORT" || true

if [ -s "$REPORT" ]; then
  echo "[!] EXPOSED KEYS FOUND — see $REPORT (values redacted). ROTATE IMMEDIATELY."
else
  echo "[+] No key material found in scanned locations."
fi

echo "[*] Hardening llm key store permissions..."
for f in "$HOME/.config/llm/keys.json" "$HOME/.config/io.datasette.llm/keys.json"; do
  [ -f "$f" ] && chmod 600 "$f" && echo "[+] chmod 600 $f"
done

echo "[*] Scrubbing key material from shell histories (keys will need rotation regardless)..."
for h in "$HOME/.bash_history" "$HOME/.zsh_history"; do
  [ -f "$h" ] && sed -i.bak -E "s/($KEY_RE)/REDACTED-ROTATE-ME/g" "$h" && echo "[+] scrubbed $h (backup: $h.bak)"
done

echo "[*] Checking running processes for keys on command lines..."
ps -eo pid,user,args | grep -E "$KEY_RE" | grep -v grep | sed -E 's/(sk-[A-Za-z0-9_-]{8})[A-Za-z0-9_-]+/\1.../g' || echo "[+] none found"

echo "[*] Done. Any redacted key above must be revoked at the provider console, not just scrubbed."

Remediation

  1. Adopt out-of-band key provisioning as policy. The pattern llm-keys-ui demonstrates — human enters the secret through a local UI, the agent never sees it — should be codified for every coding agent in your environment. Where a plugin doesn't exist, use your secrets manager's CLI (1Password op inject, Vault agent, AWS Secrets Manager) so agents fetch keys at runtime from a brokered source rather than receiving them in prompts.
  2. Rotate everything the hunts find. Scrubbing a history file does not un-log a key from a provider's servers or an EDR pipeline. Treat any key observed in command lines, transcripts, or chat sessions as compromised: revoke at the provider console (OpenAI, Anthropic, Google AI Studio, Groq, xAI) and issue fresh credentials.
  3. Set spend limits and alerts on LLM provider accounts. Compromised keys are monetized through fraudulent inference. Hard usage caps and billing anomaly alerts convert a silent compromise into a noisy one.
  4. Restrict key store file permissions (chmod 600 on keys.json), enforce full-disk encryption on developer workstations and agent runners, and ensure ephemeral agent environments are destroyed — not reimaged with key stores intact — after sessions.
  5. Deploy the Sigma and KQL content above as standing detections, tuned per your CI/CD estate. Command-line key exposure is a leading indicator you can catch in near-real-time, long before a provider billing alert fires.
  6. Educate the developer population. The single most effective control is making "never paste a secret into an agent session" as reflexive as "never commit a secret to git." Willison's one-paragraph rationale — "I don't like pasting API keys into agent sessions" — is the entire policy, and it's the right one.

Related Resources

Security Arsenal Penetration Testing Services AlertMonitor Platform Book a SOC Assessment vulnerability-management Intel Hub

Is your security operations ready?

Get a free SOC assessment or see how AlertMonitor cuts through alert noise with automated triage.

Securing API Keys in LLM Coding Agent Sessions: Lessons from llm-keys-ui 0.1 and Secrets Hygiene for AI-Driven Development | Security Arsenal | Security Arsenal