deniz.in

Markets

Weather

Loading weather

· via dev.to (home feed)

A 70-line Python AI agent demo ends in a prompt-injected .env leak

A dev.to walkthrough builds an AI agent in under 70 lines of Python, then hides a paragraph in a web page and watches it leak .env — arguing every fix belongs in the code, not the model.

A 70-line Python AI agent demo ends in a prompt-injected .env leak

The demo

A post on dev.to makes a blunt argument: strip the marketing off the phrase AI agent and what is left is a language model, a short list of functions the model may ask to have called, and a while loop that ties them together. To back it up, the author builds a working agent in under 70 lines of Python with no framework, then deliberately breaks it by hiding a paragraph inside a web page and watching the agent hand over the API key stored in .env.

The setup is intentionally modest. It needs Python 3.10 or newer and Ollama running open models locally, pulling qwen2.5:7b — a 4.7 GB download — with llama3.2:3b offered as a roughly 2 GB fallback for machines with 8 GB of RAM or less. No Docker, cloud account or credit card is required. The code uses the standard OpenAI Python client pointed at localhost, because Ollama exposes the same HTTP API; moving to a hosted model later means changing two lines. The author, who discloses a day job at Tigera on the Kubernetes end of this problem, notes the post depends on nothing that company sells.

A model alone cannot do anything

The walkthrough opens with a plain chat call that asks a local qwen2.5:7b for the current time. The model politely asks for a timezone, which the post reads as an admission that it has no clock. A language model maps text to text and cannot look anything up; every capability that separates an agent from a chatbot comes from what the developer builds around it.

The loop every framework wraps

The agent itself is a single function. It sends the conversation plus a list of available tools to the model. A plain-text reply ends the run. A tool call is handled by looking up the function by name, running it with the parsed arguments, appending the result to the conversation as a message carrying the role tool, and going around the loop again. According to the post, every agent framework reduces to this function with extra features layered on top.

Two details do the heavy lifting later in the article. First, the model never executes anything — it replies with JSON asking your code to make a call, and your Python decides whether to comply. The author flags this point as the foundation for every fix that follows. Second, the tool descriptions are all the model ever sees; it never reads the source code, so those strings determine when a tool gets used.

The demo's clock tool is then swapped for two with real reach: read_file, which returns the contents of any text file on disk, and fetch_url, which downloads a page and returns its first 4,000 characters of raw HTML.

How the .env gets out

With both tools in place, the attack is one paragraph hidden inside a web page. When the agent fetches it, that content enters the conversation as tool output, and the model treats instructions found there like any other request — including one that leads it to read .env and hand over the key. No part of the model is malfunctioning. A tool that reads arbitrary paths combined with a tool that fetches arbitrary URLs simply adds up to a route from a poisoned page to local secrets.

The fixes skip the model

The post is explicit that the interesting part comes after the leak, and that none of the fixes involve the model — no prompt rewording, no smarter weights. The reasoning follows from the loop's design: because the model only requests calls and the surrounding code decides whether to honour them, enforcement belongs in the harness, at the point where tool calls execute and where tools are defined in the first place.

Why it matters

The demo isolates why prompt injection keeps beating agent deployments: any text an agent fetches becomes input to the same predictor that can trigger real side effects. Tool output is untrusted input, and the security boundary is not the prompt but the tool surface — what each function is allowed to touch, and what the harness is willing to run on the model's behalf. The 70-line implementation is also useful as a review aid. When the whole pattern fits on one screen, the risk is visible without framework abstractions in the way, and wrapping the loop in a framework does not change where that risk lives.

  • #ai-agents
  • #prompt-injection
  • #security
  • #python
  • #ollama

Related posts