praisonai-platform: Any workspace member can rewrite workspace name, description, and settings via PATCH /workspaces/{id}
Description
Any workspace member can modify workspace name, description, and settings via PATCH endpoint due to missing role check, enabling configuration injection.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
Any workspace member can modify workspace name, description, and settings via PATCH endpoint due to missing role check, enabling configuration injection.
Vulnerability
The PATCH /workspaces/{workspace_id} endpoint in src/praisonai-platform/praisonai_platform/api/routes/workspaces.py (lines 63-74) uses Depends(require_workspace_member) with the default min_role="member". This allows any workspace member to call the endpoint. The WorkspaceService.update() method writes the provided name, description, and settings fields to the workspace row without verifying that the caller has the required role (e.g., owner or admin) to modify these fields. The settings field is a free-form JSON object that may be consumed by downstream code for configuration. All versions of praisonai-platform containing this code are affected [1][2].
Exploitation
An attacker must be an authenticated member of the target workspace (any role). They can send a PATCH request to /workspaces/{workspace_id} with a JSON body containing new values for name, description, and/or settings. No additional privileges or user interaction beyond membership are required. The endpoint will accept the request and update the workspace record accordingly [1][2].
Impact
A successful attack allows the attacker to rename the workspace (low-impact griefing) and, more critically, to overwrite the settings JSON blob. Depending on which settings the platform reads from this blob, the attacker may be able to flip feature flags, redirect webhook URLs, change LLM provider keys for shared configurations, disable audit logging, or otherwise alter workspace behavior. The impact ranges from nuisance to full configuration injection, potentially affecting all workspace members and downstream integrations [1][2].
Mitigation
As of the publication date, no official fix has been released. The vendor has been notified and a patch is expected. In the interim, users should restrict access to the PATCH /workspaces/{workspace_id} endpoint by implementing proper role-based authorization checks (e.g., requiring min_role="owner" or admin) or by disabling the endpoint entirely if not needed. Monitor the advisory for updates [1][2].
AI Insight generated on Jun 1, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected products
1Patches
18c4ab718aeebPraisonAI Call API Release
7 files changed · +43 −14
Dockerfile+1 −1 modified@@ -1,6 +1,6 @@ FROM python:3.11-slim WORKDIR /app COPY . . -RUN pip install flask praisonai==0.1.3 gunicorn markdown +RUN pip install flask praisonai==0.1.4 gunicorn markdown EXPOSE 8080 CMD ["gunicorn", "-b", "0.0.0.0:8080", "api:app"]
docs/api/praisonai/deploy.html+1 −1 modified@@ -110,7 +110,7 @@ <h2 id="raises">Raises</h2> file.write("FROM python:3.11-slim\n") file.write("WORKDIR /app\n") file.write("COPY . .\n") - file.write("RUN pip install flask praisonai==0.1.3 gunicorn markdown\n") + file.write("RUN pip install flask praisonai==0.1.4 gunicorn markdown\n") file.write("EXPOSE 8080\n") file.write('CMD ["gunicorn", "-b", "0.0.0.0:8080", "api:app"]\n')
poetry.lock+20 −2 modified@@ -6258,6 +6258,24 @@ cffi = ">=1.4.1" docs = ["sphinx (>=1.6.5)", "sphinx-rtd-theme"] tests = ["hypothesis (>=3.27.0)", "pytest (>=3.2.1,!=3.3.0)"] +[[package]] +name = "pyngrok" +version = "7.2.0" +description = "A Python wrapper for ngrok." +optional = true +python-versions = ">=3.8" +files = [ + {file = "pyngrok-7.2.0-py3-none-any.whl", hash = "sha256:1e96ab1229736e2e030fa8975805ab1fa9e463178f83337fc07fdd2b4e8dbed6"}, + {file = "pyngrok-7.2.0.tar.gz", hash = "sha256:4e43af9b2f21ceed8d213797028fe8823003f185b49792e4d383302365c81515"}, +] + +[package.dependencies] +PyYAML = ">=5.1" + +[package.extras] +dev = ["coverage[toml]", "flake8", "flake8-pyproject", "pep8-naming", "psutil"] +docs = ["Sphinx", "mypy", "sphinx-autodoc-typehints (==1.25.2)", "sphinx-notfound-page", "sphinx-substitution-extensions", "types-PyYAML"] + [[package]] name = "pyparsing" version = "3.2.0" @@ -8857,7 +8875,7 @@ type = ["pytest-mypy"] agentops = ["agentops"] anthropic = ["langchain-anthropic"] api = ["flask"] -call = ["fastapi", "flaml", "python-dotenv", "twilio", "typer", "uvicorn", "websockets"] +call = ["fastapi", "flaml", "pyngrok", "python-dotenv", "rich", "twilio", "typer", "uvicorn", "websockets"] chat = ["aiosqlite", "chainlit", "crawl4ai", "greenlet", "litellm", "tavily-python"] code = ["aiosqlite", "chainlit", "crawl4ai", "greenlet", "litellm", "tavily-python"] cohere = ["langchain-cohere"] @@ -8871,4 +8889,4 @@ ui = ["chainlit"] [metadata] lock-version = "2.0" python-versions = ">=3.10,<3.13" -content-hash = "74602750ef14d7040dad02842980b2eebbf0ac8c1f4324627602764578814a5a" +content-hash = "d246ccacd08ba3599a7b22e222e784396df2385dc76532b56e86bfccaf7522c0"
praisonai/api/call.py+16 −6 modified@@ -10,6 +10,8 @@ from dotenv import load_dotenv import typer import uvicorn +from pyngrok import ngrok +from rich import print load_dotenv() @@ -151,17 +153,26 @@ async def send_session_update(openai_ws): print('Sending session update:', json.dumps(session_update)) await openai_ws.send(json.dumps(session_update)) -def run_server(port: int): +def run_server(port: int, use_ngrok: bool = False): """Run the FastAPI server using uvicorn.""" + if use_ngrok: + public_url = ngrok.connect(port).public_url + # print(f"Ngrok tunnel established: {public_url}") + print(f"Praison AI Voice URL: {public_url}/call") + print(f"Starting Praison AI Call Server on port {port}...") - uvicorn.run(app, host="0.0.0.0", port=port) + uvicorn.run(app, host="0.0.0.0", port=port, log_level="warning") app_cli = typer.Typer() @app_cli.command() -def main(port: int = typer.Option(8090, help="Port to run the server on")): +def main( + port: int = typer.Option(8090, help="Port to run the server on"), + ngrok: bool = typer.Option(False, help="Use ngrok to expose the server") +): """Run the Praison AI Call Server.""" - print(f"Received port value: {port}") # Debug print + # print(f"Received port value: {port}") # Debug print + # print(f"Use ngrok: {ngrok}") # Debug print # Extract the actual port value from the OptionInfo object if isinstance(port, typer.models.OptionInfo): @@ -170,9 +181,8 @@ def main(port: int = typer.Option(8090, help="Port to run the server on")): port_value = port port_int = int(port_value) - print(f"Using port: {port_int}") # Debug print - run_server(port=port_int) + run_server(port=port_int, use_ngrok=ngrok) if __name__ == "__main__": app_cli()
praisonai/deploy.py+1 −1 modified@@ -56,7 +56,7 @@ def create_dockerfile(self): file.write("FROM python:3.11-slim\n") file.write("WORKDIR /app\n") file.write("COPY . .\n") - file.write("RUN pip install flask praisonai==0.1.3 gunicorn markdown\n") + file.write("RUN pip install flask praisonai==0.1.4 gunicorn markdown\n") file.write("EXPOSE 8080\n") file.write('CMD ["gunicorn", "-b", "0.0.0.0:8080", "api:app"]\n')
praisonai.rb+1 −1 modified@@ -3,7 +3,7 @@ class Praisonai < Formula desc "AI tools for various AI applications" homepage "https://github.com/MervinPraison/PraisonAI" - url "https://github.com/MervinPraison/PraisonAI/archive/refs/tags/0.1.3.tar.gz" + url "https://github.com/MervinPraison/PraisonAI/archive/refs/tags/0.1.4.tar.gz" sha256 "1828fb9227d10f991522c3f24f061943a254b667196b40b1a3e4a54a8d30ce32" # Replace with actual SHA256 checksum license "MIT"
pyproject.toml+3 −2 modified@@ -1,6 +1,6 @@ [tool.poetry] name = "PraisonAI" -version = "0.1.3" +version = "0.1.4" description = "PraisonAI application combines AutoGen and CrewAI or similar frameworks into a low-code solution for building and managing multi-agent LLM systems, focusing on simplicity, customization, and efficient human-agent collaboration." authors = ["Mervin Praison"] license = "" @@ -44,6 +44,7 @@ uvicorn = {version = ">=0.20.0", optional = true} python-dotenv = {version = ">=0.19.0", optional = true} typer = {version = ">=0.9.0", optional = true} flaml = {version = ">=2.3.1", extras = ["automl"], optional = true} +pyngrok = {version = ">=1.4.0", optional = true} [tool.poetry.group.docs.dependencies] mkdocs = "*" @@ -118,7 +119,7 @@ chat = ["chainlit", "litellm", "aiosqlite", "greenlet", "tavily-python", "crawl4 code = ["chainlit", "litellm", "aiosqlite", "greenlet", "tavily-python", "crawl4ai"] train = ["setup-conda-env"] realtime = ["chainlit", "litellm", "aiosqlite", "greenlet", "tavily-python", "crawl4ai", "websockets", "plotly", "yfinance", "duckduckgo_search"] -call = ["twilio", "fastapi", "uvicorn", "websockets", "python-dotenv", "typer", "flaml"] +call = ["twilio", "fastapi", "uvicorn", "websockets", "python-dotenv", "typer", "flaml", "pyngrok", "rich"] [tool.poetry-dynamic-versioning] enable = true
Vulnerability mechanics
Root cause
"The `PATCH /workspaces/{workspace_id}` endpoint uses `require_workspace_member` with default `min_role="member"` instead of requiring owner-level authorization, allowing any workspace member to rewrite workspace metadata and settings."
Attack vector
An attacker who is a member of a workspace (role "member") sends a `PATCH /workspaces/{workspace_id}` request with a valid JWT and a JSON body containing arbitrary `name`, `description`, and `settings` values. The `require_workspace_member` dependency passes because it defaults to `min_role="member"`, and `WorkspaceService.update()` writes the attacker-chosen fields to the workspace row. Downstream code that reads workspace settings (e.g., LLM proxying, invite flows, webhook routing) may then use attacker-controlled configuration values [ref_id=1][ref_id=2].
Affected code
The vulnerability is in `src/praisonai-platform/praisonai_platform/api/routes/workspaces.py`, lines 63-74, in the `update_workspace` endpoint. The `Depends(require_workspace_member)` dependency defaults to `min_role="member"`, and `WorkspaceService.update()` writes the `name`, `description`, and `settings` fields without any caller-permission check [ref_id=1][ref_id=2].
What the fix does
The suggested fix replaces `Depends(require_workspace_member)` with `Depends(_require_workspace_owner)`, which enforces that only workspace owners can call the endpoint. This closes the authorization bypass because member-tier tokens will fail the gate and the metadata rewrite will be rejected with a 403 response. The advisory also recommends defence-in-depth validation of allowed keys in `body.settings` against an allowlist [ref_id=1][ref_id=2].
Preconditions
- configpraisonai-platform is deployed in a multi-tenant configuration
- authAttacker holds a valid membership token with role 'member' in the target workspace
- networkAttacker can reach the PATCH /workspaces/{workspace_id} endpoint over the network
- inputAttacker sends a crafted JSON body with arbitrary name, description, and settings fields
Generated on Jun 1, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
2News mentions
0No linked articles in our index yet.