diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index adc9b34..21a1c2c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-24.04 timeout-minutes: 20 permissions: - contents: read # lecture du code + contents: write # attacher la provenance a la release (Signed-Releases) packages: write # push vers GHCR id-token: write # OIDC keyless (provenance + cosign) attestations: write # attestations natives GitHub @@ -53,6 +53,7 @@ jobs: tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} - name: Attestation de provenance SLSA (native) + id: attest uses: actions/attest-build-provenance@0f67c3f4856b2e3261c31976d6725780e5e4c373 # v4.1.1 with: subject-name: ${{ env.IMAGE }} @@ -81,3 +82,11 @@ jobs: IMAGE: ${{ env.IMAGE }} DIGEST: ${{ steps.build.outputs.digest }} run: cosign sign --yes "${IMAGE}@${DIGEST}" + - name: Attacher la provenance à la release (Scorecard Signed-Releases) + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + TAG: ${{ github.event.release.tag_name }} + BUNDLE: ${{ steps.attest.outputs.bundle-path }} + run: | + cp "$BUNDLE" provenance.intoto.jsonl + gh release upload "$TAG" provenance.intoto.jsonl --clobber diff --git a/SECURITY.md b/SECURITY.md index 467d70e..ed43cf1 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -12,15 +12,31 @@ logicielle sécurisée sur GitHub Actions. Il applique les pratiques qu'il ensei ## Signaler une vulnérabilité -Utilisez l'onglet **Security > Report a vulnerability** du dépôt (GitHub Private -Vulnerability Reporting). En l'absence de cette option, ouvrez une issue **sans -détail exploitable** et demandez un canal privé. +Nous pratiquons la **divulgation coordonnée** (coordinated disclosure). Merci de +**ne pas** ouvrir d'issue publique pour une vulnérabilité. -Délais indicatifs : +Deux canaux privés pour signaler une vulnérabilité : -- Accusé de réception sous 48 heures. -- Évaluation initiale sous 5 jours ouvrés. -- Correctif ou plan de remédiation communiqué selon la gravité. +- **De préférence**, via l'avis de sécurité privé GitHub : + +- Par courriel, à l'adresse : **security@stephane-robert.info** + +Merci d'inclure une description de la vulnérabilité, les étapes de reproduction, +la version ou le digest de l'image concernée, et l'impact estimé. + +## Engagements de délai + +Pour toute vulnérabilité signalée, nous nous engageons sur les délais suivants : + +- **Accusé de réception** sous **48 heures**. +- **Évaluation initiale** et première réponse sous **5 jours** ouvrés. +- **Correctif ou plan de remédiation** communiqué sous **30 jours**, plus tôt + selon la gravité. +- **Divulgation publique coordonnée** après correctif, dans un délai maximum de + **90 jours** à compter du signalement (coordinated disclosure window). + +Nous créditons publiquement les personnes qui signalent une vulnérabilité de +manière responsable, sauf demande contraire de leur part. ## Garanties de la chaîne de build @@ -28,6 +44,7 @@ Chaque image publiée sur `ghcr.io` est accompagnée de : - une **attestation de provenance SLSA** signée (Sigstore keyless, log Rekor) ; - un **SBOM** (CycloneDX et SPDX) attaché au registre ; -- une **signature Cosign** vérifiable du digest. +- une **signature Cosign** vérifiable du digest ; +- un fichier **`provenance.intoto.jsonl`** attaché à chaque release. -La vérification est décrite dans le README. +La procédure de vérification est décrite dans le [README](README.md). diff --git a/fuzz/fuzz_api.py b/fuzz/fuzz_api.py new file mode 100644 index 0000000..b3d35af --- /dev/null +++ b/fuzz/fuzz_api.py @@ -0,0 +1,42 @@ +"""Harnais de fuzzing Atheris pour l'API. + +Scorecard détecte le fuzzing Python par la présence de `import atheris`. +Ce harnais envoie des chemins et payloads aléatoires à l'application pour +débusquer des plantages non gérés (exceptions non attendues, 500). + +Lancement local : + pip install atheris + python fuzz/fuzz_api.py +""" + +import sys + +import atheris + +with atheris.instrument_imports(): + from fastapi.testclient import TestClient + + from app.main import app + +client = TestClient(app) + + +def test_one_input(data: bytes) -> None: + """Un tour de fuzzing : construit une requête à partir de données aléatoires.""" + fdp = atheris.FuzzedDataProvider(data) + path = fdp.ConsumeUnicodeNoSurrogates(64) + try: + response = client.get("/" + path) + except Exception: # noqa: BLE001 - le fuzzing doit tolérer toute erreur applicative + return + # Une réponse serveur (5xx) sur une entrée malformée signale un bug à corriger. + assert response.status_code < 500 + + +def main() -> None: + atheris.Setup(sys.argv, test_one_input) + atheris.Fuzz() + + +if __name__ == "__main__": + main() diff --git a/pyproject.toml b/pyproject.toml index d0d1c6e..d252fd3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,8 +22,9 @@ line-length = 88 select = ["E", "F", "I", "S", "B", "C4", "UP"] [tool.ruff.lint.per-file-ignores] -# S101 = usage d'assert, normal dans les tests pytest +# S101 = usage d'assert, normal dans les tests pytest et les harnais de fuzzing "tests/**/*.py" = ["S101"] +"fuzz/**/*.py" = ["S101"] [tool.pytest.ini_options] testpaths = ["tests"] diff --git a/requirements-dev.txt b/requirements-dev.txt index 1d54ba5..731cb8d 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -10,3 +10,6 @@ httpx>=0.28.1 # Build et compilation des dépendances épinglées par hash pip-tools>=7.4.0 + +# Fuzzing +atheris>=2.3.0