lsp/runtime: add failing test proving LspRuntime._read_file has no workspace path containment check #10489

Closed
opened 2026-04-18 10:09:51 +00:00 by HAL9000 · 1 comment
Owner

TDD Test Specification

Tags

@tdd_issue @tdd_issue_3 @tdd_expected_fail

Test Description

Add a failing unit test that proves LspRuntime._read_file() does not restrict file access to the workspace directory, allowing path traversal to read arbitrary files.

Expected Failing Test

# tests/unit/lsp/test_runtime_path_containment.py
import os
import pytest
import tempfile
from cleveragents.lsp.runtime import LspRuntime
from cleveragents.lsp.errors import LspError

def test_read_file_rejects_path_outside_workspace():
    """_read_file should reject paths that resolve outside the workspace directory."""
    # Create a temp workspace directory
    with tempfile.TemporaryDirectory() as workspace:
        # Create a file inside the workspace
        safe_file = os.path.join(workspace, "safe.py")
        with open(safe_file, "w") as f:
            f.write("# safe content")
        
        # Create a file OUTSIDE the workspace (simulating /etc/passwd scenario)
        with tempfile.NamedTemporaryFile(mode="w", suffix=".txt", delete=False) as outside_file:
            outside_file.write("sensitive content")
            outside_path = outside_file.name
        
        try:
            # Attempt to read a file outside the workspace via path traversal
            # e.g., workspace + "/../../../tmp/sensitive.txt"
            traversal_path = os.path.join(workspace, "..", os.path.basename(outside_path))
            
            # This should raise LspError due to path containment check
            with pytest.raises(LspError, match="outside workspace"):
                LspRuntime._read_file(traversal_path)
        finally:
            os.unlink(outside_path)

Why This Test Currently Fails

In src/cleveragents/lsp/runtime.py, _read_file (lines ~340-350):

@staticmethod
def _read_file(file_path: str) -> str:
    """Read file contents as UTF-8 text."""
    resolved = os.path.realpath(file_path)
    if not os.path.isfile(resolved):
        raise LspError(
            f"Not a regular file: {file_path}",
            details={"file": file_path, "resolved": resolved},
        )
    with open(resolved, encoding="utf-8") as f:
        return f.read()

os.path.realpath() resolves symlinks and .. components but does NOT restrict the path to a specific directory. An attacker who can control file_path (e.g., via a malicious LLM tool call) could read any file on the filesystem.

The method is a @staticmethod and has no access to the workspace path, so it cannot perform containment checks without a design change.

Acceptance Criteria

  • Test file exists at tests/unit/lsp/test_runtime_path_containment.py
  • Test is tagged @tdd_expected_fail and fails before the fix
  • Test passes after the bug fix in the corresponding Bug issue

Subtasks

  • Write the failing test
  • Verify it fails with current code
  • Link to the Bug issue

Definition of Done

This issue is complete when:

  • The test file exists and is tagged @tdd_expected_fail
  • The test fails with current code (proving the bug exists)
  • The corresponding Bug issue fix makes this test pass

Automated by CleverAgents Bot
Supervisor: Bug Hunt Pool | Agent: bug-hunt-pool-supervisor

## TDD Test Specification ### Tags @tdd_issue @tdd_issue_3 @tdd_expected_fail ### Test Description Add a failing unit test that proves `LspRuntime._read_file()` does not restrict file access to the workspace directory, allowing path traversal to read arbitrary files. ### Expected Failing Test ```python # tests/unit/lsp/test_runtime_path_containment.py import os import pytest import tempfile from cleveragents.lsp.runtime import LspRuntime from cleveragents.lsp.errors import LspError def test_read_file_rejects_path_outside_workspace(): """_read_file should reject paths that resolve outside the workspace directory.""" # Create a temp workspace directory with tempfile.TemporaryDirectory() as workspace: # Create a file inside the workspace safe_file = os.path.join(workspace, "safe.py") with open(safe_file, "w") as f: f.write("# safe content") # Create a file OUTSIDE the workspace (simulating /etc/passwd scenario) with tempfile.NamedTemporaryFile(mode="w", suffix=".txt", delete=False) as outside_file: outside_file.write("sensitive content") outside_path = outside_file.name try: # Attempt to read a file outside the workspace via path traversal # e.g., workspace + "/../../../tmp/sensitive.txt" traversal_path = os.path.join(workspace, "..", os.path.basename(outside_path)) # This should raise LspError due to path containment check with pytest.raises(LspError, match="outside workspace"): LspRuntime._read_file(traversal_path) finally: os.unlink(outside_path) ``` ### Why This Test Currently Fails In `src/cleveragents/lsp/runtime.py`, `_read_file` (lines ~340-350): ```python @staticmethod def _read_file(file_path: str) -> str: """Read file contents as UTF-8 text.""" resolved = os.path.realpath(file_path) if not os.path.isfile(resolved): raise LspError( f"Not a regular file: {file_path}", details={"file": file_path, "resolved": resolved}, ) with open(resolved, encoding="utf-8") as f: return f.read() ``` `os.path.realpath()` resolves symlinks and `..` components but does NOT restrict the path to a specific directory. An attacker who can control `file_path` (e.g., via a malicious LLM tool call) could read any file on the filesystem. The method is a `@staticmethod` and has no access to the workspace path, so it cannot perform containment checks without a design change. ### Acceptance Criteria - [ ] Test file exists at `tests/unit/lsp/test_runtime_path_containment.py` - [ ] Test is tagged `@tdd_expected_fail` and fails before the fix - [ ] Test passes after the bug fix in the corresponding Bug issue ### Subtasks - [x] Write the failing test - [x] Verify it fails with current code - [ ] Link to the Bug issue ## Definition of Done This issue is complete when: - The test file exists and is tagged `@tdd_expected_fail` - The test fails with current code (proving the bug exists) - The corresponding Bug issue fix makes this test pass --- **Automated by CleverAgents Bot** Supervisor: Bug Hunt Pool | Agent: bug-hunt-pool-supervisor
Author
Owner

Implementation Attempt — Tier 1: Haiku — Success

Implemented the TDD test for issue #10489 (LspRuntime._read_file path containment check).

What was done:

  • Added features/tdd_lsp_path_containment.feature with a @tdd_expected_fail scenario that proves LspRuntime._read_file() does not check workspace path containment
  • Added features/steps/tdd_lsp_path_containment_steps.py with step definitions using lsp_pc prefix
  • The test creates a workspace directory, creates a sensitive file outside the workspace, attempts path traversal, and asserts LspError is raised with 'outside workspace' message
  • The assertion FAILS (proving the bug exists) but @tdd_expected_fail inverts it to pass in CI

Quality gate status: lint ✓, typecheck ✓, unit_tests ✓ (638 features passed, 15,238 scenarios passed)

Branch: test/issue-10489-lsp-path-containment-tdd
Commit: e1b091de
PR: #10736


Automated by CleverAgents Bot
Supervisor: Implementation Pool | Agent: implementation-worker

**Implementation Attempt** — Tier 1: Haiku — Success Implemented the TDD test for issue #10489 (LspRuntime._read_file path containment check). **What was done:** - Added `features/tdd_lsp_path_containment.feature` with a `@tdd_expected_fail` scenario that proves `LspRuntime._read_file()` does not check workspace path containment - Added `features/steps/tdd_lsp_path_containment_steps.py` with step definitions using `lsp_pc` prefix - The test creates a workspace directory, creates a sensitive file outside the workspace, attempts path traversal, and asserts `LspError` is raised with 'outside workspace' message - The assertion FAILS (proving the bug exists) but `@tdd_expected_fail` inverts it to pass in CI **Quality gate status:** lint ✓, typecheck ✓, unit_tests ✓ (638 features passed, 15,238 scenarios passed) **Branch:** test/issue-10489-lsp-path-containment-tdd **Commit:** e1b091de **PR:** https://git.cleverthis.com/cleveragents/cleveragents-core/pulls/10736 --- **Automated by CleverAgents Bot** Supervisor: Implementation Pool | Agent: implementation-worker
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
cleveragents/cleveragents-core#10489
No description provided.