Back to Intelligence

Simon Willison's llm 0.32.1: Transitive Dependency Breakage in AI CLI Tooling — Supply-Chain Lessons and Hardening Guide

SA
Security Arsenal Team
August 22, 2026
10 min read

On August 21, 2026, Simon Willison shipped llm 0.32.1, an emergency dot-release for his widely used open-source CLI tool for interacting with large language models. Fresh installs of LLM had silently broken: the OpenAI Python library dropped its usage of httpx, and LLM had been depending on httpx only as a transitive dependency of the openai package. When openai 3.x removed that dependency, LLM's own imports of httpx failed on any new installation.

The immediate fix is blunt but effective: pin the dependency to openai<3. The upcoming 0.33 release will properly migrate the codebase from httpx to the new httpx2 library.

This is not a CVE. Nobody got breached. But if you run a SOC, manage CI/CD pipelines, or govern how AI tooling enters your environment, this incident is a textbook case study in transitive dependency fragility — the same class of weakness that attackers deliberately exploit in dependency-confusion and package-substitution campaigns. Today it caused an outage. Tomorrow, a threat actor exploiting the same unmanaged dependency surface causes a compromise.

Technical Analysis

Affected software

  • Product: llm (Simon Willison's LLM CLI), versions prior to 0.32.1
  • Trigger: Fresh installs (or dependency resolution refreshes) performed after the OpenAI Python SDK released its 3.x line without the httpx dependency
  • Root cause: LLM imported httpx directly in its own code but never declared it in its install requirements. It relied on openai's dependency tree to pull httpx into the environment. When openai 3.x dropped httpx, LLM's implicit dependency vanished.
  • Fix in 0.32.1: Upper-bound pin openai<3, restoring the transitive httpx install
  • Fix in 0.33 (forthcoming): Migration from httpx to httpx2, making the dependency explicit and first-party

Why this matters to defenders, not just developers

There is no vulnerability identifier here, no CVSS score, and no active exploitation. The exploitation status is non-adversarial breakage only. But the underlying condition — software that functions because of undeclared, transitively-provided packages — has three direct security implications:

  1. Uncontrolled dependency resolution is an attack surface. Any pip install that resolves floating version ranges at install time will silently pull whatever the resolver decides is current. If an attacker compromises or typosquats a package anywhere in that transitive tree (dependency confusion, PyPI account takeover, malicious release of a maintainer package), your build installs the payload with no change to your own code. The same pip install llm that broke this week because of a benign upstream change could just as easily pull a malicious one.

  2. Shadow AI tooling is proliferating. llm is exactly the kind of tool engineers install ad hoc on workstations and jump boxes to pipe data into OpenAI, Anthropic, and other model providers. Every unmanaged install is an unsanctioned egress path for potentially sensitive data — and, as this incident shows, an unpatched, unpinned dependency stack nobody owns.

  3. Break-glass upgrades bypass change control. When fresh installs broke, the fix path for most users was "upgrade and move on." Urgency-driven, unreviewed package upgrades are precisely the windows attackers time malicious releases for (as the broader ecosystem has seen repeatedly with compromised maintainer releases in npm and PyPI).

The defensive lesson

If httpx had been declared in LLM's own requirements with a pinned or hashed version, nothing would have broken. The same discipline — explicit declaration, version pinning, hash verification, and lockfiles — is what separates a reproducible, auditable software supply chain from an incident report.

Detection & Response

This is a technical supply-chain event, and there are concrete things worth hunting for in your environment: where llm (and ad hoc Python AI tooling generally) is installed, what dependency state those installs are in, and where Python processes are egressing to model-provider APIs.

Sigma Rules

YAML
---
title: Installation of llm CLI or AI Tooling via Python Package Managers
id: 4b8e2c61-7a3f-4d9e-b2a1-9c5f6e8d0a12
status: experimental
description: Detects pip/pipx/uv installation of the llm CLI or related LLM client packages. Identifies shadow AI tooling entering the environment outside of software governance and flags unreviewed dependency resolution events.
references:
  - https://simonwillison.net/2026/Aug/21/llm/
  - https://github.com/simonw/llm/releases/tag/0.32.1
author: Security Arsenal
date: 2026/08/21
tags:
  - attack.execution
  - attack.t1059.006
logsource:
  category: process_creation
  product: windows
detection:
  selection_installer:
    Image|endswith:
      - '\pip.exe'
      - '\pipx.exe'
      - '\uv.exe'
      - '\python.exe'
      - '\python3.exe'
  selection_cmd:
    CommandLine|contains:
      - 'install llm'
      - 'install "llm'
      - 'llm=='
      - 'llm>='
  condition: selection_installer and selection_cmd
falsepositives:
  - Approved developer workstations with sanctioned AI tooling
  - Data science teams with governed package installation
level: medium
---
title: Python Package Manager Resolving Unpinned Dependencies at Runtime
id: 8f1d4a92-3c6b-4e7f-a5d2-1b9c8e7f6a34
status: experimental
description: Detects pip or pipx performing package installation without version pinning or hash verification on servers, indicating uncontrolled dependency resolution in environments where reproducibility and supply-chain integrity are required.
references:
  - https://simonwillison.net/2026/Aug/21/llm/
  - https://attack.mitre.org/techniques/T1195/002/
author: Security Arsenal
date: 2026/08/21
tags:
  - attack.initial_access
  - attack.t1195.002
logsource:
  category: process_creation
  product: linux
detection:
  selection_img:
    Image|endswith:
      - '/pip'
      - '/pip3'
      - '/pipx'
      - '/uv'
  selection_install:
    CommandLine|contains:
      - ' install '
  filter_pinned:
    CommandLine|contains:
      - '=='
      - '--require-hashes'
      - '--hash='
      - '-r requirements'
      - '--constraints'
  filter_path:
    CurrentDirectory|contains:
      - '/opt/ci'
      - '/var/lib/jenkins'
      - '/home/runner'
  condition: selection_img and selection_install and not filter_pinned and not filter_path
falsepositives:
  - Developers ad hoc installing tools on sanctioned workstations
  - Managed configuration tooling (Ansible/SSM) performing approved installs
level: low

KQL (Microsoft Sentinel / Defender)

Hunt for llm CLI execution and Python processes egressing to model-provider APIs from hosts where you wouldn't expect it. In most enterprises, api.openai.com traffic should originate from a known application subnet or specific service accounts — not arbitrary endpoints.

KQL — Microsoft Sentinel / Defender
// Hunt 1: llm CLI execution and ad hoc Python AI package installs across the fleet
DeviceProcessEvents
| where TimeGenerated > ago(14d)
| where ProcessCommandLine has_any ("llm ", "install llm", "pipx install", "llm prompt", "llm chat")
    or FileName in~ ("llm.exe", "llm")
| project TimeGenerated, DeviceName, AccountName, FileName, ProcessCommandLine, InitiatingProcessFileName
| order by TimeGenerated desc;

// Hunt 2: Python processes making network connections to model-provider APIs from non-approved hosts
let ApprovedAIEgressDevices = dynamic(["appserver01", "ml-workstation-04"]); // populate with sanctioned hosts
DeviceNetworkEvents
| where TimeGenerated > ago(7d)
| where RemoteUrl has_any ("api.openai.com", "api.anthropic.com", "generativelanguage.googleapis.com")
| where InitiatingProcessFileName has_any ("python", "python3", "llm", "uv")
| where DeviceName !in~ (ApprovedAIEgressDevices)
| summarize ConnectionCount = count(), FirstSeen = min(TimeGenerated), LastSeen = max(TimeGenerated)
    by DeviceName, InitiatingProcessFileName, InitiatingProcessAccountName, RemoteUrl
| order by ConnectionCount desc;

// Hunt 3 (Syslog ingestion for Linux): unpinned pip installs on servers
Syslog
| where TimeGenerated > ago(14d)
| where ProcessName has_any ("pip", "pip3", "pipx", "uv")
| where SyslogMessage has " install "
| where SyslogMessage !has_any ("==", "--require-hashes", "--hash", "requirements")
| project TimeGenerated, Computer, ProcessName, SyslogMessage
| order by TimeGenerated desc;

Velociraptor VQL

Use this artifact to inventory which endpoints have llm installed and what the state of its dependency stack is — specifically flagging installs running openai>=3 (the combination that breaks pre-0.32.1 installs and signals a floating, unmanaged dependency set).

VQL — Velociraptor
-- Inventory llm CLI installs and their openai/httpx dependency versions
-- by parsing dist-info metadata in site-packages directories
LET distinfo = SELECT FullPath,
       parse_string_with_regex(
         string=FullPath,
         regex='(?i)(llm|openai|httpx)-([0-9][0-9a-zA-Z\.\-]*)\.dist-info$') AS Match
FROM glob(globs=[
  'C:/Users/*/AppData/**/site-packages/*.dist-info',
  'C:/**/Lib/site-packages/*.dist-info',
  '/usr/lib/python3*/site-packages/*.dist-info',
  '/usr/local/lib/python3*/**/site-packages/*.dist-info',
  '/home/*/.local/lib/python3*/site-packages/*.dist-info',
  '/home/*/.local/share/pipx/venvs/*/lib/python3*/site-packages/*.dist-info'
])

SELECT Match[1] AS Package,
       Match[2] AS Version,
       FullPath AS MetadataPath,
       if(condition=Match[1] =~ '(?i)openai' AND
            Version =~ '^[3-9]', then='FLOATING_OPENAI_3x_PRESENT') AS RiskFlag
FROM distinfo
WHERE Match
ORDER BY MetadataPath

Remediation and Verification Script

Run this on Linux/macOS endpoints and build agents to audit and remediate llm installations. It checks the installed llm and openai versions, upgrades to the fixed release, and enforces hash-pinned installs going forward.

Bash / Shell
#!/usr/bin/env bash
# Audit and remediate llm CLI installs affected by the openai>=3 / httpx transitive dependency breakage
# Reference: https://simonwillison.net/2026/Aug/21/llm/
set -euo pipefail

echo "=== llm supply-chain audit (0.32.1 transitive dependency fix) ==="

# 1. Locate llm installs (pipx, pip user, system)
for PY in python3 python; do
  if command -v "$PY" >/dev/null 2>&1; then
    echo "--- Inspecting environment: $($PY -c 'import sys; print(sys.executable)') ---"
    $PY - <<'EOF' || true
import importlib.metadata as md
for pkg in ("llm", "openai", "httpx"):
    try:
        print(f"{pkg}: {md.version(pkg)}")
    except md.PackageNotFoundError:
        print(f"{pkg}: NOT INSTALLED")
EOF
  fi
done

if command -v pipx >/dev/null 2>&1; then
  echo "--- pipx environments ---"
  pipx list --short 2>/dev/null | grep -i '^llm' || echo "llm not installed via pipx"
fi

# 2. Detect the broken state: llm < 0.32.1 alongside openai >= 3 (missing httpx)
BROKEN=$(python3 - <<'EOF' || echo "unknown"
import importlib.metadata as md
from packaging.version import Version
try:
    llm_v = Version(md.version("llm"))
    openai_v = Version(md.version("openai"))
    if llm_v < Version("0.32.1") and openai_v >= Version("3.0.0"):
        print("BROKEN")
    else:
        print("OK")
except md.PackageNotFoundError:
    print("OK")
EOF
)

echo "Status: $BROKEN"

# 3. Remediate: upgrade to fixed release, or pin openai<3 for older installs
if [ "$BROKEN" = "BROKEN" ]; then
  echo "[!] Broken dependency state detected. Remediating..."
  if command -v pipx >/dev/null 2>&1 && pipx list --short 2>/dev/null | grep -qi '^llm'; then
    pipx upgrade llm
  else
    python3 -m pip install --upgrade "llm>=0.32.1"
  fi
  echo "[+] Remediated. Verifying:"
  python3 -c "import llm, httpx; print('llm', llm.__version__, '| httpx OK')"
fi

# 4. Hardness check: warn on any floating installs lacking pin/hash governance
echo "=== Governance check ==="
if ! ls requirements*.txt 2>/dev/null | xargs grep -l -- '--hash=' >/dev/null 2>&1; then
  echo "[WARN] No hash-pinned requirements files found in CWD."
  echo "       Enforce: pip install --require-hashes -r requirements.txt"
  echo "       Generate pinned deps with: pip-compile --generate-hashes"
fi
echo "=== Audit complete ==="

Remediation

Immediate actions

  1. Upgrade to llm 0.32.1 or later. The release pins openai<3, restoring the transitive httpx dependency. If you maintain internal tooling built on llm, bump the pin in your requirements files now. Watch for the forthcoming 0.33 release, which migrates to httpx2 and removes the fragile dependency entirely — plan to adopt it after review rather than auto-upgrading in production.

  2. If you cannot upgrade, apply the vendor's own workaround: constrain the OpenAI SDK to openai<3 in any environment where llm is installed, and explicitly add httpx as a declared dependency.

  3. Audit your own codebases for the same defect class. Search for any package your code imports directly that is not listed in your declared dependencies. Tools like pipdeptree, pip-check-reqs, and deptry will surface undeclared imports in minutes. This exact failure mode — import without declaration — is what broke llm.

Structural fixes (the real remediation)

  • Lockfiles everywhere. Deploy Python services with fully pinned, hash-verified dependency manifests (pip-compile --generate-hashes, Poetry poetry.lock, or uv.lock). Install with pip install --require-hashes. This converts silent upstream changes — benign or malicious — into explicit, reviewable diffs.
  • Private package proxy / allowlist. Route all developer and CI package installation through an internal proxy (e.g., an artifact repository with upstream proxying) so you control which packages and versions can resolve. This is also your primary defense against dependency confusion and typosquatting.
  • SBOM for anything in production. If you cannot enumerate your transitive dependency tree, you cannot assess your exposure when the next httpx-style removal — or the next malicious package release — lands. Generate and retain SBOMs (CycloneDX/SPDX) per build.
  • Govern AI tooling egress. Treat llm and similar CLIs like any other data-egress-capable application. Approve specific installations, restrict API keys to managed secret stores, and alert on model-provider API traffic from unsanctioned hosts using the queries above.

References

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.