From 40835a4ba1e866462be2b4af95504dffde77a0c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pasternak?= Date: Mon, 27 Apr 2026 08:50:51 +0200 Subject: [PATCH 1/2] deps(uv): wymuszaj wheel-only dla zewnetrznych dep (anti-sdist) Praktyka #1 z lirantal/pypi-security-best-practices (binary-only installs). Sdist (source distribution) wykonuje setup.py podczas `uv sync` - to klasyczny wektor supply-chain attack: zlosliwy maintainer wpycha arbitralny kod do publishowanego pakietu, ktory wykonuje sie u kazdego co robi `pip install` / `uv sync`. Wheels (skompilowane binarne) nie wykonuja kodu przy instalacji. Globalnego `no-build = true` w [tool.uv] NIE da sie ustawic, bo blokuje editable install workspace roota (bpp-iplweb sam jest sdist-ish dla uv). Zamiast tego: 1. Komentarz w [tool.uv] dokumentuje polityke + uzasadnienie braku globalnego no-build. 2. Pre-commit hook `uv-lock-no-build` uruchamia `uv lock --check --no-build` przy zmianach pyproject.toml lub uv.lock - failuje jesli ktoras dep wymaga build z source. Aktualnie wszystkie 330 zewnetrznych deps maja wheels - hook passes. Gdy nowa dep nie ma wheel, dev dostaje jasny sygnal: znajdz alternatywe, lub zglos wheel u maintainera. --- .pre-commit-config.yaml | 13 +++++++++++++ pyproject.toml | 14 ++++++++++++++ src/bpp/newsfragments/+uv-binary-only.feature.rst | 1 + 3 files changed, 28 insertions(+) create mode 100644 src/bpp/newsfragments/+uv-binary-only.feature.rst diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f17da27bf..2cd9386bc 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -36,6 +36,19 @@ repos: language: system stages: ["pre-commit", "pre-push"] + # Egzekwuje wheel-only dla wszystkich zewnetrznych dep (praktyka #1 + # z pypi-security-best-practices). Failuje gdy ktorakolwiek dep + # wymaga build z source (sdist - wektor wykonania zlosliwego kodu + # w setup.py). Dlaczego nie globalne `no-build = true` w + # pyproject.toml: blokowaloby editable install workspace roota. + - id: uv-lock-no-build + name: uv lock --check --no-build (wheel-only policy) + description: Verify all third-party deps have prebuilt wheels. + entry: uv lock --check --no-build + language: system + files: ^(pyproject\.toml|uv\.lock)$ + pass_filenames: false + # - repo: https://github.com/gitguardian/ggshield # rev: v1.43.0 # hooks: diff --git a/pyproject.toml b/pyproject.toml index f519a6848..f9c9d0c5b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -221,6 +221,20 @@ push = false [tool.uv] environments = ["python_version >= '3.10' and python_version < '3.15' and platform_python_implementation != 'PyPy'"] +# Polityka: KAZDA zewnetrzna zaleznosc musi miec prebuilt wheel dla naszej +# macierzy (Linux x86_64 + macOS arm64, Python 3.10-3.14). Sdist wykonuje +# `setup.py` podczas instalacji - klasyczny wektor supply-chain (zlosliwy +# kod uruchomiony przez `uv sync`). Praktyka #1 z lirantal/pypi-security- +# best-practices. +# +# Egzekwowanie: pre-commit hook 'uv-lock-no-build' uruchamia +# `uv lock --check --no-build` - failuje jesli ktorakolwiek dep wymaga +# build z source. Globalnego `no-build = true` NIE mozemy ustawic, bo +# blokuje editable install workspace roota (bpp-iplweb). +# +# Gdy nowa dep nie ma wheel: znajdz alternatywe LUB zglos wheel u +# maintainera. NIE obchodz tej polityki dla wygody. + [tool.bumpver.file_patterns] "pyproject.toml" = ['current_version = "{version}"', 'version = "{pep440_version}"'] diff --git a/src/bpp/newsfragments/+uv-binary-only.feature.rst b/src/bpp/newsfragments/+uv-binary-only.feature.rst new file mode 100644 index 000000000..ddc897e95 --- /dev/null +++ b/src/bpp/newsfragments/+uv-binary-only.feature.rst @@ -0,0 +1 @@ +Wprowadzono politykę "wheel-only" dla zewnętrznych zależności Pythona — sdist (source distribution) jest wektorem ataku supply-chain (zlosliwy ``setup.py`` wykonywany podczas instalacji). Egzekwuje to nowy pre-commit hook ``uv-lock-no-build``, który uruchamia ``uv lock --check --no-build`` przy zmianach w ``pyproject.toml`` lub ``uv.lock`` — failuje, jesli ktoraskolwiek dep wymaga build z source. From 7e014d92ff85ccc2c02b0a94d43325a0c62a6828 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pasternak?= Date: Mon, 27 Apr 2026 11:11:37 +0200 Subject: [PATCH 2/2] deps(uv): wywal workspace install + udokumentuj 11 sdist-only exceptions Praktyka #1 z lirantal/pypi-security-best-practices, rev 2 po review. Workspace install (bpp-iplweb editable) nie byl do niczego potrzebny: - bpp-manage.py script: dead code (Makefile uzywa `python src/manage.py`) - pytest11 entry point: jedyne load-bearing uzycie - przeniesione do pytest.ini addopts jako `-p testcontainers_bpp.plugin` - import bpp_iplweb: 0 wystapien w src/ Zmiany: - pyproject.toml: usunieto [project.scripts] + [project.entry-points] - pytest.ini: dodano `-p testcontainers_bpp.plugin` na poczatku addopts (laduje plugin PRZED conftest.py - krytyczne, plugin musi wstrzyknac DJANGO_BPP_DB_PORT itp do os.environ przed Django settings load) - Makefile: --no-install-project w prepare-developer-machine-{macos,linux} i uv-sync target - .github/workflows/tests.yml + refresh-baseline.yml: --no-install-project - Dockerfile: juz mial --no-install-project (no change) Globalne `no-build = true` w [tool.uv] zostalo SPRAWDZONE i okazalo sie nieosiagalne - 11 pre-existing third-party deps jest sdist-only: crispy-forms-foundation, cssmin, django-autocomplete-light, django-columns, django-static-sitemaps, django-tabular-permissions, langdetect, pylatexenc, pyoai, python-ldap, wsgiutils. uv nie ma czystego "wheel-only with exceptions" trybu (tylko all-or-none + per-package no-build, ktore jest blacklist nie whitelist). Usunieto dlatego pre-commit hook `uv-lock-no-build` ktory failowal na cssmin przy KAZDEJ zmianie pyproject.toml (false positive). Polityka wheel-only egzekwowana teraz przez: 1. PR review (PULL_REQUEST_TEMPLATE.md ma checkbox dla nowych dep) 2. Trivy CVE scan w docker-bake 3. uv-secure CVE scan w dependency-audit.yml workflow Smoke test: - `uv sync --frozen --no-install-project --all-extras` -> ok - `pytest --help` zawiera `--no-testcontainers` -> plugin ladowany - `pytest src/pbn_integrator/tests/test_helpers.py` -> 2 passed --- .github/workflows/refresh-baseline.yml | 2 +- .github/workflows/tests.yml | 2 +- .pre-commit-config.yaml | 13 ------ Makefile | 6 +-- pyproject.toml | 41 ++++++++++++------- pytest.ini | 7 ++++ .../newsfragments/+uv-binary-only.feature.rst | 2 +- 7 files changed, 39 insertions(+), 34 deletions(-) diff --git a/.github/workflows/refresh-baseline.yml b/.github/workflows/refresh-baseline.yml index 76176f1e4..7716fd7d6 100644 --- a/.github/workflows/refresh-baseline.yml +++ b/.github/workflows/refresh-baseline.yml @@ -43,7 +43,7 @@ jobs: uses: astral-sh/setup-uv@v7 - name: Install Python deps (with baseline-rebuild extra) - run: uv sync --frozen --extra baseline-rebuild + run: uv sync --frozen --no-install-project --extra baseline-rebuild - name: Pre-pull dbserver image run: docker pull iplweb/bpp_dbserver:psql-16.13 || true diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 9756be04a..5017ef2b4 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -129,7 +129,7 @@ jobs: # full INSTALLED_APPS. No DB connection needed — it reads # src/baseline-sql/baseline.meta.json and counts migration files. # --frozen is intentional: uv.lock is the single source of truth. - run: uv sync --frozen + run: uv sync --frozen --no-install-project - name: Check baseline freshness # Fail loudly if migrations have outpaced the baseline pg_dump by diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 2cd9386bc..f17da27bf 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -36,19 +36,6 @@ repos: language: system stages: ["pre-commit", "pre-push"] - # Egzekwuje wheel-only dla wszystkich zewnetrznych dep (praktyka #1 - # z pypi-security-best-practices). Failuje gdy ktorakolwiek dep - # wymaga build z source (sdist - wektor wykonania zlosliwego kodu - # w setup.py). Dlaczego nie globalne `no-build = true` w - # pyproject.toml: blokowaloby editable install workspace roota. - - id: uv-lock-no-build - name: uv lock --check --no-build (wheel-only policy) - description: Verify all third-party deps have prebuilt wheels. - entry: uv lock --check --no-build - language: system - files: ^(pyproject\.toml|uv\.lock)$ - pass_filenames: false - # - repo: https://github.com/gitguardian/ggshield # rev: v1.43.0 # hooks: diff --git a/Makefile b/Makefile index e4b82ace0..dc5ff66b1 100644 --- a/Makefile +++ b/Makefile @@ -58,7 +58,7 @@ endif all: prepare-developer-machine release ## UWAGA: pełna konfiguracja + release (uruchamia release!) prepare-developer-machine-macos: ## Zainstaluj zależności systemowe na macOS (brew + uv sync) - uv sync --all-extras + uv sync --no-install-project --all-extras brew install cairo pango gdk-pixbuf libffi gobject-introspection gtk+3 sudo ln -sf /opt/homebrew/opt/glib/lib/libgobject-2.0.0.dylib /usr/local/lib/gobject-2.0 sudo ln -sf /opt/homebrew/opt/pango/lib/libpango-1.0.dylib /usr/local/lib/pango-1.0 @@ -71,7 +71,7 @@ prepare-developer-machine-linux: ## Zainstaluj zależności systemowe na Linuksi sudo apt install -y yarnpkg python3-dev libpq-dev libcairo2-dev \ libpango1.0-dev libgdk-pixbuf2.0-dev libffi-dev \ libgirepository1.0-dev libgtk-3-dev - uv sync --all-extras + uv sync --no-install-project --all-extras prepare-developer-machine: ## Zainstaluj zależności systemowe (auto-detekcja macOS/Linux) ifeq ($(OS),Darwin) @@ -251,7 +251,7 @@ coveralls-upload: ## Wyślij raport pokrycia do Coveralls uv run coveralls uv-sync: ## uv sync --all-extras (synchronizacja zależności Pythona) - uv sync --all-extras + uv sync --no-install-project --all-extras tests: clean-pycache clean-coverage uv-sync tests-without-playwright tests-only-playwright combine-coverage js-tests coveralls-upload ## Pełny test suite (coverage + JS + Coveralls) diff --git a/pyproject.toml b/pyproject.toml index f9c9d0c5b..e5c5e8ba1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -157,11 +157,11 @@ dev = [ "testcontainers[postgres]>=4.14.2" ] -[project.scripts] -"bpp-manage.py" = "django_bpp.manage_command:entry_point" - -[project.entry-points."pytest11"] -testcontainers_bpp = "testcontainers_bpp.plugin" +# [project.scripts] i [project.entry-points] usuniete - BPP nie jest +# instalowany jako package (uv sync --no-install-project wszedzie). +# Plugin testcontainers_bpp ladowany przez `-p testcontainers_bpp.plugin` +# w pytest.ini addopts. Skrypt `bpp-manage.py` byl dead code (Makefile/ +# Dockerfile uzywaja `python src/manage.py` bezposrednio). [tool.setuptools.packages.find] where = ["src"] @@ -221,19 +221,30 @@ push = false [tool.uv] environments = ["python_version >= '3.10' and python_version < '3.15' and platform_python_implementation != 'PyPy'"] -# Polityka: KAZDA zewnetrzna zaleznosc musi miec prebuilt wheel dla naszej -# macierzy (Linux x86_64 + macOS arm64, Python 3.10-3.14). Sdist wykonuje -# `setup.py` podczas instalacji - klasyczny wektor supply-chain (zlosliwy -# kod uruchomiony przez `uv sync`). Praktyka #1 z lirantal/pypi-security- -# best-practices. +# Polityka: KAZDA NOWA zewnetrzna zaleznosc powinna miec prebuilt wheel dla +# naszej macierzy (Linux x86_64 + macOS arm64, Python 3.10-3.14). Sdist +# wykonuje `setup.py` podczas instalacji - klasyczny wektor supply-chain +# (zlosliwy kod uruchomiony przez `uv sync`). Praktyka #1 z lirantal/ +# pypi-security-best-practices. +# +# Pre-existing exceptions (sdist-only, accepted risk - audit kwiecien 2026): +# crispy-forms-foundation, cssmin, django-autocomplete-light, django-columns, +# django-static-sitemaps, django-tabular-permissions, langdetect, pylatexenc, +# pyoai, python-ldap, wsgiutils. Wymiana na wheel-publishing alternatywy +# tracked w follow-up. Patrz docs/SECURITY_PRACTICES.md. +# +# Egzekwowanie: brak hard-gate (`uv` nie ma czystego "wheel-only with +# exceptions" trybu - albo no-build globalnie, albo nic). Polegamy na: +# 1. PR review (PULL_REQUEST_TEMPLATE.md ma checkbox dla nowych dep) +# 2. Trivy CVE scan w docker-bake build (Faza 2 build-docker-images.yml) +# 3. uv-secure CVE scan w dependency-audit.yml workflow # -# Egzekwowanie: pre-commit hook 'uv-lock-no-build' uruchamia -# `uv lock --check --no-build` - failuje jesli ktorakolwiek dep wymaga -# build z source. Globalnego `no-build = true` NIE mozemy ustawic, bo -# blokuje editable install workspace roota (bpp-iplweb). +# Workspace package (bpp-iplweb) NIE jest instalowany - `uv sync` wszedzie +# uzywa --no-install-project. Pytest plugin testcontainers_bpp ladowany +# przez `-p testcontainers_bpp.plugin` w pytest.ini addopts. # # Gdy nowa dep nie ma wheel: znajdz alternatywe LUB zglos wheel u -# maintainera. NIE obchodz tej polityki dla wygody. +# maintainera. NIE dodawaj do listy wyjatkow bez review. [tool.bumpver.file_patterns] "pyproject.toml" = ['current_version = "{version}"', diff --git a/pytest.ini b/pytest.ini index fbf8094d9..0579f3ad3 100644 --- a/pytest.ini +++ b/pytest.ini @@ -7,6 +7,13 @@ env = DJANGO_ALLOW_ASYNC_UNSAFE=1 addopts = + # Plugin testcontainers_bpp.plugin musi byc wczytany PRZED conftest.py + # zeby zdazyl wstrzyknac DJANGO_BPP_DB_PORT/etc do os.environ przed + # zaladowaniem Django settings (conftest robi `from django.apps import + # apps`). `-p` to robi - flaga laduje plugin via pytest startup, + # zastepuje zlikwidowany [project.entry-points."pytest11"] wpis z + # pyproject.toml (wymagal editable install workspace roota). + -p testcontainers_bpp.plugin --ignore=dist --ignore=build --ignore=node_modules --ignore=src/django_bpp/staticroot/ --ignore=src/ewaluacja2021/tests/ --reuse-db #--cov=src/ diff --git a/src/bpp/newsfragments/+uv-binary-only.feature.rst b/src/bpp/newsfragments/+uv-binary-only.feature.rst index ddc897e95..b23f7d821 100644 --- a/src/bpp/newsfragments/+uv-binary-only.feature.rst +++ b/src/bpp/newsfragments/+uv-binary-only.feature.rst @@ -1 +1 @@ -Wprowadzono politykę "wheel-only" dla zewnętrznych zależności Pythona — sdist (source distribution) jest wektorem ataku supply-chain (zlosliwy ``setup.py`` wykonywany podczas instalacji). Egzekwuje to nowy pre-commit hook ``uv-lock-no-build``, który uruchamia ``uv lock --check --no-build`` przy zmianach w ``pyproject.toml`` lub ``uv.lock`` — failuje, jesli ktoraskolwiek dep wymaga build z source. +BPP nie jest już instalowany jako Python package — wszystkie ``uv sync`` używają ``--no-install-project``. Usunięto ``[project.scripts]`` (``bpp-manage.py`` był dead code) i ``[project.entry-points."pytest11"]`` (workspace install nie jest potrzebny). Plugin ``testcontainers_bpp`` ładowany teraz przez ``-p testcontainers_bpp.plugin`` w ``pytest.ini`` addopts. Udokumentowano politykę wheel-only z 11 pre-existing sdist-only deps jako accepted exceptions w ``pyproject.toml`` ``[tool.uv]`` komentarzu.