Back to Blog
medium SEVERITY8 min read

From eval() to ast.literal_eval(): Closing a Code Injection Door in Slack Data Processing

A medium-severity vulnerability was discovered in a Slack data processing component where the use of Python's built-in `eval()` function to parse error message dictionaries could allow an attacker to inject and execute arbitrary code. The fix replaces `eval()` with the safer `ast.literal_eval()`, which safely evaluates only Python literals without executing arbitrary expressions. This change eliminates a critical attack surface that could have been exploited through crafted error messages return

O
By orbisai0security
May 28, 2026

From eval() to ast.literal_eval(): Closing a Code Injection Door in Slack Data Processing

Introduction

There's a function in Python that experienced developers have learned to fear: eval(). It's powerful, it's flexible, and in the wrong hands — or in the wrong context — it's a loaded weapon pointed at your own infrastructure. This week, we're examining a real-world fix that replaced a dangerous eval() call with its safe counterpart, ast.literal_eval(), in a Slack data processing component.

The vulnerability, catalogued under CVE-2025-3933, is classified as a Regular Expression Denial of Service (ReDoS) issue in the huggingface/transformers ecosystem, but the specific code fix addresses something arguably more dangerous: unsafe code execution via eval(). Whether you're a junior developer writing your first Python script or a seasoned engineer reviewing production code, this is a pattern you need to recognize and eliminate.


The Vulnerability Explained

What Is eval() and Why Is It Dangerous?

Python's eval() function takes a string and executes it as Python code. It's often used as a shortcut to parse data structures from strings — like turning "{'key': 'value'}" into an actual Python dictionary. On the surface, this seems harmless. But consider what happens when the string being evaluated isn't under your control.

# What the developer intended:
eval("{'error': 'not_found'}")
# Returns: {'error': 'not_found'} ✅

# What an attacker could inject:
eval("__import__('os').system('rm -rf /')")
# Executes: Deletes your entire filesystem ❌

eval() doesn't distinguish between "safe data" and "executable code." If an attacker can influence the string passed to eval(), they can execute any Python expression in the context of your application — with all the permissions and access your application has.

Where Was the Vulnerability?

The vulnerable code lived in apps/slack_data/slack_mcp_reader.py, specifically in the error handling logic of a SlackMCPReader class. When the Slack API returned an error, the code attempted to parse the error payload from the exception message using a regular expression, then passed the matched string directly to eval():

# BEFORE (Vulnerable Code)
match = re.search(r"'error':\s*(\{[^}]+\})", str(e))
if match:
    try:
        error_dict = eval(match.group(1))  # ⚠️ DANGEROUS
    except (ValueError, SyntaxError, NameError):
        pass

And again, a few lines below:

# BEFORE (Second Vulnerable Instance)
match = re.search(r"Failed to fetch messages:\s*(\{[^}]+\})", str(e))
if match:
    try:
        error_dict = eval(match.group(1))  # ⚠️ DANGEROUS
    except (ValueError, SyntaxError, NameError):
        pass

How Could This Be Exploited?

The attack surface here is the Slack API error response. If an attacker could influence what error messages the Slack API returns — or if the application connects to a malicious or compromised Slack-like endpoint — they could craft an error payload that, when parsed by the regex and passed to eval(), executes arbitrary code on the server.

Attack Scenario:

  1. An attacker identifies that the application uses a Slack integration and processes error messages.
  2. The attacker crafts a malicious error response that embeds Python code within the error dictionary string:
    Failed to fetch messages: {'error': __import__('subprocess').check_output(['whoami'])}
  3. The regex extracts the "dictionary" portion of the string.
  4. eval() executes the injected code in the application's runtime context.
  5. The attacker now has Remote Code Execution (RCE) on the server.

What's the Real-World Impact?

The consequences of successful exploitation depend on the application's runtime permissions, but could include:

  • Data exfiltration: Reading sensitive files, environment variables, API keys, or database credentials.
  • Lateral movement: Using the compromised server as a pivot point to attack internal infrastructure.
  • Service disruption: Crashing the application or consuming resources.
  • Persistent backdoors: Writing malicious files or modifying existing code.
  • Supply chain compromise: In an ML pipeline context (this is a transformers-adjacent project), corrupting model outputs or training data.

The Fix

Replacing eval() with ast.literal_eval()

The fix is elegant in its simplicity: replace eval() with ast.literal_eval() from Python's standard library.

# AFTER (Safe Code)
import ast  # Added at the top of the file

match = re.search(r"'error':\s*(\{[^}]+\})", str(e))
if match:
    try:
        error_dict = ast.literal_eval(match.group(1))  # ✅ SAFE
    except (ValueError, SyntaxError, NameError):
        pass
# AFTER (Second Instance Fixed)
match = re.search(r"Failed to fetch messages:\s*(\{[^}]+\})", str(e))
if match:
    try:
        error_dict = ast.literal_eval(match.group(1))  # ✅ SAFE
    except (ValueError, SyntaxError, NameError):
        pass

Why Is ast.literal_eval() Safe?

ast.literal_eval() is part of Python's Abstract Syntax Tree (AST) module. Unlike eval(), it only evaluates Python literals — strings, bytes, numbers, tuples, lists, dicts, sets, booleans, and None. It explicitly refuses to evaluate any expression that contains function calls, attribute access, or other executable constructs.

import ast

# Safe usage — parses data structures only
ast.literal_eval("{'error': 'not_found'}")
# Returns: {'error': 'not_found'} ✅

# Blocked — cannot execute function calls
ast.literal_eval("__import__('os').system('rm -rf /')")
# Raises: ValueError: malformed node or string ✅ (Attack neutralized)

# Blocked — cannot access attributes
ast.literal_eval("os.environ")
# Raises: ValueError ✅

The function works by parsing the string into an AST first, then inspecting the tree to ensure it contains only safe literal nodes before evaluating them. If anything unsafe is detected, it raises a ValueError — which is already caught by the existing exception handler in the code.

Before vs. After: Side-by-Side Comparison

Aspect eval() ast.literal_eval()
Executes arbitrary Python ✅ Yes ❌ No
Parses dictionaries ✅ Yes ✅ Yes
Parses lists/tuples ✅ Yes ✅ Yes
Allows function calls ✅ Yes (dangerous) ❌ No
Allows imports ✅ Yes (dangerous) ❌ No
Safe for untrusted input ❌ Never ✅ Yes

The change is a drop-in replacement for this use case — the functionality is identical for safe inputs, but the attack surface is completely eliminated.


Prevention & Best Practices

1. Never Use eval() on Untrusted Input

This is the cardinal rule. If the string being evaluated comes from anywhere outside your direct control — user input, API responses, file contents, environment variables, network data — eval() is off the table. Full stop.

# ❌ NEVER do this with external data
result = eval(user_input)
result = eval(api_response_string)
result = eval(file.read())

# ✅ Use safer alternatives
result = ast.literal_eval(data_string)  # For Python literals
result = json.loads(json_string)         # For JSON data

2. Prefer Structured Data Formats

If you're parsing error messages or structured data, consider using JSON instead of Python dictionary syntax. JSON has well-defined, safe parsers:

import json

# Safe JSON parsing
error_dict = json.loads(error_json_string)

For this specific use case, if the Slack API returns structured error objects, parse them at the API response level rather than extracting them from exception message strings.

3. Use Static Analysis Tools

Tools like Semgrep (which actually detected this vulnerability, as noted in the commit message: semgrep_python.lang.security.audit.eval-detected) can automatically flag dangerous eval() usage in your codebase:

# Semgrep rule that catches this pattern
rules:
  - id: eval-detected
    pattern: eval(...)
    message: "Dangerous use of eval() detected"
    severity: WARNING

Other tools to consider:
- Bandit: Python security linter that flags eval() as a high-severity issue (B307)
- PyLint: Can be configured to warn on eval() usage
- SonarQube: Enterprise-grade static analysis with security rules

4. Apply the Principle of Least Privilege

Even if eval() is used safely elsewhere, ensure your application runs with the minimum permissions necessary. If code injection does occur, limited OS permissions reduce the blast radius:

  • Run application processes as non-root users
  • Use containerization (Docker) with restricted capabilities
  • Apply network egress filtering to prevent data exfiltration

5. Validate and Sanitize at Boundaries

Treat all external data as untrusted. Validate the structure and content of API responses before processing them:

# Validate the structure before processing
def parse_slack_error(error_string: str) -> dict:
    """Safely parse a Slack error dictionary from a string."""
    match = re.search(r"'error':\s*(\{[^}]+\})", error_string)
    if not match:
        return {}

    try:
        # Use ast.literal_eval for safe parsing
        result = ast.literal_eval(match.group(1))
        # Validate the result is actually a dict
        if not isinstance(result, dict):
            return {}
        return result
    except (ValueError, SyntaxError, NameError):
        return {}

6. Relevant Security Standards

This vulnerability maps to several well-known security standards:

  • CWE-95: Improper Neutralization of Directives in Dynamically Evaluated Code ('Eval Injection')
  • CWE-94: Improper Control of Generation of Code ('Code Injection')
  • OWASP A03:2021: Injection — one of the OWASP Top 10, covering all forms of injection including code injection
  • OWASP ASVS V5: Validation, Sanitization and Encoding requirements

Conclusion

This fix is a perfect example of how a single function substitution can eliminate a serious security vulnerability. The developer who originally wrote eval(match.group(1)) was almost certainly focused on functionality — "I need to parse this dictionary string" — rather than security implications. This is completely understandable, and it's exactly why automated security scanning tools like Semgrep are invaluable in modern development workflows.

The key takeaways from this vulnerability:

  1. eval() is almost never the right answer for parsing data. Use json.loads() for JSON, ast.literal_eval() for Python literals, or purpose-built parsers for structured formats.
  2. Error handling code is attack surface too. It's easy to overlook security in exception handlers, but attackers specifically target edge cases and error paths.
  3. Automated scanning catches what code review misses. This fix was triggered by Semgrep detection, demonstrating the value of integrating security tools into your CI/CD pipeline.
  4. The fix was simple, safe, and non-breaking. ast.literal_eval() is a drop-in replacement for eval() when parsing literals — there's no excuse not to use it.

Security isn't about writing perfect code the first time. It's about building systems — including tooling, processes, and culture — that catch and fix these issues before they can be exploited. This fix is that system working exactly as intended.

Stay curious, stay secure, and audit your eval() calls today.


This vulnerability was automatically detected and fixed as part of an ongoing security improvement initiative. The fix was validated and is now live in production.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #163

Related Articles

high

Shell Injection via Unsafe String Concatenation in gRPCurl Command Generation

A high-severity vulnerability was discovered in PaddleOCR's deployment configuration where model download URLs were specified using unencrypted `http://`, exposing users to man-in-the-middle attacks that could allow an attacker to intercept and replace model files with malicious ones. The fix upgrades all model download URLs to use `https://`, ensuring encrypted transmission and integrity of the downloaded files. This change is a critical security baseline for any application that downloads bina

critical

HAProxy Config Injection: How Unsanitized Form Fields Can Hijack Your Load Balancer

A high-severity configuration injection vulnerability was discovered in an HAProxy dashboard where five form fields were written directly into the HAProxy configuration file without any sanitization. An attacker could exploit this by injecting newline characters and arbitrary HAProxy directives, effectively rewriting load balancer rules, adding unauthorized backends, or bypassing access controls. The fix introduces a sanitization layer that strips non-printable characters from all user-supplied

critical

Shell Injection via os.system(): How a Single Line of Code Can Compromise Your System

A critical OS command injection vulnerability (CWE-78) was discovered and patched in `voice.py`, where user-controlled input was interpolated directly into a shell command string passed to `os.system()`. An attacker who could influence the `device` variable — through a config file, environment variable, or any external input — could execute arbitrary system commands with the full privileges of the running process. The fix replaces the dangerous `os.system()` calls with Python's `subprocess.run()

critical

Command Injection via os.system() in DeepSpeed's Data Analyzer: A Critical Fix

A critical command injection vulnerability was discovered in DeepSpeed's `data_analyzer.py`, where an `os.system()` call directly interpolated an unsanitized file path variable into a shell command string. An attacker who could influence dataset configuration or file paths could execute arbitrary shell commands on the host machine. The fix replaces the dangerous shell invocation with safe, Python-native file operations that never touch a shell interpreter.

medium

Command Injection in Python Subprocess: A Security Fix Case Study

A medium-severity command injection vulnerability was discovered and fixed in a Python testing utility where unsanitized input could be passed to subprocess calls. This fix demonstrates the critical importance of input validation and safe subprocess handling to prevent attackers from executing arbitrary system commands.

medium

Mass Assignment Vulnerability: Why Your Rails Models Need attr_accessible

A medium-severity mass assignment vulnerability was identified in a Ruby on Rails model that lacked proper attribute whitelisting via `attr_accessible` or strong parameters. Without this protection, attackers can manipulate any model attribute through crafted HTTP requests, potentially escalating privileges or corrupting data. The fix enforces explicit attribute allowlisting, closing the door on unauthorized mass assignment exploitation.