Threats Tagged 'ghsa-q7m5-3jmv-vm48'
1 threat
Most recently published first
| Severity | CVSS | Signals | Threat | Source | Published | Score and star |
|---|---|---|---|---|---|---|
| Medium | 6.8 | EPSS 0.4% | CVE-2026-61431PraisonAI · Path Traversal Medium6.8EPSS 0.4%CVE Database V5CVE-2026-61431 # ContextGatherer include resolution permits absolute and traversal reads outside the workspace ## Summary PraisonAI's `praisonai.ui.context.ContextGatherer` treats the configured `directory` as the project workspace, but project-controlled `.praisoncontext` and `.praisoninclude` files can name absolute paths or `..` traversal paths. When context gathering runs, PraisonAI opens those outside paths and appends their contents to the generated context bundle. An attacker who can supply or modify a workspace repository can therefore cause process-readable files outside the intended project root to be sent to the caller or model as project context. ## Technical Details `ContextGatherer.get_include_paths()` reads include entries directly from `.praisoncontext` and `.praisoninclude` under the configured workspace. It stores each non-comment line as a raw include path: ```python include_file = os.path.join(self.directory, '.praisoncontext') if os.path.exists(include_file): with open(include_file, 'r') as f: include_paths.extend( line.strip() for line in f if line.strip() and not line.startswith('#') ) ``` When `.praisoncontext` is present, `gather_context()` passes every include entry through `os.path.join(self.directory, include_path)` and then processes the result: ```python for include_path in self.include_paths: full_path = os.path.join(self.directory, include_path) process_path(full_path) ``` The `.praisoninclude` path has the same unsafe join after first processing the workspace: ```python process_path(self.directory) for include_path in self.include_paths: full_path = os.path.join(self.directory, include_path) process_path(full_path) ``` There is no canonicalization or containment check before `process_path()` opens files or recursively walks directories. In Python, `os.path.join(workspace, absolute_path)` returns the absolute path and discards `workspace`; `os.path.join(workspace, "../outside.py")` remains outside the workspace once normalized by filesystem operations. `add_file_content()` then opens the supplied path and appends file contents to the context before display bookkeeping: ```python with open(file_path, 'r', encoding='utf-8') as f: content = f.read() context.append( f"File: {file_path}\n\n{content}\n\n{'=' * 50}\n" ) self.included_files.append( Path(file_path).relative_to(self.directory) ) ``` For parent traversal paths, `Path(file_path).relative_to(self.directory)` raises after the outside file content has already been appended, so the caller receives the outside content even if an error is logged. For absolute paths, the outside content is appended as well. This violates the workspace invariant for a context-gathering feature: repository-local include metadata should select files within the project, not arbitrary process-readable host files. ## PoV The minimal vulnerable shape is a workspace containing only a normal source file and one include file: ```text workspace/ .praisoncontext # contains: ../outside_secret.py inside.py outside_secret.py # outside the workspace ``` Running `ContextGatherer(directory="workspace").run()` returns context containing `outside_secret.py` even though that file is outside the configured workspace. The same result occurs when `.praisoncontext` contains an absolute path to the outside file, and when `.praisoninclude` contains either the parent traversal path or the absolute path. ## PoC Save the self-contained script from the Appendix below as `context_include_workspace_pov.py`, then run it against a local checkout: ```bash export PRAISONAI=/path/to/PraisonAI PYTHONPATH="$PRAISONAI/src/praisonai" python context_include_workspace_pov.py ``` Expected vulnerable output: ```json { "expectations": { "control_inside_file_is_collected": true, "control_without_include_does_not_read_outside": true, "praisoncontext_absolute_path_discloses_outside": true, "praisoncontext_parent_traversal_discloses_outside": true, "praisoninclude_absolute_path_discloses_outside": true, "praisoninclude_parent_traversal_discloses_outside": true }, "source_commit": "1620b49f36945d8cc8ee5635b906c960df5097a0", "source_file": "$PRAISONAI/src/praisonai/praisonai/ui/context.py", "vulnerable": true } ``` The version sweep sampled old and current releases. All sampled versions are vulnerable: ```text {"ref":"v2.3.10","praisonai_version":"2.3.10","status":"vulnerable","control_without_include_does_not_read_outside":true,"relative_praisoncontext_discloses_outside":true,"absolute_praisoncontext_discloses_outside":true,"relative_praisoninclude_discloses_outside":true,"absolute_praisoninclude_discloses_outside":true} {"ref":"v2.3.11","praisonai_version":"2.3.11","status":"vulnerable","control_without_include_does_not_read_outside":true,"relative_praisoncontext_discloses_outside":true,"absolute_praisoncontext_discloses_outside":true,"relative_ | CVE Database V5 |
Showing 1–1 of 1 result