# PYTHON-LANG-SEC-004: Dangerous globals() Usage Detected

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

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

## Description

Python's built-in globals() function returns a reference to the current module's global
namespace as a live, mutable dictionary. Any code that receives this dictionary can read
all global variables, overwrite them, inject new names, or delete existing ones.

Passing globals() to eval(), exec(), or any function that modifies the dictionary enables
an attacker to redefine security-critical functions, inject callable backdoors, overwrite
configuration values, or access credentials stored in module-level constants.

Common dangerous patterns include passing globals() as the namespace argument to eval() or
exec(), passing it to template rendering functions, serializing it to disk or network, or
using it as a lookup table to resolve user-supplied function names.


## Vulnerable Code

```python
import code
import importlib
import typing

# SEC-004: globals
def render(template, **kwargs):
    return template.format(**globals())
```

## Secure Code

```python
# SECURE: Use an explicit allowlist for dynamic function lookup
import math

ALLOWED_FUNCTIONS = {
    "sqrt": math.sqrt,
    "abs": abs,
    "round": round,
}

def call_function(func_name: str, arg: float) -> float:
    if func_name not in ALLOWED_FUNCTIONS:
        raise ValueError(f"Function not allowed: {func_name}")
    return ALLOWED_FUNCTIONS[func_name](arg)

# SECURE: Use getattr() with an allowlist instead of globals() lookup
import myapp.handlers as handlers

ALLOWED_HANDLERS = {"handle_get", "handle_post", "handle_delete"}

def dispatch(method: str, request):
    if method not in ALLOWED_HANDLERS:
        raise ValueError(f"Handler not found: {method}")
    return getattr(handlers, method)(request)

```

## 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-004",
    name="Dangerous globals() Usage",
    severity="MEDIUM",
    category="lang",
    cwe="CWE-96",
    tags="python,globals,code-injection,CWE-96",
    message="globals() passed to a function. This may allow arbitrary attribute access.",
    owasp="A03:2021",
)
def detect_globals():
    """Detects globals() being passed to functions."""
    return calls("globals")
```

## How to Fix

- Replace globals()-based function lookup with an explicit dictionary mapping allowed names to their callables.
- Never pass globals() as the namespace argument to eval() or exec(); create a minimal, read-only namespace with only the names required.
- Do not serialize, log, or transmit the globals() dictionary as it contains credentials and sensitive configuration.
- Use getattr() with an allowlist on a specific module or object for dynamic dispatch rather than searching the entire global namespace.
- Audit all uses of globals() in code that processes external input to ensure no user-controlled key can reach the globals dict.

## Security Implications

- **Global Namespace Corruption:** An attacker who gains write access to the dictionary returned by globals() can overwrite
any module-level variable or function. This includes authentication functions, access
control checks, cryptographic keys, and database connection strings.

- **Code Execution via Namespace Injection:** When globals() is passed as the namespace to eval() or exec(), an attacker controlling
the evaluated code can define new entries in the global namespace that persist after the
eval/exec call, creating persistent backdoors in the running process.

- **Credential Exposure:** The global namespace typically contains imported modules, configuration constants, and
application-level secrets. Serializing or logging globals() can expose API keys, database
passwords, cryptographic secrets, and other sensitive values.

- **Authentication Bypass:** Overwriting a global function such as is_authenticated or check_permission through the
globals() dictionary allows an attacker to replace security checks with no-ops, effectively
bypassing all authorization logic in the module.


## FAQ

**Q: Is globals() always dangerous to use?**

Not always. globals() is safe when used for introspection in development tools, test
frameworks, or debugging utilities that only read from the dictionary. It becomes dangerous
when the dictionary is passed to code that can modify it, when it is used for user-controlled
name resolution, or when it is serialized and exposed over a network.


**Q: Why not just pass a copy of globals() to be safe?**

A shallow copy of globals() still contains references to all module objects. An attacker
who can call methods on those objects (e.g., imported modules) can still execute arbitrary
code. A deep copy is impractical and would not include live module state. The correct fix
is to use a minimal, purpose-built namespace with only required names.


**Q: What is a safe alternative for dynamic function dispatch?**

Create an explicit allowlist dictionary mapping string names to their corresponding
callables. Check that the user-supplied name exists in the allowlist before calling it.
This is both more secure and more explicit about what operations are permitted.


**Q: Does this rule flag globals() used in __init__.py module setup code?**

Yes, all globals() calls are flagged. Module setup code in __init__.py typically does not
need to call globals() and is usually better expressed with explicit variable assignments.
If the usage is purely for package introspection during import (not at request time), it
can be reviewed and suppressed with a comment explaining the safety rationale.


**Q: How does globals() interact with eval() and exec()?**

When globals() is passed as the first (global namespace) argument to eval() or exec(),
any names defined in the evaluated code are written into the module's live global
namespace and persist after the eval/exec returns. This is one of the most dangerous
combinations possible in Python code.


**Q: Are there performance implications from calling globals() frequently?**

globals() is O(1) and has minimal performance overhead. Performance is not a valid
reason to use globals() for dynamic dispatch; the security risk outweighs any benefit
compared to a simple allowlist dictionary lookup.


## References

- [CWE-95: Eval Injection](https://cwe.mitre.org/data/definitions/95.html)
- [Python docs: built-in globals()](https://docs.python.org/3/library/functions.html#globals)
- [OWASP Code Injection](https://owasp.org/www-community/attacks/Code_Injection)
- [OWASP Top 10 A03:2021 Injection](https://owasp.org/Top10/A03_2021-Injection/)
- [Python Namespace and Scope Documentation](https://docs.python.org/3/reference/executionmodel.html#names-and-binding)

---

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