LspRuntime._read_file had no check that the requested file path was contained within the workspace directory, allowing path traversal attacks to read arbitrary files on the filesystem.
Changes:
- Add _workspace_paths dict to LspRuntime to track per-server workspace roots
- start_server now stores the resolved workspace path for each server
- stop_server and stop_all clean up workspace path entries
- _read_file now accepts an optional workspace_path parameter; when
provided, it resolves both paths and raises LspError if the file
is outside the workspace (prevents ../../etc/passwd style attacks)
- get_diagnostics, get_completions, get_hover, get_definitions all
pass the registered workspace path to _read_file
- Add features/lsp_path_containment.feature with 10 BDD scenarios
- Add features/steps/lsp_path_containment_steps.py step definitions
ISSUES CLOSED: #10490