HKUDS/DeepTutor · error · HTTPException
Access denied
Error message
Access denied
What it means
Raised by _safe_join_raw as a second line of defense: after resolving (raw_dir / rel_path), the result does not live under raw_dir, so the resolved target escaped the KB root (symlink, traversal, or absolute path). Unlike the 400 in _sanitize_rel_subdir this returns 403 Access denied.
Source
Thrown at deeptutor/api/routers/knowledge.py:345
for raw_seg in str(rel_path).replace("\\", "/").split("/"):
seg = raw_seg.strip()
if seg in ("", "."):
continue
if seg == "..":
raise HTTPException(status_code=400, detail="Invalid folder path")
safe = _sanitize_path_segment(seg)
if safe:
parts.append(safe)
return "/".join(parts)
def _safe_join_raw(raw_dir: Path, rel_path: str) -> Path:
"""Resolve ``rel_path`` under ``raw_dir``, rejecting traversal."""
target = (raw_dir / rel_path).resolve()
try:
target.relative_to(raw_dir.resolve())
except ValueError as exc:
raise HTTPException(status_code=403, detail="Access denied") from exc
return target
def _save_uploaded_files(
files: list[UploadFile],
target_dir: Path,
allowed_extensions: set[str] | None = None,
kb_name: str | None = None,
rel_paths: list[str] | None = None,
dest_subdir: str = "",
) -> tuple[list[str], list[str]]:
"""
Save uploaded files to the local raw/ directory.
When PocketBase is enabled and ``kb_name`` is supplied, each file is also
uploaded to the PocketBase knowledge_bases record as a file attachment
(best-effort — local write is always the primary path).
"""View on GitHub (pinned to 3e82f13042)
Solutions
- Audit the KB raw directory for symlinks pointing outside it (find . -type l -exec readlink {} \;)
- Ensure the client only sends simple relative folder names
- Re-run with the same input after removing offending symlinks; keep the segment-level sanitizer upstream of this call
Example fix
# before rel = '../../../../home/user/secret' # after rel = 'semester1/cs101'
Defensive patterns
Strategy: validation
Validate before calling
from pathlib import Path
def joins_inside(root: Path, rel: str) -> bool:
try:
(root / rel).resolve().relative_to(root.resolve())
return True
except ValueError:
return False Prevention
- Mirror the resolve+relative_to check client-side
- Keep KB directories free of symlinks escaping the root
- Send only plain relative folder names
When it happens
Trigger: create_kb_folder or move_kb_file where rel_path contains traversal that survived sanitization, or where a symlink inside raw_dir resolves outside it; passing an absolute path that resolve() anchors elsewhere.
Common situations: Symlinks inside the KB directory pointing to external locations; race between check and use; crafted payloads bypassing segment-level checks.
Understand the failure class
Background: Path traversal blocked: "path escapes the workspace" and "outside site root" errors when a path will not stay inside its allowed directory — this error's family across 26 libraries.
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- Invalid folder path
- Invalid math animator config: {details}
- Document not found
- Partner is not assigned to you
- You cannot manage this partner
AI-assisted analysis of HKUDS/DeepTutor@3e82f13042 (2026-08-27).
Data as JSON: /api/errors/29977aa572a4d5dc.
Report an issue: GitHub.