Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 11 additions & 10 deletions backend/services/github.py
Original file line number Diff line number Diff line change
Expand Up @@ -423,16 +423,17 @@ def _extract_zip_locked(upload_path: Path):
def iter_files(root: Path):
# Never follow a repository-controlled symbolic link outside its tree.
trusted_root = root.resolve()
for p in root.rglob("*"):
if p.is_symlink() or not p.is_file(): continue
if trusted_root not in p.resolve().parents: continue
rel = p.relative_to(root)
if any(part.casefold() in SKIP_DIRS for part in rel.parts[:-1]): continue
try: size = p.stat().st_size
except OSError: continue
if size > 300_000: continue
if p.suffix.lower() in TEXT_EXTENSIONS or p.name.lower().endswith(".env.example") or p.name.lower() in {"dockerfile","makefile",".gitignore","go.mod","gemfile"}:
yield p
for directory, subdirs, files in os.walk(root, followlinks=False):
subdirs[:] = [name for name in subdirs if name.casefold() not in SKIP_DIRS]
for name in files:
p = Path(directory) / name
if p.is_symlink() or not p.is_file(): continue
if trusted_root not in p.resolve().parents: continue
try: size = p.stat().st_size
except OSError: continue
if size > 300_000: continue
if p.suffix.lower() in TEXT_EXTENSIONS or p.name.lower().endswith(".env.example") or p.name.lower() in {"dockerfile","makefile",".gitignore","go.mod","gemfile"}:
yield p

def read_text(path: Path): return path.read_text(encoding="utf-8", errors="replace")

Expand Down
28 changes: 28 additions & 0 deletions backend/tests/test_source_traversal.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import tempfile
import unittest
from pathlib import Path
from unittest.mock import patch

from services.github import iter_files


class SourceTraversalTests(unittest.TestCase):
def test_walk_skips_file_checks_in_ignored_subtrees(self):
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
ignored = root / "src" / "Node_Modules"
ignored.mkdir(parents=True)
dependency = ignored / "vendor.js"
dependency.touch()
source = root / "src" / "app.js"
source.touch()
inspected = []
is_file = Path.is_file

def track_file_check(path):
inspected.append(path)
return is_file(path)

with patch.object(Path, "is_file", track_file_check):
self.assertEqual(list(iter_files(root)), [source])
self.assertNotIn(dependency, inspected)
Loading