From 79790ea63c6a709edf4cb2c1e06bed4cfa7684da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20ROBERT?= Date: Thu, 16 Jul 2026 19:05:32 +0200 Subject: [PATCH 1/2] feat(scorecard): execute reellement le fuzz atheris (dynamic analysis) + politique de tests - fuzz.yml : lance fuzz/fuzz_api.py en CI (runs+time bornes), deps hash-pinnees. Rend dynamic_analysis VRAI pour bestpractices.dev (pas juste import atheris). - CONTRIBUTING : toute feature/fix accompagnee dun test (test_policy). --- .github/workflows/fuzz.yml | 39 ++++++++++++++++++++++++++++++++++ CONTRIBUTING.md | 3 +++ requirements-fuzz.in | 2 ++ requirements-fuzz.txt | 43 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 87 insertions(+) create mode 100644 .github/workflows/fuzz.yml create mode 100644 requirements-fuzz.in create mode 100644 requirements-fuzz.txt diff --git a/.github/workflows/fuzz.yml b/.github/workflows/fuzz.yml new file mode 100644 index 0000000..2711b04 --- /dev/null +++ b/.github/workflows/fuzz.yml @@ -0,0 +1,39 @@ +name: Fuzz + +on: + pull_request: + branches: [main] + schedule: + - cron: "0 3 * * 1" + +permissions: {} + +jobs: + fuzz: + name: Fuzzing (Atheris) + runs-on: ubuntu-24.04 + timeout-minutes: 10 + permissions: + contents: read + steps: + - name: Harden runner + uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 + with: + egress-policy: audit + - name: Checkout + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false + - name: Setup Python + uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0 + with: + python-version: "3.11" + - name: Install (épinglé par hash) + run: | + pip install --require-hashes -r requirements.txt + pip install --require-hashes -r requirements-fuzz.txt + - name: Run fuzzing (budget limité) + env: + PYTHONPATH: src + # -runs / -max_total_time bornent l'exécution en CI ; un crash (5xx) échoue. + run: python fuzz/fuzz_api.py -runs=20000 -max_total_time=120 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 258c6ce..739f0da 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -36,6 +36,9 @@ pip-audit -r requirements.txt # vulnérabilités des dépendances - Les **dépendances Python** passent par `requirements.in` puis `pip-compile --generate-hashes`. On ne modifie jamais `requirements.txt` à la main. - Aucun **secret en clair** dans le code ou les workflows. +- **Toute nouvelle fonctionnalité ou correction de bug est accompagnée d'un + test** qui échoue sans le changement et passe avec. Le harnais de fuzzing + (`fuzz/`) est exécuté en CI et complète les tests unitaires. ## Processus diff --git a/requirements-fuzz.in b/requirements-fuzz.in new file mode 100644 index 0000000..70e8874 --- /dev/null +++ b/requirements-fuzz.in @@ -0,0 +1,2 @@ +atheris==2.3.0 +httpx==0.28.1 diff --git a/requirements-fuzz.txt b/requirements-fuzz.txt new file mode 100644 index 0000000..10afddd --- /dev/null +++ b/requirements-fuzz.txt @@ -0,0 +1,43 @@ +# This file was autogenerated by uv via the following command: +# uv pip compile --generate-hashes requirements-fuzz.in -o requirements-fuzz.txt +anyio==4.14.2 \ + --hash=sha256:9f505dda5ac9f0c8309b5e8bd445a8c2bf7246f3ce950121e45ea15bc41d1494 \ + --hash=sha256:cfa139f3ed1a23ee8f88a145ddb5ac7605b8bbfd8592baacd7ce3d8bb4313c7f + # via httpx +atheris==2.3.0 \ + --hash=sha256:005bde4b5a70e998b7fa097e9aa195972dcc2e04092156a0149cff7aa0de970e \ + --hash=sha256:6d4e83c8a518e7f7d1c82ee52453255bf6309b7b335657534e83747d4fdfa110 \ + --hash=sha256:9877b6c2bd5386f9fbbb2989a939c717383d9639f5476411c06fe50fe2fe09a6 \ + --hash=sha256:b91cb296d60915c3efa4f6db48f09c4678b574cddb7ca98035f1cb9d9fb96f64 \ + --hash=sha256:c353952ee375bf851527e8b2ea57fdefd7ad16aadfe18143801998075485eccd \ + --hash=sha256:cf1fdf5fa220a41a2f262b32363fc566549502b2cb0addf4e1baad5531c0e825 \ + --hash=sha256:e4e43d1ee4760916a84ff73c9c6cf9ac6eee80fc030479bbed43fe0b8e994981 + # via -r requirements-fuzz.in +certifi==2026.6.17 \ + --hash=sha256:024c88eeec92ca068db80f02b8b07c9cef7b9fe261d1d535abfd5abd6f6af432 \ + --hash=sha256:2227dcbaafe0d2f59279d1762ddddc37783ed4354594f194ffc31d20f41fc3db + # via + # httpcore + # httpx +h11==0.16.0 \ + --hash=sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1 \ + --hash=sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86 + # via httpcore +httpcore==1.0.9 \ + --hash=sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55 \ + --hash=sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8 + # via httpx +httpx==0.28.1 \ + --hash=sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc \ + --hash=sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad + # via -r requirements-fuzz.in +idna==3.18 \ + --hash=sha256:7f952cbe720b688055e3f87de14f5c3e5fdaa8bc3928985c4077ca689de849a2 \ + --hash=sha256:ffb385a7e039654cef1ab9ef32c6fafe283c0c0467bba1d9029738ce4a14a848 + # via + # anyio + # httpx +typing-extensions==4.16.0 \ + --hash=sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8 \ + --hash=sha256:dc983d19a509c94dba722ee6abd33940f7c05a89e243c47e907eb4db6f1a43e5 + # via anyio From 1e7d284c75b74054e52164847d7acc1525e49bfa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20ROBERT?= Date: Thu, 16 Jul 2026 19:09:04 +0200 Subject: [PATCH 2/2] fix(fuzz): retire instrument_imports (segfault sur pydantic-core compile) atheris.instrument_imports() plante (exit 139) en instrumentant pydantic-core (extension Rust, custom loader). Import normal : le fuzz sexecute reellement (2000 runs OK en local) sans coverage-guided sur les libs compilees. --- fuzz/fuzz_api.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/fuzz/fuzz_api.py b/fuzz/fuzz_api.py index b3d35af..cb1a976 100644 --- a/fuzz/fuzz_api.py +++ b/fuzz/fuzz_api.py @@ -13,10 +13,12 @@ import atheris -with atheris.instrument_imports(): - from fastapi.testclient import TestClient +# On n'instrumente PAS les imports : pydantic-core est une extension compilée +# (Rust) avec un custom loader qui fait planter atheris.instrument_imports() +# (segfault). Le fuzzing reste valide sur la logique de routage/validation. +from fastapi.testclient import TestClient # noqa: E402 - from app.main import app +from app.main import app # noqa: E402 client = TestClient(app)