bytedance/deer-flow · warning · HTTPException

Invalid path

Error message

Invalid path

What it means

HTTP 400 raised by DELETE /threads/{thread_id}/uploads/{filename} when the filename triggers PathTraversalError — the path-safety layer rejects URL segments that would escape the thread's uploads directory (../, absolute paths, symlink tricks). This is a deliberate guard, not a bug.

Source

Thrown at backend/app/gateway/routers/uploads.py:478

    """List all files in a thread's uploads directory."""
    try:
        result = await run_file_io(_list_uploaded_files_for_thread, thread_id, get_effective_user_id())
    except ValueError as e:
        raise HTTPException(status_code=400, detail=str(e))

    return UploadListResponse(**result)


@router.delete("/{filename}")
@require_permission("threads", "delete", owner_check=True, require_existing=True)
async def delete_uploaded_file(thread_id: ThreadId, filename: str, request: Request) -> dict:
    """Delete a file from a thread's uploads directory."""
    try:
        return await run_file_io(_delete_uploaded_file_for_thread, thread_id, filename, get_effective_user_id())
    except FileNotFoundError:
        raise HTTPException(status_code=404, detail=f"File not found: {filename}")
    except PathTraversalError:
        raise HTTPException(status_code=400, detail="Invalid path")
    except ValueError as e:
        raise HTTPException(status_code=400, detail=str(e))
    except Exception as e:
        logger.error(f"Failed to delete {filename}: {e}")
        raise HTTPException(status_code=500, detail=f"Failed to delete {filename}: {str(e)}")

View on GitHub (pinned to 1dd6ba1acb)

Solutions

  1. Delete by the exact plain filename returned by the uploads list endpoint — no directories, no '..', no absolute paths.
  2. URL-encode the filename once (encodeURIComponent) when building the path.
  3. Strip path components client-side before constructing the request.

Example fix

// before
await fetch(`/api/threads/${tid}/uploads/${file.fullPath}`);

// after
const name = file.fullPath.split('/').pop();
await fetch(`/api/threads/${tid}/uploads/${encodeURIComponent(name)}`);
Defensive patterns

Strategy: validation

Validate before calling

function isSafeFilename(name) {
  return typeof name === 'string'
    && !name.includes('/')
    && !name.includes('\\')
    && name !== '.' && name !== '..'
    && !name.startsWith('.');
}
if (!isSafeFilename(filename)) throw new Error('Invalid filename');

Type guard

const isSafeFilename = (n: unknown): n is string =>
  typeof n === 'string' && /^[-. A-Za-z0-9_()]+$/.test(n) && n !== '.' && n !== '..' && !n.includes('/');

Prevention

When it happens

Trigger: Passing a filename containing '..' segments, a leading slash, or encoded traversal sequences (e.g. %2e%2e%2f) in the DELETE path.

Common situations: Client concatenating unvalidated user input into the URL; penetration tests / scanners probing the endpoint; mis-encoded filenames from scripts.

Related errors


AI-assisted analysis of bytedance/deer-flow@1dd6ba1acb (2026-08-14). Data as JSON: /api/errors/adcfc0bba1236c70. Report an issue: GitHub.