CVE-2026-29872
Cross-session vulnerability in a Streamlit app leaks user API tokens.
- CVSS 8.2
- CWE-200
- Authentication, Authorization, and Session Management
- Remote
A cross-session information disclosure vulnerability exists in the awesome-llm-apps project in commit e46690f99c3f08be80a9877fab52acacf7ab8251 (2026-01-19). The affected Streamlit-based GitHub MCP Agent stores user-supplied API tokens in process-wide environment variables using os.environ without proper session isolation. Because Streamlit serves multiple concurrent users from a single Python process, credentials provided by one user remain accessible to subsequent unauthenticated users. An attacker can exploit this issue to retrieve sensitive information such as GitHub Personal Access Tokens or LLM API keys, potentially leading to unauthorized access to private resources and financial abuse.
- CWE
- CWE-200
- CVSS base score
- 8.2
- Published
- 2026-03-30
- OWASP
- A01 Broken Access Control
- Orthogonal defect classification
- Assignment
- Code defect classification
- Incorrect Assignment
- Category
- Authentication, Authorization, and Session Management
- Subcategory
- Session Management Issues
- Accessibility scope
- Remote
- Impact
- Information Disclosure
- Affected component
- awesome-llm-
Solution
Use `st.session_state` instead of `os.environ` to store user-specific credentials, as this vulnerability pertains to a specific source code commit and has no versioned patch.
Vulnerable code sample
import streamlit as st
import os
# Set a title for the app
st.title("Vulnerable Credential Handler")
# 1. A user provides their sensitive API key through a text input.
api_key = st.text_input("Enter your API Key:", type="password")
# 2. The application takes the user-supplied key and stores it in a
# process-wide environment variable. This is the core of the vulnerability.
if api_key:
os.environ['API_KEY'] = api_key
st.success("API Key has been set for the current process.")
st.info("The key is now stored insecurely and is accessible to other sessions.")
# A divider to simulate a separate user session or an attacker's view.
st.divider()
st.header("Attacker's View")
st.warning("This section demonstrates how a subsequent user can access the key.")
# 3. An attacker or another user interacting with the same Streamlit process
# can press a button to check the environment variables.
if st.button("Check for Leaked API Key"):
# 4. The application retrieves the 'API_KEY' from the process-wide environment.
# If it was set by a previous user, it will be available here, demonstrating
# the cross-session information disclosure.
leaked_key = os.environ.get('API_KEY')
if leaked_key:
st.error("VULNERABILITY CONFIRMED: Found a leaked API key!")
st.code(f"Leaked Key: {leaked_key}")
else:
st.info("No API Key found in the environment.")Patched code sample
import streamlit as st
# This code demonstrates the fix for a vulnerability where sensitive data
# was stored in a process-wide environment variable (os.environ).
#
# The vulnerability (as described in the fictional CVE-2026-29872):
# Storing user-supplied tokens in os.environ in a multi-user Streamlit app
# leads to cross-session information disclosure, as os.environ is shared
# across all user sessions handled by the same Python process.
#
# The Fix:
# Use Streamlit's built-in session state (`st.session_state`) to store
# sensitive, user-specific data. `st.session_state` is a dictionary-like
# object that is isolated to a single user's browser session, preventing
# data from leaking between concurrent users.
st.set_page_config(layout="wide")
st.title("Secure Credential Handling (Fixed Example)")
st.markdown(
"""
This application demonstrates the correct, secure way to handle user-specific
secrets like API keys in a Streamlit application. It uses `st.session_state`
to ensure that each user's data is isolated to their own session.
"""
)
# --- User Input Section ---
st.header("1. Enter Your Credentials")
st.warning(
"This is a demonstration. Do not use real production credentials.", icon="⚠️"
)
# Use a form to group inputs and the submission button.
with st.form("api_key_form"):
api_key_input = st.text_input(
"Enter your API Key (e.g., GitHub PAT):",
type="password",
help="The key will be stored securely in your session state.",
)
submitted = st.form_submit_button("Store Key for this Session")
if submitted and api_key_input:
# FIXED: Store the API key in the isolated session_state.
# This is the core of the vulnerability fix. Data in st.session_state
# is only accessible to the current user's session.
st.session_state["user_api_key"] = api_key_input
st.success("API Key has been securely stored for this session only.")
elif submitted and not api_key_input:
st.error("Please provide an API key.")
# --- Action Section ---
st.header("2. Use the Stored Credential")
st.markdown(
"""
Click the button below to simulate an action that requires the API key.
The application will attempt to retrieve the key from the secure
`st.session_state`. If you open this app in another browser tab, you will
see that the key is not shared.
"""
)
if st.button("Simulate API Call"):
# Securely retrieve the key from the current user's session state.
# The .get() method is used to safely access the key, returning None if not found.
retrieved_key = st.session_state.get("user_api_key")
if retrieved_key:
st.info("An API key was found in your session state.")
# In a real application, you would now use this key to initialize an API client.
# For example: `api_client = SomeAPIClient(api_key=retrieved_key)`
#
# For demonstration purposes, we show a masked version of the key
# to prove it was retrieved without exposing it fully.
masked_key = retrieved_key[:4] + "****" + retrieved_key[-4:]
st.code(f"Simulating API call with key: {masked_key}", language="text")
st.success("API call simulation complete.")
else:
# This is what a new, unauthenticated user would see. They cannot access
# the key set by another user.
st.error(
"No API key found in your current session. "
"Please enter a key in the form above."
)
# --- Debug/Info Section ---
st.sidebar.header("Session State Inspector")
st.sidebar.markdown(
"This shows the contents of `st.session_state` for *your* session."
)
# This will show that 'user_api_key' is present only after the user submits it.
st.sidebar.write(st.session_state)Cite this entry
@misc{vaitp:cve202629872,
title = {{Cross-session vulnerability in a Streamlit app leaks user API tokens.}},
author = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
year = {2026},
note = {VAITP Python Vulnerability Dataset, entry CVE-2026-29872},
howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2026-29872/}}
}
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 ::
