a2ui-project / a2ui-project/a2ui
[BUG]: Missing JSON Pointer Sanitization Allows Prototype Pollution Payloads in DataModel
- Vorherrschende Sprache
- TypeScript
- Sterne
- 16.4k
- Forks
- 1.3k
- Ø Merge
- 2 T. 13 Std.
- Gemergte PRs (30 T.)
- 134
Beschreibung
Location
agent_sdks/python/a2ui_core/src/a2ui/core/state/data_model.py:91
Description
The DataModel.set() method in agent_sdks/python/a2ui_core/src/a2ui/core/state/data_model.py is vulnerable to Prototype Pollution. The function takes a JSON Pointer path string, parses it into tokens, and iterates through these tokens to perform auto-vivification (creating intermediate dictionaries and lists as needed) before setting the value at the final token.
During this traversal, the code checks if current is a dictionary, and if so, it creates or retrieves current[token]. However, it fails to sanitize or block potentially dangerous tokens like __proto__, constructor, or prototype. If an attacker can control the path string passed to DataModel.set(), they can craft a payload with __proto__ as a segment to overwrite properties on the global dict or object prototypes.
Specifically, in this block:
if isinstance(current, dict):
if token not in current or not isinstance(current[token], (dict, list)):
current[token] = [] if is_next_numeric else {}
current = current[token]
When token is "__class__" or similar reflection/prototype attributes, it allows writing to current["__class__"]. While Python dictionary keys are distinct from attributes, certain frameworks or deserializers might unsafely map these dict keys to object properties later, or the structure might be evaluated unsafely. Wait, in Python, dict keys are just strings, so setting d[\"__proto__\"] = value just adds a key-value pair to the dictionary, it doesn't pollute the actual class prototype like in JavaScript. However, if this data structure is later serialized and sent to a JavaScript client (which the A2UI threat model strongly suggests), the __proto__ key in the generated JSON will cause prototype pollution on the JS side when parsed/merged by the client-side renderer.
The threat model mentions: "A1 — Malicious / Prompt-Injected Agent [...] updateDataModel with __proto__/constructor JSON-Pointer segments (CWE-1321)". The web_core renderer is stated to have this issue pending merge, but the Python SDK (which manages the DataModel on the server) mirrors this data model and processes incoming paths, then sends updates back, or acts as a conduit. Wait, if the Python server receives a crafted event or snapshot (Actor A2) with __proto__ paths, it might blindly store it and replay it to clients, or if it processes A2UI JSON from the agent (Actor A1), it updates its internal DataModel and then forwards it. Either way, the Python implementation must reject these unsafe path segments to prevent pollution, both on the server (if it happens to be using JS-like environments anywhere) and to protect downstream JS clients from malicious agents/users.
The fix should block \"__proto__\", \"constructor\", and \"prototype\" in _parse_pointer or inside set().
--- Additional Context (from agent_sdks/python/a2ui_core/src/a2ui/core/state/data_model.py:82) --- The DataModel.set() method uses _parse_pointer() to split a JSON Pointer path into segments and iterate through them to auto-vivify dictionaries or lists. It fails to filter out prototype pollution keys like __proto__, constructor, or prototype from the path segments. This allows an attacker to supply a crafted A2UI payload with a malicious updateDataModel path that assigns a value to Python's internal dunder methods or object attributes if not strictly validated elsewhere. Note that prototype pollution in Python works differently than JavaScript but the same logic is susceptible to object attribute overwrite.
--- Additional Context (from agent_sdks/python/a2ui_core/src/a2ui/core/state/data_model.py:84) --- The DataModel.set() method processes JSON Pointer paths and performs auto-vivification of nested dictionary structures without restricting dangerous property names. An attacker can supply a malicious path containing __proto__, constructor, or prototype. While Python dictionaries themselves are immune to JavaScript-style prototype pollution, this Python SDK serves as a server-side mirror of the reactive data model. When these unrestricted keys are stored in the server's data model and synchronized back to the JavaScript-based client renderer, they can trigger prototype pollution on the client.
--- Additional Context (from agent_sdks/python/a2ui_core/src/a2ui/core/state/data_model.py:91) --- The DataModel.set method in agent_sdks/python/a2ui_core/src/a2ui/core/state/data_model.py is vulnerable to Prototype Pollution. The _parse_pointer method splits a JSON pointer path into tokens, and the set method iterates through these tokens to set the value. However, there is no validation to ensure that the tokens do not contain dangerous properties like __class__, __bases__, or __mro__. An attacker can craft a malicious UpdateDataModel message with a path like /user/__class__/__base__/__subclasses__/0/... to pollute the Python prototype chain, potentially leading to Remote Code Execution (RCE) or other severe consequences in the agent server process. Note that while this is a Python implementation, __class__ and similar attributes pose a similar risk to JavaScript's __proto__.
--- Additional Context (from agent_sdks/python/a2ui_core/src/a2ui/core/state/data_model.py:84) --- The Python agent SDK's DataModel implementation in a2ui_core/src/a2ui/core/state/data_model.py performs a JSON Pointer traversal to get or set values (in get and set). However, it fails to filter out prototype pollution vectors such as __proto__, constructor, or __class__. While the web_core/src/v0_9/state/data-model.ts TypeScript file properly sanitizes and refuses accesses involving __proto__, constructor, and prototype, its server-side Python mirror allows these segments to pass through during JSON pointer parsing (_parse_pointer) and subsequently during data mutation or access traversing. Although Python's dictionaries protect against standard __proto__ pollution, a payload crafted to abuse Python's magic attributes (e.g. __class__, __init__, __globals__ if the target _data becomes or contains a vulnerable object structure down the line rather than raw dicts) or simply dictionary pollution where an attacker overwrites structural metadata, is possible if untrusted agent output calls updateDataModel.path. It is a well-known vulnerability in recursive descent updates missing explicit blocklists.
The threat model explicitly calls out CWE-1321 (Prototype Pollution) for DataModel.set() as a known regression watch missing __proto__/constructor/prototype filtering. The patch VERIFIED_SECURE mentioned in the threat model is noted to exist for the web_core but the Python port shares the flawed lack of explicit check.
--- Additional Context (from agent_sdks/python/a2ui_core/src/a2ui/core/state/data_model.py:91) --- The set method in DataModel uses JSON Pointer to update nested state by path. It splits the path on / and automatically traverses or vivifies intermediate dictionaries. However, it lacks a filter for prototype-polluting property names, such as __class__ or __init__ in Python. While this exact bug class is "Prototype Pollution" (CWE-1321), in Python, it can manifest as arbitrary attribute modification if object internals are inadvertently exposed, or dictionary poisoning. If _data acts as a generic configuration dictionary that later merges or populates other objects, an agent could inject payload keys (like __class__, __init__, mro) causing application instability or unexpected behavior. The threat model explicitly notes a pending VERIFIED_SECURE patch for prototype pollution in DataModel.set().
--- Additional Context (from agent_sdks/python/a2ui_core/src/a2ui/core/state/data_model.py:91) --- The DataModel.set method in agent_sdks/python/a2ui_core/src/a2ui/core/state/data_model.py is vulnerable to Prototype Pollution. The JSON Pointer path segments are not filtered for dangerous keys like __proto__, constructor, or prototype. An attacker controlling the updateDataModel message can send a crafted path (e.g., /__proto__/polluted) and value that will be processed by MessageProcessor._process_update_data_model, which directly calls surface.data_model.set(path, value). This auto-vivifies and mutates the global prototype chain of the Python process' dictionaries or objects if not properly defended (though Python dictionaries don't have __proto__ in the JS sense, the same library logic typically ports to JS/TypeScript renderers where it is critical, and the Python implementation lacks parity with the TS one which adds these checks in renderers/web_core/src/v0_9/state/data-model.ts). If this Python code evaluates or reflects these attributes or is used in a context where attributes mirror dict keys, it can cause unexpected behavior or mirror the known JS prototype pollution issue present in older versions of the codebase (as noted in the threat model "CWE-1321 Prototype Pollution"). Even though Python does not have JS prototype pollution, the codebase is a polyglot implementation and the TS implementation tests explicitly check for this. The Python implementation allows arbitrary key assignment which could lead to dictionary pollution or bypasses.
--- Additional Context (from agent_sdks/python/a2ui_core/src/a2ui/core/state/data_model.py:97) --- The set() method within DataModel (in data_model.py, exported by state/__init__.py) fails to sanitize JSON-Pointer segments during its auto-vivification traversal. It allows updating arbitrary paths, including keys like __proto__, constructor, or prototype. While Python dictionaries are not directly vulnerable to JS prototype pollution, storing these keys in the Python state allows a malicious actor to inject prototype pollution payloads that will be serialized and transmitted to the JavaScript client renderer. This can lead to client-side Prototype Pollution when the renderer processes these synchronized data model updates.
--- Additional Context (from agent_sdks/python/a2ui_core/src/a2ui/core/state/data_model.py:34) --- The DataModel class in a2ui_core/src/a2ui/core/state/data_model.py lacks sanitization for forbidden keys (such as __proto__, constructor, prototype) when resolving or auto-vivifying JSON Pointer paths. An attacker controlling the input path string (e.g., via a malicious agent emitting an updateDataModel message) can use segments like /__proto__/polluted_key to pollute the prototype of Python dictionaries (dict). Because DataModel._parse_pointer and the access/mutation loops (get, has_path, set) simply use the token strings verbatim for dictionary access and creation, this can lead to arbitrary properties being set on the underlying object graph or prototype pollution equivalents if exposed in a context that allows it (though Python is generally more resilient to global prototype pollution than JavaScript, dict structure poisoning is still a severe vulnerability that can bypass validation checks or cause unexpected behavior/crashes in the Python SDK environment). This violates the requirement to explicitly filter these keys, as mentioned in the threat model (Historically-Relevant Bug Classes) and as implemented in the TypeScript version (renderers/web_core/src/v0_9/state/data-model.ts).
--- Additional Context (from agent_sdks/python/a2ui_core/src/a2ui/core/state/data_model.py:65) --- The DataModel.set(self, path, value) method uses JSON Pointer to resolve the target path. It splits the path into tokens and traverses the current data structure (self._data). If a part of the path is missing, it auto-vivifies intermediate objects (dictionaries or lists) to build out the full path before setting the final value.
However, when traversing the path, it does not validate or sanitize the path segments against known JavaScript or Python prototype pollution keys. A malicious path like /__class__/__init__/__globals__/foo could potentially exploit object pollution or internal state corruption. Although in Python this is less common than JS prototype pollution (e.g. __proto__), modifying restricted internal dictionary keys such as __class__ can still cause severe bugs. A more direct translation of the threat model points to a documented prototype pollution issue (CWE-1321) regarding __proto__, constructor, or prototype missing checks in DataModel.set(). Python equivalent pollution vectors exist, but primarily the flaw matches the known missing sanitization in the JSON Pointer walk for DataModel.set() described in the threat model.
--- Additional Context (from agent_sdks/python/a2ui_core/src/a2ui/core/state/data_model.py:84) --- The DataModel class in the python agent sdk uses a JSON Pointer (path) to traverse and update a dictionary via auto-vivification. The tokens are iterated and intermediate dictionaries are created if they do not exist. However, the path segments (tokens) are not validated against dangerous prototype pollution keys such as __proto__, constructor, or __class__. Since Python dictionaries don't have JavaScript-style prototype chains, this is not directly exploitable for code execution in Python the same way as JavaScript, but the A2UI threat model (§4, A1 & §7) explicitly lists updateDataModel with __proto__/constructor JSON-Pointer segments as a CWE-1321 vulnerability because this same data flow hits both the Python server-side state mirror and the JS/TS client-side renderer. If the python backend reflects or processes this data model, it could cause issues. More importantly, the TS implementation handles this with FORBIDDEN_KEYS = new Set(['__proto__', 'constructor', 'prototype']); but the python implementation lacks this parity. If the python server acts as a relay or processes these fields dynamically, an attacker can corrupt the server-side model.
--- Additional Context (from agent_sdks/python/a2ui_core/src/a2ui/core/state/data_model.py:82) --- The DataModel.set() method in the Python Agent SDK performs auto-vivification of nested dictionaries and lists based on a provided JSON Pointer path. It lacks sanitization for unsafe keys such as __proto__, constructor, or prototype. When an agent or client sends an updateDataModel message, the MessageProcessor passes the attacker-controlled path directly to DataModel.set() without validation. If this path contains __proto__, it can corrupt the Python server's data model state. More critically, when this polluted model is synced back to a JavaScript-based web_core client, it triggers an Object.prototype pollution in the host application's browser environment.
--- Additional Context (from agent_sdks/python/a2ui_core/src/a2ui/core/state/data_model.py:91) --- The DataModel.set() method implements JSON Pointer auto-vivification but fails to filter dangerous prototype keys (__proto__, constructor, prototype). Although Python dictionaries are not directly susceptible to JavaScript-style prototype pollution, the DataModel state is synchronized between the Python agent and the client renderer. If an attacker injects a JSON Pointer targeting these keys (e.g., via user events or prompt-injected agent responses), the Python server stores the polluted payload. Upon state synchronization, this payload is forwarded to the client's DataModel, triggering prototype pollution in the host browser environment.
--- Additional Context (from agent_sdks/python/a2ui_core/src/a2ui/core/state/data_model.py:84) --- The python DataModel.set implementation in agent_sdks/python/a2ui_core/src/a2ui/core/state/data_model.py splits JSON pointers and uses auto-vivification to traverse dicts and set values. However, it lacks validation against dangerous path segments like __class__, __mro__, __subclasses__, __init__, etc. If an attacker controls the JSON pointer path in A2UI messages (which cross the Trust Boundary TB-1), they can manipulate Python object internals. Although Python dictionary operations typically do not pollute objects implicitly like JS's __proto__, this code directly processes user input (paths). Wait, in python, traversing a dict key like __proto__ or __class__ just creates a dict key of that name, UNLESS the object being traversed is NOT a dict. Here, isinstance(current, dict) is checked. If it is a dictionary, current[token] accesses the dictionary key, not an attribute. Since initial_data is a dict, it will just create a key named __class__. Therefore, it might not be a prototype pollution vulnerability in Python. Let me re-examine the JS implementation.
In renderers/web_core/src/v0_9/state/data-model.ts, there is protection:
const FORBIDDEN_KEYS = new Set(['__proto__', 'constructor', 'prototype']);
This protects set() and get() in the v0_9 TypeScript implementation.
However, the threat model mentions: - **CWE-1321 Prototype Pollution** — DataModel.set() JSON-Pointer walk lacks __proto__/constructor/prototype filtering **in-tree**; a VERIFIED_SECURE patch exists in workspace/patch/ but is **pending merge**.
Wait, if renderers/web_core/src/v0_9/state/data-model.ts has FORBIDDEN_KEYS, then the prototype pollution vulnerability must exist in another version! The structural notes say: - **Multi-version coexistence**: v0_8, v0_9, v0_9_1, v1_0 live side by side — a fix in one does not propagate to the others...
Let me check other versions of data-model.ts.
Impact
Prototype pollution on downstream JavaScript clients, potentially leading to Cross-Site Scripting (XSS) or application logic bypass when the maliciously structured data model is synchronized to the web_core renderer.
Mitigation
Add a filtering check in DataModel._parse_pointer() or DataModel.set() to reject paths containing unsafe segments such as __proto__, constructor, or prototype. For example, raise a ValueError if token in [\"__proto__\", \"constructor\", \"prototype\"].
Reproduction Steps
Initialize a DataModel instance.
Call set('/__proto__/polluted', 'value').
Call get('/__proto__/polluted') and observe that the value was stored.
Serialize the DataModel's internal data to JSON. Observe that {\"__proto__\": {\"polluted\": \"value\"}} is present in the output, which serves as a payload for client-side prototype pollution.
--- Alternative Exploitation/Repro (from agent_sdks/python/a2ui_core/src/a2ui/core/state/data_model.py:82) ---
Construct an A2UI message containing an updateDataModel action with a path like /__proto__/foo.
Pass the message to the MessageProcessor / DataModel.
Check if the internal model or surrounding context gets populated with the value.
--- Alternative Exploitation/Repro (from agent_sdks/python/a2ui_core/src/a2ui/core/state/data_model.py:84) ---
Send a client event payload or A2UI message that calls updateDataModel with a path like /__proto__/polluted and a value of true.
The Python DataModel.set() processes this without rejection and stores the value under the __proto__ key in _data.
This state is serialized and synchronized back to the JS renderer.
The client-side renderer processes the mirrored update, inadvertently injecting polluted: true into the global Object.prototype.
--- Alternative Exploitation/Repro (from agent_sdks/python/a2ui_core/src/a2ui/core/state/data_model.py:84) ---
Initiate an A2UI interaction using the Python Agent SDK.
Formulate an updateDataModel operation containing a path segment such as /__proto__/hacked or /__class__/hacked.
The server-side Python data model will successfully execute the set operation, potentially polluting the underlying state if it interacts with susceptible objects.
--- Alternative Exploitation/Repro (from agent_sdks/python/a2ui_core/src/a2ui/core/state/data_model.py:91) --- Call DataModel.set('/__class__/some_attr', 'val') and observe if the internal dictionary processes the key without filtering.
--- Alternative Exploitation/Repro (from agent_sdks/python/a2ui_core/src/a2ui/core/state/data_model.py:97) ---
Initialize a DataModel instance.
Call model.set('/__proto__/polluted', 'value').
Observe that the underlying _data dict now contains {'__proto__': {'polluted': 'value'}}.
When serialized and dispatched to a JS client, this payload triggers prototype pollution.
--- Alternative Exploitation/Repro (from agent_sdks/python/a2ui_core/src/a2ui/core/state/data_model.py:34) ---
Initialize DataModel in the Python SDK.
Call model.set('/__proto__/polluted_key', 'hacked').
Verify if the dictionary structure incorrectly auto-vivifies and assigns the value under the __proto__ key.
Call model.get('/__proto__/polluted_key') to confirm the value was stored and returned.
--- Alternative Exploitation/Repro (from agent_sdks/python/a2ui_core/src/a2ui/core/state/data_model.py:65) ---
Instantiate a DataModel instance.
Call set(\"/__class__/some_key\", \"value\") or set(\"/__proto__/polluted\", \"true\").
Observe that the payload successfully sets the value on the restricted attribute namespace, simulating pollution.
--- Alternative Exploitation/Repro (from agent_sdks/python/a2ui_core/src/a2ui/core/state/data_model.py:91) ---
Initialize a DataModel instance. 2. Call dm.set('/__proto__/polluted', 'true'). 3. Observe that the internal dictionary _data now contains {'__proto__': {'polluted': 'true'}}. 4. In a production scenario, when this model state is synced to the A2UI client renderer, the updateDataModel JSON stream instructs the TypeScript client to set __proto__.polluted = true, polluting Object.prototype in the host browser.
Evidence
def set(self, path: str, value: Any) -> None:
\"\"\"Sets a value atomically at a JSON Pointer path with auto-vivification.\"\"\"
tokens = self._parse_pointer(path)
# ...
current = self._data
for i, token in enumerate(tokens[:-1]):
# ...
if isinstance(current, dict):
if token not in current or not isinstance(current[token], (dict, list)):
current[token] = [] if is_next_numeric else {}
current = current[token]
# ...
# Set final leaf value
last_token = tokens[-1]
if isinstance(current, dict):
if value is None:
current.pop(last_token, None)
else:
current[last_token] = copy.deepcopy(value)
--- Alternative Evidence (from agent_sdks/python/a2ui_core/src/a2ui/core/state/data_model.py:82) ---
# Auto-vivification: traverse and construct intermediate dicts/lists
current = self._data
for i, token in enumerate(tokens[:-1]):
next_token = tokens[i + 1]
is_next_numeric = bool(NUMERIC_PATTERN.match(next_token))
if isinstance(current, dict):
if token not in current or not isinstance(current[token], (dict, list)):
current[token] = [] if is_next_numeric else {}
current = current[token]
--- Alternative Evidence (from agent_sdks/python/a2ui_core/src/a2ui/core/state/data_model.py:91) ---
def set(self, path: str, value: Any) -> None:
\"\"\"Sets a value atomically at a JSON Pointer path with auto-vivification.\"\"\"
tokens = self._parse_pointer(path)
# ... no sanitization of tokens ...
current = self._data
for i, token in enumerate(tokens[:-1]):
# ... traverses using token ...
if isinstance(current, dict):
if token not in current or not isinstance(current[token], (dict, list)):
current[token] = [] if is_next_numeric else {}
current = current[token]
--- Alternative Evidence (from agent_sdks/python/a2ui_core/src/a2ui/core/state/data_model.py:84) ---
@staticmethod
def _parse_pointer(path: str) -> List[str]:
\"\"\"Splits a JSON Pointer path into individual unescaped tokens.\"\"\"
if not path or path == \"/\":
return []
if not path.startswith(\"/\"):
# Support relative scope path resolution
return [t.replace(\"~1\", \"/\").replace(\"~0\", \"~\") for t in path.split(\"/\")]
tokens = path[1:].split(\"/\")
return [t.replace(\"~1\", \"/\").replace(\"~0\", \"~\") for t in tokens]
No validation against forbidden JSON pointer keys is present here, contrary to the TS version.
--- Alternative Evidence (from agent_sdks/python/a2ui_core/src/a2ui/core/state/data_model.py:91) --- In agent_sdks/python/a2ui_core/src/a2ui/core/state/data_model.py:
def set(self, path: str, value: Any) -> None:
# ...
for i, token in enumerate(tokens[:-1]):
# ...
if isinstance(current, dict):
if token not in current or not isinstance(current[token], (dict, list)):
current[token] = [] if is_next_numeric else {}
current = current[token]
# ...
last_token = tokens[-1]
if isinstance(current, dict):
# ...
current[last_token] = copy.deepcopy(value)
No check for token in [\"__proto__\", \"constructor\", \"prototype\"] is performed.
--- Alternative Evidence (from agent_sdks/python/a2ui_core/src/a2ui/core/state/data_model.py:34) ---
@staticmethod
def _parse_pointer(path: str) -> List[str]:
\"\"\"Splits a JSON Pointer path into individual unescaped tokens.\"\"\"
if not path or path == \"/\":
return []
if not path.startswith(\"/\"):
# Support relative scope path resolution
return [t.replace(\"~1\", \"/\").replace(\"~0\", \"~\") for t in path.split(\"/\")]
tokens = path[1:].split(\"/\")
return [t.replace(\"~1\", \"/\").replace(\"~0\", \"~\") for t in tokens]
# ... inside get(), set(), has_path():
for token in tokens:
if isinstance(current, dict) and token in current:
current = current[token]
--- Alternative Evidence (from agent_sdks/python/a2ui_core/src/a2ui/core/state/data_model.py:65) ---
# Auto-vivification: traverse and construct intermediate dicts/lists
current = self._data
for i, token in enumerate(tokens[:-1]):
next_token = tokens[i + 1]
is_next_numeric = bool(NUMERIC_PATTERN.match(next_token))
if isinstance(current, dict):
if token not in current or not isinstance(current[token], (dict, list)):
current[token] = [] if is_next_numeric else {}
current = current[token]
--- Alternative Evidence (from agent_sdks/python/a2ui_core/src/a2ui/core/state/data_model.py:84) --- agent_sdks/python/a2ui_core/src/a2ui/core/state/data_model.py:
def _parse_pointer(path: str) -> List[str]:
\"\"\"Splits a JSON Pointer path into individual unescaped tokens.\"\"\"
if not path or path == \"/\":
return []
if not path.startswith(\"/\"):
# Support relative scope path resolution
return [t.replace(\"~1\", \"/\").replace(\"~0\", \"~\") for t in path.split(\"/\")]
tokens = path[1:].split(\"/\")
return [t.replace(\"~1\", \"/\").replace(\"~0\", \"~\") for t in tokens]
No validation blocks __proto__ or constructor.
--- Alternative Evidence (from agent_sdks/python/a2ui_core/src/a2ui/core/state/data_model.py:82) --- In DataModel.set, the traversal loop does not filter unsafe keys before assignment:
for i, token in enumerate(tokens[:-1]):
...
if isinstance(current, dict):
if token not in current or not isinstance(current[token], (dict, list)):
current[token] = [] if is_next_numeric else {}
current = current[token]
--- Alternative Evidence (from agent_sdks/python/a2ui_core/src/a2ui/core/state/data_model.py:91) --- In data_model.py at line 91, the set() method splits the JSON Pointer into tokens and iterates over them, directly assigning keys to the dictionary via current[token] = ... and current[last_token] = value without any validation against dangerous keys like __proto__.
Reasoning
The DataModel.set method in agent_sdks/python/a2ui_core/src/a2ui/core/state/data_model.py iterates over tokens derived from a JSON Pointer. If the token is __proto__, constructor, or prototype, there is no validation to block these tokens in the Python implementation, contrary to what might be expected from a JS/TS parallel. While Python dictionary accesses like current["__proto__"] = ... do not cause Python prototype pollution because they simply set string keys on dict objects, the threat model explicitly mentions this scenario: The python SDK serves as a server-side mirror of the data model. When an attacker sends a malicious path over TB-3, the python SDK stores the polluted dictionary key. When synchronized back to a JavaScript renderer, the renderer will parse it and pollute Object.prototype locally, leading to full XSS. The threat model under historically relevant bug classes explicitly calls out CWE-1321 as missing filtering in DataModel.set() for __proto__, constructor, or prototype. The Python implementation acts as a confused deputy, storing and echoing the exploit payload to the client. This confirms the validity of the finding.
[RISK CALIBRATION JUSTIFICATION]
Calculated Impact: S1 (High). The vulnerability allows a malicious actor (e.g., prompt-injected agent or malicious client) to inject a prototype pollution payload (__proto__) into the Python server's data model. When synchronized to a JavaScript client renderer, this results in Cross-Site Scripting (XSS) in the host embedding application. According to the base ORQ guidelines, an easily exploited vulnerability compromising sensitive content or multiple customers maps to S1. Additionally, the threat model lists the Host DOM as a high-risk asset, and XSS allows full compromise of the embedding application.
Calculated Likelihood: High ("Easy"). The attack vector relies on an A2UI message (updateDataModel) crossing the TB-1 or TB-3 trust boundaries. The Python implementation provides no validation against forbidden JSON pointer keys (__proto__, etc.), making it trivial to inject the payload.
Multipliers Applied:
Exposure: 1.0 (Exposed interface across TB-1 and TB-3).
User Interaction: 1.0 (No user interaction required; an agent or client can emit the message).
Sanity Triage Rules Applied:
"Strict XSS Caps: All XSS (only stored XSS on critical admin pages with zero-click can reach S1)." This rule is relevant. While the direct result is Prototype Pollution on the client, it leads to XSS. However, the A2UI framework is a component embedded in other applications. The impact is technically client-side code execution in the context of the host application. Given the severity of prototype pollution leading to XSS in a broad, generic UI framework, an S1 rating is appropriate as it acts as a systemic compromise of the client-side environment.
"Force-Cap to High (S1) - Static Confirmation." The finding has not been explicitly reproduced with a running PoC in the context of the review, but is statically confirmed.
Final Reasoning: The finding describes a clear, easily exploitable path to inject prototype pollution payloads via the Python SDK, which acts as a conduit to vulnerable JavaScript renderers. This can lead to XSS in the host application. The impact is significant (S1 level), the likelihood is high, and no downgrading multipliers apply. The finding is capped at S1 due to being statically confirmed and being an XSS-equivalent client-side compromise.
PoC Hints
Craft an A2UI message with updateDataModel containing a path like /user/__proto__/polluted_key and observe that the Python SDK successfully updates its DataModel. Serialize this data model to JSON and verify that the resulting JSON string contains the \"__proto__\" key, which would trigger prototype pollution when parsed by a vulnerable JavaScript client.
Beitragsleitfaden
Bewertung
Dieses Issue wurde noch nicht bewertet.