From 5a3e13d3c6ccdb1d3cc0ad1770d6ea7082494d9d Mon Sep 17 00:00:00 2001 From: Harshit Sharma <66710144+harshitethic@users.noreply.github.com> Date: Wed, 23 Sep 2026 12:42:41 +0530 Subject: [PATCH 1/3] security: confine indexed files to repository root --- backend/services/github.py | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/backend/services/github.py b/backend/services/github.py index d345c48..94cd75a 100644 --- a/backend/services/github.py +++ b/backend/services/github.py @@ -73,16 +73,30 @@ def extract_zip(upload_path: Path): raise return repo_id, dest +def _resolve_repo_file(root: Path, candidate: Path): + root = root.resolve() + try: + resolved = candidate.resolve() + resolved.relative_to(root) + except (OSError, ValueError): + return None + if not resolved.is_file(): + return None + return resolved + + def iter_files(root: Path): + root = root.resolve() for p in root.rglob("*"): - if not p.is_file(): continue rel = p.relative_to(root) if any(part in SKIP_DIRS for part in rel.parts): continue - try: size = p.stat().st_size + resolved = _resolve_repo_file(root, p) + if resolved is None: continue + try: size = resolved.stat().st_size except OSError: continue if size > 300_000: continue if p.suffix.lower() in TEXT_EXTENSIONS or p.name.lower() in {"dockerfile","makefile"}: - yield p + yield resolved def read_text(path: Path): return path.read_text(encoding="utf-8", errors="replace") From 7df6311fc6aa482ba77e68ad799b23cfddd27ff7 Mon Sep 17 00:00:00 2001 From: Harshit Sharma <66710144+harshitethic@users.noreply.github.com> Date: Wed, 23 Sep 2026 12:42:43 +0530 Subject: [PATCH 2/3] test: cover repository file confinement --- backend/tests/test_repository_boundary.py | 39 +++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 backend/tests/test_repository_boundary.py diff --git a/backend/tests/test_repository_boundary.py b/backend/tests/test_repository_boundary.py new file mode 100644 index 0000000..293ab5b --- /dev/null +++ b/backend/tests/test_repository_boundary.py @@ -0,0 +1,39 @@ +import tempfile +import unittest +from pathlib import Path + +from services.github import iter_files + + +class RepositoryFileBoundaryTests(unittest.TestCase): + def test_external_symlink_is_not_indexed(self): + with tempfile.TemporaryDirectory() as temp_dir: + root = Path(temp_dir) + repo = root / "repo" + repo.mkdir() + outside = root / "outside.py" + outside.write_text("SECRET = True\n", encoding="utf-8") + link = repo / "linked.py" + try: + link.symlink_to(outside) + except (OSError, NotImplementedError): + self.skipTest("symlinks are unavailable on this platform") + + indexed = list(iter_files(repo)) + + self.assertEqual(indexed, []) + + def test_internal_file_is_still_indexed(self): + with tempfile.TemporaryDirectory() as temp_dir: + repo = Path(temp_dir) + source = repo / "src" / "main.py" + source.parent.mkdir() + source.write_text("print('ok')\n", encoding="utf-8") + + indexed = list(iter_files(repo)) + + self.assertEqual(indexed, [source.resolve()]) + + +if __name__ == "__main__": + unittest.main() From 1cf01698cd3d63ee5eac814d504db0103e567fa9 Mon Sep 17 00:00:00 2001 From: Harshit Sharma <66710144+harshitethic@users.noreply.github.com> Date: Wed, 23 Sep 2026 12:42:46 +0530 Subject: [PATCH 3/3] ci: run backend unit tests --- .github/workflows/ci.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1a3293e..d8b03cb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -26,6 +26,10 @@ jobs: - name: Compile backend run: python -m compileall -q backend + - name: Run backend unit tests + working-directory: backend + run: python -m unittest discover -s tests -v + frontend: runs-on: ubuntu-latest steps: