Skip to content

Commit 6a7061d

Browse files
committed
Add more warnings about auto mode
1 parent c33ed0f commit 6a7061d

3 files changed

Lines changed: 109 additions & 2 deletions

File tree

.github/workflows/ci.yml

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
pull_request:
6+
7+
jobs:
8+
test:
9+
name: ${{ matrix.os }} / Python ${{ matrix.python-version }} / ${{ matrix.installer }}
10+
runs-on: ${{ matrix.os }}
11+
strategy:
12+
fail-fast: false
13+
matrix:
14+
os:
15+
- ubuntu-latest
16+
- macos-latest
17+
- windows-latest
18+
python-version:
19+
- "3.10"
20+
installer:
21+
- pip
22+
- uv
23+
24+
steps:
25+
- name: Check out repository
26+
uses: actions/checkout@v4
27+
28+
- name: Set up Python
29+
uses: actions/setup-python@v5
30+
with:
31+
python-version: ${{ matrix.python-version }}
32+
33+
- name: Install dependencies with pip
34+
if: matrix.installer == 'pip'
35+
run: |
36+
python -m pip install --upgrade pip
37+
python -m pip install -e . pytest ruff
38+
39+
- name: Install uv and sync environment
40+
if: matrix.installer == 'uv'
41+
run: |
42+
python -m pip install --upgrade pip
43+
python -m pip install uv
44+
uv sync --group dev
45+
46+
- name: Lint with pip
47+
if: matrix.installer == 'pip'
48+
run: python -m ruff check .
49+
50+
- name: Run tests with pip
51+
if: matrix.installer == 'pip'
52+
run: python -m pytest -q
53+
54+
- name: Lint with uv
55+
if: matrix.installer == 'uv'
56+
run: uv run python -m ruff check .
57+
58+
- name: Run tests with uv
59+
if: matrix.installer == 'uv'
60+
run: uv run python -m pytest -q

mini_coding_agent.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import argparse
22
import json
3-
import os
43
import re
54
import shutil
65
import subprocess
@@ -734,11 +733,23 @@ def reset(self):
734733
self.session["memory"] = {"task": "", "files": [], "notes": []}
735734
self.session_store.save(self.session)
736735

736+
def path_is_within_root(self, resolved):
737+
probe = resolved
738+
while not probe.exists() and probe.parent != probe:
739+
probe = probe.parent
740+
for candidate in (probe, *probe.parents):
741+
try:
742+
if candidate.samefile(self.root):
743+
return True
744+
except OSError:
745+
continue
746+
return False
747+
737748
def path(self, raw_path):
738749
path = Path(raw_path)
739750
path = path if path.is_absolute() else self.root / path
740751
resolved = path.resolve()
741-
if os.path.commonpath([str(self.root), str(resolved)]) != str(self.root):
752+
if not self.path_is_within_root(resolved):
742753
raise ValueError(f"path escapes workspace: {raw_path}")
743754
return resolved
744755

tests/test_mini_coding_agent.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import json
2+
import pytest
23
from unittest.mock import patch
34

45
from mini_coding_agent import (
@@ -188,6 +189,41 @@ def test_list_files_hides_internal_agent_state(tmp_path):
188189
assert "[F] hello.txt" in result
189190

190191

192+
def test_path_rejects_parent_escape(tmp_path):
193+
agent = build_agent(tmp_path, [])
194+
195+
with pytest.raises(ValueError, match="path escapes workspace"):
196+
agent.path("../outside.txt")
197+
198+
199+
def test_path_rejects_symlink_escape(tmp_path):
200+
agent = build_agent(tmp_path, [])
201+
outside = tmp_path.parent / f"{tmp_path.name}-outside"
202+
outside.mkdir()
203+
link = tmp_path / "outside-link"
204+
try:
205+
link.symlink_to(outside, target_is_directory=True)
206+
except (OSError, NotImplementedError):
207+
pytest.skip("symlink creation is not available in this environment")
208+
209+
with pytest.raises(ValueError, match="path escapes workspace"):
210+
agent.path("outside-link/secret.txt")
211+
212+
213+
def test_path_accepts_case_variant_on_case_insensitive_filesystems(tmp_path):
214+
project_root = tmp_path / "Proj"
215+
project_root.mkdir()
216+
agent = build_agent(project_root, [])
217+
variant = project_root.parent / project_root.name.lower() / "README.md"
218+
219+
if not variant.exists():
220+
pytest.skip("case-sensitive filesystem")
221+
222+
resolved = agent.path(str(variant))
223+
224+
assert resolved.samefile(project_root / "README.md")
225+
226+
191227
def test_repeated_identical_tool_call_is_rejected(tmp_path):
192228
agent = build_agent(tmp_path, [])
193229
agent.record({"role": "tool", "name": "list_files", "args": {}, "content": "(empty)", "created_at": "1"})

0 commit comments

Comments
 (0)