Skip to content

Commit 390be55

Browse files
dobyclaude
andcommitted
ci(sast): ajoute bandit + remontée SARIF vers l'onglet Security
- .github/workflows/sast.yml : bandit sur push/PR vers main, fail si severity ≥ MEDIUM. Upload SARIF (toutes sévérités) consommé par l'onglet Security du repo via codeql-action/upload-sarif. - pyproject.toml [tool.bandit] : exclut tests/assets/build/dist, skip B110/B112 (try/except: pass défensif sur sockets et parse EXIF — motif intentionnel documenté dans CLAUDE.md). - utils.py: hashlib.md5(usedforsecurity=False) — l'empreinte sert à la déduplication, pas à la sécurité (silence CWE-327 à juste titre). - READMEs : badge SAST live de l'action ajouté à côté du badge tests. - CLAUDE.md : nouvelle entrée Conventions décrit le périmètre bandit. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent 64f5c50 commit 390be55

6 files changed

Lines changed: 67 additions & 1 deletion

File tree

.github/workflows/sast.yml

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
name: sast
2+
3+
on:
4+
push:
5+
branches: [main, master]
6+
pull_request:
7+
branches: [main, master]
8+
workflow_dispatch:
9+
10+
permissions:
11+
contents: read
12+
security-events: write # SARIF upload → onglet Security du repo
13+
14+
jobs:
15+
bandit:
16+
name: bandit (Python SAST)
17+
runs-on: ubuntu-latest
18+
steps:
19+
- uses: actions/checkout@v4
20+
21+
- uses: actions/setup-python@v5
22+
with:
23+
python-version: '3.12'
24+
cache: pip
25+
cache-dependency-path: pyproject.toml
26+
27+
- name: Install bandit
28+
run: pip install 'bandit[toml,sarif]'
29+
30+
# Affichage humain dans le log (fail si severity ≥ MEDIUM).
31+
- name: Bandit — rapport console
32+
run: bandit -r nikon_transfer -c pyproject.toml --severity-level medium
33+
34+
# Génère un SARIF complet (toutes sévérités) pour l'onglet Security.
35+
# `continue-on-error` : la qualité du rapport ne doit pas planter le job
36+
# si la sévérité console a déjà tranché.
37+
- name: Bandit — rapport SARIF
38+
if: always()
39+
run: bandit -r nikon_transfer -c pyproject.toml -f sarif -o bandit.sarif || true
40+
41+
- name: Upload SARIF vers GitHub Security
42+
if: always()
43+
uses: github/codeql-action/upload-sarif@v3
44+
with:
45+
sarif_file: bandit.sarif
46+
category: bandit

CLAUDE.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,11 @@ ne pas les défaire sans raison.
139139
- CI : `.github/workflows/tests.yml` lance `pytest --cov` sur Ubuntu × {3.11, 3.12,
140140
3.13} + un run macOS 3.12. Aucun secret requis, aucune dépendance externe — les
141141
tests sont 100 % stdlib + `pytest`/`pytest-cov` (extra `[dev]`).
142+
- SAST : `.github/workflows/sast.yml` lance `bandit` (config dans `[tool.bandit]`
143+
de `pyproject.toml`) sur le package, fail si severity ≥ MEDIUM. Upload SARIF
144+
vers l'onglet Security du repo. **B110 / B112 skipés** : nos `try/except: pass`
145+
sur les sockets et le parse EXIF sont du *best-effort* défensif intentionnel —
146+
ne pas les transformer en `log+raise` sans réfléchir.
142147
- Tailles d'octets : `format_size` (base 1024, pour la taille des fichiers)
143148
vs `format_storage_size` (base 1000 SI, pour l'affichage carte mémoire — colle
144149
à l'étiquette « 32 Go » du fabricant). Ne pas mélanger.

README.fr.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ abandonnée et cassée sur les macOS récents), pas de `libgphoto2`. Le cœur es
88
en **Python stdlib pur** ; la GUI optionnelle ajoute PySide6 + Pillow.
99

1010
[![tests](https://github.com/doby/nikon-transfer-wifi-dseries/actions/workflows/tests.yml/badge.svg)](https://github.com/doby/nikon-transfer-wifi-dseries/actions/workflows/tests.yml)
11+
[![sast](https://github.com/doby/nikon-transfer-wifi-dseries/actions/workflows/sast.yml/badge.svg)](https://github.com/doby/nikon-transfer-wifi-dseries/actions/workflows/sast.yml)
1112
[![python](https://img.shields.io/badge/python-3.11%2B-blue)]()
1213
[![license](https://img.shields.io/badge/license-MIT-lightgrey)]()
1314

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ on recent macOS), no `libgphoto2`. The core is **pure Python stdlib**; the optio
88
GUI adds PySide6 + Pillow.
99

1010
[![tests](https://github.com/doby/nikon-transfer-wifi-dseries/actions/workflows/tests.yml/badge.svg)](https://github.com/doby/nikon-transfer-wifi-dseries/actions/workflows/tests.yml)
11+
[![sast](https://github.com/doby/nikon-transfer-wifi-dseries/actions/workflows/sast.yml/badge.svg)](https://github.com/doby/nikon-transfer-wifi-dseries/actions/workflows/sast.yml)
1112
[![python](https://img.shields.io/badge/python-3.11%2B-blue)]()
1213
[![license](https://img.shields.io/badge/license-MIT-lightgrey)]()
1314

nikon_transfer/utils.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,10 @@ def _read_uint16_array(data: bytes, offset: int) -> tuple[list[int], int]:
3939

4040

4141
def md5_file(path: Path) -> str:
42-
h = hashlib.md5()
42+
# usedforsecurity=False : empreinte utilisée pour la déduplication de
43+
# fichiers, pas pour de la signature — MD5 reste adapté et plus rapide
44+
# que SHA-256. Le flag fait taire les SAST (CWE-327) à juste titre.
45+
h = hashlib.md5(usedforsecurity=False)
4346
with open(path, "rb") as f:
4447
for chunk in iter(lambda: f.read(65536), b""):
4548
h.update(chunk)

pyproject.toml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,3 +55,13 @@ addopts = "-v --tb=short"
5555
[tool.coverage.run]
5656
source = ["nikon_transfer"]
5757
omit = ["tests/*"]
58+
59+
[tool.bandit]
60+
# SAST appliqué uniquement au package — `tests/` mocke des sockets et `assets/`
61+
# n'est que de l'outillage dev.
62+
exclude_dirs = ["tests", "assets", "build", "dist", ".venv", "venv"]
63+
# B110 (try/except/pass) et B112 (try/except/continue) : motif de tolérance
64+
# défensive volontaire pour les opérations réseau (close d'une socket déjà
65+
# morte, skip d'un objet caméra dont les méta sont corrompues, parse d'un
66+
# sub-IFD EXIF optionnel). Voir CLAUDE.md §« Pièges PTP/IP ».
67+
skips = ["B110", "B112"]

0 commit comments

Comments
 (0)