How Python Stops Prototype Pollution in Web APIs
Prototype pollution is primarily known as a high-severity JavaScript vulnerability, but Python web APIs can face analogous threats through dynamic attribute manipulation, mass assignment, and object pollution. While Python does not use prototypal inheritance, malicious JSON payloads can attempt to alter internal object attributes or class definitions if applications dynamically assign keys to objects without validation. Python neutralizes these risks through an immutable built-in type hierarchy, strict namespace controls, and robust deserialization practices implemented by modern web frameworks.
Prototypal vs. Class-Based Inheritance
In JavaScript, objects inherit properties directly from prototypes,
meaning modifying Object.prototype alters every object in
the runtime environment. Python relies on a strict, class-based
inheritance model governed by the Method Resolution Order (MRO).
Instances inherit behavior from classes, but objects do not share a single dynamic prototype object that can be globally modified at runtime through standard assignment. Attempting to assign an attribute to a base object will not automatically cascade across unrelated instances, inherently preventing direct prototype pollution.
Immutability of Built-in Classes
In Python, built-in types such as object,
dict, list, and str are
implemented in C and possess immutable namespaces:
obj = object()
obj.polluted = True # Raises: AttributeError: 'object' object has no attribute 'polluted'
object.polluted = True # Raises: TypeError: cannot set 'polluted' attribute of immutable type 'object'Because an attacker cannot inject properties into base built-ins, system-wide behavior cannot be hijacked via native base classes.
Protection of Special Attributes
Python objects use dunder (double underscore) attributes to manage
metadata, such as __class__, __dict__, and
__mro__. While attackers often target these attributes to
achieve Remote Code Execution (RCE) in Server-Side Template Injection
(SSTI) or deserialization exploits, standard Python operations limit
write access to these internals:
- Restricted
__class__Assignment: Python prevents reassigning__class__if the memory layouts of the old and new classes are incompatible. - Protected
__dict__References: While an instance's dynamic attributes reside in its__dict__, direct modification of a class's internal mapping proxy (cls.__dict__) is prohibited. - Explicit
setattr()Behavior: While unsafe usage ofsetattr(target, key, value)with user-controlled input can overwrite instance attributes, it only affects that specific instance unless the target is explicitly a mutable custom class object.
Mitigating
Dynamic Attribute Manipulation with __slots__
To explicitly block attribute injection on custom instances, Python
provides __slots__. By defining __slots__, a
class skips the creation of a dynamic __dict__, permitting
only a predefined set of attributes:
class SecureProfile:
__slots__ = ('username', 'email')
profile = SecureProfile()
profile.username = "alice"
profile.admin = True # Raises: AttributeError: 'SecureProfile' object has no attribute 'admin'Using __slots__ ensures that arbitrary properties
supplied in an API payload cannot be attached to internal objects.
Framework-Level Payload Validation
Python web APIs avoid attribute manipulation largely through structural separation of incoming data from business logic objects.
Modern frameworks like FastAPI, Django REST Framework, and libraries like Pydantic and Marshmallow enforce strict data validation:
- Pydantic (FastAPI): Requests are validated against
explicit schemas. By default, extra fields in a JSON request are ignored
(
extra = 'ignore') or explicitly rejected (extra = 'forbid'). - Django Models: Mass assignment is mitigated by
ModelFormdefinitions that require explicitfieldsdeclarations, preventing clients from modifying sensitive model fields such asis_superuser. - Safe Serialization: Standard JSON parsers
(
json.loads) deserialize payloads into standard Python dictionaries rather than dynamic custom classes, ensuring untrusted keys remain inert data instead of executable attributes.