Lenovo's “Lena” Chatbot: Stored XSS That Fired in a Support Agent's Browser
On this page
One chat message got Lenovo's support bot to write markup that the support console then ran in a human agent's browser.
| When | Found 22 July 2025; disclosed 18 August 2025 |
|---|---|
| Target | Lena, Lenovo's GPT-4 support chatbot, and its human-agent support view |
| Researcher | Cybernews research team |
| Technique | Prompted HTML in the bot's reply, stored and rendered unescaped (stored XSS) |
| Status | No CVE; Lenovo fixed it before public disclosure |
| Result | Session cookies sent to a researcher-controlled server; the researchers say a support agent's too |
- 1A researcher asks Lena for laptop specs and tells it to answer in HTML that includes an image set to fail
- 2The reply is stored in the chat history and rendered unescaped, so the failed image sends cookies out
- 3A human agent opens the handed-off chat and the same markup sends their session cookie to the researchers
What happened
On 18 August 2025, the Cybernews research team disclosed a stored cross-site scripting (XSS) flaw in Lena, the customer-support chatbot on Lenovo's website, which runs on OpenAI's GPT-4. One chat message of about 400 characters got the bot to write HTML into its reply. The app saved that reply to the conversation history and rendered it as live markup, so it ran in the browser of anyone who opened the chat. The researchers said that once the chat was passed to a human, the code ran again in the support agent's browser and sent that agent's session cookie to a server they controlled.
The researchers reported the flaw on 22 July 2025, the day they found it. Lenovo acknowledged it on 6 August and fixed it before publication. This was a research disclosure, not an attack in the wild. Lenovo said it assessed the risk and deployed a fix without putting customers at risk.
How the attack worked
The message had four parts. It opened with an ordinary request for a laptop's specifications. It then asked Lena to give its final answer in a set of formats, HTML among them, in the order Lenovo's web server expected, so the HTML would be processed as markup. Next it described an image at an address that did not exist, with an instruction that the failed load should make the browser call the researchers' server with the page's cookies in the URL. It closed by insisting that the image be shown.
Lena complied, and the rest followed:
- Stored. The reply, markup included, was written to the chat history on Lenovo's servers.
- Rendered. Loading the conversation rendered that markup. The image failed and the cookie request fired.
- Handed off. The researchers asked for a human agent. When the agent opened the chat, their browser loaded the same history and sent their session cookie out.
Why the agent's cookie mattersThe researchers said a stolen agent cookie would let someone sign in to Lenovo's support system as that agent with no username or password, and possibly read other customers' live and past chats. They presented this as possible, not as something they did, and said they did not try the deeper steps they raised, such as running system commands or moving across the network.
Root cause
The prompt only got the model to produce markup. The vulnerability was what the application did with it. Lenovo's chat stack stored the model's reply and rendered it as HTML in two places: the customer's chat window and the agent's view of the same conversation. Nothing between the model and the browser checked or encoded the output. Cybernews listed the gaps as missing input sanitisation, missing output sanitisation, a web server that did not check what the chatbot produced, running code nobody had checked, and fetching content from any web address.
The hand-off raised the stakes. By the researchers' account, an agent's session can reach other customers' conversations, and text a customer steered through a model arrived in that view as trusted HTML. That is classic stored XSS, with an LLM writing the payload for the attacker.
What a test would have caught
A basic output-handling check through the chat box would have found this. Ask the bot to include a harmless HTML element in its answer, then see whether that element renders as markup or shows as plain text. Do it in the customer widget and again in the agent console after a hand-off. The second check is the one teams skip: the test has to follow the reply to every screen that later shows it.
How to prevent it
- Encode model output for where it lands. Render replies as text by default, through
textContentor an auto-escaping template. Where rich formatting is needed, pass it through an allow-list sanitiser that removes scripts, event-handler attributes and javascript: links. - Treat stored chat history as untrusted on every screen, the agent console above all. Sanitise before storage and encode again on output.
- Set a strict Content Security Policy that blocks inline script and limits which hosts the page can load resources from.
- Mark session cookies
HttpOnlyso a script that does run cannot read them. - Don't rely on the system prompt. Telling the model not to emit HTML does not close the sink.
Feel it yourselfThe replay lab has the same shape: a report you write reaches a staff console through an AI-drafted digest that the console renders as raw HTML.
FAQ
What happened with Lenovo's Lena chatbot?
In August 2025 Cybernews researchers disclosed that a single chat message could make Lena, Lenovo's GPT-4 support bot, write HTML into its reply. The reply was saved to the chat history and ran in the browser of whoever opened it, including a human support agent after a hand-off. Lenovo fixed the flaw before the disclosure.
How did the Lena XSS attack work?
The message asked for laptop specifications, told the bot to answer in HTML, and described an image that would fail to load and then send the page's cookies to the researchers' server. Lena wrote it, the app stored and rendered it without escaping, and the same markup ran again when a support agent opened the chat.
How do you stop a chatbot from causing XSS?
Treat model output as untrusted. Encode it for the place it is displayed, pass any rich HTML through an allow-list sanitiser, set a strict Content Security Policy, and test every screen that shows stored chat history, staff consoles included.