# PYTHON-LANG-SEC-006: Dangerous typing.get_type_hints() Usage

> **Severity:** MEDIUM | **CWE:** CWE-95 | **OWASP:** A03:2021

- **Language:** Python
- **Category:** Python Core
- **URL:** https://codepathfinder.dev/registry/python/lang/PYTHON-LANG-SEC-006
- **Detection:** `pathfinder scan --ruleset python/PYTHON-LANG-SEC-006 --project .`

## Description

Python's typing.get_type_hints() resolves type annotations by evaluating string annotations
(also known as PEP 563 postponed annotations) using eval() in the context of the annotated
object's module namespace. When from __future__ import annotations is used or annotations are
stored as string literals, get_type_hints() calls eval() on those strings to resolve them.

If annotations are dynamically constructed or sourced from untrusted data, an attacker can
inject arbitrary Python expressions into the annotation string that will be evaluated when
get_type_hints() is called. This is particularly relevant in frameworks that use annotations
for serialization, validation, or ORM field definitions and call get_type_hints() at runtime.

In most applications, get_type_hints() is called on developer-controlled class definitions
and poses no direct risk. The risk arises when annotation strings are generated dynamically
from external input or when get_type_hints() is called on classes loaded from untrusted sources.


## Vulnerable Code

```python
import code
import importlib
import typing

# SEC-006: dangerous annotations
class Foo:
    x: "eval('malicious')" = 1

hints = typing.get_type_hints(Foo)
```

## Secure Code

```python
import typing

# SECURE: Only call get_type_hints() on statically defined, trusted classes
class UserSchema:
    name: str
    age: int
    email: str

hints = typing.get_type_hints(UserSchema)  # Safe: class is developer-defined

# SECURE: If dynamic classes are needed, avoid string annotations
# and use typing constructs directly without from __future__ import annotations
def create_typed_class(field_definitions: dict):
    # Validate field types against an allowlist
    ALLOWED_TYPES = {
        "str": str,
        "int": int,
        "float": float,
        "bool": bool,
    }
    namespace = {}
    for field_name, type_name in field_definitions.items():
        if type_name not in ALLOWED_TYPES:
            raise ValueError(f"Type not allowed: {type_name}")
        namespace[field_name] = ALLOWED_TYPES[type_name]
    return type("DynamicClass", (), {"__annotations__": namespace})

```

## Detection Rule (Python SDK)

```python
from rules.python_decorators import python_rule
from codepathfinder import calls, flows, QueryType
from codepathfinder.presets import PropagationPresets


@python_rule(
    id="PYTHON-LANG-SEC-006",
    name="Dangerous Annotations Usage",
    severity="MEDIUM",
    category="lang",
    cwe="CWE-95",
    tags="python,annotations,pep-563,CWE-95",
    message="typing.get_type_hints() may execute string annotations. Be cautious with untrusted code.",
    owasp="A03:2021",
)
def detect_dangerous_annotations():
    """Detects typing.get_type_hints() which evaluates string annotations."""
    return calls("typing.get_type_hints", "get_type_hints")
```

## How to Fix

- Only call get_type_hints() on classes and functions defined in trusted, version-controlled source code.
- Never construct annotation strings dynamically from external input or store annotation strings in untrusted data sources.
- When building dynamic schemas from external configuration, use typing constructs as Python objects (str, int, List[str]) rather than string representations.
- Audit frameworks that call get_type_hints() on plugin or user-defined classes to ensure annotation content cannot be influenced by attackers.
- Consider using inspect.get_annotations() in Python 3.10+ for lower-level annotation access that avoids the eval() behavior in specific contexts.

## Security Implications

- **Annotation String Evaluation:** With from __future__ import annotations enabled, all annotations are stored as strings
and evaluated lazily. get_type_hints() calls eval() on these strings. If annotation
strings can be influenced by an attacker, this becomes an eval injection vulnerability.

- **Framework-Level Risk in Pydantic and Dataclasses:** Frameworks such as pydantic, dataclasses, and attrs call get_type_hints() to resolve
field types. If these frameworks process class definitions constructed from external
data, the annotation evaluation can be exploited. This is a known risk in dynamic
schema generation.

- **Serialization and ORM Reflection Attacks:** ORMs and serialization libraries that use annotations to determine field types and
call get_type_hints() for schema reflection can be attacked via malicious annotation
strings in serialized object metadata.

- **Plugin and Extension Loading:** Systems that dynamically load classes from user-specified modules and then call
get_type_hints() on those classes for introspection may execute malicious annotation
expressions defined in attacker-controlled plugin code.


## FAQ

**Q: Is get_type_hints() always dangerous to call?**

No. When called on developer-defined classes whose annotations are written directly in
source code, get_type_hints() is safe. The risk is only present when the object being
inspected has annotations that were constructed from external input or loaded from an
untrusted source.


**Q: What is the relationship between PEP 563 and this vulnerability?**

PEP 563 (from __future__ import annotations) changes Python to store all annotations
as strings rather than evaluating them at class definition time. get_type_hints() then
evaluates these strings lazily. This defers the evaluation, which means that any
dynamically constructed annotation string that contains malicious content is not executed
until get_type_hints() is called.


**Q: Does pydantic v2 use get_type_hints() and is it vulnerable?**

Pydantic v2 uses its own annotation resolution mechanism (pydantic._internal._generate_schema)
that is separate from typing.get_type_hints(). Pydantic v1 uses get_type_hints() for model
field resolution. In both cases, if models are dynamically constructed from untrusted data,
the annotation resolution can be exploited. Prefer defining pydantic models statically in
source code.


**Q: How does Python 3.11+ change this behavior?**

Python 3.11+ (and PEP 649 in Python 3.14) introduces deferred annotation evaluation with
a different mechanism that does not immediately eval all annotation strings. However,
get_type_hints() still evaluates annotation strings, so the risk remains whenever
get_type_hints() is explicitly called on objects with string annotations.


**Q: Can I suppress this finding for framework code that legitimately uses get_type_hints()?**

Yes. Framework initialization code that calls get_type_hints() on statically defined
model classes during application startup is generally safe. Document the suppression with
the classes being inspected and confirmation that their annotations cannot be influenced
by external input at runtime.


**Q: What is inspect.get_annotations() and is it safer?**

inspect.get_annotations() (Python 3.10+) returns the raw annotation dictionary without
evaluating string annotations. It is safer when you only need the raw annotation strings
for documentation or metadata purposes, not for type resolution. Use it when get_type_hints()
evaluation is not needed.


## References

- [Python docs: typing.get_type_hints()](https://docs.python.org/3/library/typing.html#typing.get_type_hints)
- [PEP 563: Postponed Evaluation of Annotations](https://peps.python.org/pep-0563/)
- [CWE-95: Eval Injection](https://cwe.mitre.org/data/definitions/95.html)
- [OWASP Top 10 A03:2021 Injection](https://owasp.org/Top10/A03_2021-Injection/)
- [PEP 649: Deferred Evaluation of Annotations](https://peps.python.org/pep-0649/)

---

Source: https://codepathfinder.dev/registry/python/lang/PYTHON-LANG-SEC-006
Code Pathfinder — Open source, type-aware SAST with cross-file dataflow analysis
