Summary
The backend endpoint GET /local_repo/structure?path=<path> (api/routers/wiki.py) takes a caller-supplied filesystem path, runs os.walk() on it, and returns the full recursive file tree plus the content of any README.md found under it. The route has no authentication dependency and no path confinement, and the API is unauthenticated by default (DEEPWIKI_AUTH_MODE=False in api/config.py). A remote, unauthenticated client can therefore enumerate the server's filesystem and read README files anywhere the server process can access.
Affected version: confirmed on commit d92819a (v0.1.0, current main).
Details
@router.get("/local_repo/structure")
async def get_local_repo_structure(path: str = Query(None, ...)):
if not path: ...
if not os.path.isdir(path): ...
for root, dirs, files in os.walk(path):
...
for file in files:
file_tree_lines.append(rel_file)
if file.lower() == "readme.md" and not readme_content:
with open(os.path.join(root, file)) as f:
readme_content = f.read()
return {"file_tree": ..., "readme": readme_content}
path is used directly. Routers are mounted in api/main.py with app.include_router(module.router) and no global auth dependency, and this handler never checks WIKI_AUTH_MODE/WIKI_AUTH_CODE (unlike delete_wiki in the same file).
The same local-path notion feeds the RAG pipeline: a local repo_url makes Repo.is_local true, save_path becomes the raw path, and read_all_documents(path) (api/rag/pipeline.py) reads the full content of every code file under it into the store, which the chat/wiki endpoints return. So the missing confinement escalates from README disclosure to arbitrary code-file read. The attached PR fixes the direct /local_repo/structure endpoint; the RAG local-path intake should get the same confinement.
POC
(available upon request)
Impact
An unauthenticated remote client can:
- Enumerate the server filesystem (
?path=/, /home, /etc, ...), revealing usernames, app layout, and the location of config/secret files.
- Read README files at any path (often containing internal runbooks, env-var names, sometimes credentials).
- Via the local-repository RAG path, have arbitrary code/config file contents returned through wiki/chat output.
A fix PR (path confinement to an allowlisted root) is attached.
Summary
The backend endpoint
GET /local_repo/structure?path=<path>(api/routers/wiki.py) takes a caller-supplied filesystem path, runsos.walk()on it, and returns the full recursive file tree plus the content of anyREADME.mdfound under it. The route has no authentication dependency and no path confinement, and the API is unauthenticated by default (DEEPWIKI_AUTH_MODE=Falseinapi/config.py). A remote, unauthenticated client can therefore enumerate the server's filesystem and read README files anywhere the server process can access.Affected version: confirmed on commit
d92819a(v0.1.0, currentmain).Details
pathis used directly. Routers are mounted inapi/main.pywithapp.include_router(module.router)and no global auth dependency, and this handler never checksWIKI_AUTH_MODE/WIKI_AUTH_CODE(unlikedelete_wikiin the same file).The same local-path notion feeds the RAG pipeline: a local
repo_urlmakesRepo.is_localtrue,save_pathbecomes the raw path, andread_all_documents(path)(api/rag/pipeline.py) reads the full content of every code file under it into the store, which the chat/wiki endpoints return. So the missing confinement escalates from README disclosure to arbitrary code-file read. The attached PR fixes the direct/local_repo/structureendpoint; the RAG local-path intake should get the same confinement.POC
(available upon request)
Impact
An unauthenticated remote client can:
?path=/,/home,/etc, ...), revealing usernames, app layout, and the location of config/secret files.A fix PR (path confinement to an allowlisted root) is attached.