GHSA-2fjj-qqg8-fg7x: praisonai-platform Authorization Bypass Through User-Controlled Key Vulnerability
MODERATEGHSA-2fjj-qqg8-fg7x is a MODERATE (CVSS 4.3) authorization bypass through user-controlled key vulnerability (CWE-639: Authorization Bypass Through User-Controlled Key) affecting praisonai-platform libraries in the PyPI ecosystem. The issue involves authorization bypass through user-controlled key. Remediation is available by upgrading affected packages to version 0.1.8.
GHSA-2fjj-qqg8-fg7x at a Glance
Security Impact
CVSS: 4.3
The security impact metrics below are parsed directly from the CVSS v3.1 vector string (CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:N).
How to Fix GHSA-2fjj-qqg8-fg7x
To remediate this vulnerability, upgrade all affected praisonai-platform dependencies to a patched release. Both direct and transitive dependencies should be checked, as frameworks or security libraries may pull in vulnerable versions transitively.
pip install praisonai-platform>=0.1.8
Run the following command in your project directory to verify that all resolved dependencies use the patched release:
pip show praisonai-platform
How to Check if You Are Affected
To determine whether your Python environment is affected by GHSA-2fjj-qqg8-fg7x, inspect your installed packages using pip:
pip show praisonai-platform
Analyze the terminal output to locate any version of praisonai-platform in your dependency tree. If any resolved version matches the vulnerable ranges listed in the table below (for example, versions earlier than 0.1.8), your application is affected.
Note: The pip show praisonai-platform command checks currently installed packages in your active Python environment.
Technical Details
Vulnerability Mechanism & Technical Analysis
This vulnerability is classified under CWE-639: Authorization Bypass Through User-Controlled Key, where the system's authorization functionality does not prevent one user from gaining access to another user's data or record by modifying the key value identifying the data. In praisonai-platform, 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
GHSA-2fjj-qqg8-fg7x Affected praisonai-platform Versions
1 component(s)| Product / Package | Ecosystem | Affected Versions | Fixed Version |
|---|---|---|---|
|
praisonai-platform
|
PyPI |
< 0.1.8
|
0.1.8
|
References
Frequently Asked Questions
What is GHSA-2fjj-qqg8-fg7x?
Which praisonai-platform versions are affected?
What version fixes GHSA-2fjj-qqg8-fg7x?
Is praisonai-platform affected?
What is the CVSS score of GHSA-2fjj-qqg8-fg7x?
What CWE is associated with GHSA-2fjj-qqg8-fg7x?
How can I check PyPI dependencies for GHSA-2fjj-qqg8-fg7x?
Machine-readable OSV JSON
OSV v1.5 Schema
{
"schema_version" : "1.4.0",
"id" : "GHSA-2fjj-qqg8-fg7x",
"modified" : "2026-06-18T14:48:35Z",
"published" : "2026-06-18T14:48:35Z",
"aliases" : [ ],
"summary" : "praisonai-platform: Authorization Bypass Through User-Controlled Key",
"details" : "## Summary\n\nThe issue create and update endpoints in `praisonai-platform` accept a `project_id` in the request body and persist it without validating that the project belongs to the URL workspace. A user who is a member of workspace `W_B` (and has no access to workspace `W_A`) can create issues that reference a project owned by `W_A`. Because `ProjectService.get_stats()` aggregates issues by `project_id` with no workspace constraint, those foreign issues are then counted in the victim's own legitimate view of their project statistics. This is a cross-tenant integrity violation reachable by an outsider.\n\nThis is distinct from the path-parameter IDOR family fixed in 0.1.4 (CVE-2026-47415, CVE-2026-47418, CVE-2026-47419). Those fixes scoped object references supplied in the URL path. This report concerns an object reference supplied in the request body at write time, which the 0.1.4 fixes did not cover.\n\nVersion 0.1.4 fixed a set of path-parameter IDORs by threading `workspace_id` into the service-layer lookups (`get` / `update` / `delete`) and by adding the helpers `ensure_resource_in_workspace()` and `require_issue_in_workspace()` in `api/deps.py`. Those helpers are applied to object references that arrive in the URL path. They are not applied to object references that arrive in the request body on create or update.\n\n## Details\n\n`api/routes/issues.py`, `create_issue` passes the body's `project_id` straight through with no workspace validation:\n\n```python\n@router.post(\"/\", response_model=IssueResponse, status_code=201)\nasync def create_issue(workspace_id: str, body: IssueCreate,\n user=Depends(require_workspace_member), session=Depends(get_db)):\n svc = IssueService(session)\n issue = await svc.create(\n workspace_id=workspace_id,\n title=body.title,\n creator_id=user.id,\n project_id=body.project_id, # attacker-controlled, not validated against workspace_id\n ...\n )\n```\n\n`services/issue_service.py`, `create` persists it as-is:\n\n```python\nissue = Issue(\n workspace_id=workspace_id,\n project_id=project_id, # no check that project_id belongs to workspace_id\n ...\n)\n```\n\n`services/issue_service.py`, `update` has the identical gap on the update path:\n\n```python\nif project_id is not None:\n issue.project_id = project_id # re-parent to any project, no workspace check\n```\n\n`services/project_service.py`, `get_stats` aggregates by `project_id` only:\n\n```python\nasync def get_stats(self, project_id: str) -> dict:\n stmt = (\n select(Issue.status, func.count(Issue.id))\n .where(Issue.project_id == project_id) # no workspace_id constraint\n .group_by(Issue.status)\n )\n ...\n```\n\nNote that the read path is not directly vulnerable. The stats route scopes the project first, so a cross-workspace stats read returns 404:\n\n```python\n@router.get(\"/{project_id}/stats\")\nasync def project_stats(workspace_id, project_id, user=Depends(require_workspace_member), ...):\n project = await svc.get(project_id, workspace_id=workspace_id) # 404 for a foreign project\n if project is None:\n raise HTTPException(404, \"Project not found\")\n return await svc.get_stats(project_id)\n```\n\nThe pollution therefore enters through the write side (issue create/update accepting a foreign `project_id`) and surfaces in the victim's own legitimate read of their project statistics.\n\n## Proof of concept\n\nTwo unrelated users:\n\n- Alice, member of workspace `W_A`, owns project `P_A`.\n- Bob, member of workspace `W_B` only. Bob has no access to `W_A` (every direct call to `W_A` resources returns 403).\n\nSteps:\n\n1. Alice's project `P_A` has one in-progress issue.\n `GET /workspaces/W_A/projects/P_A/stats` returns `{\"total\": 1, \"by_status\": {\"in_progress\": 1}}`.\n\n2. Bob creates issues in his own workspace that reference Alice's project. Repeat 7 times:\n ```http\n POST /workspaces/W_B/issues\n Authorization: Bearer <Bob's token>\n Content-Type: application/json\n\n {\"title\": \"x\", \"project_id\": \"P_A\", \"status\": \"done\"}\n ```\n Each returns 201. Each issue is stored with `workspace_id = W_B` and `project_id = P_A`.\n\n3. Alice reads her own project stats:\n `GET /workspaces/W_A/projects/P_A/stats` now returns\n `{\"total\": 8, \"by_status\": {\"done\": 7, \"in_progress\": 1}}`.\n\nBob is not a member of `W_A`, yet data he wrote appears in Alice's project dashboard.\n\n## Impact\n\nAn unauthorized outsider can inflate or skew the issue counts shown in any workspace's project-statistics view, given only the target `project_id` (a UUID that can be harvested or guessed). The effect is limited to the statistics aggregation; it does not expose the victim's issue contents to the attacker and does not appear in the victim's workspace-scoped issue list. The same unvalidated write path also accepts cross-workspace `parent_issue_id` and `assignee_id` values, which have no aggregation read endpoint today but represent the same dangling cross-workspace reference class and should be fixed together.\n\n## Suggested fix\n\nOn both issue create and update, validate that any body-supplied object reference resolves within the URL workspace before persisting, reusing the existing pattern:\n\n```python\nif body.project_id is not None:\n project = await ProjectService(session).get(body.project_id, workspace_id=workspace_id)\n if project is None:\n raise HTTPException(404, \"Project not found\")\n```\n\nApply the same check to `parent_issue_id` (via `require_issue_in_workspace`) and to `assignee_id`. As defense in depth, scope `get_stats` so it only counts issues whose `workspace_id` matches the project's workspace.",
"severity" : [ {
"type" : "CVSS_V3",
"score" : "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:N"
} ],
"affected" : [ {
"package" : {
"ecosystem" : "PyPI",
"name" : "praisonai-platform"
},
"ranges" : [ {
"type" : "ECOSYSTEM",
"events" : [ {
"introduced" : "0"
}, {
"fixed" : "0.1.8"
} ]
} ]
} ],
"references" : [ {
"type" : "WEB",
"url" : "https://github.com/MervinPraison/PraisonAI/security/advisories/GHSA-2fjj-qqg8-fg7x"
}, {
"type" : "PACKAGE",
"url" : "https://github.com/MervinPraison/PraisonAI"
} ],
"database_specific" : {
"cwe_ids" : [ "CWE-639" ],
"severity" : "MODERATE",
"github_reviewed" : true,
"github_reviewed_at" : "2026-06-18T14:48:35Z",
"nvd_published_at" : null
}
}