CVE-2026-10044
Description
Usagi-org ai-goofish-monitor contains an unauthenticated arbitrary file read vulnerability in the GET /api/prompts/{filename} endpoint on Windows deployments that allows unauthenticated remote attackers to read arbitrary files by supplying absolute Windows paths or backslash-based traversal sequences. Attackers can bypass the incomplete path traversal guard, which only blocks forward slashes and '..', by providing absolute paths such as Windows system file locations, causing os.path.join to discard the intended prompts directory prefix and expose files accessible to the application process.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
ai-goofish-monitor on Windows allows unauthenticated attackers to read arbitrary files via absolute paths or backslash traversal in GET /api/prompts/{filename}.
Vulnerability
ai-goofish-monitor, an open-source monitoring tool by Usagi-org, contains an unauthenticated arbitrary file read vulnerability in the GET /api/prompts/{filename} endpoint on Windows deployments. The endpoint attempts to guard against path traversal by checking for forward slashes (/) and .. in the filename [1][2][3]. However, this guard is incomplete: on Windows, backslashes (\) are valid path separators, and absolute paths like C:\Windows\... contain neither / nor ... When such a filename is passed to os.path.join("prompts", filename), Python's ntpath.join drops the "prompts" prefix because the filename is an absolute path, allowing the attacker to read any file accessible to the application process [2]. The issue affects all versions before the fix introduced in pull request #489 [1].
Exploitation
An unauthenticated attacker can exploit this vulnerability from any network position reachable by the ai-goofish-monitor HTTP service. No authentication is required for the endpoint [2]. The attacker crafts a request to GET /api/prompts/{filename} where {filename} is an absolute Windows path (e.g., C:\Windows\System32\drivers\etc\hosts) or a backslash-based traversal sequence (e.g., ..\..\path\to\file). The guard only blocks forward slashes and .., so the absolute path passes the check. The os.path.join call then resolves to the attacker-supplied absolute path, and the server reads and returns the file contents [2][3]. For example, requesting /api/prompts/C:\Windows\System32\drivers\etc\hosts returns the hosts file content [2].
Impact
Successful exploitation allows an unauthenticated remote attacker to read arbitrary files on the Windows filesystem, subject to the permissions of the application process. This can lead to disclosure of sensitive information such as configuration files, credentials, or system data. The attack achieves information disclosure (breach of confidentiality) with no required authentication [2][3]. The application process typically runs with a specific user account, so the scope is limited to files readable by that user, but this often includes critical system files and application secrets.
Mitigation
The vulnerability is fixed in pull request #489 on GitHub [1]. The fix refactors the prompt management routes using pathlib, introduces a helper function to properly validate file paths, and avoids exposing raw exception messages in API responses [1]. Affected users should update to a version that includes this fix or apply the changes from the pull request. Until patched, users operating on Windows should restrict network access to the ai-goofish-monitor service or use a reverse proxy with path validation. No formal CVE or KEV listing update is available at this time [1][2][3].
- fix: path traversal vulnerability in /api/prompts/{filename} (Windows) by AAtomical · Pull Request #489 · Usagi-org/ai-goofish-monitor
- Unauthenticated arbitrary file read via Windows absolute-path bypass in `GET /api/prompts/{filename}`
- ai-goofish-monitor Unauthenticated Arbitrary File Read via GET /api/prompts/
AI Insight generated on May 28, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected products
2(expand)+ 1 more
- (no CPE)
- (no CPE)
Patches
1f85d140b6b45Merge pull request #489 from AAtomical/fix/path-traversal-prompts-endpoint
1 file changed · +21 −15
src/api/routes/prompts.py+21 −15 modified@@ -3,12 +3,25 @@ """ import os import aiofiles +from pathlib import Path from fastapi import APIRouter, HTTPException from pydantic import BaseModel router = APIRouter(prefix="/api/prompts", tags=["prompts"]) +_PROMPTS_DIR = Path("prompts").resolve() + + +def _safe_prompt_path(filename: str) -> Path: + """返回经过 containment 检查的绝对路径,防止任意 OS 上的路径穿越。""" + try: + resolved = (_PROMPTS_DIR / filename).resolve() + resolved.relative_to(_PROMPTS_DIR) + except (ValueError, OSError): + raise HTTPException(status_code=400, detail="无效的文件名") + return resolved + class PromptUpdate(BaseModel): """Prompt 更新模型""" @@ -18,25 +31,21 @@ class PromptUpdate(BaseModel): @router.get("") async def list_prompts(): """列出所有 prompt 文件""" - prompts_dir = "prompts" - if not os.path.isdir(prompts_dir): + if not _PROMPTS_DIR.is_dir(): return [] - return [f for f in os.listdir(prompts_dir) if f.endswith(".txt")] + return [f for f in os.listdir(_PROMPTS_DIR) if f.endswith(".txt")] @router.get("/{filename}") async def get_prompt(filename: str): """获取 prompt 文件内容""" - if "/" in filename or ".." in filename: - raise HTTPException(status_code=400, detail="无效的文件名") - - filepath = os.path.join("prompts", filename) - if not os.path.exists(filepath): + filepath = _safe_prompt_path(filename) + if not filepath.exists(): raise HTTPException(status_code=404, detail="Prompt 文件未找到") async with aiofiles.open(filepath, 'r', encoding='utf-8') as f: content = await f.read() - return {"filename": filename, "content": content} + return {"filename": filepath.name, "content": content} @router.put("/{filename}") @@ -45,16 +54,13 @@ async def update_prompt( prompt_update: PromptUpdate, ): """更新 prompt 文件内容""" - if "/" in filename or ".." in filename: - raise HTTPException(status_code=400, detail="无效的文件名") - - filepath = os.path.join("prompts", filename) - if not os.path.exists(filepath): + filepath = _safe_prompt_path(filename) + if not filepath.exists(): raise HTTPException(status_code=404, detail="Prompt 文件未找到") try: async with aiofiles.open(filepath, 'w', encoding='utf-8') as f: await f.write(prompt_update.content) - return {"message": f"Prompt 文件 '{filename}' 更新成功"} + return {"message": f"Prompt 文件 '{filepath.name}' 更新成功"} except Exception as e: raise HTTPException(status_code=500, detail=f"写入文件时出错: {e}")
Vulnerability mechanics
Root cause
"Incomplete path traversal guard in `GET /api/prompts/{filename}` blocks only `"/"` and `".."` but not backslashes, allowing absolute Windows paths to bypass the check and cause `os.path.join` to discard the intended prompts directory prefix."
Attack vector
An unauthenticated remote attacker sends a GET request to `/api/prompts/{filename}` where `{filename}` is an absolute Windows path (e.g., `C:\Windows\System32\drivers\etc\hosts`) or a backslash-based traversal sequence [ref_id=1]. The inline guard only blocks `"/"` and `".."`, so absolute paths containing backslashes pass the check [ref_id=1]. On Windows, `os.path.join` discards the `"prompts"` prefix because the attacker-supplied argument is an absolute path, causing the server to read and return the contents of any file the application process can access [ref_id=1]. No authentication is required [ref_id=1].
Affected code
The vulnerable endpoint is `GET /api/prompts/{filename}` in `src/api/routes/prompts.py` [ref_id=1]. The handler calls `os.path.join("prompts", filename)` and then reads the resulting file path with `aiofiles.open()` [ref_id=1]. On Windows, `ntpath.join` discards the `"prompts"` prefix when `filename` is an absolute path such as `C:\Windows\System32\drivers\etc\hosts` [ref_id=1].
What the fix does
The suggested fix replaces the denylist guard with a post-join containment check using `Path.resolve()` [ref_id=1]. After joining the prompts directory with the user-supplied filename, the code calls `filepath.relative_to(PROMPTS_DIR_ABS)` and raises an exception if the resolved path is not inside the intended directory [ref_id=1]. This approach is OS-agnostic because `Path.resolve()` normalises drive letters and backslashes on Windows before the containment check [ref_id=1]. No official patch has been published by the vendor at the time of the advisory [ref_id=1].
Preconditions
- configThe application must be deployed on a Windows host
- networkThe endpoint must be reachable over the network
- authNo authentication is required
- inputThe attacker supplies an absolute Windows path or backslash-based traversal as the filename parameter
Reproduction
1. Deploy the application on a Windows host (or verify the guard bypass on any OS by checking for a 404 instead of a 400 response). 2. Send a GET request to `http://TARGET/api/prompts/C%3A%5CWindows%5CSystem32%5Cdrivers%5Cetc%5Chosts` (URL-encoded absolute path). 3. On a Windows deployment, the server returns HTTP 200 with the file contents in the JSON response body [ref_id=1].
Generated on May 28, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
3News mentions
0No linked articles in our index yet.